// Copyright 2012 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 "content/renderer/browser_plugin/browser_plugin.h"

#include <stddef.h>

#include <map>
#include <string>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/viz/common/features.h"
#include "components/viz/common/surfaces/surface_info.h"
#include "content/common/browser_plugin/browser_plugin_constants.h"
#include "content/common/browser_plugin/browser_plugin_messages.h"
#include "content/common/frame_messages.h"
#include "content/common/view_messages.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/browser_plugin_delegate.h"
#include "content/public/renderer/content_renderer_client.h"
#include "content/renderer/accessibility/render_accessibility_impl.h"
#include "content/renderer/browser_plugin/browser_plugin_manager.h"
#include "content/renderer/child_frame_compositing_helper.h"
#include "content/renderer/cursor_utils.h"
#include "content/renderer/drop_data_builder.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/sad_plugin.h"
#include "third_party/blink/public/platform/web_coalesced_input_event.h"
#include "third_party/blink/public/platform/web_gesture_event.h"
#include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/platform/web_mouse_wheel_event.h"
#include "third_party/blink/public/platform/web_rect.h"
#include "third_party/blink/public/web/web_ax_object.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_element.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_view.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/keycodes/keyboard_codes.h"

#if defined(USE_AURA) && !defined(TOOLKIT_QT)
#include "content/renderer/mus/mus_embedded_frame.h"
#include "content/renderer/mus/renderer_window_tree_client.h"
#endif

using blink::WebPluginContainer;
using blink::WebPoint;
using blink::WebRect;
using blink::WebURL;
using blink::WebVector;

namespace {

using PluginContainerMap =
    std::map<blink::WebPluginContainer*, content::BrowserPlugin*>;
static base::LazyInstance<PluginContainerMap>::DestructorAtExit
    g_plugin_container_map = LAZY_INSTANCE_INITIALIZER;

}  // namespace

namespace content {

// static
BrowserPlugin* BrowserPlugin::GetFromNode(blink::WebNode& node) {
  blink::WebPluginContainer* container = node.PluginContainer();
  if (!container)
    return nullptr;

  PluginContainerMap* browser_plugins = g_plugin_container_map.Pointer();
  PluginContainerMap::iterator it = browser_plugins->find(container);
  return it == browser_plugins->end() ? nullptr : it->second;
}

BrowserPlugin::BrowserPlugin(
    RenderFrame* render_frame,
    const base::WeakPtr<BrowserPluginDelegate>& delegate)
    : attached_(false),
      render_frame_routing_id_(render_frame->GetRoutingID()),
      container_(nullptr),
      guest_crashed_(false),
      plugin_focused_(false),
      visible_(true),
      mouse_locked_(false),
      ready_(false),
      browser_plugin_instance_id_(browser_plugin::kInstanceIDNone),
      delegate_(delegate),
      task_runner_(
          render_frame->GetTaskRunner(blink::TaskType::kInternalDefault)),
      weak_ptr_factory_(this) {
  browser_plugin_instance_id_ =
      BrowserPluginManager::Get()->GetNextInstanceID();

  if (delegate_)
    delegate_->SetElementInstanceID(browser_plugin_instance_id_);

  enable_surface_synchronization_ = features::IsSurfaceSynchronizationEnabled();
}

BrowserPlugin::~BrowserPlugin() {
  Detach();

  if (delegate_) {
    delegate_->DidDestroyElement();
    delegate_.reset();
  }

  BrowserPluginManager::Get()->RemoveBrowserPlugin(browser_plugin_instance_id_);
}

bool BrowserPlugin::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(BrowserPlugin, message)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_AdvanceFocus, OnAdvanceFocus)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_Attach_ACK, OnAttachACK)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_GuestGone, OnGuestGone)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_GuestReady, OnGuestReady)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_EnableAutoResize, OnEnableAutoResize)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_DisableAutoResize, OnDisableAutoResize)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_DidUpdateVisualProperties,
                        OnDidUpdateVisualProperties)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_SetCursor, OnSetCursor)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_SetMouseLock, OnSetMouseLock)
