// Copyright (c) 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/browser/browser_plugin/browser_plugin_guest.h"

#include <stddef.h>

#include <algorithm>

#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/pickle.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "cc/surfaces/surface.h"
#include "cc/surfaces/surface_manager.h"
#include "content/browser/browser_plugin/browser_plugin_embedder.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/compositor/surface_utils.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/frame_host/render_frame_proxy_host.h"
#include "content/browser/frame_host/render_widget_host_view_child_frame.h"
#include "content/browser/frame_host/render_widget_host_view_guest.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view_guest.h"
#include "content/common/browser_plugin/browser_plugin_constants.h"
#include "content/common/browser_plugin/browser_plugin_messages.h"
#include "content/common/content_constants_internal.h"
#include "content/common/drag_messages.h"
#include "content/common/host_shared_bitmap_manager.h"
#include "content/common/input_messages.h"
#include "content/common/site_isolation_policy.h"
#include "content/common/text_input_state.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_plugin_guest_manager.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/guest_host.h"
#include "content/public/browser/guest_mode.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/user_metrics.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/drop_data.h"
#include "ui/events/blink/web_input_event_traits.h"
#include "ui/gfx/geometry/size_conversions.h"

#if defined(OS_MACOSX) && defined(USE_EXTERNAL_POPUP_MENU)
#include "content/browser/browser_plugin/browser_plugin_popup_menu_helper_mac.h"
#include "content/common/frame_messages.h"
#endif

