/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 * Copyright (C) 2014 Opera Software ASA. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "web/WebPluginContainerImpl.h"

#include "bindings/core/v8/ScriptController.h"
#include "bindings/core/v8/ScriptSourceCode.h"
#include "bindings/core/v8/V8Element.h"
#include "core/HTMLNames.h"
#include "core/clipboard/DataObject.h"
#include "core/clipboard/DataTransfer.h"
#include "core/dom/DocumentUserGestureToken.h"
#include "core/dom/ExecutionContext.h"
#include "core/dom/Fullscreen.h"
#include "core/events/DragEvent.h"
#include "core/events/EventQueue.h"
#include "core/events/GestureEvent.h"
#include "core/events/KeyboardEvent.h"
#include "core/events/MouseEvent.h"
#include "core/events/ProgressEvent.h"
#include "core/events/ResourceProgressEvent.h"
#include "core/events/TouchEvent.h"
#include "core/events/WheelEvent.h"
#include "core/frame/EventHandlerRegistry.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/HTMLFormElement.h"
#include "core/html/HTMLPlugInElement.h"
#include "core/input/EventHandler.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutBox.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LayoutPartItem.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/loader/FrameLoadRequest.h"
#include "core/page/FocusController.h"
#include "core/page/Page.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/paint/LayoutObjectDrawingRecorder.h"
#include "core/paint/PaintLayer.h"
#include "modules/plugins/PluginOcclusionSupport.h"
#include "platform/HostWindow.h"
#include "platform/KeyboardCodes.h"
#include "platform/PlatformGestureEvent.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/UserGestureIndicator.h"
#include "platform/exported/WrappedResourceResponse.h"
#include "platform/geometry/LayoutRect.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/graphics/paint/CullRect.h"
#include "platform/graphics/paint/ForeignLayerDisplayItem.h"
#include "platform/scroll/ScrollAnimatorBase.h"
#include "platform/scroll/ScrollbarTheme.h"
#include "public/platform/Platform.h"
#include "public/platform/WebClipboard.h"
#include "public/platform/WebCompositorSupport.h"
#include "public/platform/WebCursorInfo.h"
#include "public/platform/WebDragData.h"
#include "public/platform/WebExternalTextureLayer.h"
#include "public/platform/WebInputEvent.h"
#include "public/platform/WebRect.h"
#include "public/platform/WebString.h"
#include "public/platform/WebURL.h"
#include "public/platform/WebURLError.h"
#include "public/platform/WebURLRequest.h"
#include "public/web/WebDOMMessageEvent.h"
#include "public/web/WebDocument.h"
#include "public/web/WebElement.h"
#include "public/web/WebFrameClient.h"
#include "public/web/WebPlugin.h"
#include "public/web/WebPrintParams.h"
#include "public/web/WebPrintPresetOptions.h"
#include "public/web/WebViewClient.h"
#include "web/ChromeClientImpl.h"
#include "web/WebDataSourceImpl.h"
#include "web/WebInputEventConversion.h"
#include "web/WebLocalFrameImpl.h"
#include "web/WebViewImpl.h"
#include "wtf/Assertions.h"

namespace blink {

// Public methods --------------------------------------------------------------

void WebPluginContainerImpl::setFrameRect(const IntRect& frameRect) {
  Widget::setFrameRect(frameRect);
}

void WebPluginContainerImpl::updateAllLifecyclePhases() {
  if (!m_webPlugin)
    return;

  m_webPlugin->updateAllLifecyclePhases();
}

void WebPluginContainerImpl::paint(GraphicsContext& context,
                                   const CullRect& cullRect) const {
  if (!parent())
    return;

  // Don't paint anything if the plugin doesn't intersect.
  if (!cullRect.intersectsCullRect(frameRect()))
    return;

  if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && m_webLayer) {
    // With Slimming Paint v2, composited plugins should have their layers
    // inserted rather than invoking WebPlugin::paint.
    recordForeignLayer(context, *m_element->layoutObject(),
                       DisplayItem::kForeignLayerPlugin, m_webLayer, location(),
                       size());
    return;
  }