#if defined(USE_AURA) && !defined(TOOLKIT_QT)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_SetMusEmbedToken, OnSetMusEmbedToken)
#endif
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_ShouldAcceptTouchEvents,
                        OnShouldAcceptTouchEvents)
    IPC_MESSAGE_HANDLER(BrowserPluginMsg_FirstSurfaceActivation,
                        OnFirstSurfaceActivation)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void BrowserPlugin::OnFirstSurfaceActivation(
    int browser_plugin_instance_id,
    const viz::SurfaceInfo& surface_info) {
  if (!attached() || !features::IsAshInBrowserProcess())
    return;

  if (!enable_surface_synchronization_) {
    compositing_helper_->SetPrimarySurfaceId(
        surface_info.id(), screen_space_rect().size(),
        cc::DeadlinePolicy::UseDefaultDeadline());
  }
  compositing_helper_->SetFallbackSurfaceId(surface_info.id(),
                                            screen_space_rect().size());
}

void BrowserPlugin::UpdateDOMAttribute(const std::string& attribute_name,
                                       const base::string16& attribute_value) {
  if (!Container())
    return;

  blink::WebElement element = Container()->GetElement();
  element.SetAttribute(blink::WebString::FromUTF8(attribute_name),
                       blink::WebString::FromUTF16(attribute_value));
}

void BrowserPlugin::Attach() {
  Detach();

  BrowserPluginHostMsg_Attach_Params attach_params;
  attach_params.focused = ShouldGuestBeFocused();
  attach_params.visible = visible_;
  attach_params.frame_rect = screen_space_rect();
  attach_params.is_full_page_plugin = false;
  if (Container()) {
    blink::WebLocalFrame* frame = Container()->GetDocument().GetFrame();
    attach_params.is_full_page_plugin =
        frame->View()->MainFrame()->IsWebLocalFrame() &&
        frame->View()
            ->MainFrame()
            ->ToWebLocalFrame()
            ->GetDocument()
            .IsPluginDocument();
  }
#if defined(USE_AURA) && !defined(TOOLKIT_QT)
  if (pending_embed_token_) {
    base::Optional<base::UnguessableToken> embed_token =
        std::move(pending_embed_token_);
    CreateMusWindowAndEmbed(*embed_token);
  }
#endif

  BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_Attach(
      render_frame_routing_id_,
      browser_plugin_instance_id_,
      attach_params));

  // Post an update event to the associated accessibility object.
  auto* render_frame =
      RenderFrameImpl::FromRoutingID(render_frame_routing_id());
  if (render_frame && render_frame->render_accessibility() && Container()) {
    blink::WebElement element = Container()->GetElement();
    blink::WebAXObject ax_element = blink::WebAXObject::FromWebNode(element);
    if (!ax_element.IsDetached()) {
      render_frame->render_accessibility()->HandleAXEvent(
          ax_element, ax::mojom::Event::kChildrenChanged);
    }
  }

  sent_visual_properties_ = base::nullopt;
}

void BrowserPlugin::Detach() {
  if (!attached())
    return;

  attached_ = false;
  guest_crashed_ = false;
  embedded_layer_ = nullptr;

  BrowserPluginManager::Get()->Send(
      new BrowserPluginHostMsg_Detach(browser_plugin_instance_id_));
}

const viz::LocalSurfaceId& BrowserPlugin::GetLocalSurfaceId() const {
  return parent_local_surface_id_allocator_.GetCurrentLocalSurfaceId();
}

