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 "content/web_test/renderer/test_plugin.h"

#include <stddef.h>
#include <stdint.h>

#include <utility>

#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "cc/layers/texture_layer.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "content/web_test/renderer/test_runner.h"
#include "content/web_test/renderer/web_frame_test_proxy.h"
#include "gin/handle.h"
#include "gin/interceptor.h"
#include "gin/object_template_builder.h"
#include "gin/wrappable.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/ipc/client/client_shared_image_interface.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
#include "third_party/blink/public/common/input/web_gesture_event.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/input/web_touch_event.h"
#include "third_party/blink/public/common/input/web_touch_point.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_plugin_params.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "v8/include/cppgc/allocation.h"
#include "v8/include/v8-cppgc.h"

namespace content {

namespace {

void PremultiplyAlpha(base::span<const uint8_t, 3> color_in,
                      float alpha,
                      base::span<float, 4> color_out) {
  for (int i = 0; i < 3; ++i)
    color_out[i] = (color_in[i] / 255.0f) * alpha;

  color_out[3] = alpha;
}

const char* PointState(blink::WebTouchPoint::State state) {
  switch (state) {
    case blink::WebTouchPoint::State::kStateReleased:
      return "Released";
    case blink::WebTouchPoint::State::kStatePressed:
      return "Pressed";
    case blink::WebTouchPoint::State::kStateMoved:
      return "Moved";
    case blink::WebTouchPoint::State::kStateCancelled:
      return "Cancelled";
    default:
      return "Unknown";
  }
}

void PrintTouchList(TestRunner* test_runner,
                    WebFrameTestProxy& frame_proxy,
                    base::span<const blink::WebTouchPoint> points) {
  for (const blink::WebTouchPoint& point : points) {
    test_runner->PrintMessage(
        base::StringPrintf("* %.2f, %.2f: %s\n", point.PositionInWidget().x(),
                           point.PositionInWidget().y(),
                           PointState(point.state)),
        frame_proxy);
  }
}

void PrintEventDetails(TestRunner* test_runner,
                       WebFrameTestProxy& frame_proxy,
                       const blink::WebInputEvent& event) {
  if (blink::WebInputEvent::IsTouchEventType(event.GetType())) {
    const blink::WebTouchEvent& touch =
        static_cast<const blink::WebTouchEvent&>(event);
    PrintTouchList(test_runner, frame_proxy,
                   base::span(touch.touches).first(touch.touches_length));
  } else if (blink::WebInputEvent::IsMouseEventType(event.GetType()) ||
             event.GetType() == blink::WebInputEvent::Type::kMouseWheel) {
    const blink::WebMouseEvent& mouse =
        static_cast<const blink::WebMouseEvent&>(event);
    test_runner->PrintMessage(
        base::StringPrintf("* %.2f, %.2f\n", mouse.PositionInWidget().x(),
                           mouse.PositionInWidget().y()),
        frame_proxy);
  } else if (blink::WebInputEvent::IsGestureEventType(event.GetType())) {
    const blink::WebGestureEvent& gesture =
        static_cast<const blink::WebGestureEvent&>(event);
    test_runner->PrintMessage(
        base::StringPrintf("* %.2f, %.2f\n", gesture.PositionInWidget().x(),
                           gesture.PositionInWidget().y()),
        frame_proxy);
  }
}

blink::WebPluginContainer::TouchEventRequestType ParseTouchEventRequestType(
    const blink::WebString& string) {
  if (string == blink::WebString::FromUTF8("raw")) {
    return blink::WebPluginContainer::kTouchEventRequestTypeRaw;
  }
  if (string == blink::WebString::FromUTF8("raw-lowlatency")) {
    return blink::WebPluginContainer::kTouchEventRequestTypeRawLowLatency;
  }
  if (string == blink::WebString::FromUTF8("synthetic")) {
    return blink::WebPluginContainer::kTouchEventRequestTypeSynthesizedMouse;
  }
  return blink::WebPluginContainer::kTouchEventRequestTypeNone;
}

class ScriptableObject
    : public gin::WrappableWithNamedPropertyInterceptor<ScriptableObject> {
 public:
  static constexpr gin::WrapperInfo kWrapperInfo = {
      {gin::kEmbedderNativeGin},
      gin::kTestPluginScriptableObject};

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

  static v8::Local<v8::Object> Create(v8::Isolate* isolate) {
    auto* scriptable_object = cppgc::MakeGarbageCollected<ScriptableObject>(
        isolate->GetCppHeap()->GetAllocationHandle());
    return gin::ConvertToV8(isolate, scriptable_object)
        .ToLocalChecked()
        .As<v8::Object>();
  }

  // gin::NamedPropertyInterceptor
  v8::Local<v8::Value> GetNamedProperty(
      v8::Isolate* isolate,
      const std::string& identifier) override {
    if (identifier == "loaded") {
      return v8::True(isolate);
    }
    return v8::Local<v8::Value>();
  }

  ScriptableObject() = default;

  // gin::DeprecatedWrappable
  gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
      v8::Isolate* isolate) override {
    return gin::WrappableWithNamedPropertyInterceptor<
               ScriptableObject>::GetObjectTemplateBuilder(isolate)
        .template AddNamedPropertyInterceptor<kWrapperInfo.pointer_tag>();
  }
};

}  // namespace