  if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(
          context, *m_element->layoutObject(), DisplayItem::Type::kWebPlugin))
    return;

  LayoutObjectDrawingRecorder drawingRecorder(
      context, *m_element->layoutObject(), DisplayItem::Type::kWebPlugin,
      cullRect.m_rect);
  context.save();

  DCHECK(parent()->isFrameView());
  FrameView* view = toFrameView(parent());

  // The plugin is positioned in the root frame's coordinates, so it needs to
  // be painted in them too.
  IntPoint origin = view->contentsToRootFrame(IntPoint(0, 0));
  context.translate(static_cast<float>(-origin.x()),
                    static_cast<float>(-origin.y()));

  WebCanvas* canvas = context.canvas();

  IntRect windowRect = view->contentsToRootFrame(cullRect.m_rect);
  m_webPlugin->paint(canvas, windowRect);

  context.restore();
}

void WebPluginContainerImpl::invalidateRect(const IntRect& rect) {
  if (!parent())
    return;

  LayoutBox* layoutObject = toLayoutBox(m_element->layoutObject());
  if (!layoutObject)
    return;

  IntRect dirtyRect = rect;
  dirtyRect.move(
      (layoutObject->borderLeft() + layoutObject->paddingLeft()).toInt(),
      (layoutObject->borderTop() + layoutObject->paddingTop()).toInt());

  m_pendingInvalidationRect.unite(dirtyRect);

  layoutObject->setMayNeedPaintInvalidation();
}

void WebPluginContainerImpl::setFocused(bool focused, WebFocusType focusType) {
  Widget::setFocused(focused, focusType);
  m_webPlugin->updateFocus(focused, focusType);
}

void WebPluginContainerImpl::show() {
  setSelfVisible(true);
  m_webPlugin->updateVisibility(true);

  Widget::show();
}

void WebPluginContainerImpl::hide() {
  setSelfVisible(false);
  m_webPlugin->updateVisibility(false);

  Widget::hide();
}

void WebPluginContainerImpl::handleEvent(Event* event) {
  // The events we pass are defined at:
  //    http://devedge-temp.mozilla.org/library/manuals/2002/plugin/1.0/structures5.html#1000000
  // Don't take the documentation as truth, however.  There are many cases
  // where mozilla behaves differently than the spec.
  if (event->isMouseEvent())
    handleMouseEvent(toMouseEvent(event));
  else if (event->isWheelEvent())
    handleWheelEvent(toWheelEvent(event));
  else if (event->isKeyboardEvent())
    handleKeyboardEvent(toKeyboardEvent(event));
  else if (event->isTouchEvent())
    handleTouchEvent(toTouchEvent(event));
  else if (event->isGestureEvent())
    handleGestureEvent(toGestureEvent(event));
  else if (event->isDragEvent() && m_webPlugin->canProcessDrag())
    handleDragEvent(toDragEvent(event));

  // FIXME: it would be cleaner if Widget::handleEvent returned true/false and
  // HTMLPluginElement called setDefaultHandled or defaultEventHandler.
  if (!event->defaultHandled())
    m_element->Node::defaultEventHandler(event);
}

void WebPluginContainerImpl::frameRectsChanged() {
  Widget::frameRectsChanged();
  reportGeometry();
}

void WebPluginContainerImpl::widgetGeometryMayHaveChanged() {
  Widget::widgetGeometryMayHaveChanged();
  reportGeometry();
}

void WebPluginContainerImpl::eventListenersRemoved() {
  // We're no longer registered to receive touch events, so don't try to remove
  // the touch event handlers in our destructor.
  m_touchEventRequestType = TouchEventRequestTypeNone;
}

void WebPluginContainerImpl::setParentVisible(bool parentVisible) {
  // We override this function to make sure that geometry updates are sent
  // over to the plugin. For e.g. when a plugin is instantiated it does not
  // have a valid parent. As a result the first geometry update from webkit
  // is ignored. This function is called when the plugin eventually gets a
  // parent.

  if (isParentVisible() == parentVisible)
    return;  // No change.

  Widget::setParentVisible(parentVisible);
  if (!isSelfVisible())
    return;  // This widget has explicitely been marked as not visible.

  if (m_webPlugin)
    m_webPlugin->updateVisibility(isVisible());
}

void WebPluginContainerImpl::setPlugin(WebPlugin* plugin) {
  if (plugin == m_webPlugin)
    return;

  m_element->resetInstance();
  m_webPlugin = plugin;
  m_isDisposed = false;
}

float WebPluginContainerImpl::deviceScaleFactor() {
  Page* page = m_element->document().page();
  if (!page)
    return 1.0;
  return page->deviceScaleFactor();
}