#if defined(USE_AURA) && !defined(TOOLKIT_QT)
void BrowserPlugin::CreateMusWindowAndEmbed(
    const base::UnguessableToken& embed_token) {
  RenderFrameImpl* render_frame =
      RenderFrameImpl::FromRoutingID(render_frame_routing_id_);
  if (!render_frame) {
    pending_embed_token_ = embed_token;
    return;
  }
  RendererWindowTreeClient* renderer_window_tree_client =
      RendererWindowTreeClient::Get(
          render_frame->GetRenderWidget()->routing_id());
  DCHECK(renderer_window_tree_client);
  mus_embedded_frame_ =
      renderer_window_tree_client->CreateMusEmbeddedFrame(this, embed_token);
  if (attached() && GetLocalSurfaceId().is_valid()) {
    mus_embedded_frame_->SetWindowBounds(GetLocalSurfaceId(),
                                         FrameRectInPixels());
  }
}
#endif

void BrowserPlugin::SynchronizeVisualProperties() {
  bool size_changed = !sent_visual_properties_ ||
                      sent_visual_properties_->auto_resize_enabled !=
                          pending_visual_properties_.auto_resize_enabled ||
                      sent_visual_properties_->min_size_for_auto_resize !=
                          pending_visual_properties_.min_size_for_auto_resize ||
                      sent_visual_properties_->max_size_for_auto_resize !=
                          pending_visual_properties_.max_size_for_auto_resize ||
                      sent_visual_properties_->local_frame_size !=
                          pending_visual_properties_.local_frame_size ||
                      sent_visual_properties_->screen_space_rect.size() !=
                          pending_visual_properties_.screen_space_rect.size();

  bool zoom_changed =
      !sent_visual_properties_ || sent_visual_properties_->zoom_level !=
                                      pending_visual_properties_.zoom_level;

  // Note that the following flag is true if the capture sequence number
  // actually changed. That is, it is false if we did not have
  // |sent_visual_properties_|, which is different from the other local flags
  // here.
  bool capture_sequence_number_changed =
      sent_visual_properties_ &&
      sent_visual_properties_->capture_sequence_number !=
          pending_visual_properties_.capture_sequence_number;

  bool synchronized_props_changed =
      !sent_visual_properties_ || size_changed || zoom_changed ||
      sent_visual_properties_->screen_info !=
          pending_visual_properties_.screen_info ||
      capture_sequence_number_changed;

  if (synchronized_props_changed)
    parent_local_surface_id_allocator_.GenerateId();

  if (enable_surface_synchronization_ && frame_sink_id_.is_valid()) {
    // If we're synchronizing surfaces, then use an infinite deadline to ensure
    // everything is synchronized.
    cc::DeadlinePolicy deadline =
        capture_sequence_number_changed
            ? cc::DeadlinePolicy::UseInfiniteDeadline()
            : cc::DeadlinePolicy::UseDefaultDeadline();
    compositing_helper_->SetPrimarySurfaceId(
        viz::SurfaceId(frame_sink_id_, GetLocalSurfaceId()),
        screen_space_rect().size(), deadline);
  }

  bool position_changed =
      !sent_visual_properties_ ||
      sent_visual_properties_->screen_space_rect.origin() !=
          pending_visual_properties_.screen_space_rect.origin();
  bool visual_properties_changed =
      synchronized_props_changed || position_changed;

  if (visual_properties_changed && attached()) {
    // Let the browser know about the updated view rect.
    BrowserPluginManager::Get()->Send(
        new BrowserPluginHostMsg_SynchronizeVisualProperties(
            browser_plugin_instance_id_, GetLocalSurfaceId(),
            pending_visual_properties_));
  }

  if (delegate_ && size_changed)
    delegate_->DidResizeElement(screen_space_rect().size());

  if (visual_properties_changed && attached())
    sent_visual_properties_ = pending_visual_properties_;

#if defined(USE_AURA) && !defined(TOOLKIT_QT)
  if (!features::IsAshInBrowserProcess() && mus_embedded_frame_) {
    mus_embedded_frame_->SetWindowBounds(GetLocalSurfaceId(),
                                         FrameRectInPixels());
  }
#endif
}