namespace content {

class BrowserPluginGuest::EmbedderVisibilityObserver
    : public WebContentsObserver {
 public:
  explicit EmbedderVisibilityObserver(BrowserPluginGuest* guest)
      : WebContentsObserver(guest->embedder_web_contents()),
        browser_plugin_guest_(guest) {
  }

  ~EmbedderVisibilityObserver() override {}

  // WebContentsObserver implementation.
  void WasShown() override {
    browser_plugin_guest_->EmbedderVisibilityChanged(true);
  }

  void WasHidden() override {
    browser_plugin_guest_->EmbedderVisibilityChanged(false);
  }

 private:
  BrowserPluginGuest* browser_plugin_guest_;

  DISALLOW_COPY_AND_ASSIGN(EmbedderVisibilityObserver);
};

BrowserPluginGuest::BrowserPluginGuest(bool has_render_view,
                                       WebContentsImpl* web_contents,
                                       BrowserPluginGuestDelegate* delegate)
    : WebContentsObserver(web_contents),
      owner_web_contents_(nullptr),
      attached_(false),
      has_attached_since_surface_set_(false),
      browser_plugin_instance_id_(browser_plugin::kInstanceIDNone),
      focused_(false),
      mouse_locked_(false),
      pending_lock_request_(false),
      guest_visible_(false),
      embedder_visible_(true),
      is_full_page_plugin_(false),
      has_render_view_(has_render_view),
      is_in_destruction_(false),
      initialized_(false),
      guest_proxy_routing_id_(MSG_ROUTING_NONE),
      last_drag_status_(blink::WebDragStatusUnknown),
      seen_embedder_system_drag_ended_(false),
      seen_embedder_drag_source_ended_at_(false),
      ignore_dragged_url_(true),
      delegate_(delegate),
      can_use_cross_process_frames_(delegate->CanUseCrossProcessFrames()),
      weak_ptr_factory_(this) {
  DCHECK(web_contents);
  DCHECK(delegate);
  RecordAction(base::UserMetricsAction("BrowserPlugin.Guest.Create"));
  web_contents->SetBrowserPluginGuest(this);
  delegate->SetGuestHost(this);
}

int BrowserPluginGuest::GetGuestProxyRoutingID() {
  if (GuestMode::IsCrossProcessFrameGuest(GetWebContents())) {
    // We don't use the proxy to send postMessage in --site-per-process, since
    // we use the contentWindow directly from the frame element instead.
    return MSG_ROUTING_NONE;
  }

  if (guest_proxy_routing_id_ != MSG_ROUTING_NONE)
    return guest_proxy_routing_id_;

  // In order to enable the embedder to post messages to the
  // guest, we need to create a RenderFrameProxyHost in root node of guest
  // WebContents' frame tree (i.e., create a RenderFrameProxy in the embedder
  // process which can be used by the embedder to post messages to the guest).
  // The creation of RFPH for the reverse path, which enables the guest to post
  // messages to the embedder, will be postponed to when the embedder posts its
  // first message to the guest.
  //
  // TODO(fsamuel): Make sure this works for transferring guests across
  // owners in different processes. We probably need to clear the
  // |guest_proxy_routing_id_| and perform any necessary cleanup on Detach
  // to enable this.
  //
  // TODO(ekaramad): If the guest is embedded inside a cross-process <iframe>
  // (e.g., <embed>-ed PDF), the reverse proxy will not be created and the
  // posted message's source attribute will be null which in turn breaks the
  // two-way messaging between the guest and the embedder. We should either
  // create a RenderFrameProxyHost for the reverse path, or implement
  // MimeHandlerViewGuest using OOPIF (https://crbug.com/659750).
  SiteInstance* owner_site_instance = delegate_->GetOwnerSiteInstance();
  int proxy_routing_id = GetWebContents()
                             ->GetFrameTree()
                             ->root()
                             ->render_manager()
                             ->CreateRenderFrameProxy(owner_site_instance);
  guest_proxy_routing_id_ = RenderFrameProxyHost::FromID(
      owner_site_instance->GetProcess()->GetID(), proxy_routing_id)
          ->GetRenderViewHost()->GetRoutingID();

  return guest_proxy_routing_id_;
}

int BrowserPluginGuest::LoadURLWithParams(
    const NavigationController::LoadURLParams& load_params) {
  GetWebContents()->GetController().LoadURLWithParams(load_params);
  return GetGuestProxyRoutingID();
}

void BrowserPluginGuest::GuestResizeDueToAutoResize(const gfx::Size& new_size) {
  if (last_seen_view_size_ != new_size) {
    delegate_->GuestSizeChanged(new_size);
    last_seen_view_size_ = new_size;
  }
}

void BrowserPluginGuest::SizeContents(const gfx::Size& new_size) {
  GetWebContents()->GetView()->SizeContents(new_size);
}

void BrowserPluginGuest::WillDestroy() {
  is_in_destruction_ = true;
  owner_web_contents_ = nullptr;
  attached_ = false;
}

RenderWidgetHostImpl* BrowserPluginGuest::GetOwnerRenderWidgetHost() const {
  return static_cast<RenderWidgetHostImpl*>(
      delegate_->GetOwnerRenderWidgetHost());
}

void BrowserPluginGuest::Init() {
  if (initialized_)
    return;
  initialized_ = true;

  // TODO(fsamuel): Initiailization prior to attachment should be behind a
  // command line flag once we introduce experimental guest types that rely on
  // this functionality.
  if (!delegate_->CanRunInDetachedState())
    return;

  WebContentsImpl* owner_web_contents = static_cast<WebContentsImpl*>(
      delegate_->GetOwnerWebContents());
  owner_web_contents->CreateBrowserPluginEmbedderIfNecessary();
  InitInternal(BrowserPluginHostMsg_Attach_Params(), owner_web_contents);
}

base::WeakPtr<BrowserPluginGuest> BrowserPluginGuest::AsWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void BrowserPluginGuest::SetFocus(RenderWidgetHost* rwh,
                                  bool focused,
                                  blink::WebFocusType focus_type) {
  focused_ = focused;
  if (!rwh)
    return;

  if ((focus_type == blink::WebFocusTypeForward) ||
      (focus_type == blink::WebFocusTypeBackward)) {
    static_cast<RenderViewHostImpl*>(RenderViewHost::From(rwh))->
        SetInitialFocus(focus_type == blink::WebFocusTypeBackward);
  }
  rwh->Send(new InputMsg_SetFocus(rwh->GetRoutingID(), focused));
  if (!focused && mouse_locked_)
    OnUnlockMouse();

  // Restore the last seen state of text input to the view.
  RenderWidgetHostViewBase* rwhv = static_cast<RenderWidgetHostViewBase*>(
      rwh->GetView());
  SendTextInputTypeChangedToView(rwhv);
}

void BrowserPluginGuest::SetTooltipText(const base::string16& tooltip_text) {
  if (tooltip_text == current_tooltip_text_)
    return;
  current_tooltip_text_ = tooltip_text;

  SendMessageToEmbedder(base::MakeUnique<BrowserPluginMsg_SetTooltipText>(
      browser_plugin_instance_id_, tooltip_text));
}

bool BrowserPluginGuest::LockMouse(bool allowed) {
  if (!attached() || (mouse_locked_ == allowed))
    return false;

  return embedder_web_contents()->GotResponseToLockMouseRequest(allowed);
}

WebContentsImpl* BrowserPluginGuest::CreateNewGuestWindow(
    const WebContents::CreateParams& params) {
  WebContentsImpl* new_contents =
      static_cast<WebContentsImpl*>(delegate_->CreateNewGuestWindow(params));
  DCHECK(new_contents);
  return new_contents;
}

bool BrowserPluginGuest::OnMessageReceivedFromEmbedder(
    const IPC::Message& message) {
  RenderWidgetHostViewGuest* rwhv = static_cast<RenderWidgetHostViewGuest*>(
      web_contents()->GetRenderWidgetHostView());

  // Until the guest is attached, it should not be handling input events.
  if (attached() && rwhv &&
      rwhv->OnMessageReceivedFromEmbedder(message,
                                          GetOwnerRenderWidgetHost())) {
    return true;
  }

  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuest, message)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_Detach, OnDetach)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_DragStatusUpdate,
                        OnDragStatusUpdate)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ExecuteEditCommand,
                        OnExecuteEditCommand)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ExtendSelectionAndDelete,
                        OnExtendSelectionAndDelete)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ImeCommitText, OnImeCommitText)

    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ImeFinishComposingText,
                        OnImeFinishComposingText)

    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_ImeSetComposition,
                        OnImeSetComposition)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_LockMouse_ACK, OnLockMouseAck)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetEditCommandsForNextKeyEvent,
                        OnSetEditCommandsForNextKeyEvent)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetFocus, OnSetFocus)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SetVisibility, OnSetVisibility)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_UnlockMouse_ACK, OnUnlockMouseAck)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_UpdateGeometry, OnUpdateGeometry)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_SatisfySequence, OnSatisfySequence)
    IPC_MESSAGE_HANDLER(BrowserPluginHostMsg_RequireSequence, OnRequireSequence)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void BrowserPluginGuest::InitInternal(
    const BrowserPluginHostMsg_Attach_Params& params,
    WebContentsImpl* owner_web_contents) {
  focused_ = params.focused;
  OnSetFocus(browser_plugin::kInstanceIDNone,
             focused_,
             blink::WebFocusTypeNone);

  guest_visible_ = params.visible;
  UpdateVisibility();

  is_full_page_plugin_ = params.is_full_page_plugin;
  guest_window_rect_ = params.view_rect;

  if (owner_web_contents_ != owner_web_contents) {
    WebContentsViewGuest* new_view = nullptr;
    if (!GuestMode::IsCrossProcessFrameGuest(GetWebContents())) {
      new_view =
          static_cast<WebContentsViewGuest*>(GetWebContents()->GetView());
    }

    if (owner_web_contents_ && new_view)
      new_view->OnGuestDetached(owner_web_contents_->GetView());

    // Once a BrowserPluginGuest has an embedder WebContents, it's considered to
    // be attached.
    owner_web_contents_ = owner_web_contents;
    if (new_view)
      new_view->OnGuestAttached(owner_web_contents_->GetView());
  }

  RendererPreferences* renderer_prefs =
      GetWebContents()->GetMutableRendererPrefs();
  std::string guest_user_agent_override = renderer_prefs->user_agent_override;
  // Copy renderer preferences (and nothing else) from the embedder's
  // WebContents to the guest.
  //
  // For GTK and Aura this is necessary to get proper renderer configuration
  // values for caret blinking interval, colors related to selection and
  // focus.
  *renderer_prefs = *owner_web_contents_->GetMutableRendererPrefs();
  renderer_prefs->user_agent_override = guest_user_agent_override;

  // We would like the guest to report changes to frame names so that we can
  // update the BrowserPlugin's corresponding 'name' attribute.
  // TODO(fsamuel): Remove this once http://crbug.com/169110 is addressed.
  renderer_prefs->report_frame_name_changes = true;
  // Navigation is disabled in Chrome Apps. We want to make sure guest-initiated
  // navigations still continue to function inside the app.
  renderer_prefs->browser_handles_all_top_level_requests = false;
  // Disable "client blocked" error page for browser plugin.
  renderer_prefs->disable_client_blocked_error_page = true;

  embedder_visibility_observer_.reset(new EmbedderVisibilityObserver(this));

  DCHECK(GetWebContents()->GetRenderViewHost());

  // Initialize the device scale factor by calling |NotifyScreenInfoChanged|.
  auto* render_widget_host = RenderWidgetHostImpl::From(
      GetWebContents()->GetRenderViewHost()->GetWidget());
  render_widget_host->NotifyScreenInfoChanged();

  // TODO(chrishtr): this code is wrong. The navigate_on_drag_drop field will
  // be reset again the next time preferences are updated.
  WebPreferences prefs =
      GetWebContents()->GetRenderViewHost()->GetWebkitPreferences();
  prefs.navigate_on_drag_drop = false;
  GetWebContents()->GetRenderViewHost()->UpdateWebkitPreferences(prefs);
}