float WebPluginContainerImpl::pageScaleFactor() {
  Page* page = m_element->document().page();
  if (!page)
    return 1.0;
  return page->pageScaleFactor();
}

float WebPluginContainerImpl::pageZoomFactor() {
  LocalFrame* frame = m_element->document().frame();
  if (!frame)
    return 1.0;
  return frame->pageZoomFactor();
}

void WebPluginContainerImpl::setWebLayer(WebLayer* layer) {
  if (m_webLayer == layer)
    return;

  if (m_webLayer)
    GraphicsLayer::unregisterContentsLayer(m_webLayer);
  if (layer)
    GraphicsLayer::registerContentsLayer(layer);

  m_webLayer = layer;

  if (m_element)
    m_element->setNeedsCompositingUpdate();
}

void WebPluginContainerImpl::requestFullscreen() {
  Fullscreen::requestFullscreen(*m_element, Fullscreen::PrefixedRequest);
}

bool WebPluginContainerImpl::isFullscreenElement() const {
  return Fullscreen::isCurrentFullScreenElement(*m_element);
}

void WebPluginContainerImpl::cancelFullscreen() {
  Fullscreen::fullyExitFullscreen(m_element->document());
}

bool WebPluginContainerImpl::supportsPaginatedPrint() const {
  return m_webPlugin->supportsPaginatedPrint();
}

bool WebPluginContainerImpl::isPrintScalingDisabled() const {
  return m_webPlugin->isPrintScalingDisabled();
}

bool WebPluginContainerImpl::getPrintPresetOptionsFromDocument(
    WebPrintPresetOptions* presetOptions) const {
  return m_webPlugin->getPrintPresetOptionsFromDocument(presetOptions);
}

int WebPluginContainerImpl::printBegin(
    const WebPrintParams& printParams) const {
  return m_webPlugin->printBegin(printParams);
}

void WebPluginContainerImpl::printPage(int pageNumber,
                                       GraphicsContext& gc,
                                       const IntRect& printRect) {
  if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(
          gc, *m_element->layoutObject(), DisplayItem::Type::kWebPlugin))
    return;

  LayoutObjectDrawingRecorder drawingRecorder(
      gc, *m_element->layoutObject(), DisplayItem::Type::kWebPlugin, printRect);
  gc.save();
  WebCanvas* canvas = gc.canvas();
  m_webPlugin->printPage(pageNumber, canvas);
  gc.restore();
}

void WebPluginContainerImpl::printEnd() {
  m_webPlugin->printEnd();
}

void WebPluginContainerImpl::copy() {
  if (!m_webPlugin->hasSelection())
    return;

  Platform::current()->clipboard()->writeHTML(
      m_webPlugin->selectionAsMarkup(), WebURL(),
      m_webPlugin->selectionAsText(), false);
}

bool WebPluginContainerImpl::executeEditCommand(const WebString& name) {
  if (m_webPlugin->executeEditCommand(name))
    return true;

  if (name != "Copy")
    return false;

  copy();
  return true;
}

bool WebPluginContainerImpl::executeEditCommand(const WebString& name,
                                                const WebString& value) {
  return m_webPlugin->executeEditCommand(name, value);
}

WebElement WebPluginContainerImpl::element() {
  return WebElement(m_element);
}

WebDocument WebPluginContainerImpl::document() {
  return WebDocument(&m_element->document());
}

void WebPluginContainerImpl::dispatchProgressEvent(const WebString& type,
                                                   bool lengthComputable,
                                                   unsigned long long loaded,
                                                   unsigned long long total,
                                                   const WebString& url) {
  ProgressEvent* event;
  if (url.isEmpty()) {
    event = ProgressEvent::create(type, lengthComputable, loaded, total);
  } else {
    event = ResourceProgressEvent::create(type, lengthComputable, loaded, total,
                                          url);
  }
  m_element->dispatchEvent(event);
}

void WebPluginContainerImpl::enqueueMessageEvent(
    const WebDOMMessageEvent& event) {
  static_cast<Event*>(event)->setTarget(m_element);
  m_element->getExecutionContext()->getEventQueue()->enqueueEvent(event);
}

void WebPluginContainerImpl::invalidate() {
  Widget::invalidate();
}

void WebPluginContainerImpl::invalidateRect(const WebRect& rect) {
  invalidateRect(static_cast<IntRect>(rect));
}