TestPlugin::TestPlugin(const blink::WebPluginParams& params,
                       TestRunner* test_runner,
                       blink::WebLocalFrame* frame)
    : test_runner_(test_runner),
      web_local_frame_(frame),
      is_persistent_(params.mime_type == PluginPersistsMimeType()) {
  DCHECK_EQ(params.attribute_names.size(), params.attribute_values.size());
  size_t size = params.attribute_names.size();
  for (size_t i = 0; i < size; ++i) {
    const blink::WebString& attribute_name = params.attribute_names[i];
    const blink::WebString& attribute_value = params.attribute_values[i];

    if (attribute_name == "primitive") {
      scene_.primitive = ParsePrimitive(attribute_value);
    } else if (attribute_name == "background-color") {
      ParseColor(attribute_value, scene_.background_color);
    } else if (attribute_name == "primitive-color") {
      ParseColor(attribute_value, scene_.primitive_color);
    } else if (attribute_name == "opacity") {
      scene_.opacity = ParseOpacity(attribute_value);
    } else if (attribute_name == "accepts-touch") {
      touch_event_request_ = ParseTouchEventRequestType(attribute_value);
    } else if (attribute_name == "re-request-touch") {
      re_request_touch_events_ = ParseBoolean(attribute_value);
    } else if (attribute_name == "print-event-details") {
      print_event_details_ = ParseBoolean(attribute_value);
    } else if (attribute_name == "can-process-drag") {
      can_process_drag_ = ParseBoolean(attribute_value);
    } else if (attribute_name == "supports-keyboard-focus") {
      supports_keyboard_focus_ = ParseBoolean(attribute_value);
    } else if (attribute_name == "print-user-gesture-status") {
      print_user_gesture_status_ = ParseBoolean(attribute_value);
    }
  }
}

TestPlugin::~TestPlugin() {}

bool TestPlugin::Initialize(blink::WebPluginContainer* container) {
  DCHECK(container);
  DCHECK_EQ(this, container->Plugin());

  container_ = container;

  blink::WebURL url = container->GetDocument().Url();
  blink::Platform::WebGLContextInfo gl_info;
  std::unique_ptr<blink::WebGraphicsContext3DProvider> context_provider =
      blink::Platform::Current()->CreateWebGLGraphicsContextProvider(
          /*prefer_low_power_gpu=*/true,
          /*fail_if_major_performance_caveat=*/false,
          blink::Platform::kWebGL1ContextType, url, &gl_info);
  if (context_provider && !context_provider->BindToCurrentSequence()) {
    context_provider = nullptr;
  }

  if (context_provider) {
    gl_ = context_provider->ContextGL();
    context_provider_ =
        base::MakeRefCounted<ContextProviderRef>(std::move(context_provider));
  } else {
    scoped_refptr<gpu::GpuChannelHost> gpu_channel =
        blink::Platform::Current()->EstablishGpuChannelSync();
    if (!gpu_channel) {
      return false;
    }

    shared_image_interface_ = gpu_channel->CreateClientSharedImageInterface();
    DCHECK(shared_image_interface_);
  }

  if (!InitScene()) {
    return false;
  }

  layer_ = cc::TextureLayer::Create(this);
  container_->SetCcLayer(layer_.get());
  if (re_request_touch_events_) {
    container_->RequestTouchEventType(
        blink::WebPluginContainer::kTouchEventRequestTypeSynthesizedMouse);
    container_->RequestTouchEventType(
        blink::WebPluginContainer::kTouchEventRequestTypeRaw);
  }
  container_->RequestTouchEventType(touch_event_request_);
  container_->SetWantsWheelEvents(true);
  return true;
}