BrowserPluginGuest::~BrowserPluginGuest() {
}

// static
BrowserPluginGuest* BrowserPluginGuest::Create(
    WebContentsImpl* web_contents,
    BrowserPluginGuestDelegate* delegate) {
  return new BrowserPluginGuest(
      web_contents->HasOpener(), web_contents, delegate);
}

// static
bool BrowserPluginGuest::IsGuest(WebContentsImpl* web_contents) {
  return web_contents && web_contents->GetBrowserPluginGuest();
}

// static
bool BrowserPluginGuest::IsGuest(RenderViewHostImpl* render_view_host) {
  return render_view_host && IsGuest(
      static_cast<WebContentsImpl*>(WebContents::FromRenderViewHost(
          render_view_host)));
}

RenderWidgetHostView* BrowserPluginGuest::GetOwnerRenderWidgetHostView() {
  if (RenderWidgetHostImpl* owner = GetOwnerRenderWidgetHost())
    return owner->GetView();
  return nullptr;
}

void BrowserPluginGuest::UpdateVisibility() {
  OnSetVisibility(browser_plugin_instance_id(), visible());
}

BrowserPluginGuestManager*
BrowserPluginGuest::GetBrowserPluginGuestManager() const {
  return GetWebContents()->GetBrowserContext()->GetGuestManager();
}

void BrowserPluginGuest::EmbedderVisibilityChanged(bool visible) {
  embedder_visible_ = visible;
  UpdateVisibility();
}

void BrowserPluginGuest::PointerLockPermissionResponse(bool allow) {
  SendMessageToEmbedder(base::MakeUnique<BrowserPluginMsg_SetMouseLock>(
      browser_plugin_instance_id(), allow));
}

void BrowserPluginGuest::SetChildFrameSurface(
    const cc::SurfaceId& surface_id,
    const gfx::Size& frame_size,
    float scale_factor,
    const cc::SurfaceSequence& sequence) {
  has_attached_since_surface_set_ = false;
  SendMessageToEmbedder(base::MakeUnique<BrowserPluginMsg_SetChildFrameSurface>(
      browser_plugin_instance_id(), surface_id, frame_size, scale_factor,
      sequence));
}

void BrowserPluginGuest::OnSatisfySequence(
    int instance_id,
    const cc::SurfaceSequence& sequence) {
  std::vector<uint32_t> sequences;
  sequences.push_back(sequence.sequence);
  cc::SurfaceManager* manager = GetSurfaceManager();
  manager->DidSatisfySequences(sequence.frame_sink_id, &sequences);
}