void WebPluginContainerImpl::scrollRect(const WebRect& rect) {
  invalidateRect(rect);
}

void WebPluginContainerImpl::scheduleAnimation() {
  if (auto* frameView = m_element->document().view())
    frameView->scheduleAnimation();
}

void WebPluginContainerImpl::reportGeometry() {
  // We cannot compute geometry without a parent or layoutObject.
  if (!parent() || !m_element || !m_element->layoutObject() || !m_webPlugin)
    return;

  IntRect windowRect, clipRect, unobscuredRect;
  Vector<IntRect> cutOutRects;
  calculateGeometry(windowRect, clipRect, unobscuredRect, cutOutRects);
  m_webPlugin->updateGeometry(windowRect, clipRect, unobscuredRect, cutOutRects,
                              isVisible());
}

v8::Local<v8::Object> WebPluginContainerImpl::v8ObjectForElement() {
  LocalFrame* frame = m_element->document().frame();
  if (!frame)
    return v8::Local<v8::Object>();

  if (!frame->script().canExecuteScripts(NotAboutToExecuteScript))
    return v8::Local<v8::Object>();

  ScriptState* scriptState = ScriptState::forMainWorld(frame);
  if (!scriptState)
    return v8::Local<v8::Object>();

  v8::Local<v8::Value> v8value =
      toV8(m_element.get(), scriptState->context()->Global(),
           scriptState->isolate());
  if (v8value.IsEmpty())
    return v8::Local<v8::Object>();
  DCHECK(v8value->IsObject());

  return v8::Local<v8::Object>::Cast(v8value);
}

WebString WebPluginContainerImpl::executeScriptURL(const WebURL& url,
                                                   bool popupsAllowed) {
  LocalFrame* frame = m_element->document().frame();
  if (!frame)
    return WebString();

  if (!m_element->document().contentSecurityPolicy()->allowJavaScriptURLs(
          m_element, m_element->document().url(), OrdinalNumber())) {
    return WebString();
  }

  const KURL& kurl = url;
  DCHECK(kurl.protocolIs("javascript"));

  String script = decodeURLEscapeSequences(
      kurl.getString().substring(strlen("javascript:")));

  UserGestureIndicator gestureIndicator(
      popupsAllowed ? DocumentUserGestureToken::create(
                          frame->document(), UserGestureToken::NewGesture)
                    : nullptr);
  v8::HandleScope handleScope(toIsolate(frame));
  v8::Local<v8::Value> result =
      frame->script().executeScriptInMainWorldAndReturnValue(
          ScriptSourceCode(script));

  // Failure is reported as a null string.
  if (result.IsEmpty() || !result->IsString())
    return WebString();
  return toCoreString(v8::Local<v8::String>::Cast(result));
}

void WebPluginContainerImpl::loadFrameRequest(const WebURLRequest& request,
                                              const WebString& target) {
  LocalFrame* frame = m_element->document().frame();
  if (!frame || !frame->loader().documentLoader())
    return;  // FIXME: send a notification in this case?

  FrameLoadRequest frameRequest(frame->document(), request.toResourceRequest(),
                                target);
  frame->loader().load(frameRequest);
}

bool WebPluginContainerImpl::isRectTopmost(const WebRect& rect) {
  // Disallow access to the frame during dispose(), because it is not guaranteed
  // to be valid memory once this object has started disposal. In particular,
  // we might be being disposed because the frame has already be deleted and
  // then something else dropped the
  // last reference to the this object.
  if (m_isDisposed || !m_element)
    return false;

  LocalFrame* frame = m_element->document().frame();
  if (!frame)
    return false;

  IntRect documentRect(x() + rect.x, y() + rect.y, rect.width, rect.height);
  // hitTestResultAtPoint() takes a padding rectangle.
  // FIXME: We'll be off by 1 when the width or height is even.
  LayoutPoint center = documentRect.center();
  // Make the rect we're checking (the point surrounded by padding rects)
  // contained inside the requested rect. (Note that -1/2 is 0.)
  LayoutSize padding((documentRect.width() - 1) / 2,
                     (documentRect.height() - 1) / 2);
  HitTestResult result = frame->eventHandler().hitTestResultAtPoint(
      center, HitTestRequest::ReadOnly | HitTestRequest::Active |
                  HitTestRequest::ListBased,
      padding);
  const HitTestResult::NodeSet& nodes = result.listBasedTestResult();
  if (nodes.size() != 1)
    return false;
  return nodes.first().get() == m_element;
}

