/*
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "config.h"
#include "PluginControllerProxy.h"

#if ENABLE(PLUGIN_PROCESS)

#include "DataReference.h"
#include "NPObjectProxy.h"
#include "NPRemoteObjectMap.h"
#include "NPRuntimeUtilities.h"
#include "NPVariantData.h"
#include "NetscapePlugin.h"
#include "PluginCreationParameters.h"
#include "PluginProcess.h"
#include "PluginProxyMessages.h"
#include "ShareableBitmap.h"
#include "WebCoreArgumentCoders.h"
#include "WebProcessConnection.h"
#include <WebCore/GraphicsContext.h>
#include <WebCore/IdentifierRep.h>
#include <WebCore/NotImplemented.h>
#include <wtf/text/WTFString.h>

using namespace WebCore;

namespace WebKit {

PassOwnPtr<PluginControllerProxy> PluginControllerProxy::create(WebProcessConnection* connection, const PluginCreationParameters& creationParameters)
{
    return adoptPtr(new PluginControllerProxy(connection, creationParameters));
}

PluginControllerProxy::PluginControllerProxy(WebProcessConnection* connection, const PluginCreationParameters& creationParameters)
    : m_connection(connection)
    , m_pluginInstanceID(creationParameters.pluginInstanceID)
    , m_userAgent(creationParameters.userAgent)
    , m_isPrivateBrowsingEnabled(creationParameters.isPrivateBrowsingEnabled)
#if USE(ACCELERATED_COMPOSITING)
    , m_isAcceleratedCompositingEnabled(creationParameters.isAcceleratedCompositingEnabled)
#endif
    , m_paintTimer(RunLoop::main(), this, &PluginControllerProxy::paint)
    , m_pluginDestructionProtectCount(0)
    , m_pluginDestroyTimer(RunLoop::main(), this, &PluginControllerProxy::destroy)
    , m_pluginCreationParameters(0)
    , m_waitingForDidUpdate(false)
    , m_pluginCanceledManualStreamLoad(false)
#if PLATFORM(MAC)
    , m_isComplexTextInputEnabled(false)
#endif
    , m_contentsScaleFactor(creationParameters.contentsScaleFactor)
    , m_windowNPObject(0)
    , m_pluginElementNPObject(0)
{
}

PluginControllerProxy::~PluginControllerProxy()
{
    ASSERT(!m_plugin);

    if (m_windowNPObject)
        releaseNPObject(m_windowNPObject);

    if (m_pluginElementNPObject)
        releaseNPObject(m_pluginElementNPObject);
}

bool PluginControllerProxy::initialize(const PluginCreationParameters& creationParameters)
{
    ASSERT(!m_plugin);

    m_plugin = NetscapePlugin::create(PluginProcess::shared().netscapePluginModule());
    if (!m_plugin) {
        // This will delete the plug-in controller proxy object.
        m_connection->removePluginControllerProxy(this, 0);
        return false;
    }

    m_windowNPObject = m_connection->npRemoteObjectMap()->createNPObjectProxy(creationParameters.windowNPObjectID, m_plugin.get());
    ASSERT(m_windowNPObject);

    m_pluginCreationParameters = &creationParameters;
    bool returnValue = m_plugin->initialize(this, creationParameters.parameters);
    m_pluginCreationParameters = 0;

    if (!returnValue) {
        // Get the plug-in so we can pass it to removePluginControllerProxy. The pointer is only
        // used as an identifier so it's OK to just get a weak reference.
        Plugin* plugin = m_plugin.get();
        
        m_plugin = 0;

        // This will delete the plug-in controller proxy object.
        m_connection->removePluginControllerProxy(this, plugin);
        return false;
    }

    platformInitialize();

    return true;
}

void PluginControllerProxy::destroy()
{
    ASSERT(m_plugin);

    if (m_pluginDestructionProtectCount) {
        // We have plug-in code on the stack so we can't destroy it right now.
        // Destroy it later.
        m_pluginDestroyTimer.startOneShot(0);
        return;
    }

    // Get the plug-in so we can pass it to removePluginControllerProxy. The pointer is only
    // used as an identifier so it's OK to just get a weak reference.
    Plugin* plugin = m_plugin.get();

    m_plugin->destroyPlugin();
    m_plugin = 0;

    platformDestroy();

    // This will delete the plug-in controller proxy object.
    m_connection->removePluginControllerProxy(this, plugin);
}

void PluginControllerProxy::paint()
{
    ASSERT(!m_dirtyRect.isEmpty());
    m_paintTimer.stop();

    if (!m_backingStore)
        return;

    IntRect dirtyRect = m_dirtyRect;
    m_dirtyRect = IntRect();

    ASSERT(m_plugin);

    // Create a graphics context.
    OwnPtr<GraphicsContext> graphicsContext = m_backingStore->createGraphicsContext();

#if PLATFORM(MAC)
    // FIXME: We should really call applyDeviceScaleFactor instead of scale, but that ends up calling into WKSI
    // which we currently don't have initiated in the plug-in process.
    graphicsContext->scale(FloatSize(m_contentsScaleFactor, m_contentsScaleFactor));
#endif

    if (m_plugin->isTransparent())
        graphicsContext->clearRect(dirtyRect);

    m_plugin->paint(graphicsContext.get(), dirtyRect);

    m_connection->connection()->send(Messages::PluginProxy::Update(dirtyRect), m_pluginInstanceID);
}

void PluginControllerProxy::startPaintTimer()
{
    // Check if we should start the timer.
    
    if (m_dirtyRect.isEmpty())
        return;

    // FIXME: Check clip rect.
    
    if (m_paintTimer.isActive())
        return;

    if (m_waitingForDidUpdate)
        return;

    // Start the timer.
    m_paintTimer.startOneShot(0);

    m_waitingForDidUpdate = true;
}

bool PluginControllerProxy::isPluginVisible()
{
    // FIXME: Implement this.
    notImplemented();
    return false;
}

void PluginControllerProxy::invalidate(const IntRect& rect)
{
    IntRect dirtyRect = rect;

    // Make sure that the dirty rect is not greater than the plug-in itself.
    dirtyRect.intersect(IntRect(IntPoint(), m_pluginSize));
    m_dirtyRect.unite(dirtyRect);

    startPaintTimer();
}

String PluginControllerProxy::userAgent()
{
    return m_userAgent;
}

void PluginControllerProxy::loadURL(uint64_t requestID, const String& method, const String& urlString, const String& target, const HTTPHeaderMap& headerFields, const Vector<uint8_t>& httpBody, bool allowPopups)
{
    m_connection->connection()->send(Messages::PluginProxy::LoadURL(requestID, method, urlString, target, headerFields, httpBody, allowPopups), m_pluginInstanceID);
}

void PluginControllerProxy::cancelStreamLoad(uint64_t streamID)
{
    m_connection->connection()->send(Messages::PluginProxy::CancelStreamLoad(streamID), m_pluginInstanceID);
}

void PluginControllerProxy::cancelManualStreamLoad()
{
    m_pluginCanceledManualStreamLoad = true;

    m_connection->connection()->send(Messages::PluginProxy::CancelManualStreamLoad(), m_pluginInstanceID);
}

NPObject* PluginControllerProxy::windowScriptNPObject()
{
    retainNPObject(m_windowNPObject);
    return m_windowNPObject;
}

NPObject* PluginControllerProxy::pluginElementNPObject()
{
    if (!m_pluginElementNPObject) {
        uint64_t pluginElementNPObjectID = 0;

        if (!m_connection->connection()->sendSync(Messages::PluginProxy::GetPluginElementNPObject(), Messages::PluginProxy::GetPluginElementNPObject::Reply(pluginElementNPObjectID), m_pluginInstanceID))
            return 0;

        if (!pluginElementNPObjectID)
            return 0;

        m_pluginElementNPObject = m_connection->npRemoteObjectMap()->createNPObjectProxy(pluginElementNPObjectID, m_plugin.get());
        ASSERT(m_pluginElementNPObject);
    }

    retainNPObject(m_pluginElementNPObject);
    return m_pluginElementNPObject;
}

bool PluginControllerProxy::evaluate(NPObject* npObject, const String& scriptString, NPVariant* result, bool allowPopups)
{
    if (tryToShortCircuitEvaluate(npObject, scriptString, result))
        return true;

    PluginDestructionProtector protector(this);

    NPVariant npObjectAsNPVariant;
    OBJECT_TO_NPVARIANT(npObject, npObjectAsNPVariant);

    // Send the NPObject over as an NPVariantData.
    NPVariantData npObjectAsNPVariantData = m_connection->npRemoteObjectMap()->npVariantToNPVariantData(npObjectAsNPVariant, m_plugin.get());

    bool returnValue = false;
    NPVariantData resultData;

    if (!m_connection->connection()->sendSync(Messages::PluginProxy::Evaluate(npObjectAsNPVariantData, scriptString, allowPopups), Messages::PluginProxy::Evaluate::Reply(returnValue, resultData), m_pluginInstanceID))
        return false;

    if (!returnValue)
        return false;

    *result = m_connection->npRemoteObjectMap()->npVariantDataToNPVariant(resultData, m_plugin.get());
    return true;
}

bool PluginControllerProxy::tryToShortCircuitInvoke(NPObject* npObject, NPIdentifier methodName, const NPVariant* arguments, uint32_t argumentCount, bool& returnValue, NPVariant& result)
{
    // Only try to short circuit evaluate for plug-ins that have the quirk specified.
#if PLUGIN_ARCHITECTURE(MAC)
    if (!PluginProcess::shared().netscapePluginModule()->pluginQuirks().contains(PluginQuirks::CanShortCircuitSomeNPRuntimeCallsDuringInitialization))
        return false;
#else
    return false;
#endif

    // And only when we're in initialize.
    if (!inInitialize())
        return false;
    
    // And only when the NPObject is the window NPObject.
    if (npObject != m_windowNPObject)
        return false;

    // And only when we don't have any arguments.
    if (argumentCount)
        return false;

    IdentifierRep* methodNameRep = static_cast<IdentifierRep*>(methodName);
    if (!methodNameRep->isString())
        return false;

    if (!strcmp(methodNameRep->string(), "__flash_getWindowLocation")) {
        result.type = NPVariantType_String;
        result.value.stringValue = createNPString(m_pluginCreationParameters->parameters.documentURL.utf8()); 
        returnValue = true;
        return true;
    }
    
    if (!strcmp(methodNameRep->string(), "__flash_getTopLocation")) {
        if (m_pluginCreationParameters->parameters.toplevelDocumentURL.isNull()) {
            // If the toplevel document is URL it means that the frame that the plug-in is in doesn't have access to the toplevel document.
            // In this case, just pass the string "[object]" to Flash.
            result.type = NPVariantType_String;
            result.value.stringValue = createNPString("[object]");
            returnValue = true;
            return true;
        }

        result.type = NPVariantType_String;
        result.value.stringValue = createNPString(m_pluginCreationParameters->parameters.toplevelDocumentURL.utf8()); 
        returnValue = true;
        return true;
    }

    return false;
}

void PluginControllerProxy::setStatusbarText(const String& statusbarText)
{
    m_connection->connection()->send(Messages::PluginProxy::SetStatusbarText(statusbarText), m_pluginInstanceID);
}

bool PluginControllerProxy::isAcceleratedCompositingEnabled()
{
    return m_isAcceleratedCompositingEnabled;
}

void PluginControllerProxy::pluginProcessCrashed()
{
    // This should never be called from here.
    ASSERT_NOT_REACHED();
}

void PluginControllerProxy::willSendEventToPlugin()
{
    // This is only used when running plugins in the web process.
    ASSERT_NOT_REACHED();
}

float PluginControllerProxy::contentsScaleFactor()
{
    return m_contentsScaleFactor;
}

String PluginControllerProxy::proxiesForURL(const String& urlString)
{
    String proxyString;

    if (!m_connection->connection()->sendSync(Messages::PluginProxy::ProxiesForURL(urlString), Messages::PluginProxy::ProxiesForURL::Reply(proxyString), m_pluginInstanceID))
        return String();

    return proxyString;
}

String PluginControllerProxy::cookiesForURL(const String& urlString)
{
    String cookieString;

    if (!m_connection->connection()->sendSync(Messages::PluginProxy::CookiesForURL(urlString), Messages::PluginProxy::CookiesForURL::Reply(cookieString), m_pluginInstanceID))
        return String();

    return cookieString;
}

void PluginControllerProxy::setCookiesForURL(const String& urlString, const String& cookieString)
{
    m_connection->connection()->send(Messages::PluginProxy::SetCookiesForURL(urlString, cookieString), m_pluginInstanceID);
}

bool PluginControllerProxy::isPrivateBrowsingEnabled()
{
    return m_isPrivateBrowsingEnabled;
}

bool PluginControllerProxy::getAuthenticationInfo(const ProtectionSpace& protectionSpace, String& username, String& password)
{
    bool returnValue;
    if (!m_connection->connection()->sendSync(Messages::PluginProxy::GetAuthenticationInfo(protectionSpace), Messages::PluginProxy::GetAuthenticationInfo::Reply(returnValue, username, password), m_pluginInstanceID))
        return false;

    return returnValue;
}

void PluginControllerProxy::protectPluginFromDestruction()
{
    m_pluginDestructionProtectCount++;
}

void PluginControllerProxy::unprotectPluginFromDestruction()
{
    ASSERT(m_pluginDestructionProtectCount);

    m_pluginDestructionProtectCount--;
}

void PluginControllerProxy::frameDidFinishLoading(uint64_t requestID)
{
    m_plugin->frameDidFinishLoading(requestID);
}

void PluginControllerProxy::frameDidFail(uint64_t requestID, bool wasCancelled)
{
    m_plugin->frameDidFail(requestID, wasCancelled);
}

void PluginControllerProxy::geometryDidChange(const IntSize& pluginSize, const IntRect& clipRect, const AffineTransform& pluginToRootViewTransform, float contentsScaleFactor, const ShareableBitmap::Handle& backingStoreHandle)
{
    ASSERT(m_plugin);

    m_pluginSize = pluginSize;

    if (contentsScaleFactor != m_contentsScaleFactor) {
        m_contentsScaleFactor = contentsScaleFactor;
        m_plugin->contentsScaleFactorChanged(m_contentsScaleFactor);
    }

    platformGeometryDidChange();

    if (!backingStoreHandle.isNull()) {
        // Create a new backing store.
        m_backingStore = ShareableBitmap::create(backingStoreHandle);
    }

    m_plugin->geometryDidChange(pluginSize, clipRect, pluginToRootViewTransform);
}

void PluginControllerProxy::didEvaluateJavaScript(uint64_t requestID, const String& result)
{
    m_plugin->didEvaluateJavaScript(requestID, result);
}

void PluginControllerProxy::streamDidReceiveResponse(uint64_t streamID, const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
{
    m_plugin->streamDidReceiveResponse(streamID, KURL(ParsedURLString, responseURLString), streamLength, lastModifiedTime, mimeType, headers, String());
}

void PluginControllerProxy::streamDidReceiveData(uint64_t streamID, const CoreIPC::DataReference& data)
{
    m_plugin->streamDidReceiveData(streamID, reinterpret_cast<const char*>(data.data()), data.size());
}

void PluginControllerProxy::streamDidFinishLoading(uint64_t streamID)
{
    m_plugin->streamDidFinishLoading(streamID);
}

void PluginControllerProxy::streamDidFail(uint64_t streamID, bool wasCancelled)
{
    m_plugin->streamDidFail(streamID, wasCancelled);
}

void PluginControllerProxy::manualStreamDidReceiveResponse(const String& responseURLString, uint32_t streamLength, uint32_t lastModifiedTime, const String& mimeType, const String& headers)
{
    if (m_pluginCanceledManualStreamLoad)
        return;

    m_plugin->manualStreamDidReceiveResponse(KURL(ParsedURLString, responseURLString), streamLength, lastModifiedTime, mimeType, headers, String());
}

void PluginControllerProxy::manualStreamDidReceiveData(const CoreIPC::DataReference& data)
{
    if (m_pluginCanceledManualStreamLoad)
        return;

    m_plugin->manualStreamDidReceiveData(reinterpret_cast<const char*>(data.data()), data.size());
}

void PluginControllerProxy::manualStreamDidFinishLoading()
{
    if (m_pluginCanceledManualStreamLoad)
        return;
    
    m_plugin->manualStreamDidFinishLoading();
}

void PluginControllerProxy::manualStreamDidFail(bool wasCancelled)
{
    if (m_pluginCanceledManualStreamLoad)
        return;
    
    m_plugin->manualStreamDidFail(wasCancelled);
}
    
void PluginControllerProxy::handleMouseEvent(const WebMouseEvent& mouseEvent, PassRefPtr<Messages::PluginControllerProxy::HandleMouseEvent::DelayedReply> reply)
{
    // Always let the web process think that we've handled this mouse event, even before passing it along to the plug-in.
    // This is a workaround for 
    // <rdar://problem/9299901> UI process thinks the page is unresponsive when a plug-in is showing a context menu.
    // The web process sends a synchronous HandleMouseEvent message and the plug-in process spawns a nested
    // run loop when showing the context menu, so eventually the unresponsiveness timer kicks in in the UI process.
    // FIXME: We should come up with a better way to do this.
    reply->send(true);

    m_plugin->handleMouseEvent(mouseEvent);
}

void PluginControllerProxy::handleWheelEvent(const WebWheelEvent& wheelEvent, bool& handled)
{
    handled = m_plugin->handleWheelEvent(wheelEvent);
}

void PluginControllerProxy::handleMouseEnterEvent(const WebMouseEvent& mouseEnterEvent, bool& handled)
{
    handled = m_plugin->handleMouseEnterEvent(mouseEnterEvent);
}

void PluginControllerProxy::handleMouseLeaveEvent(const WebMouseEvent& mouseLeaveEvent, bool& handled)
{
    handled = m_plugin->handleMouseLeaveEvent(mouseLeaveEvent);
}

void PluginControllerProxy::handleKeyboardEvent(const WebKeyboardEvent& keyboardEvent, bool& handled)
{
    handled = m_plugin->handleKeyboardEvent(keyboardEvent);
}

void PluginControllerProxy::paintEntirePlugin()
{
    if (m_pluginSize.isEmpty())
        return;

    m_dirtyRect = IntRect(IntPoint(), m_pluginSize);
    paint();
}

void PluginControllerProxy::snapshot(ShareableBitmap::Handle& backingStoreHandle)
{
    ASSERT(m_plugin);
    RefPtr<ShareableBitmap> bitmap = m_plugin->snapshot();
    if (!bitmap)
        return;

    bitmap->createHandle(backingStoreHandle);
}

void PluginControllerProxy::setFocus(bool hasFocus)
{
    m_plugin->setFocus(hasFocus);
}

void PluginControllerProxy::didUpdate()
{
    m_waitingForDidUpdate = false;
    startPaintTimer();
}

void PluginControllerProxy::getPluginScriptableNPObject(uint64_t& pluginScriptableNPObjectID)
{
    NPObject* pluginScriptableNPObject = m_plugin->pluginScriptableNPObject();
    if (!pluginScriptableNPObject) {
        pluginScriptableNPObjectID = 0;
        return;
    }
    
    pluginScriptableNPObjectID = m_connection->npRemoteObjectMap()->registerNPObject(pluginScriptableNPObject, m_plugin.get());
    releaseNPObject(pluginScriptableNPObject);
}

void PluginControllerProxy::privateBrowsingStateChanged(bool isPrivateBrowsingEnabled)
{
    m_isPrivateBrowsingEnabled = isPrivateBrowsingEnabled;

    m_plugin->privateBrowsingStateChanged(isPrivateBrowsingEnabled);
}

void PluginControllerProxy::getFormValue(bool& returnValue, String& formValue)
{
    returnValue = m_plugin->getFormValue(formValue);
}

bool PluginControllerProxy::tryToShortCircuitEvaluate(NPObject* npObject, const String& scriptString, NPVariant* result)
{
    // Only try to short circuit evaluate for plug-ins that have the quirk specified.
#if PLUGIN_ARCHITECTURE(MAC)
    if (!PluginProcess::shared().netscapePluginModule()->pluginQuirks().contains(PluginQuirks::CanShortCircuitSomeNPRuntimeCallsDuringInitialization))
        return false;
#else
    return false;
#endif

    // And only when we're in initialize.
    if (!inInitialize())
        return false;

    // And only when the NPObject is the window NPObject.
    if (npObject != m_windowNPObject)
        return false;

    // Now, check for the right strings.
    if (scriptString != "function __flash_getWindowLocation() { return window.location; }"
        && scriptString != "function __flash_getTopLocation() { return top.location; }")
        return false;

    VOID_TO_NPVARIANT(*result);
    return true;
}

} // namespace WebKit

#endif // ENABLE(PLUGIN_PROCESS)