void BrowserPluginGuest::OnRequireSequence(
    int instance_id,
    const cc::SurfaceId& id,
    const cc::SurfaceSequence& sequence) {
  cc::SurfaceManager* manager = GetSurfaceManager();
  cc::Surface* surface = manager->GetSurfaceForId(id);
  if (!surface) {
    LOG(ERROR) << "Attempting to require callback on nonexistent surface";
    return;
  }
  surface->AddDestructionDependency(sequence);
}

bool BrowserPluginGuest::HandleFindForEmbedder(
    int request_id,
    const base::string16& search_text,
    const blink::WebFindOptions& options) {
  return delegate_->HandleFindForEmbedder(request_id, search_text, options);
}

bool BrowserPluginGuest::HandleStopFindingForEmbedder(StopFindAction action) {
  return delegate_->HandleStopFindingForEmbedder(action);
}

void BrowserPluginGuest::ResendEventToEmbedder(
    const blink::WebInputEvent& event) {
  if (!attached() || !owner_web_contents_)
    return;

  DCHECK(browser_plugin_instance_id_);
  RenderWidgetHostViewBase* view =
      static_cast<RenderWidgetHostViewBase*>(GetOwnerRenderWidgetHostView());

  gfx::Vector2d offset_from_embedder = guest_window_rect_.OffsetFromOrigin();
  if (event.type == blink::WebInputEvent::GestureScrollUpdate) {
    blink::WebGestureEvent resent_gesture_event;
    memcpy(&resent_gesture_event, &event, sizeof(blink::WebGestureEvent));
    resent_gesture_event.x += offset_from_embedder.x();
    resent_gesture_event.y += offset_from_embedder.y();
    // Mark the resend source with the browser plugin's instance id, so the
    // correct browser_plugin will know to ignore the event.
    resent_gesture_event.resendingPluginId = browser_plugin_instance_id_;
    ui::LatencyInfo latency_info =
        ui::WebInputEventTraits::CreateLatencyInfoForWebGestureEvent(
            resent_gesture_event);
    view->ProcessGestureEvent(resent_gesture_event, latency_info);
  } else if (event.type == blink::WebInputEvent::MouseWheel) {
    blink::WebMouseWheelEvent resent_wheel_event;
    memcpy(&resent_wheel_event, &event, sizeof(blink::WebMouseWheelEvent));
    resent_wheel_event.x += offset_from_embedder.x();
    resent_wheel_event.y += offset_from_embedder.y();
    resent_wheel_event.resendingPluginId = browser_plugin_instance_id_;
    // TODO(wjmaclean): Initialize latency info correctly for OOPIFs.
    // https://crbug.com/613628
    ui::LatencyInfo latency_info(ui::SourceEventType::WHEEL);
    view->ProcessMouseWheelEvent(resent_wheel_event, latency_info);
  } else {
    NOTIMPLEMENTED();
  }
}

WebContentsImpl* BrowserPluginGuest::GetWebContents() const {
  return static_cast<WebContentsImpl*>(web_contents());
}

gfx::Point BrowserPluginGuest::GetScreenCoordinates(
    const gfx::Point& relative_position) const {
  if (!attached())
    return relative_position;

  gfx::Point screen_pos(relative_position);
  screen_pos += guest_window_rect_.OffsetFromOrigin();
  return screen_pos;
}

void BrowserPluginGuest::SendMessageToEmbedder(
    std::unique_ptr<IPC::Message> msg) {
  // During tests, attache() may be true when there is no owner_web_contents_;
  // in this case just queue any messages we receive.
  if (!attached() || !owner_web_contents_) {
    // Some pages such as data URLs, javascript URLs, and about:blank
    // do not load external resources and so they load prior to attachment.
    // As a result, we must save all these IPCs until attachment and then
    // forward them so that the embedder gets a chance to see and process
    // the load events.
    pending_messages_.push_back(std::move(msg));
    return;
  }

  // If the guest is inside a cross-process frame, it is possible to get here
  // after the owner frame is detached. Then, the owner RenderWidgetHost will
  // be null and the message is dropped.
  if (auto* rwh = GetOwnerRenderWidgetHost())
    rwh->Send(msg.release());
}

void BrowserPluginGuest::DragSourceEndedAt(int client_x,
                                           int client_y,
                                           int screen_x,
                                           int screen_y,
                                           blink::WebDragOperation operation) {
  web_contents()->GetRenderViewHost()->GetWidget()->DragSourceEndedAt(
      gfx::Point(client_x, client_y),
      gfx::Point(screen_x, screen_y),
      operation);
  seen_embedder_drag_source_ended_at_ = true;
  EndSystemDragIfApplicable();
}