void WebPluginContainerImpl::requestTouchEventType(
    TouchEventRequestType requestType) {
  if (m_touchEventRequestType == requestType || !m_element)
    return;

  if (FrameHost* frameHost = m_element->document().frameHost()) {
    EventHandlerRegistry& registry = frameHost->eventHandlerRegistry();
    if (requestType != TouchEventRequestTypeNone &&
        m_touchEventRequestType == TouchEventRequestTypeNone)
      registry.didAddEventHandler(
          *m_element, EventHandlerRegistry::TouchStartOrMoveEventBlocking);
    else if (requestType == TouchEventRequestTypeNone &&
             m_touchEventRequestType != TouchEventRequestTypeNone)
      registry.didRemoveEventHandler(
          *m_element, EventHandlerRegistry::TouchStartOrMoveEventBlocking);
  }
  m_touchEventRequestType = requestType;
}

void WebPluginContainerImpl::setWantsWheelEvents(bool wantsWheelEvents) {
  if (m_wantsWheelEvents == wantsWheelEvents)
    return;
  if (FrameHost* frameHost = m_element->document().frameHost()) {
    EventHandlerRegistry& registry = frameHost->eventHandlerRegistry();
    if (wantsWheelEvents)
      registry.didAddEventHandler(*m_element,
                                  EventHandlerRegistry::WheelEventBlocking);
    else
      registry.didRemoveEventHandler(*m_element,
                                     EventHandlerRegistry::WheelEventBlocking);
  }

  m_wantsWheelEvents = wantsWheelEvents;
  if (Page* page = m_element->document().page()) {
    if (ScrollingCoordinator* scrollingCoordinator =
            page->scrollingCoordinator()) {
      if (parent() && parent()->isFrameView())
        scrollingCoordinator->notifyGeometryChanged();
    }
  }
}

WebPoint WebPluginContainerImpl::rootFrameToLocalPoint(
    const WebPoint& pointInRootFrame) {
  FrameView* view = toFrameView(parent());
  if (!view)
    return pointInRootFrame;
  WebPoint pointInContent = view->rootFrameToContents(pointInRootFrame);
  return roundedIntPoint(m_element->layoutObject()->absoluteToLocal(
      FloatPoint(pointInContent), UseTransforms));
}

WebPoint WebPluginContainerImpl::localToRootFramePoint(
    const WebPoint& pointInLocal) {
  FrameView* view = toFrameView(parent());
  if (!view)
    return pointInLocal;
  IntPoint absolutePoint =
      roundedIntPoint(m_element->layoutObject()->localToAbsolute(
          FloatPoint(pointInLocal), UseTransforms));
  return view->contentsToRootFrame(absolutePoint);
}

void WebPluginContainerImpl::didReceiveResponse(
    const ResourceResponse& response) {
  // Make sure that the plugin receives window geometry before data, or else
  // plugins misbehave.
  frameRectsChanged();

  WrappedResourceResponse urlResponse(response);
  m_webPlugin->didReceiveResponse(urlResponse);
}

void WebPluginContainerImpl::didReceiveData(const char* data, int dataLength) {
  m_webPlugin->didReceiveData(data, dataLength);
}

void WebPluginContainerImpl::didFinishLoading() {
  m_webPlugin->didFinishLoading();
}

void WebPluginContainerImpl::didFailLoading(const ResourceError& error) {
  m_webPlugin->didFailLoading(error);
}

WebLayer* WebPluginContainerImpl::platformLayer() const {
  return m_webLayer;
}

v8::Local<v8::Object> WebPluginContainerImpl::scriptableObject(
    v8::Isolate* isolate) {
  // With Oilpan, on plugin element detach dispose() will be called to safely
  // clear out references, including the pre-emptive destruction of the plugin.
  //
  // It clearly has no scriptable object if in such a disposed state.
  if (!m_webPlugin)
    return v8::Local<v8::Object>();

  v8::Local<v8::Object> object = m_webPlugin->v8ScriptableObject(isolate);

  // If the plugin has been destroyed and the reference on the stack is the
  // only one left, then don't return the scriptable object.
  if (!m_webPlugin)
    return v8::Local<v8::Object>();

  return object;
}

bool WebPluginContainerImpl::supportsKeyboardFocus() const {
  return m_webPlugin->supportsKeyboardFocus();
}