void BrowserPlugin::OnAdvanceFocus(int browser_plugin_instance_id,
                                   bool reverse) {
  auto* render_frame =
      RenderFrameImpl::FromRoutingID(render_frame_routing_id());
  auto* render_view = render_frame ? render_frame->GetRenderView() : nullptr;
  if (!render_view)
    return;
  render_view->GetWebView()->AdvanceFocus(reverse);
}

void BrowserPlugin::OnAttachACK(
    int browser_plugin_instance_id,
    const base::Optional<viz::LocalSurfaceId>& child_local_surface_id) {
  attached_ = true;
  if (child_local_surface_id)
    parent_local_surface_id_allocator_.Reset(*child_local_surface_id);
  SynchronizeVisualProperties();
}

void BrowserPlugin::OnGuestGone(int browser_plugin_instance_id) {
  guest_crashed_ = true;
  compositing_helper_->ChildFrameGone(screen_space_rect().size(),
                                      screen_info().device_scale_factor);
}

void BrowserPlugin::OnGuestReady(int browser_plugin_instance_id,
                                 const viz::FrameSinkId& frame_sink_id) {
  guest_crashed_ = false;
  frame_sink_id_ = frame_sink_id;
  sent_visual_properties_ = base::nullopt;
  SynchronizeVisualProperties();
}

void BrowserPlugin::OnDidUpdateVisualProperties(
    int browser_plugin_instance_id,
    const cc::RenderFrameMetadata& metadata) {
  if (!parent_local_surface_id_allocator_.UpdateFromChild(
          metadata.local_surface_id.value_or(viz::LocalSurfaceId()))) {
    return;
  }

  // The viz::LocalSurfaceId has changed so we call SynchronizeVisualProperties
  // here to embed it.
  SynchronizeVisualProperties();
}

void BrowserPlugin::OnEnableAutoResize(int browser_plugin_instance_id,
                                       const gfx::Size& min_size,
                                       const gfx::Size& max_size) {
  pending_visual_properties_.auto_resize_enabled = true;
  pending_visual_properties_.min_size_for_auto_resize = min_size;
  pending_visual_properties_.max_size_for_auto_resize = max_size;
  SynchronizeVisualProperties();
}

void BrowserPlugin::OnDisableAutoResize(int browser_plugin_instance_id) {
  pending_visual_properties_.auto_resize_enabled = false;
  SynchronizeVisualProperties();
}

void BrowserPlugin::OnSetCursor(int browser_plugin_instance_id,
                                const WebCursor& cursor) {
  cursor_ = cursor;
}

void BrowserPlugin::OnSetMouseLock(int browser_plugin_instance_id,
                                   bool enable) {
  RenderWidget* render_widget = GetMainWidget();
  if (enable) {
    if (mouse_locked_ || !render_widget)
      return;
    render_widget->mouse_lock_dispatcher()->LockMouse(this);
  } else {
    if (!mouse_locked_) {
      OnLockMouseACK(false);
      return;
    }
    if (!render_widget)
      return;
    render_widget->mouse_lock_dispatcher()->UnlockMouse(this);
  }
}

#if defined(USE_AURA) && !defined(TOOLKIT_QT)
void BrowserPlugin::OnSetMusEmbedToken(
    int instance_id,
    const base::UnguessableToken& embed_token) {
  DCHECK(!features::IsAshInBrowserProcess());
  if (!attached_) {
    pending_embed_token_ = embed_token;
  } else {
    pending_embed_token_.reset();
    CreateMusWindowAndEmbed(embed_token);
  }
}
#endif

void BrowserPlugin::OnShouldAcceptTouchEvents(int browser_plugin_instance_id,
                                              bool accept) {
  if (Container()) {
    Container()->RequestTouchEventType(
        accept ? WebPluginContainer::kTouchEventRequestTypeRaw
               : WebPluginContainer::kTouchEventRequestTypeNone);
  }
}

gfx::Rect BrowserPlugin::FrameRectInPixels() const {
  const float device_scale_factor = GetDeviceScaleFactor();
  return gfx::Rect(
      gfx::ScaleToFlooredPoint(screen_space_rect().origin(),
                               device_scale_factor),
      gfx::ScaleToCeiledSize(screen_space_rect().size(), device_scale_factor));
}