void BrowserPluginGuest::EndSystemDragIfApplicable() {
  // Ideally we'd want either WebDragStatusDrop or WebDragStatusLeave...
  // Call guest RVH->DragSourceSystemDragEnded() correctly on the guest where
  // the drag was initiated. Calling DragSourceSystemDragEnded() correctly
  // means we call it in all cases and also make sure we only call it once.
  // This ensures that the input state of the guest stays correct, otherwise
  // it will go stale and won't accept any further input events.
  //
  // The strategy used here to call DragSourceSystemDragEnded() on the RVH
  // is when the following conditions are met:
  //   a. Embedder has seen SystemDragEnded()
  //   b. Embedder has seen DragSourceEndedAt()
  //   c. The guest has seen some drag status update other than
  //      WebDragStatusUnknown. Note that this step should ideally be done
  //      differently: The guest has seen at least one of
  //      {WebDragStatusOver, WebDragStatusDrop}. However, if a user drags
  //      a source quickly outside of <webview> bounds, then the
  //      BrowserPluginGuest never sees any of these drag status updates,
  //      there we just check whether we've seen any drag status update or
  //      not.
  if (last_drag_status_ != blink::WebDragStatusOver &&
      seen_embedder_drag_source_ended_at_ && seen_embedder_system_drag_ended_) {
    RenderViewHostImpl* guest_rvh = static_cast<RenderViewHostImpl*>(
        GetWebContents()->GetRenderViewHost());
    guest_rvh->GetWidget()->DragSourceSystemDragEnded();
    last_drag_status_ = blink::WebDragStatusUnknown;
    seen_embedder_system_drag_ended_ = false;
    seen_embedder_drag_source_ended_at_ = false;
    ignore_dragged_url_ = true;
  }
}

void BrowserPluginGuest::EmbedderSystemDragEnded() {
  seen_embedder_system_drag_ended_ = true;
  EndSystemDragIfApplicable();
}

// TODO(wjmaclean): Replace this approach with ones based on std::function
// as in https://codereview.chromium.org/1404353004/ once all Chrome platforms
// support this. https://crbug.com/544212
std::unique_ptr<IPC::Message> BrowserPluginGuest::UpdateInstanceIdIfNecessary(
    std::unique_ptr<IPC::Message> msg) const {
  DCHECK(msg.get());

  int msg_browser_plugin_instance_id = browser_plugin::kInstanceIDNone;
  base::PickleIterator iter(*msg.get());
  if (!iter.ReadInt(&msg_browser_plugin_instance_id) ||
      msg_browser_plugin_instance_id != browser_plugin::kInstanceIDNone) {
    return msg;
  }

  // This method may be called with no browser_plugin_instance_id in tests.
  if (!browser_plugin_instance_id())
    return msg;

  std::unique_ptr<IPC::Message> new_msg(
      new IPC::Message(msg->routing_id(), msg->type(), msg->priority()));
  new_msg->WriteInt(browser_plugin_instance_id());

  // Copy remaining payload from original message.
  // TODO(wjmaclean): it would be nice if IPC::PickleIterator had a method
  // like 'RemainingBytes()' so that we don't have to include implementation-
  // specific details like sizeof() in the next line.
  DCHECK(msg->payload_size() > sizeof(int));
  size_t remaining_bytes = msg->payload_size() - sizeof(int);
  const char* data = nullptr;
  bool read_success = iter.ReadBytes(&data, remaining_bytes);
  CHECK(read_success)
      << "Unexpected failure reading remaining IPC::Message payload.";
  bool write_success = new_msg->WriteBytes(data, remaining_bytes);
  CHECK(write_success)
      << "Unexpected failure writing remaining IPC::Message payload.";

  return new_msg;
}

void BrowserPluginGuest::SendQueuedMessages() {
  if (!attached())
    return;

  while (!pending_messages_.empty()) {
    std::unique_ptr<IPC::Message> message_ptr =
        std::move(pending_messages_.front());
    pending_messages_.pop_front();
    SendMessageToEmbedder(UpdateInstanceIdIfNecessary(std::move(message_ptr)));
  }
}

void BrowserPluginGuest::SendTextInputTypeChangedToView(
    RenderWidgetHostViewBase* guest_rwhv) {
  if (!guest_rwhv)
    return;

  if (!owner_web_contents_) {
    // If we were showing an interstitial, then we can end up here during
    // embedder shutdown or when the embedder navigates to a different page.
    // The call stack is roughly:
    // BrowserPluginGuest::SetFocus()
    // content::InterstitialPageImpl::Hide()
    // content::InterstitialPageImpl::DontProceed().
    //
    // TODO(lazyboy): Write a WebUI test once http://crbug.com/463674 is fixed.
    return;
  }

  if (last_text_input_state_.get())
    guest_rwhv->TextInputStateChanged(*last_text_input_state_);
}

void BrowserPluginGuest::DidCommitProvisionalLoadForFrame(
    RenderFrameHost* render_frame_host,
    const GURL& url,
    ui::PageTransition transition_type) {
  RecordAction(base::UserMetricsAction("BrowserPlugin.Guest.DidNavigate"));
}

void BrowserPluginGuest::RenderViewReady() {
  RenderViewHost* rvh = GetWebContents()->GetRenderViewHost();
  // TODO(fsamuel): Investigate whether it's possible to update state earlier
  // here (see http://crbug.com/158151).
  Send(new InputMsg_SetFocus(routing_id(), focused_));
  UpdateVisibility();

  // In case we've created a new guest render process after a crash, let the
  // associated BrowserPlugin know. We only need to send this if we're attached,
  // as guest_crashed_ is cleared automatically on attach anyways.
  if (attached()) {
    SendMessageToEmbedder(base::MakeUnique<BrowserPluginMsg_GuestReady>(
        browser_plugin_instance_id()));
  }

  RenderWidgetHostImpl::From(rvh->GetWidget())
      ->set_hung_renderer_delay(
          base::TimeDelta::FromMilliseconds(kHungRendererDelayMs));
}