void TestPlugin::Destroy() {
  if (layer_.get()) {
    layer_->ClearTexture();
  }
  if (container_) {
    container_->SetCcLayer(nullptr);
  }
  layer_ = nullptr;
  DestroyScene();

  gl_ = nullptr;
  context_provider_.reset();
  scriptable_object_.Reset();

  container_ = nullptr;

  blink::scheduler::GetSingleThreadTaskRunnerForTesting()->DeleteSoon(FROM_HERE,
                                                                      this);
}

blink::WebPluginContainer* TestPlugin::Container() const {
  return container_;
}

bool TestPlugin::CanProcessDrag() const {
  return can_process_drag_;
}

bool TestPlugin::SupportsKeyboardFocus() const {
  return supports_keyboard_focus_;
}

void TestPlugin::UpdateGeometry(const gfx::Rect& window_rect,
                                const gfx::Rect& clip_rect,
                                const gfx::Rect& unobscured_rect,
                                bool is_visible) {
  if (clip_rect == rect_) {
    return;
  }
  rect_ = clip_rect;

  if (shared_image_) {
    shared_image_->UpdateDestructionSyncToken(sync_token_);
    shared_image_ = nullptr;
    sync_token_ = gpu::SyncToken();
  }

  if (!rect_.IsEmpty() && gl_) {
    DCHECK(context_provider_);
    auto* sii = context_provider_->data->SharedImageInterface();
    // We will draw to the SI via GL directly below and then send it off to the
    // display compositor later.
    shared_image_ = sii->CreateSharedImage(
        {viz::SinglePlaneFormat::kRGBA_8888, rect_.size(), gfx::ColorSpace(),
         kBottomLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
         gpu::SHARED_IMAGE_USAGE_GLES2_WRITE |
             gpu::SHARED_IMAGE_USAGE_DISPLAY_READ,
         "TestLabel"},
        gpu::kNullSurfaceHandle);
    CHECK(shared_image_);
    {
      std::unique_ptr<gpu::SharedImageTexture> color_texture =
          shared_image_->CreateGLTexture(gl_);
      std::unique_ptr<gpu::SharedImageTexture::ScopedAccess>
          color_texture_scoped_access =
              color_texture->BeginAccess(gpu::SyncToken(), /*readonly=*/false);

      gl_->BindFramebuffer(GL_FRAMEBUFFER, framebuffer_);
      gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                GL_TEXTURE_2D,
                                color_texture_scoped_access->texture_id(), 0);

      gl_->Viewport(0, 0, rect_.width(), rect_.height());
      DrawSceneGL();

      sync_token_ = gpu::SharedImageTexture::ScopedAccess::EndAccess(
          std::move(color_texture_scoped_access));
    }
  } else if (!rect_.IsEmpty()) {
    DCHECK(shared_image_interface_);
    const viz::SharedImageFormat format = viz::SinglePlaneFormat::kBGRA_8888;
    shared_image_ =
        shared_image_interface_->CreateSharedImageForSoftwareCompositor(
            {format, rect_.size(), gfx::ColorSpace(),
             kBottomLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
             gpu::SHARED_IMAGE_USAGE_CPU_WRITE_ONLY, "TestPluginSharedBitmap"});
    sync_token_ = shared_image_interface_->GenVerifiedSyncToken();

    auto scoped_mapping = shared_image_->Map();
    DrawSceneSoftware(scoped_mapping->GetMemoryForPlane(0).data());
  }

  content_changed_ = true;
  layer_->SetNeedsDisplay();
}

v8::Local<v8::Object> TestPlugin::V8ScriptableObject(v8::Isolate* isolate) {
  if (scriptable_object_.IsEmpty()) {
    scriptable_object_.Reset(isolate, ScriptableObject::Create(isolate));
  }
  return scriptable_object_.Get(isolate);
}