float BrowserPlugin::GetDeviceScaleFactor() const {
  return pending_visual_properties_.screen_info.device_scale_factor;
}

RenderWidget* BrowserPlugin::GetMainWidget() const {
  RenderFrameImpl* frame =
      RenderFrameImpl::FromRoutingID(render_frame_routing_id());
  if (frame) {
    RenderViewImpl* render_view =
        static_cast<RenderViewImpl*>(frame->GetRenderView());
    if (render_view) {
      return render_view->GetWidget();
    }
  }

  return nullptr;
}

void BrowserPlugin::UpdateInternalInstanceId() {
  // This is a way to notify observers of our attributes that this plugin is
  // available in render tree.
  // TODO(lazyboy): This should be done through the delegate instead. Perhaps
  // by firing an event from there.
  UpdateDOMAttribute(
      "internalinstanceid",
      base::UTF8ToUTF16(base::IntToString(browser_plugin_instance_id_)));
}

void BrowserPlugin::UpdateGuestFocusState(blink::WebFocusType focus_type) {
  if (!attached())
    return;
  bool should_be_focused = ShouldGuestBeFocused();
  BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_SetFocus(
      browser_plugin_instance_id_,
      should_be_focused,
      focus_type));
}

void BrowserPlugin::ScreenInfoChanged(const ScreenInfo& screen_info) {
  pending_visual_properties_.screen_info = screen_info;
  if (guest_crashed_) {
    // Update the sad page to match the current ScreenInfo.
    compositing_helper_->ChildFrameGone(screen_space_rect().size(),
                                        screen_info.device_scale_factor);
    return;
  }
  SynchronizeVisualProperties();
}

void BrowserPlugin::OnZoomLevelChanged(double zoom_level) {
  pending_visual_properties_.zoom_level = zoom_level;
  SynchronizeVisualProperties();
}

void BrowserPlugin::UpdateCaptureSequenceNumber(
    uint32_t capture_sequence_number) {
  pending_visual_properties_.capture_sequence_number = capture_sequence_number;
  SynchronizeVisualProperties();
}

bool BrowserPlugin::ShouldGuestBeFocused() const {
  bool embedder_focused = false;
  RenderWidget* render_widget = GetMainWidget();
  if (render_widget)
    embedder_focused = render_widget->has_focus();
  return plugin_focused_ && embedder_focused;
}

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

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

  container_ = container;
  container_->SetWantsWheelEvents(true);

  g_plugin_container_map.Get().insert(std::make_pair(container_, this));

  BrowserPluginManager::Get()->AddBrowserPlugin(
      browser_plugin_instance_id_, this);

  // Defer attach call so that if there's any pending browser plugin
  // destruction, then it can progress first.
  task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&BrowserPlugin::UpdateInternalInstanceId,
                                weak_ptr_factory_.GetWeakPtr()));

  compositing_helper_ = std::make_unique<ChildFrameCompositingHelper>(this);

  embedding_render_widget_ =
      RenderFrameImpl::FromWebFrame(container_->GetDocument().GetFrame())
          ->GetRenderWidget()
          ->AsWeakPtr();
  embedding_render_widget_->RegisterBrowserPlugin(this);

  return true;
}

void BrowserPlugin::Destroy() {
  if (embedding_render_widget_) {
    embedding_render_widget_->UnregisterBrowserPlugin(this);
    embedding_render_widget_ = nullptr;
  }

  if (container_) {
    // The BrowserPlugin's WebPluginContainer is deleted immediately after this
    // call returns, so let's not keep a reference to it around.
    g_plugin_container_map.Get().erase(container_);
  }

  container_ = nullptr;
  // Will be a no-op if the mouse is not currently locked.
  RenderWidget* render_widget = GetMainWidget();
  if (render_widget)
    render_widget->mouse_lock_dispatcher()->OnLockTargetDestroyed(this);

  task_runner_->DeleteSoon(FROM_HERE, this);
}