void BrowserPluginGuest::RenderProcessGone(base::TerminationStatus status) {
  SendMessageToEmbedder(base::MakeUnique<BrowserPluginMsg_GuestGone>(
      browser_plugin_instance_id()));
  switch (status) {
#if defined(OS_CHROMEOS)
    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
#endif
    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
      RecordAction(base::UserMetricsAction("BrowserPlugin.Guest.Killed"));
      break;
    case base::TERMINATION_STATUS_PROCESS_CRASHED:
      RecordAction(base::UserMetricsAction("BrowserPlugin.Guest.Crashed"));
      break;
    case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
      RecordAction(
          base::UserMetricsAction("BrowserPlugin.Guest.AbnormalDeath"));
      break;
    case base::TERMINATION_STATUS_LAUNCH_FAILED:
      RecordAction(base::UserMetricsAction("BrowserPlugin.Guest.LaunchFailed"));
      break;
    default:
      break;
  }
}

// static
bool BrowserPluginGuest::ShouldForwardToBrowserPluginGuest(
    const IPC::Message& message) {
  return (message.type() != BrowserPluginHostMsg_Attach::ID) &&
      (IPC_MESSAGE_CLASS(message) == BrowserPluginMsgStart);
}

bool BrowserPluginGuest::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  // In --site-per-process, we do not need most of BrowserPluginGuest to drive
  // inner WebContents.
  // Right now InputHostMsg_ImeCompositionRangeChanged hits NOTREACHED() in
  // RWHVChildFrame, so we're disabling message handling entirely here.
  // TODO(lazyboy): Fix this as part of http://crbug.com/330264. The required
  // parts of code from this class should be extracted to a separate class for
  // --site-per-process.
  if (GuestMode::IsCrossProcessFrameGuest(GetWebContents()))
    return false;

  IPC_BEGIN_MESSAGE_MAP(BrowserPluginGuest, message)
    IPC_MESSAGE_HANDLER(InputHostMsg_ImeCancelComposition,
                        OnImeCancelComposition)
#if defined(OS_MACOSX) || defined(USE_AURA)
    IPC_MESSAGE_HANDLER(InputHostMsg_ImeCompositionRangeChanged,
                        OnImeCompositionRangeChanged)
#endif
    IPC_MESSAGE_HANDLER(ViewHostMsg_HasTouchEventHandlers,
                        OnHasTouchEventHandlers)
    IPC_MESSAGE_HANDLER(ViewHostMsg_LockMouse, OnLockMouse)
    IPC_MESSAGE_HANDLER(ViewHostMsg_ShowWidget, OnShowWidget)
    IPC_MESSAGE_HANDLER(ViewHostMsg_TakeFocus, OnTakeFocus)
    IPC_MESSAGE_HANDLER(ViewHostMsg_TextInputStateChanged,
                        OnTextInputStateChanged)
    IPC_MESSAGE_HANDLER(ViewHostMsg_UnlockMouse, OnUnlockMouse)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

bool BrowserPluginGuest::OnMessageReceived(const IPC::Message& message,
                                           RenderFrameHost* render_frame_host) {
  // This will eventually be the home for more IPC handlers that depend on
  // RenderFrameHost. Until more are moved here, though, the IPC_* macros won't
  // compile if there are no handlers for a platform. So we have both #if guards
  // around the whole thing (unfortunate but temporary), and #if guards where
  // they belong, only around the one IPC handler. TODO(avi): Move more of the
  // frame-based handlers to this function and remove the outer #if layer.
#if defined(OS_MACOSX) && defined(USE_EXTERNAL_POPUP_MENU)
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(BrowserPluginGuest, message,
                                   render_frame_host)
#if defined(OS_MACOSX)
    // MacOS X creates and populates platform-specific select drop-down menus
    // whereas other platforms merely create a popup window that the guest
    // renderer process paints inside.
    IPC_MESSAGE_HANDLER(FrameHostMsg_ShowPopup, OnShowPopup)
#endif
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
#else
  return false;
#endif
}

void BrowserPluginGuest::Attach(
    int browser_plugin_instance_id,
    WebContentsImpl* embedder_web_contents,
    const BrowserPluginHostMsg_Attach_Params& params) {
  browser_plugin_instance_id_ = browser_plugin_instance_id;
  // The guest is owned by the embedder. Attach is queued up so we cannot
  // change embedders before attach completes. If the embedder goes away,
  // so does the guest and so we will never call WillAttachComplete because
  // we have a weak ptr.
  delegate_->WillAttach(embedder_web_contents, browser_plugin_instance_id,
                        params.is_full_page_plugin,
                        base::Bind(&BrowserPluginGuest::OnWillAttachComplete,
                                   weak_ptr_factory_.GetWeakPtr(),
                                   embedder_web_contents, params));
}