// static
void TestPlugin::ReleaseSharedImage(
    scoped_refptr<gpu::ClientSharedImage> shared_image,
    const gpu::SyncToken& sync_token,
    bool lost) {
  shared_image->UpdateDestructionSyncToken(sync_token);
  shared_image = nullptr;
}

bool TestPlugin::PrepareTransferableResource(
    viz::TransferableResource* resource,
    viz::ReleaseCallback* release_callback) {
  if (!content_changed_) {
    return false;
  }

  if (shared_image_) {
    *resource = viz::TransferableResource::Make(
        shared_image_, viz::TransferableResource::ResourceSource::kCanvas,
        sync_token_);
    // We pass ownership of the shared image to the callback.
    *release_callback = base::BindOnce(&ReleaseSharedImage,
                                       std::exchange(shared_image_, nullptr));
    sync_token_ = gpu::SyncToken();
  }

  content_changed_ = false;
  return true;
}

TestPlugin::Primitive TestPlugin::ParsePrimitive(
    const blink::WebString& string) {
  static const base::NoDestructor<blink::WebString> kPrimitiveNone("none");
  static const base::NoDestructor<blink::WebString> kPrimitiveTriangle(
      "triangle");

  Primitive primitive = PrimitiveNone;
  if (string == *kPrimitiveNone) {
    primitive = PrimitiveNone;
  } else if (string == *kPrimitiveTriangle) {
    primitive = PrimitiveTriangle;
  } else {
    NOTREACHED();
  }
  return primitive;
}

// FIXME: This method should already exist. Use it.
// For now just parse primary colors.
void TestPlugin::ParseColor(const blink::WebString& string,
                            base::span<uint8_t, 3> color) {
  color[0] = color[1] = color[2] = 0;
  if (string == "black") {
    return;
  }

  if (string == "red") {
    color[0] = 255;
  } else if (string == "green") {
    color[1] = 255;
  } else if (string == "blue") {
    color[2] = 255;
  } else {
    NOTREACHED();
  }
}

float TestPlugin::ParseOpacity(const blink::WebString& string) {
  return static_cast<float>(atof(string.Utf8().data()));
}

bool TestPlugin::ParseBoolean(const blink::WebString& string) {
  static const base::NoDestructor<blink::WebString> kPrimitiveTrue("true");
  return string == *kPrimitiveTrue;
}

bool TestPlugin::InitScene() {
  if (!gl_) {
    return true;
  }

  float color[4];
  PremultiplyAlpha(scene_.background_color, scene_.opacity, color);

  gl_->GenFramebuffers(1, &framebuffer_);

  gl_->Viewport(0, 0, rect_.width(), rect_.height());
  gl_->Disable(GL_DEPTH_TEST);
  gl_->Disable(GL_SCISSOR_TEST);

  gl_->ClearColor(color[0], color[1], color[2], color[3]);

  gl_->Enable(GL_BLEND);
  gl_->BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

  return scene_.primitive != PrimitiveNone ? InitProgram() && InitPrimitive()
                                           : true;
}

void TestPlugin::DrawSceneGL() {
  gl_->Viewport(0, 0, rect_.width(), rect_.height());
  gl_->Clear(GL_COLOR_BUFFER_BIT);

  if (scene_.primitive != PrimitiveNone) {
    DrawPrimitive();
  }
}

void TestPlugin::DrawSceneSoftware(void* memory) {
  SkColor background_color = SkColorSetARGB(
      static_cast<uint8_t>(scene_.opacity * 255), scene_.background_color[0],
      scene_.background_color[1], scene_.background_color[2]);

  const SkImageInfo info =
      SkImageInfo::MakeN32Premul(rect_.width(), rect_.height());
  SkBitmap bitmap;
  bitmap.installPixels(info, memory, info.minRowBytes());
  SkCanvas canvas(bitmap, SkSurfaceProps{});
  canvas.clear(background_color);

  if (scene_.primitive != PrimitiveNone) {
    DCHECK_EQ(PrimitiveTriangle, scene_.primitive);
    SkColor foreground_color = SkColorSetARGB(
        static_cast<uint8_t>(scene_.opacity * 255), scene_.primitive_color[0],
        scene_.primitive_color[1], scene_.primitive_color[2]);
    const SkPath triangle_path = SkPath::Polygon(
        {
            {0.5f * rect_.width(), 0.9f * rect_.height()},
            {0.1f * rect_.width(), 0.1f * rect_.height()},
            {0.9f * rect_.width(), 0.1f * rect_.height()},
        },
        /*isClosed=*/false);

    SkPaint paint;
    paint.setColor(foreground_color);
    paint.setStyle(SkPaint::kFill_Style);
    canvas.drawPath(triangle_path, paint);
  }
}