bool WebPluginContainerImpl::supportsInputMethod() const {
  return m_webPlugin->supportsInputMethod();
}

bool WebPluginContainerImpl::canProcessDrag() const {
  return m_webPlugin->canProcessDrag();
}

bool WebPluginContainerImpl::wantsWheelEvents() {
  return m_wantsWheelEvents;
}

// Private methods -------------------------------------------------------------

WebPluginContainerImpl::WebPluginContainerImpl(HTMLPlugInElement* element,
                                               WebPlugin* webPlugin)
    : DOMWindowProperty(element->document().frame()),
      m_element(element),
      m_webPlugin(webPlugin),
      m_webLayer(nullptr),
      m_touchEventRequestType(TouchEventRequestTypeNone),
      m_wantsWheelEvents(false),
      m_isDisposed(false) {
  ThreadState::current()->registerPreFinalizer(this);
}

WebPluginContainerImpl::~WebPluginContainerImpl() {
  // The plugin container must have been disposed of by now.
  DCHECK(!m_webPlugin);
}

void WebPluginContainerImpl::dispose() {
  m_isDisposed = true;

  requestTouchEventType(TouchEventRequestTypeNone);
  setWantsWheelEvents(false);

  if (m_webPlugin) {
    CHECK(m_webPlugin->container() == this);
    m_webPlugin->destroy();
    m_webPlugin = nullptr;
  }

  if (m_webLayer) {
    GraphicsLayer::unregisterContentsLayer(m_webLayer);
    m_webLayer = nullptr;
  }
}

DEFINE_TRACE(WebPluginContainerImpl) {
  visitor->trace(m_element);
  DOMWindowProperty::trace(visitor);
  PluginView::trace(visitor);
}

void WebPluginContainerImpl::handleMouseEvent(MouseEvent* event) {
  DCHECK(parent()->isFrameView());

  // We cache the parent FrameView here as the plugin widget could be deleted
  // in the call to HandleEvent. See http://b/issue?id=1362948
  FrameView* parentView = toFrameView(parent());

  WebMouseEventBuilder webEvent(this, LayoutItem(m_element->layoutObject()),
                                *event);
  if (webEvent.type == WebInputEvent::Undefined)
    return;

  if (event->type() == EventTypeNames::mousedown)
    focusPlugin();

  WebCursorInfo cursorInfo;
  if (m_webPlugin->handleInputEvent(webEvent, cursorInfo) !=
      WebInputEventResult::NotHandled)
    event->setDefaultHandled();

  // A windowless plugin can change the cursor in response to a mouse move
  // event.  We need to reflect the changed cursor in the frame view as the
  // mouse is moved in the boundaries of the windowless plugin.
  Page* page = parentView->frame().page();
  if (!page)
    return;
  toChromeClientImpl(page->chromeClient())
      .setCursorForPlugin(cursorInfo, parentView->frame().localFrameRoot());
}

void WebPluginContainerImpl::handleDragEvent(MouseEvent* event) {
  DCHECK(event->isDragEvent());

  WebDragStatus dragStatus = WebDragStatusUnknown;
  if (event->type() == EventTypeNames::dragenter)
    dragStatus = WebDragStatusEnter;
  else if (event->type() == EventTypeNames::dragleave)
    dragStatus = WebDragStatusLeave;
  else if (event->type() == EventTypeNames::dragover)
    dragStatus = WebDragStatusOver;
  else if (event->type() == EventTypeNames::drop)
    dragStatus = WebDragStatusDrop;

  if (dragStatus == WebDragStatusUnknown)
    return;

  DataTransfer* dataTransfer = event->getDataTransfer();
  WebDragData dragData = dataTransfer->dataObject()->toWebDragData();
  WebDragOperationsMask dragOperationMask =
      static_cast<WebDragOperationsMask>(dataTransfer->sourceOperation());
  WebPoint dragScreenLocation(event->screenX(), event->screenY());
  WebPoint dragLocation(event->absoluteLocation().x() - location().x(),
                        event->absoluteLocation().y() - location().y());

  m_webPlugin->handleDragStatusUpdate(dragStatus, dragData, dragOperationMask,
                                      dragLocation, dragScreenLocation);
}