v8::Local<v8::Object> BrowserPlugin::V8ScriptableObject(v8::Isolate* isolate) {
  if (!delegate_)
    return v8::Local<v8::Object>();

  return delegate_->V8ScriptableObject(isolate);
}

bool BrowserPlugin::SupportsKeyboardFocus() const {
  return visible_;
}

bool BrowserPlugin::SupportsEditCommands() const {
  return true;
}

bool BrowserPlugin::SupportsInputMethod() const {
  return true;
}

bool BrowserPlugin::CanProcessDrag() const {
  return true;
}

// static
bool BrowserPlugin::ShouldForwardToBrowserPlugin(
    const IPC::Message& message) {
  return IPC_MESSAGE_CLASS(message) == BrowserPluginMsgStart;
}

void BrowserPlugin::UpdateGeometry(const WebRect& plugin_rect_in_viewport,
                                   const WebRect& clip_rect,
                                   const WebRect& unobscured_rect,
                                   bool is_visible) {
  // Ignore this call during teardown. If the embedding RenderWidget is gone,
  // don't bother sending new geometry to the child because it's not being shown
  // anymore.
  if (!embedding_render_widget_)
    return;

  // Convert the plugin_rect_in_viewport to window coordinates, which is css.
  WebRect rect_in_css(plugin_rect_in_viewport);

  // We will use the local root's RenderWidget to convert coordinates to Window.
  // If this local root belongs to an OOPIF, on the browser side we will have to
  // consider the displacement of the child frame in root window.
  embedding_render_widget_->ConvertViewportToWindow(&rect_in_css);
  gfx::Rect screen_space_rect = rect_in_css;

  if (!ready_) {
    if (delegate_)
      delegate_->Ready();
    ready_ = true;
  }

  pending_visual_properties_.screen_space_rect = screen_space_rect;
  if (guest_crashed_) {
    // Update the sad page to match the current ScreenInfo.
    compositing_helper_->ChildFrameGone(screen_space_rect.size(),
                                        screen_info().device_scale_factor);
    return;
  }
  SynchronizeVisualProperties();
}

void BrowserPlugin::UpdateFocus(bool focused, blink::WebFocusType focus_type) {
  plugin_focused_ = focused;
  UpdateGuestFocusState(focus_type);
}

void BrowserPlugin::UpdateVisibility(bool visible) {
  if (visible_ == visible)
    return;

  visible_ = visible;
  if (!attached())
    return;

  compositing_helper_->UpdateVisibility(visible);

  BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_SetVisibility(
      browser_plugin_instance_id_,
      visible));
}