void TestPlugin::DestroyScene() {
  if (scene_.program) {
    gl_->DeleteProgram(scene_.program);
    scene_.program = 0;
  }
  if (scene_.vbo) {
    gl_->DeleteBuffers(1, &scene_.vbo);
    scene_.vbo = 0;
  }

  if (framebuffer_) {
    gl_->DeleteFramebuffers(1, &framebuffer_);
    framebuffer_ = 0;
  }

  if (shared_image_) {
    shared_image_->UpdateDestructionSyncToken(sync_token_);
    shared_image_ = nullptr;
    sync_token_ = gpu::SyncToken();
  }
}

bool TestPlugin::InitProgram() {
  const std::string vertex_source(
      "attribute vec4 position;  \n"
      "void main() {             \n"
      "  gl_Position = position; \n"
      "}                         \n");

  const std::string fragment_source(
      "precision mediump float; \n"
      "uniform vec4 color;      \n"
      "void main() {            \n"
      "  gl_FragColor = color;  \n"
      "}                        \n");

  scene_.program = LoadProgram(vertex_source, fragment_source);
  if (!scene_.program) {
    return false;
  }

  scene_.color_location = gl_->GetUniformLocation(scene_.program, "color");
  scene_.position_location = gl_->GetAttribLocation(scene_.program, "position");
  return true;
}

bool TestPlugin::InitPrimitive() {
  DCHECK_EQ(scene_.primitive, PrimitiveTriangle);

  gl_->GenBuffers(1, &scene_.vbo);
  if (!scene_.vbo) {
    return false;
  }

  const float vertices[] = {0.0f, 0.8f, 0.0f,  -0.8f, -0.8f,
                            0.0f, 0.8f, -0.8f, 0.0f};
  gl_->BindBuffer(GL_ARRAY_BUFFER, scene_.vbo);
  gl_->BufferData(GL_ARRAY_BUFFER, sizeof(vertices), nullptr, GL_STATIC_DRAW);
  gl_->BufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
  return true;
}

void TestPlugin::DrawPrimitive() {
  DCHECK_EQ(scene_.primitive, PrimitiveTriangle);
  DCHECK(scene_.vbo);
  DCHECK(scene_.program);

  gl_->UseProgram(scene_.program);

  // Bind primitive color.
  float color[4];
  PremultiplyAlpha(scene_.primitive_color, scene_.opacity, color);
  gl_->Uniform4f(scene_.color_location, color[0], color[1], color[2], color[3]);

  // Bind primitive vertices.
  gl_->BindBuffer(GL_ARRAY_BUFFER, scene_.vbo);
  gl_->EnableVertexAttribArray(scene_.position_location);
  gl_->VertexAttribPointer(scene_.position_location, 3, GL_FLOAT, GL_FALSE, 0,
                           nullptr);
  gl_->DrawArrays(GL_TRIANGLES, 0, 3);
}

GLuint TestPlugin::LoadShader(GLenum type, const std::string& source) {
  GLuint shader = gl_->CreateShader(type);
  if (shader) {
    const GLchar* shader_data = source.data();
    GLint shader_length = strlen(shader_data);  // source.length();
    gl_->ShaderSource(shader, 1, &shader_data, &shader_length);
    gl_->CompileShader(shader);

    int compiled = 0;
    gl_->GetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if (!compiled) {
      gl_->DeleteShader(shader);
      shader = 0;
    }
  }
  return shader;
}