void BrowserPluginGuest::OnWillAttachComplete(
    WebContentsImpl* embedder_web_contents,
    const BrowserPluginHostMsg_Attach_Params& params) {
  // If a RenderView has already been created for this new window, then we need
  // to initialize the browser-side state now so that the RenderFrameHostManager
  // does not create a new RenderView on navigation.
  if (has_render_view_) {
    // This will trigger a callback to RenderViewReady after a round-trip IPC.
    static_cast<RenderViewHostImpl*>(GetWebContents()->GetRenderViewHost())
        ->GetWidget()
        ->Init();
    GetWebContents()->GetMainFrame()->Init();
    WebContentsViewGuest* web_contents_view =
        static_cast<WebContentsViewGuest*>(GetWebContents()->GetView());
    if (!web_contents()->GetRenderViewHost()->GetWidget()->GetView()) {
      web_contents_view->CreateViewForWidget(
          web_contents()->GetRenderViewHost()->GetWidget(), true);
    }
  }

  InitInternal(params, embedder_web_contents);

  attached_ = true;
  has_attached_since_surface_set_ = true;
  SendQueuedMessages();

  delegate_->DidAttach(GetGuestProxyRoutingID());
  RenderWidgetHostViewGuest* rwhv = static_cast<RenderWidgetHostViewGuest*>(
      web_contents()->GetRenderWidgetHostView());
  if (rwhv)
    rwhv->RegisterFrameSinkId();
  has_render_view_ = true;

  RecordAction(base::UserMetricsAction("BrowserPlugin.Guest.Attached"));
}

void BrowserPluginGuest::OnDetach(int browser_plugin_instance_id) {
  if (!attached())
    return;

  // This tells BrowserPluginGuest to queue up all IPCs to BrowserPlugin until
  // it's attached again.
  attached_ = false;

  RenderWidgetHostViewChildFrame* rwhv =
      static_cast<RenderWidgetHostViewChildFrame*>(
          web_contents()->GetRenderWidgetHostView());
  // If the guest is terminated, our host may already be gone.
  if (rwhv)
    rwhv->UnregisterFrameSinkId();

  delegate_->DidDetach();
}

void BrowserPluginGuest::OnDragStatusUpdate(int browser_plugin_instance_id,
                                            blink::WebDragStatus drag_status,
                                            const DropData& drop_data,
                                            blink::WebDragOperationsMask mask,
                                            const gfx::Point& location) {
  RenderViewHost* host = GetWebContents()->GetRenderViewHost();
  auto* embedder = owner_web_contents_->GetBrowserPluginEmbedder();
  DropData filtered_data(drop_data);
  // TODO(paulmeyer): This will need to target the correct specific
  // RenderWidgetHost to work with OOPIFs. See crbug.com/647249.
  RenderWidgetHost* widget = host->GetWidget();
  widget->FilterDropData(&filtered_data);
  switch (drag_status) {
    case blink::WebDragStatusEnter:
      widget->DragTargetDragEnter(filtered_data, location, location, mask,
                                  drop_data.key_modifiers);
      // Only track the URL being dragged over the guest if the link isn't
      // coming from the guest.
      if (!embedder->DragEnteredGuest(this))
        ignore_dragged_url_ = false;
      break;
    case blink::WebDragStatusOver:
      widget->DragTargetDragOver(location, location, mask,
                                 drop_data.key_modifiers);
      break;
    case blink::WebDragStatusLeave:
      embedder->DragLeftGuest(this);
      widget->DragTargetDragLeave();
      ignore_dragged_url_ = true;
      break;
    case blink::WebDragStatusDrop:
      widget->DragTargetDrop(filtered_data, location, location,
                             drop_data.key_modifiers);

      if (!ignore_dragged_url_ && filtered_data.url.is_valid())
        delegate_->DidDropLink(filtered_data.url);
      ignore_dragged_url_ = true;
      break;
    case blink::WebDragStatusUnknown:
      ignore_dragged_url_ = true;
      NOTREACHED();
  }
  last_drag_status_ = drag_status;
  EndSystemDragIfApplicable();
}

void BrowserPluginGuest::OnExecuteEditCommand(int browser_plugin_instance_id,
                                              const std::string& name) {
  RenderFrameHost* focused_frame = web_contents()->GetFocusedFrame();
  if (!focused_frame)
    return;

  focused_frame->Send(new InputMsg_ExecuteNoValueEditCommand(
      focused_frame->GetRoutingID(), name));
}

void BrowserPluginGuest::OnImeSetComposition(
    int browser_plugin_instance_id,
    const std::string& text,
    const std::vector<blink::WebCompositionUnderline>& underlines,
    int selection_start,
    int selection_end) {
  Send(new InputMsg_ImeSetComposition(routing_id(),
                                      base::UTF8ToUTF16(text), underlines,
                                      gfx::Range::InvalidRange(),
                                      selection_start, selection_end));
}

void BrowserPluginGuest::OnImeCommitText(int browser_plugin_instance_id,
                                         const std::string& text,
                                         int relative_cursor_pos) {
  Send(new InputMsg_ImeCommitText(routing_id(), base::UTF8ToUTF16(text),
                                  gfx::Range::InvalidRange(),
                                  relative_cursor_pos));
}

void BrowserPluginGuest::OnImeFinishComposingText(bool keep_selection) {
  Send(new InputMsg_ImeFinishComposingText(routing_id(), keep_selection));
}

void BrowserPluginGuest::OnExtendSelectionAndDelete(
    int browser_plugin_instance_id,
    int before,
    int after) {
  RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
      web_contents()->GetFocusedFrame());
  if (rfh)
    rfh->ExtendSelectionAndDelete(before, after);
}

void BrowserPluginGuest::OnLockMouse(bool user_gesture,
                                     bool last_unlocked_by_target,
                                     bool privileged) {
  if (pending_lock_request_) {
    // Immediately reject the lock because only one pointerLock may be active
    // at a time.
    Send(new ViewMsg_LockMouse_ACK(routing_id(), false));
    return;
  }

  pending_lock_request_ = true;

  delegate_->RequestPointerLockPermission(
      user_gesture,
      last_unlocked_by_target,
      base::Bind(&BrowserPluginGuest::PointerLockPermissionResponse,
                 weak_ptr_factory_.GetWeakPtr()));
}