void WebPluginContainerImpl::handleWheelEvent(WheelEvent* event) {
  WebMouseWheelEventBuilder webEvent(
      this, LayoutItem(m_element->layoutObject()), *event);
  if (webEvent.type == WebInputEvent::Undefined)
    return;

  WebCursorInfo cursorInfo;
  if (m_webPlugin->handleInputEvent(webEvent, cursorInfo) !=
      WebInputEventResult::NotHandled)
    event->setDefaultHandled();
}

void WebPluginContainerImpl::handleKeyboardEvent(KeyboardEvent* event) {
  WebKeyboardEventBuilder webEvent(*event);
  if (webEvent.type == WebInputEvent::Undefined)
    return;

  if (webEvent.type == WebInputEvent::KeyDown) {
#if OS(MACOSX)
    if ((webEvent.modifiers & WebInputEvent::InputModifiers) ==
            WebInputEvent::MetaKey
#else
    if ((webEvent.modifiers & WebInputEvent::InputModifiers) ==
            WebInputEvent::ControlKey
#endif
        && (webEvent.windowsKeyCode == VKEY_C ||
            webEvent.windowsKeyCode == VKEY_INSERT)
        // Only copy if there's a selection, so that we only ever do this
        // for Pepper plugins that support copying.  Windowless NPAPI
        // plugins will get the event as before.
        && m_webPlugin->hasSelection()) {
      copy();
      event->setDefaultHandled();
      return;
    }
  }

  const WebInputEvent* currentInputEvent = WebViewImpl::currentInputEvent();

  // Copy stashed info over, and only copy here in order not to interfere
  // the ctrl-c logic above.
  if (currentInputEvent &&
      WebInputEvent::isKeyboardEventType(currentInputEvent->type)) {
    webEvent.modifiers |=
        currentInputEvent->modifiers &
        (WebInputEvent::CapsLockOn | WebInputEvent::NumLockOn);
  }

  // Give the client a chance to issue edit comamnds.
  WebLocalFrameImpl* webFrame =
      WebLocalFrameImpl::fromFrame(m_element->document().frame());
  if (m_webPlugin->supportsEditCommands())
    webFrame->client()->handleCurrentKeyboardEvent();

  WebCursorInfo cursorInfo;
  if (m_webPlugin->handleInputEvent(webEvent, cursorInfo) !=
      WebInputEventResult::NotHandled)
    event->setDefaultHandled();
}

void WebPluginContainerImpl::handleTouchEvent(TouchEvent* event) {
  switch (m_touchEventRequestType) {
    case TouchEventRequestTypeNone:
      return;
    case TouchEventRequestTypeRaw: {
      WebTouchEventBuilder webEvent(LayoutItem(m_element->layoutObject()),
                                    *event);
      if (webEvent.type == WebInputEvent::Undefined)
        return;

      if (event->type() == EventTypeNames::touchstart)
        focusPlugin();

      WebCursorInfo cursorInfo;
      if (m_webPlugin->handleInputEvent(webEvent, cursorInfo) !=
          WebInputEventResult::NotHandled)
        event->setDefaultHandled();
      // FIXME: Can a plugin change the cursor from a touch-event callback?
      return;
    }
    case TouchEventRequestTypeSynthesizedMouse:
      synthesizeMouseEventIfPossible(event);
      return;
  }
}

void WebPluginContainerImpl::handleGestureEvent(GestureEvent* event) {
  WebGestureEventBuilder webEvent(LayoutItem(m_element->layoutObject()),
                                  *event);
  if (webEvent.type == WebInputEvent::Undefined)
    return;
  if (event->type() == EventTypeNames::gesturetapdown)
    focusPlugin();
  WebCursorInfo cursorInfo;
  if (m_webPlugin->handleInputEvent(webEvent, cursorInfo) !=
      WebInputEventResult::NotHandled) {
    event->setDefaultHandled();
    return;
  }

  // FIXME: Can a plugin change the cursor from a touch-event callback?
}

void WebPluginContainerImpl::synthesizeMouseEventIfPossible(TouchEvent* event) {
  WebMouseEventBuilder webEvent(this, LayoutItem(m_element->layoutObject()),
                                *event);
  if (webEvent.type == WebInputEvent::Undefined)
    return;

  WebCursorInfo cursorInfo;
  if (m_webPlugin->handleInputEvent(webEvent, cursorInfo) !=
      WebInputEventResult::NotHandled)
    event->setDefaultHandled();
}

void WebPluginContainerImpl::focusPlugin() {
  LocalFrame& containingFrame = toFrameView(parent())->frame();
  if (Page* currentPage = containingFrame.page())
    currentPage->focusController().setFocusedElement(m_element,
                                                     &containingFrame);
  else
    containingFrame.document()->setFocusedElement(
        m_element,
        FocusParams(SelectionBehaviorOnFocus::None, WebFocusTypeNone, nullptr));
}

void WebPluginContainerImpl::issuePaintInvalidations() {
  if (m_pendingInvalidationRect.isEmpty())
    return;

  LayoutBox* layoutObject = toLayoutBox(m_element->layoutObject());
  if (!layoutObject)
    return;

  layoutObject->invalidatePaintRectangle(LayoutRect(m_pendingInvalidationRect));
  m_pendingInvalidationRect = IntRect();
}

void WebPluginContainerImpl::computeClipRectsForPlugin(
    const HTMLFrameOwnerElement* ownerElement,
    IntRect& windowRect,
    IntRect& clippedLocalRect,
    IntRect& unclippedIntLocalRect) const {
  DCHECK(ownerElement);

  if (!ownerElement->layoutObject()) {
    clippedLocalRect = IntRect();
    unclippedIntLocalRect = IntRect();
    return;
  }

  LayoutView* rootView = m_element->document().view()->layoutView();
  while (rootView->frame()->ownerLayoutObject())
    rootView = rootView->frame()->ownerLayoutObject()->view();

  LayoutBox* box = toLayoutBox(ownerElement->layoutObject());

  // Note: frameRect() for this plugin is equal to contentBoxRect, mapped to the
  // containing view space, and rounded off.
  // See LayoutPart.cpp::updateWidgetGeometryInternal. To remove the lossy
  // effect of rounding off, use contentBoxRect directly.
  LayoutRect unclippedAbsoluteRect(box->contentBoxRect());
  box->mapToVisualRectInAncestorSpace(rootView, unclippedAbsoluteRect);

  // The frameRect is already in absolute space of the local frame to the
  // plugin.
  windowRect = frameRect();
  // Map up to the root frame.
  LayoutRect layoutWindowRect =
      LayoutRect(m_element->document()
                     .view()
                     ->layoutViewItem()
                     .localToAbsoluteQuad(FloatQuad(FloatRect(frameRect())),
                                          TraverseDocumentBoundaries)
                     .boundingBox());
  // Finally, adjust for scrolling of the root frame, which the above does not
  // take into account.
  layoutWindowRect.moveBy(-rootView->viewRect().location());
  windowRect = pixelSnappedIntRect(layoutWindowRect);

  LayoutRect layoutClippedLocalRect = unclippedAbsoluteRect;
  LayoutRect unclippedLayoutLocalRect = layoutClippedLocalRect;
  layoutClippedLocalRect.intersect(
      LayoutRect(rootView->frameView()->visibleContentRect()));

  unclippedIntLocalRect =
      box->absoluteToLocalQuad(FloatRect(unclippedLayoutLocalRect),
                               TraverseDocumentBoundaries | UseTransforms)
          .enclosingBoundingBox();
  // As a performance optimization, map the clipped rect separately if is
  // different than the unclipped rect.
  if (layoutClippedLocalRect != unclippedLayoutLocalRect)
    clippedLocalRect =
        box->absoluteToLocalQuad(FloatRect(layoutClippedLocalRect),
                                 TraverseDocumentBoundaries | UseTransforms)
            .enclosingBoundingBox();
  else
    clippedLocalRect = unclippedIntLocalRect;
}

void WebPluginContainerImpl::calculateGeometry(IntRect& windowRect,
                                               IntRect& clipRect,
                                               IntRect& unobscuredRect,
                                               Vector<IntRect>& cutOutRects) {
  // document().layoutView() can be null when we receive messages from the
  // plugins while we are destroying a frame.
  // FIXME: Can we just check m_element->document().isActive() ?
  if (!m_element->layoutObject()->document().layoutViewItem().isNull()) {
    // Take our element and get the clip rect from the enclosing layer and
    // frame view.
    computeClipRectsForPlugin(m_element, windowRect, clipRect, unobscuredRect);
  }
  getPluginOcclusions(m_element, this->parent(), frameRect(), cutOutRects);
  // Convert to the plugin position.
  for (size_t i = 0; i < cutOutRects.size(); i++)
    cutOutRects[i].move(-frameRect().x(), -frameRect().y());
}

}  // namespace blink