GLuint TestPlugin::LoadProgram(const std::string& vertex_source,
                               const std::string& fragment_source) {
  GLuint vertex_shader = LoadShader(GL_VERTEX_SHADER, vertex_source);
  GLuint fragment_shader = LoadShader(GL_FRAGMENT_SHADER, fragment_source);
  GLuint program = gl_->CreateProgram();
  if (vertex_shader && fragment_shader && program) {
    gl_->AttachShader(program, vertex_shader);
    gl_->AttachShader(program, fragment_shader);
    gl_->LinkProgram(program);

    int linked = 0;
    gl_->GetProgramiv(program, GL_LINK_STATUS, &linked);
    if (!linked) {
      gl_->DeleteProgram(program);
      program = 0;
    }
  }
  if (vertex_shader) {
    gl_->DeleteShader(vertex_shader);
  }
  if (fragment_shader) {
    gl_->DeleteShader(fragment_shader);
  }

  return program;
}

blink::WebInputEventResult TestPlugin::HandleInputEvent(
    const blink::WebCoalescedInputEvent& coalesced_event,
    ui::Cursor* cursor) {
  const blink::WebInputEvent& event = coalesced_event.Event();

  // Don't log gesture events, which aren't exposed to the Pepper API (see
  // ClassifyInputEvent in content/renderer/pepper/event_conversion.cc).
  if (blink::WebInputEvent::IsGestureEventType(event.GetType())) {
    return blink::WebInputEventResult::kNotHandled;
  }

  auto* frame_proxy = static_cast<WebFrameTestProxy*>(
      RenderFrame::FromWebFrame(web_local_frame_));
  const char* event_name = blink::WebInputEvent::GetName(event.GetType());
  if (!UNSAFE_TODO(strcmp(event_name, "")) ||
      !UNSAFE_TODO(strcmp(event_name, "Undefined"))) {
    event_name = "unknown";
  }
  test_runner_->PrintMessage(
      std::string("Plugin received event: ") + event_name + "\n", *frame_proxy);
  if (print_event_details_) {
    PrintEventDetails(test_runner_, *frame_proxy, event);
  }

  if (print_user_gesture_status_) {
    bool has_transient_user_activation =
        web_local_frame_->HasTransientUserActivation();
    test_runner_->PrintMessage(
        std::string("* ") + (has_transient_user_activation ? "" : "not ") +
            "handling user gesture\n",
        *frame_proxy);
  }

  if (is_persistent_) {
    test_runner_->PrintMessage(std::string("TestPlugin: isPersistent\n"),
                               *frame_proxy);
  }
  return blink::WebInputEventResult::kNotHandled;
}

bool TestPlugin::HandleDragStatusUpdate(blink::WebDragStatus drag_status,
                                        const blink::WebDragData& data,
                                        blink::DragOperationsMask mask,
                                        const gfx::PointF& position,
                                        const gfx::PointF& screen_position) {
  auto* frame_proxy = static_cast<WebFrameTestProxy*>(
      RenderFrame::FromWebFrame(web_local_frame_));
  const char* drag_status_name = nullptr;
  switch (drag_status) {
    case blink::kWebDragStatusEnter:
      drag_status_name = "DragEnter";
      break;
    case blink::kWebDragStatusOver:
      drag_status_name = "DragOver";
      break;
    case blink::kWebDragStatusLeave:
      drag_status_name = "DragLeave";
      break;
    case blink::kWebDragStatusDrop:
      drag_status_name = "DragDrop";
      break;
    case blink::kWebDragStatusUnknown:
      NOTREACHED();
  }
  test_runner_->PrintMessage(
      std::string("Plugin received event: ") + drag_status_name + "\n",
      *frame_proxy);
  return false;
}

TestPlugin* TestPlugin::Create(const blink::WebPluginParams& params,
                               TestRunner* test_runner,
                               blink::WebLocalFrame* frame) {
  return new TestPlugin(params, test_runner, frame);
}

const blink::WebString& TestPlugin::MimeType() {
  static const base::NoDestructor<blink::WebString> kMimeType(
      "application/x-webkit-test-webplugin");
  return *kMimeType;
}

const blink::WebString& TestPlugin::PluginPersistsMimeType() {
  static const base::NoDestructor<blink::WebString> kPluginPersistsMimeType(
      "application/x-webkit-test-webplugin-persistent");
  return *kPluginPersistsMimeType;
}

bool TestPlugin::IsSupportedMimeType(const blink::WebString& mime_type) {
  return mime_type == TestPlugin::MimeType() ||
         mime_type == PluginPersistsMimeType();
}

}  // namespace content