blink::WebInputEventResult BrowserPlugin::HandleInputEvent(
    const blink::WebCoalescedInputEvent& coalesced_event,
    blink::WebCursorInfo& cursor_info) {
  const blink::WebInputEvent& event = coalesced_event.Event();
  if (guest_crashed_ || !attached())
    return blink::WebInputEventResult::kNotHandled;

  DCHECK(!blink::WebInputEvent::IsTouchEventType(event.GetType()));

  // With direct event routing turned on, BrowserPlugin should almost never
  // see wheel events any more. The two exceptions are (1) scroll bubbling, and
  // (2) synthetic mouse wheels generated by touchpad GesturePinch events on
  // Mac, which always go to the mainframe and thus may hit BrowserPlugin if
  // it's in a top-level embedder. In both cases we should indicate the event
  // as not handled (for GesturePinch on Mac, indicating the event has been
  // handled leads to touchpad pinch not working).
  if (event.GetType() == blink::WebInputEvent::kMouseWheel)
    return blink::WebInputEventResult::kNotHandled;

  if (blink::WebInputEvent::IsGestureEventType(event.GetType())) {
    auto gesture_event = static_cast<const blink::WebGestureEvent&>(event);
    DCHECK(blink::WebInputEvent::kGestureTapDown == event.GetType() ||
           gesture_event.resending_plugin_id == browser_plugin_instance_id_);

    // We shouldn't be forwarding GestureEvents to the Guest anymore. Indicate
    // we handled this only if it's a non-resent event.
    return gesture_event.resending_plugin_id == browser_plugin_instance_id_
               ? blink::WebInputEventResult::kNotHandled
               : blink::WebInputEventResult::kHandledApplication;
  }

  if (event.GetType() == blink::WebInputEvent::kContextMenu)
    return blink::WebInputEventResult::kHandledSuppressed;

  if (blink::WebInputEvent::IsKeyboardEventType(event.GetType()) &&
      !edit_commands_.empty()) {
    BrowserPluginManager::Get()->Send(
        new BrowserPluginHostMsg_SetEditCommandsForNextKeyEvent(
            browser_plugin_instance_id_,
            edit_commands_));
    edit_commands_.clear();
  }

  BrowserPluginManager::Get()->Send(
      new BrowserPluginHostMsg_HandleInputEvent(browser_plugin_instance_id_,
                                                &event));
  GetWebCursorInfo(cursor_, &cursor_info);

  // Although we forward this event to the guest, we don't report it as consumed
  // since other targets of this event in Blink never get that chance either.
  if (event.GetType() == blink::WebInputEvent::kGestureFlingStart)
    return blink::WebInputEventResult::kNotHandled;

  return blink::WebInputEventResult::kHandledApplication;
}

bool BrowserPlugin::HandleDragStatusUpdate(blink::WebDragStatus drag_status,
                                           const blink::WebDragData& drag_data,
                                           blink::WebDragOperationsMask mask,
                                           const blink::WebFloatPoint& position,
                                           const blink::WebFloatPoint& screen) {
  if (guest_crashed_ || !attached())
    return false;
  BrowserPluginManager::Get()->Send(
      new BrowserPluginHostMsg_DragStatusUpdate(
        browser_plugin_instance_id_,
        drag_status,
        DropDataBuilder::Build(drag_data),
        mask,
        position));
  return true;
}

void BrowserPlugin::DidReceiveResponse(const blink::WebURLResponse& response) {}

void BrowserPlugin::DidReceiveData(const char* data, int data_length) {
  if (delegate_)
    delegate_->PluginDidReceiveData(data, data_length);
}

void BrowserPlugin::DidFinishLoading() {
  if (delegate_)
    delegate_->PluginDidFinishLoading();
}

void BrowserPlugin::DidFailLoading(const blink::WebURLError& error) {}

bool BrowserPlugin::ExecuteEditCommand(const blink::WebString& name) {
  BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_ExecuteEditCommand(
      browser_plugin_instance_id_, name.Utf8()));

  // BrowserPlugin swallows edit commands.
  return true;
}

bool BrowserPlugin::ExecuteEditCommand(const blink::WebString& name,
                                       const blink::WebString& value) {
  edit_commands_.push_back(EditCommand(name.Utf8(), value.Utf8()));
  // BrowserPlugin swallows edit commands.
  return true;
}

bool BrowserPlugin::SetComposition(
    const blink::WebString& text,
    const blink::WebVector<blink::WebImeTextSpan>& ime_text_spans,
    const blink::WebRange& replacementRange,
    int selectionStart,
    int selectionEnd) {
  if (!attached())
    return false;

  BrowserPluginHostMsg_SetComposition_Params params;
  params.text = text.Utf16();
  for (size_t i = 0; i < ime_text_spans.size(); ++i) {
    params.ime_text_spans.push_back(ime_text_spans[i]);
  }

  params.replacement_range =
      replacementRange.IsNull()
          ? gfx::Range::InvalidRange()
          : gfx::Range(static_cast<uint32_t>(replacementRange.StartOffset()),
                       static_cast<uint32_t>(replacementRange.EndOffset()));
  params.selection_start = selectionStart;
  params.selection_end = selectionEnd;

  BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_ImeSetComposition(
      browser_plugin_instance_id_, params));
  // TODO(kochi): This assumes the IPC handling always succeeds.
  return true;
}