void BrowserPluginGuest::OnLockMouseAck(int browser_plugin_instance_id,
                                        bool succeeded) {
  Send(new ViewMsg_LockMouse_ACK(routing_id(), succeeded));
  pending_lock_request_ = false;
  if (succeeded)
    mouse_locked_ = true;
}

void BrowserPluginGuest::OnSetFocus(int browser_plugin_instance_id,
                                    bool focused,
                                    blink::WebFocusType focus_type) {
  RenderWidgetHostView* rwhv = web_contents()->GetRenderWidgetHostView();
  RenderWidgetHost* rwh = rwhv ? rwhv->GetRenderWidgetHost() : nullptr;
  SetFocus(rwh, focused, focus_type);
}

void BrowserPluginGuest::OnSetEditCommandsForNextKeyEvent(
    int browser_plugin_instance_id,
    const std::vector<EditCommand>& edit_commands) {
  Send(new InputMsg_SetEditCommandsForNextKeyEvent(routing_id(),
                                                   edit_commands));
}

void BrowserPluginGuest::OnSetVisibility(int browser_plugin_instance_id,
                                         bool visible) {
  // For OOPIF-<webivew>, the remote frame will handle visibility state.
  if (GuestMode::IsCrossProcessFrameGuest(GetWebContents()))
    return;

  guest_visible_ = visible;
  if (embedder_visible_ && guest_visible_)
    GetWebContents()->WasShown();
  else
    GetWebContents()->WasHidden();
}

void BrowserPluginGuest::OnUnlockMouse() {
  SendMessageToEmbedder(base::MakeUnique<BrowserPluginMsg_SetMouseLock>(
      browser_plugin_instance_id(), false));
}

void BrowserPluginGuest::OnUnlockMouseAck(int browser_plugin_instance_id) {
  // mouse_locked_ could be false here if the lock attempt was cancelled due
  // to window focus, or for various other reasons before the guest was informed
  // of the lock's success.
  if (mouse_locked_)
    Send(new ViewMsg_MouseLockLost(routing_id()));
  mouse_locked_ = false;
}

void BrowserPluginGuest::OnUpdateGeometry(int browser_plugin_instance_id,
                                          const gfx::Rect& view_rect) {
  // The plugin has moved within the embedder without resizing or the
  // embedder/container's view rect changing.
  guest_window_rect_ = view_rect;
  GetWebContents()->SendScreenRects();
}

void BrowserPluginGuest::OnHasTouchEventHandlers(bool accept) {
  SendMessageToEmbedder(
      base::MakeUnique<BrowserPluginMsg_ShouldAcceptTouchEvents>(
          browser_plugin_instance_id(), accept));
}

#if defined(OS_MACOSX) && defined(USE_EXTERNAL_POPUP_MENU)
void BrowserPluginGuest::OnShowPopup(
    RenderFrameHost* render_frame_host,
    const FrameHostMsg_ShowPopup_Params& params) {
  gfx::Rect translated_bounds(params.bounds);
  translated_bounds.Offset(guest_window_rect_.OffsetFromOrigin());
  BrowserPluginPopupMenuHelper popup_menu_helper(
      owner_web_contents_->GetMainFrame(), render_frame_host);
  popup_menu_helper.ShowPopupMenu(translated_bounds,
                                  params.item_height,
                                  params.item_font_size,
                                  params.selected_item,
                                  params.popup_items,
                                  params.right_aligned,
                                  params.allow_multiple_selection);
}
#endif

void BrowserPluginGuest::OnShowWidget(int route_id,
                                      const gfx::Rect& initial_rect) {
  int process_id = GetWebContents()->GetRenderProcessHost()->GetID();
  GetWebContents()->ShowCreatedWidget(process_id, route_id, initial_rect);
}

void BrowserPluginGuest::OnTakeFocus(bool reverse) {
  SendMessageToEmbedder(base::MakeUnique<BrowserPluginMsg_AdvanceFocus>(
      browser_plugin_instance_id(), reverse));
}

void BrowserPluginGuest::OnTextInputStateChanged(const TextInputState& params) {
  // Save the state of text input so we can restore it on focus.
  last_text_input_state_.reset(new TextInputState(params));

  SendTextInputTypeChangedToView(
      static_cast<RenderWidgetHostViewBase*>(
          web_contents()->GetRenderWidgetHostView()));
}

void BrowserPluginGuest::OnImeCancelComposition() {
  static_cast<RenderWidgetHostViewBase*>(
      web_contents()->GetRenderWidgetHostView())->ImeCancelComposition();
}

#if defined(OS_MACOSX) || defined(USE_AURA)
void BrowserPluginGuest::OnImeCompositionRangeChanged(
      const gfx::Range& range,
      const std::vector<gfx::Rect>& character_bounds) {
  static_cast<RenderWidgetHostViewBase*>(
      web_contents()->GetRenderWidgetHostView())->ImeCompositionRangeChanged(
          range, character_bounds);
}
#endif

void BrowserPluginGuest::SetContextMenuPosition(const gfx::Point& position) {
  if (delegate_)
    delegate_->SetContextMenuPosition(position);
}

}  // namespace content