bool BrowserPlugin::CommitText(
    const blink::WebString& text,
    const blink::WebVector<blink::WebImeTextSpan>& ime_text_spans,
    const blink::WebRange& replacementRange,
    int relative_cursor_pos) {
  if (!attached())
    return false;

  std::vector<blink::WebImeTextSpan> std_ime_text_spans;
  for (size_t i = 0; i < ime_text_spans.size(); ++i) {
    std_ime_text_spans.push_back(ime_text_spans[i]);
  }
  gfx::Range replacement_range =
      replacementRange.IsNull()
          ? gfx::Range::InvalidRange()
          : gfx::Range(static_cast<uint32_t>(replacementRange.StartOffset()),
                       static_cast<uint32_t>(replacementRange.EndOffset()));

  BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_ImeCommitText(
      browser_plugin_instance_id_, text.Utf16(), std_ime_text_spans,
      replacement_range, relative_cursor_pos));
  // TODO(kochi): This assumes the IPC handling always succeeds.
  return true;
}

bool BrowserPlugin::FinishComposingText(
    blink::WebInputMethodController::ConfirmCompositionBehavior
        selection_behavior) {
  if (!attached())
    return false;
  bool keep_selection =
      (selection_behavior == blink::WebInputMethodController::kKeepSelection);
  BrowserPluginManager::Get()->Send(
      new BrowserPluginHostMsg_ImeFinishComposingText(
          browser_plugin_instance_id_, keep_selection));
  // TODO(kochi): This assumes the IPC handling always succeeds.
  return true;
}

void BrowserPlugin::ExtendSelectionAndDelete(int before, int after) {
  if (!attached())
    return;
  BrowserPluginManager::Get()->Send(
      new BrowserPluginHostMsg_ExtendSelectionAndDelete(
          browser_plugin_instance_id_,
          before,
          after));
}

void BrowserPlugin::OnLockMouseACK(bool succeeded) {
  mouse_locked_ = succeeded;
  BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_LockMouse_ACK(
      browser_plugin_instance_id_,
      succeeded));
}

void BrowserPlugin::OnMouseLockLost() {
  mouse_locked_ = false;
  BrowserPluginManager::Get()->Send(new BrowserPluginHostMsg_UnlockMouse_ACK(
      browser_plugin_instance_id_));
}

bool BrowserPlugin::HandleMouseLockedInputEvent(
    const blink::WebMouseEvent& event) {
  BrowserPluginManager::Get()->Send(
      new BrowserPluginHostMsg_HandleInputEvent(browser_plugin_instance_id_,
                                                &event));
  return true;
}

#if defined(USE_AURA) && !defined(TOOLKIT_QT)
void BrowserPlugin::OnMusEmbeddedFrameSurfaceChanged(
    const viz::SurfaceInfo& surface_info) {
  if (!attached_)
    return;

  compositing_helper_->SetFallbackSurfaceId(surface_info.id(),
                                            screen_space_rect().size());
}

void BrowserPlugin::OnMusEmbeddedFrameSinkIdAllocated(
    const viz::FrameSinkId& frame_sink_id) {
  // RendererWindowTreeClient should only call this when mus is hosting viz.
  DCHECK(!features::IsAshInBrowserProcess());
  OnGuestReady(browser_plugin_instance_id_, frame_sink_id);
}
#endif

cc::Layer* BrowserPlugin::GetLayer() {
  return embedded_layer_.get();
}

void BrowserPlugin::SetLayer(scoped_refptr<cc::Layer> layer,
                             bool prevent_contents_opaque_changes) {
  if (container_)
    container_->SetCcLayer(layer.get(), prevent_contents_opaque_changes);
  embedded_layer_ = std::move(layer);
}

SkBitmap* BrowserPlugin::GetSadPageBitmap() {
  return GetContentClient()->renderer()->GetSadWebViewBitmap();
}

}  // namespace content
