/*
 * 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 "NetscapePlugin.h"

#if ENABLE(NETSCAPE_PLUGIN_API)

#include "NPRuntimeObjectMap.h"
#include "NPRuntimeUtilities.h"
#include "NetscapePluginStream.h"
#include "PluginController.h"
#include "ShareableBitmap.h"
#include <JavaScriptCore/JSObject.h>
#include <WebCore/GraphicsContext.h>
#include <WebCore/HTTPHeaderMap.h>
#include <WebCore/IntRect.h>
#include <WebCore/SharedBuffer.h>
#include <utility>
#include <wtf/URL.h>
#include <wtf/text/CString.h>

#if PLUGIN_ARCHITECTURE(UNIX)
#include "NetscapePluginUnix.h"
#endif

namespace WebKit {
using namespace WebCore;

// The plug-in that we're currently calling NPP_New for.
static NetscapePlugin* currentNPPNewPlugin;

RefPtr<NetscapePlugin> NetscapePlugin::create(RefPtr<NetscapePluginModule>&& pluginModule)
{
    if (!pluginModule)
        return nullptr;

    return adoptRef(*new NetscapePlugin(pluginModule.releaseNonNull()));
}
    
NetscapePlugin::NetscapePlugin(Ref<NetscapePluginModule>&& pluginModule)
    : Plugin(NetscapePluginType)
    , m_nextRequestID(0)
    , m_pluginModule(WTFMove(pluginModule))
    , m_npWindow()
    , m_isStarted(false)
#if PLATFORM(COCOA)
    , m_isWindowed(false)
#else
    , m_isWindowed(true)
#endif
    , m_isTransparent(false)
    , m_inNPPNew(false)
    , m_shouldUseManualLoader(false)
    , m_hasCalledSetWindow(false)
    , m_isVisible(false)
    , m_nextTimerID(0)
#if PLATFORM(COCOA)
    , m_drawingModel(static_cast<NPDrawingModel>(-1))
    , m_eventModel(static_cast<NPEventModel>(-1))
    , m_pluginReturnsNonretainedLayer(!m_pluginModule->pluginQuirks().contains(PluginQuirks::ReturnsRetainedCoreAnimationLayer))
    , m_layerHostingMode(LayerHostingMode::InProcess)
    , m_currentMouseEvent(0)
    , m_pluginHasFocus(false)
    , m_windowHasFocus(false)
    , m_pluginWantsLegacyCocoaTextInput(true)
    , m_isComplexTextInputEnabled(false)
    , m_hasHandledAKeyDownEvent(false)
    , m_ignoreNextKeyUpEventCounter(0)
#ifndef NP_NO_CARBON
    , m_nullEventTimer(RunLoop::main(), this, &NetscapePlugin::nullEventTimerFired)
    , m_npCGContext()
#endif
#endif
{
    m_npp.ndata = this;
    m_npp.pdata = 0;
    
    m_pluginModule->incrementLoadCount();
}

NetscapePlugin::~NetscapePlugin()
{
    ASSERT(!m_isStarted);
    ASSERT(m_timers.isEmpty());

    m_pluginModule->decrementLoadCount();
}

RefPtr<NetscapePlugin> NetscapePlugin::fromNPP(NPP npp)
{
    if (!npp)
        return nullptr;

    return static_cast<NetscapePlugin*>(npp->ndata);
}

void NetscapePlugin::invalidate(const NPRect* invalidRect)
{
    IntRect rect;
    
    if (!invalidRect)
        rect = IntRect(0, 0, m_pluginSize.width(), m_pluginSize.height());
    else
        rect = IntRect(invalidRect->left, invalidRect->top, invalidRect->right - invalidRect->left, invalidRect->bottom - invalidRect->top);
    
    if (platformInvalidate(rect))
        return;

    controller()->invalidate(rect);
}

const char* NetscapePlugin::userAgent(NPP npp)
{
    if (npp)
        return fromNPP(npp)->userAgent();

    if (currentNPPNewPlugin)
        return currentNPPNewPlugin->userAgent();

    return 0;
}

const char* NetscapePlugin::userAgent()
{
    if (m_userAgent.isNull()) {
        String userAgent = controller()->userAgent();
        ASSERT(!userAgent.isNull());

#if PLUGIN_ARCHITECTURE(MAC)
        if (quirks().contains(PluginQuirks::AppendVersion3UserAgent))
            userAgent.append(" Version/3.2.1");
#endif

        m_userAgent = userAgent.utf8();
    }
    return m_userAgent.data();
}

void NetscapePlugin::loadURL(const String& method, const String& urlString, const String& target, const HTTPHeaderMap& headerFields, const Vector<uint8_t>& httpBody,
                             bool sendNotification, void* notificationData)
{
    uint64_t requestID = ++m_nextRequestID;
    
    controller()->loadURL(requestID, method, urlString, target, headerFields, httpBody, allowPopups());

    if (target.isNull()) {
        // The browser is going to send the data in a stream, create a plug-in stream.
        auto pluginStream = NetscapePluginStream::create(*this, requestID, urlString, sendNotification, notificationData);
        ASSERT(!m_streams.contains(requestID));

        m_streams.set(requestID, WTFMove(pluginStream));
        return;
    }

    if (sendNotification) {
        // Eventually we are going to get a frameDidFinishLoading or frameDidFail call for this request.
        // Keep track of the notification data so we can call NPP_URLNotify.
        ASSERT(!m_pendingURLNotifications.contains(requestID));
        m_pendingURLNotifications.set(requestID, std::make_pair(urlString, notificationData));
    }
}

NPError NetscapePlugin::destroyStream(NPStream* stream, NPReason reason)
{
    NetscapePluginStream* pluginStream = 0;

    for (StreamsMap::const_iterator it = m_streams.begin(), end = m_streams.end(); it != end; ++it) {
        if (it->value->npStream() == stream) {
            pluginStream = it->value.get();
            break;
        }
    }

    if (!pluginStream)
        return NPERR_INVALID_INSTANCE_ERROR;

    return pluginStream->destroy(reason);
}

void NetscapePlugin::setIsWindowed(bool isWindowed)
{
    // Once the plugin has started, it's too late to change whether the plugin is windowed or not.
    // (This is true in Firefox and Chrome, too.) Disallow setting m_isWindowed in that case to
    // keep our internal state consistent.
    if (m_isStarted)
        return;

    m_isWindowed = isWindowed;
}

void NetscapePlugin::setIsTransparent(bool isTransparent)
{
    m_isTransparent = isTransparent;
}

void NetscapePlugin::setStatusbarText(const String& statusbarText)
{
    controller()->setStatusbarText(statusbarText);
}

static void (*setExceptionFunction)(const String&);

void NetscapePlugin::setSetExceptionFunction(void (*function)(const String&))
{
    ASSERT(!setExceptionFunction || setExceptionFunction == function);
    setExceptionFunction = function;
}

void NetscapePlugin::setException(const String& exceptionString)
{
    ASSERT(setExceptionFunction);
    setExceptionFunction(exceptionString);
}

bool NetscapePlugin::evaluate(NPObject* npObject, const String& scriptString, NPVariant* result)
{
    return controller()->evaluate(npObject, scriptString, result, allowPopups());
}

bool NetscapePlugin::isPrivateBrowsingEnabled()
{
    return controller()->isPrivateBrowsingEnabled();
}

bool NetscapePlugin::isMuted() const
{
    return controller()->isMuted();
}

NPObject* NetscapePlugin::windowScriptNPObject()
{
    return controller()->windowScriptNPObject();
}

NPObject* NetscapePlugin::pluginElementNPObject()
{
    return controller()->pluginElementNPObject();
}

void NetscapePlugin::cancelStreamLoad(NetscapePluginStream* pluginStream)
{
    if (pluginStream == m_manualStream) {
        controller()->cancelManualStreamLoad();
        return;
    }

    // Ask the plug-in controller to cancel this stream load.
    controller()->cancelStreamLoad(pluginStream->streamID());
}

void NetscapePlugin::removePluginStream(NetscapePluginStream* pluginStream)
{
    if (pluginStream == m_manualStream) {
        m_manualStream = nullptr;
        return;
    }

    ASSERT(m_streams.get(pluginStream->streamID()) == pluginStream);
    m_streams.remove(pluginStream->streamID());
}

bool NetscapePlugin::isAcceleratedCompositingEnabled()
{
    return controller()->isAcceleratedCompositingEnabled();
}

void NetscapePlugin::pushPopupsEnabledState(bool state)
{
    m_popupEnabledStates.append(state);
}
 
void NetscapePlugin::popPopupsEnabledState()
{
    ASSERT(!m_popupEnabledStates.isEmpty());

    m_popupEnabledStates.removeLast();
}

void NetscapePlugin::pluginThreadAsyncCall(void (*function)(void*), void* userData)
{
    RunLoop::main().dispatch([protectedThis = makeRef(*this), function, userData] {
        if (!protectedThis->m_isStarted)
            return;

        function(userData);
    });
}

NetscapePlugin::Timer::Timer(NetscapePlugin* netscapePlugin, unsigned timerID, unsigned interval, bool repeat, TimerFunc timerFunc)
    : m_netscapePlugin(netscapePlugin)
    , m_timerID(timerID)
    , m_interval(interval)
    , m_repeat(repeat)
    , m_timerFunc(timerFunc)
    , m_timer(RunLoop::main(), this, &Timer::timerFired)
{
}

NetscapePlugin::Timer::~Timer()
{
}

void NetscapePlugin::Timer::start()
{
    Seconds timeInterval = 1_ms * m_interval;

    if (m_repeat)
        m_timer.startRepeating(timeInterval);
    else
        m_timer.startOneShot(timeInterval);
}

void NetscapePlugin::Timer::stop()
{
    m_timer.stop();
}

void NetscapePlugin::Timer::timerFired()
{
    m_timerFunc(&m_netscapePlugin->m_npp, m_timerID);

    if (!m_repeat)
        m_netscapePlugin->unscheduleTimer(m_timerID);
}

uint32_t NetscapePlugin::scheduleTimer(unsigned interval, bool repeat, void (*timerFunc)(NPP, unsigned timerID))
{
    if (!timerFunc)
        return 0;

    // FIXME: Handle wrapping around.
    unsigned timerID = ++m_nextTimerID;

    auto timer = std::make_unique<Timer>(this, timerID, interval, repeat, timerFunc);
    
    // FIXME: Based on the plug-in visibility, figure out if we should throttle the timer, or if we should start it at all.
    timer->start();
    m_timers.set(timerID, WTFMove(timer));

    return timerID;
}

void NetscapePlugin::unscheduleTimer(unsigned timerID)
{
    if (auto timer = m_timers.take(timerID))
        timer->stop();
}

double NetscapePlugin::contentsScaleFactor()
{
    return controller()->contentsScaleFactor();
}

String NetscapePlugin::proxiesForURL(const String& urlString)
{
    return controller()->proxiesForURL(urlString);
}
    
String NetscapePlugin::cookiesForURL(const String& urlString)
{
    return controller()->cookiesForURL(urlString);
}

void NetscapePlugin::setCookiesForURL(const String& urlString, const String& cookieString)
{
    controller()->setCookiesForURL(urlString, cookieString);
}

bool NetscapePlugin::getAuthenticationInfo(const ProtectionSpace& protectionSpace, String& username, String& password)
{
    return controller()->getAuthenticationInfo(protectionSpace, username, password);
}

void NetscapePlugin::registerRedirect(NetscapePluginStream* stream, const URL& requestURL, int redirectResponseStatus, void* notificationData)
{
    // NPP_URLRedirectNotify may synchronously request this stream back out, so set it first
    m_redirects.set(notificationData, std::make_pair(stream, requestURL.string()));
    if (!NPP_URLRedirectNotify(requestURL.string().utf8().data(), redirectResponseStatus, notificationData)) {
        m_redirects.take(notificationData);
        controller()->continueStreamLoad(stream->streamID());
    }
}

void NetscapePlugin::urlRedirectResponse(void* notifyData, bool allow)
{
    if (!m_redirects.contains(notifyData))
        return;

    auto redirect = m_redirects.take(notifyData);
    if (!redirect.first)
        return;

    RefPtr<NetscapePluginStream> stream = redirect.first;
    if (!allow) {
        controller()->cancelStreamLoad(stream->streamID());
        stream->stop(NPRES_USER_BREAK);
    } else {
        stream->setURL(redirect.second);
        controller()->continueStreamLoad(stream->streamID());
    }
}

void NetscapePlugin::setIsPlayingAudio(bool isPlayingAudio)
{
    controller()->setPluginIsPlayingAudio(isPlayingAudio);
}

NPError NetscapePlugin::NPP_New(NPMIMEType pluginType, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* savedData)
{
    return m_pluginModule->pluginFuncs().newp(pluginType, &m_npp, mode, argc, argn, argv, savedData);
}
    
NPError NetscapePlugin::NPP_Destroy(NPSavedData** savedData)
{
    return m_pluginModule->pluginFuncs().destroy(&m_npp, savedData);
}

NPError NetscapePlugin::NPP_SetWindow(NPWindow* npWindow)
{
    return m_pluginModule->pluginFuncs().setwindow(&m_npp, npWindow);
}

NPError NetscapePlugin::NPP_NewStream(NPMIMEType mimeType, NPStream* stream, NPBool seekable, uint16_t* streamType)
{
    return m_pluginModule->pluginFuncs().newstream(&m_npp, mimeType, stream, seekable, streamType);
}

NPError NetscapePlugin::NPP_DestroyStream(NPStream* stream, NPReason reason)
{
    return m_pluginModule->pluginFuncs().destroystream(&m_npp, stream, reason);
}

void NetscapePlugin::NPP_StreamAsFile(NPStream* stream, const char* filename)
{
    return m_pluginModule->pluginFuncs().asfile(&m_npp, stream, filename);
}

int32_t NetscapePlugin::NPP_WriteReady(NPStream* stream)
{
    return m_pluginModule->pluginFuncs().writeready(&m_npp, stream);
}

int32_t NetscapePlugin::NPP_Write(NPStream* stream, int32_t offset, int32_t len, void* buffer)
{
    return m_pluginModule->pluginFuncs().write(&m_npp, stream, offset, len, buffer);
}

int16_t NetscapePlugin::NPP_HandleEvent(void* event)
{
    return m_pluginModule->pluginFuncs().event(&m_npp, event);
}

void NetscapePlugin::NPP_URLNotify(const char* url, NPReason reason, void* notifyData)
{
    m_pluginModule->pluginFuncs().urlnotify(&m_npp, url, reason, notifyData);
}

bool NetscapePlugin::NPP_URLRedirectNotify(const char* url, int32_t status, void* notifyData)
{
    if (!m_pluginModule->pluginFuncs().urlredirectnotify)
        return false;

    m_pluginModule->pluginFuncs().urlredirectnotify(&m_npp, url, status, notifyData);
    return true;
}

NPError NetscapePlugin::NPP_GetValue(NPPVariable variable, void *value)
{
    if (!m_pluginModule->pluginFuncs().getvalue)
        return NPERR_GENERIC_ERROR;

    return m_pluginModule->pluginFuncs().getvalue(&m_npp, variable, value);
}

NPError NetscapePlugin::NPP_SetValue(NPNVariable variable, void *value)
{
    if (!m_pluginModule->pluginFuncs().setvalue)
        return NPERR_GENERIC_ERROR;

    return m_pluginModule->pluginFuncs().setvalue(&m_npp, variable, value);
}

void NetscapePlugin::callSetWindow()
{
    if (wantsPluginRelativeNPWindowCoordinates()) {
        m_npWindow.x = 0;
        m_npWindow.y = 0;
        m_npWindow.clipRect.top = m_clipRect.y();
        m_npWindow.clipRect.left = m_clipRect.x();
    } else {
        IntPoint pluginLocationInRootViewCoordinates = convertToRootView(IntPoint());
        IntPoint clipRectInRootViewCoordinates = convertToRootView(m_clipRect.location());

        m_npWindow.x = pluginLocationInRootViewCoordinates.x();
        m_npWindow.y = pluginLocationInRootViewCoordinates.y();
        m_npWindow.clipRect.top = clipRectInRootViewCoordinates.y();
        m_npWindow.clipRect.left = clipRectInRootViewCoordinates.x();
    }

    m_npWindow.width = m_pluginSize.width();
    m_npWindow.height = m_pluginSize.height();
    m_npWindow.clipRect.right = m_npWindow.clipRect.left + m_clipRect.width();
    m_npWindow.clipRect.bottom = m_npWindow.clipRect.top + m_clipRect.height();

    NPP_SetWindow(&m_npWindow);
    m_hasCalledSetWindow = true;
}

void NetscapePlugin::callSetWindowInvisible()
{
    NPWindow invisibleWindow = m_npWindow;
    
    invisibleWindow.window = 0;
    invisibleWindow.clipRect.top = 0;
    invisibleWindow.clipRect.left = 0;
    invisibleWindow.clipRect.bottom = 0;
    invisibleWindow.clipRect.right = 0;
    
    NPP_SetWindow(&invisibleWindow);
    m_hasCalledSetWindow = true;
}

bool NetscapePlugin::shouldLoadSrcURL()
{
#if PLATFORM(X11)
    // Flash crashes when NPP_GetValue is called for NPPVpluginCancelSrcStream in windowed mode.
    if (m_isWindowed && m_pluginModule->pluginQuirks().contains(PluginQuirks::DoNotCancelSrcStreamInWindowedMode))
        return true;
#endif

    // Check if we should cancel the load
    NPBool cancelSrcStream = false;

    if (NPP_GetValue(NPPVpluginCancelSrcStream, &cancelSrcStream) != NPERR_NO_ERROR)
        return true;

    return !cancelSrcStream;
}

NetscapePluginStream* NetscapePlugin::streamFromID(uint64_t streamID)
{
    return m_streams.get(streamID);
}

void NetscapePlugin::stopAllStreams()
{
    for (auto& stream : copyToVector(m_streams.values()))
        stream->stop(NPRES_USER_BREAK);
}

bool NetscapePlugin::allowPopups() const
{
    if (m_pluginModule->pluginFuncs().version >= NPVERS_HAS_POPUPS_ENABLED_STATE) {
        if (!m_popupEnabledStates.isEmpty())
            return m_popupEnabledStates.last();
    }

    // FIXME: Check if the current event is a user gesture.
    // Really old versions of Flash required this for popups to work, but all newer versions
    // support NPN_PushPopupEnabledState/NPN_PopPopupEnabledState.
    return false;
}

#if PLUGIN_ARCHITECTURE(MAC)
static bool isTransparentSilverlightBackgroundValue(const String& lowercaseBackgroundValue)
{
    // This checks if the background color value is transparent, according to
    // the format documented at http://msdn.microsoft.com/en-us/library/cc838148(VS.95).aspx
    if (lowercaseBackgroundValue.startsWith('#')) {
        if (lowercaseBackgroundValue.length() == 5 && lowercaseBackgroundValue[1] != 'f') {
            // An 8-bit RGB value with alpha transparency, in the form #ARGB.
            return true;
        }

        if (lowercaseBackgroundValue.length() == 9 && !(lowercaseBackgroundValue[1] == 'f' && lowercaseBackgroundValue[2] == 'f')) {
            // A 16-bit RGB value with alpha transparency, in the form #AARRGGBB.
            return true;
        }
    } else if (lowercaseBackgroundValue.startsWith("sc#")) {
        Vector<String> components = lowercaseBackgroundValue.substring(3).split(',');

        // An ScRGB value with alpha transparency, in the form sc#A,R,G,B.
        if (components.size() == 4) {
            if (components[0].toDouble() < 1)
                return true;
        }
    } else if (lowercaseBackgroundValue == "transparent")
        return true;

    // This is an opaque color.
    return false;
}
#endif

bool NetscapePlugin::initialize(const Parameters& parameters)
{
    uint16_t mode = parameters.isFullFramePlugin ? NP_FULL : NP_EMBED;
    
    m_shouldUseManualLoader = parameters.shouldUseManualLoader;

    CString mimeTypeCString = parameters.mimeType.utf8();

    ASSERT(parameters.names.size() == parameters.values.size());

    Vector<CString> paramNames;
    Vector<CString> paramValues;
    for (size_t i = 0; i < parameters.names.size(); ++i) {
        String parameterName = parameters.names[i];

#if PLUGIN_ARCHITECTURE(MAC)
        if (m_pluginModule->pluginQuirks().contains(PluginQuirks::WantsLowercaseParameterNames))
            parameterName = parameterName.convertToASCIILowercase();
#endif

        paramNames.append(parameterName.utf8());
        paramValues.append(parameters.values[i].utf8());
    }

#if PLATFORM(X11)
    if (equalLettersIgnoringASCIICase(parameters.mimeType, "application/x-shockwave-flash")) {
        size_t wmodeIndex = parameters.names.find("wmode");
        if (wmodeIndex != notFound) {
            // Transparent window mode is not supported by X11 backend.
            if (equalLettersIgnoringASCIICase(parameters.values[wmodeIndex], "transparent")
                || (m_pluginModule->pluginQuirks().contains(PluginQuirks::ForceFlashWindowlessMode) && equalLettersIgnoringASCIICase(parameters.values[wmodeIndex], "window")))
                paramValues[wmodeIndex] = "opaque";
        } else if (m_pluginModule->pluginQuirks().contains(PluginQuirks::ForceFlashWindowlessMode)) {
            paramNames.append("wmode");
            paramValues.append("opaque");
        }
    }
#endif

    if (equalLettersIgnoringASCIICase(parameters.mimeType, "application/x-webkit-test-netscape")) {
        paramNames.append("windowedPlugin");
        paramValues.append("false");
    }

    // The strings that these pointers point to are kept alive by paramNames and paramValues.
    Vector<const char*> names;
    Vector<const char*> values;
    for (size_t i = 0; i < paramNames.size(); ++i) {
        names.append(paramNames[i].data());
        values.append(paramValues[i].data());
    }

#if PLUGIN_ARCHITECTURE(MAC)
    if (m_pluginModule->pluginQuirks().contains(PluginQuirks::MakeOpaqueUnlessTransparentSilverlightBackgroundAttributeExists)) {
        for (size_t i = 0; i < parameters.names.size(); ++i) {
            if (equalLettersIgnoringASCIICase(parameters.names[i], "background")) {
                setIsTransparent(isTransparentSilverlightBackgroundValue(parameters.values[i].convertToASCIILowercase()));
                break;
            }
        }
    }

    m_layerHostingMode = parameters.layerHostingMode;
#endif

    platformPreInitialize();

    NetscapePlugin* previousNPPNewPlugin = currentNPPNewPlugin;
    
    m_inNPPNew = true;
    currentNPPNewPlugin = this;

    NPError error = NPP_New(const_cast<char*>(mimeTypeCString.data()), mode, names.size(),
                            const_cast<char**>(names.data()), const_cast<char**>(values.data()), 0);

    m_inNPPNew = false;
    currentNPPNewPlugin = previousNPPNewPlugin;

    if (error != NPERR_NO_ERROR)
        return false;

    m_isStarted = true;

    // FIXME: This is not correct in all cases.
    m_npWindow.type = NPWindowTypeDrawable;

    if (!platformPostInitialize()) {
        destroy();
        return false;
    }

    // Load the src URL if needed.
    if (!parameters.shouldUseManualLoader && !parameters.url.isEmpty() && shouldLoadSrcURL())
        loadURL("GET", parameters.url.string(), String(), HTTPHeaderMap(), Vector<uint8_t>(), false, 0);
    
    return true;
}
    
void NetscapePlugin::destroy()
{
    ASSERT(m_isStarted);

    // Stop all streams.
    stopAllStreams();

#if !PLUGIN_ARCHITECTURE(MAC) && !PLUGIN_ARCHITECTURE(UNIX)
    m_npWindow.window = 0;
    callSetWindow();
#endif

    NPP_Destroy(0);

    m_isStarted = false;

    platformDestroy();

    m_timers.clear();
}
    
void NetscapePlugin::paint(GraphicsContext& context, const IntRect& dirtyRect)
{
    ASSERT(m_isStarted);
    
    platformPaint(context, dirtyRect);
}

RefPtr<ShareableBitmap> NetscapePlugin::snapshot()
{
    if (!supportsSnapshotting() || m_pluginSize.isEmpty())
        return nullptr;

    ASSERT(m_isStarted);

    IntSize backingStoreSize = m_pluginSize;
    backingStoreSize.scale(contentsScaleFactor());

    auto bitmap = ShareableBitmap::createShareable(backingStoreSize, { });
    auto context = bitmap->createGraphicsContext();

    // 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.
    context->scale(contentsScaleFactor());

    platformPaint(*context, IntRect(IntPoint(), m_pluginSize), true);

    return bitmap;
}

bool NetscapePlugin::isTransparent()
{
    return m_isTransparent;
}

bool NetscapePlugin::wantsWheelEvents()
{
    return m_pluginModule->pluginQuirks().contains(PluginQuirks::WantsWheelEvents);
}

void NetscapePlugin::geometryDidChange(const IntSize& pluginSize, const IntRect& clipRect, const AffineTransform& pluginToRootViewTransform)
{
    ASSERT(m_isStarted);

    if (pluginSize == m_pluginSize && m_clipRect == clipRect && m_pluginToRootViewTransform == pluginToRootViewTransform) {
        // Nothing to do.
        return;
    }

    bool shouldCallSetWindow = true;

    // If the plug-in doesn't want window relative coordinates, we don't need to call setWindow unless its size or clip rect changes.
    if (m_hasCalledSetWindow && wantsPluginRelativeNPWindowCoordinates() && m_pluginSize == pluginSize && m_clipRect == clipRect)
        shouldCallSetWindow = false;

    m_pluginSize = pluginSize;
    m_clipRect = clipRect;
    m_pluginToRootViewTransform = pluginToRootViewTransform;

#if PLUGIN_ARCHITECTURE(UNIX)
    IntPoint frameRectLocationInWindowCoordinates = m_pluginToRootViewTransform.mapPoint(IntPoint());
    m_frameRectInWindowCoordinates = IntRect(frameRectLocationInWindowCoordinates, m_pluginSize);
#endif

    platformGeometryDidChange();

    if (!shouldCallSetWindow)
        return;

    callSetWindow();
}

void NetscapePlugin::visibilityDidChange(bool isVisible)
{
    ASSERT(m_isStarted);

    if (m_isVisible == isVisible)
        return;

    m_isVisible = isVisible;
    platformVisibilityDidChange();
}

void NetscapePlugin::frameDidFinishLoading(uint64_t requestID)
{
    ASSERT(m_isStarted);
    
    auto notification = m_pendingURLNotifications.take(requestID);
    if (notification.first.isEmpty())
        return;

    NPP_URLNotify(notification.first.utf8().data(), NPRES_DONE, notification.second);
}

void NetscapePlugin::frameDidFail(uint64_t requestID, bool wasCancelled)
{
    ASSERT(m_isStarted);
    
    auto notification = m_pendingURLNotifications.take(requestID);
    if (notification.first.isNull())
        return;
    
    NPP_URLNotify(notification.first.utf8().data(), wasCancelled ? NPRES_USER_BREAK : NPRES_NETWORK_ERR, notification.second);
}

void NetscapePlugin::didEvaluateJavaScript(uint64_t requestID, const String& result)
{
    ASSERT(m_isStarted);
    
    if (NetscapePluginStream* pluginStream = streamFromID(requestID))
        pluginStream->sendJavaScriptStream(result);
}

void NetscapePlugin::streamWillSendRequest(uint64_t streamID, const URL& requestURL, const URL& redirectResponseURL, int redirectResponseStatus)
{
    ASSERT(m_isStarted);

    if (NetscapePluginStream* pluginStream = streamFromID(streamID))
        pluginStream->willSendRequest(requestURL, redirectResponseURL, redirectResponseStatus);
}

void NetscapePlugin::streamDidReceiveResponse(uint64_t streamID, const URL& responseURL, uint32_t streamLength, 
                                              uint32_t lastModifiedTime, const String& mimeType, const String& headers, const String& /* suggestedFileName */)
{
    ASSERT(m_isStarted);
    
    if (NetscapePluginStream* pluginStream = streamFromID(streamID))
        pluginStream->didReceiveResponse(responseURL, streamLength, lastModifiedTime, mimeType, headers);
}

void NetscapePlugin::streamDidReceiveData(uint64_t streamID, const char* bytes, int length)
{
    ASSERT(m_isStarted);
    
    if (NetscapePluginStream* pluginStream = streamFromID(streamID))
        pluginStream->didReceiveData(bytes, length);
}

void NetscapePlugin::streamDidFinishLoading(uint64_t streamID)
{
    ASSERT(m_isStarted);
    
    if (NetscapePluginStream* pluginStream = streamFromID(streamID))
        pluginStream->didFinishLoading();
}

void NetscapePlugin::streamDidFail(uint64_t streamID, bool wasCancelled)
{
    ASSERT(m_isStarted);
    
    if (NetscapePluginStream* pluginStream = streamFromID(streamID))
        pluginStream->didFail(wasCancelled);
}

void NetscapePlugin::manualStreamDidReceiveResponse(const URL& responseURL, uint32_t streamLength, uint32_t lastModifiedTime, 
                                                    const String& mimeType, const String& headers, const String& /* suggestedFileName */)
{
    ASSERT(m_isStarted);
    ASSERT(m_shouldUseManualLoader);
    ASSERT(!m_manualStream);
    
    m_manualStream = NetscapePluginStream::create(*this, 0, responseURL.string(), false, 0);
    m_manualStream->didReceiveResponse(responseURL, streamLength, lastModifiedTime, mimeType, headers);
}

void NetscapePlugin::manualStreamDidReceiveData(const char* bytes, int length)
{
    ASSERT(m_isStarted);
    ASSERT(m_shouldUseManualLoader);
    ASSERT(m_manualStream);

    m_manualStream->didReceiveData(bytes, length);
}

void NetscapePlugin::manualStreamDidFinishLoading()
{
    ASSERT(m_isStarted);
    ASSERT(m_shouldUseManualLoader);
    ASSERT(m_manualStream);

    m_manualStream->didFinishLoading();
}

void NetscapePlugin::manualStreamDidFail(bool wasCancelled)
{
    ASSERT(m_isStarted);
    ASSERT(m_shouldUseManualLoader);

    if (!m_manualStream)
        return;
    m_manualStream->didFail(wasCancelled);
}

bool NetscapePlugin::handleMouseEvent(const WebMouseEvent& mouseEvent)
{
    ASSERT(m_isStarted);
    
    return platformHandleMouseEvent(mouseEvent);
}
    
bool NetscapePlugin::handleWheelEvent(const WebWheelEvent& wheelEvent)
{
    ASSERT(m_isStarted);

    return platformHandleWheelEvent(wheelEvent);
}

bool NetscapePlugin::handleMouseEnterEvent(const WebMouseEvent& mouseEvent)
{
    ASSERT(m_isStarted);

    return platformHandleMouseEnterEvent(mouseEvent);
}

bool NetscapePlugin::handleMouseLeaveEvent(const WebMouseEvent& mouseEvent)
{
    ASSERT(m_isStarted);

    return platformHandleMouseLeaveEvent(mouseEvent);
}

bool NetscapePlugin::handleContextMenuEvent(const WebMouseEvent&)
{
    // We don't know if the plug-in has handled mousedown event by displaying a context menu, so we never want WebKit to show a default one.
    return true;
}

bool NetscapePlugin::handleKeyboardEvent(const WebKeyboardEvent& keyboardEvent)
{
    ASSERT(m_isStarted);

    return platformHandleKeyboardEvent(keyboardEvent);
}

bool NetscapePlugin::handleEditingCommand(const String& /* commandName */, const String& /* argument */)
{
    return false;
}

bool NetscapePlugin::isEditingCommandEnabled(const String& /* commandName */)
{
    return false;
}

bool NetscapePlugin::shouldAllowScripting()
{
    return true;
}

bool NetscapePlugin::shouldAllowNavigationFromDrags()
{
    return false;
}

bool NetscapePlugin::handlesPageScaleFactor() const
{
    return false;
}

void NetscapePlugin::setFocus(bool hasFocus)
{
    ASSERT(m_isStarted);

    platformSetFocus(hasFocus);
}

NPObject* NetscapePlugin::pluginScriptableNPObject()
{
    ASSERT(m_isStarted);
    NPObject* scriptableNPObject = 0;
    
    if (NPP_GetValue(NPPVpluginScriptableNPObject, &scriptableNPObject) != NPERR_NO_ERROR)
        return 0;

#if PLUGIN_ARCHITECTURE(MAC)
    if (m_pluginModule->pluginQuirks().contains(PluginQuirks::ReturnsNonRetainedScriptableNPObject))
        retainNPObject(scriptableNPObject);        
#endif    

    return scriptableNPObject;
}
    
unsigned NetscapePlugin::countFindMatches(const String&, WebCore::FindOptions, unsigned)
{
    return 0;
}

bool NetscapePlugin::findString(const String&, WebCore::FindOptions, unsigned)
{
    return false;
}

void NetscapePlugin::contentsScaleFactorChanged(float scaleFactor)
{
    ASSERT(m_isStarted);

#if PLUGIN_ARCHITECTURE(MAC)
    double contentsScaleFactor = scaleFactor;
    NPP_SetValue(NPNVcontentsScaleFactor, &contentsScaleFactor);
#else
    UNUSED_PARAM(scaleFactor);
#endif
}

void NetscapePlugin::storageBlockingStateChanged(bool storageBlockingEnabled)
{
    if (m_storageBlockingState != storageBlockingEnabled) {
        m_storageBlockingState = storageBlockingEnabled;
        updateNPNPrivateMode();
    }
}

void NetscapePlugin::privateBrowsingStateChanged(bool privateBrowsingEnabled)
{
    if (m_privateBrowsingState != privateBrowsingEnabled) {
        m_privateBrowsingState = privateBrowsingEnabled;
        updateNPNPrivateMode();
    }
}

void NetscapePlugin::updateNPNPrivateMode()
{
    ASSERT(m_isStarted);

    // From https://wiki.mozilla.org/Plugins:PrivateMode
    //   When the browser turns private mode on or off it will call NPP_SetValue for "NPNVprivateModeBool" 
    //   (assigned enum value 18) with a pointer to an NPBool value on all applicable instances.
    //   Plugins should check the boolean value pointed to, not the pointer itself. 
    //   The value will be true when private mode is on.
    NPBool value = m_privateBrowsingState || m_storageBlockingState;
    NPP_SetValue(NPNVprivateModeBool, &value);
}

bool NetscapePlugin::getFormValue(String& formValue)
{
    ASSERT(m_isStarted);

    char* formValueString = 0;
    if (NPP_GetValue(NPPVformValue, &formValueString) != NPERR_NO_ERROR)
        return false;

    formValue = String::fromUTF8(formValueString);

    // The plug-in allocates the form value string with NPN_MemAlloc so it needs to be freed with NPN_MemFree.
    npnMemFree(formValueString);
    return true;
}

bool NetscapePlugin::handleScroll(ScrollDirection, ScrollGranularity)
{
    return false;
}

Scrollbar* NetscapePlugin::horizontalScrollbar()
{
    return 0;
}

Scrollbar* NetscapePlugin::verticalScrollbar()
{
    return 0;
}

bool NetscapePlugin::supportsSnapshotting() const
{
#if PLATFORM(COCOA)
    return m_pluginModule->pluginQuirks().contains(PluginQuirks::SupportsSnapshotting);
#endif
    return false;
}

RefPtr<WebCore::SharedBuffer> NetscapePlugin::liveResourceData() const
{
    return nullptr;
}

IntPoint NetscapePlugin::convertToRootView(const IntPoint& pointInPluginCoordinates) const
{
    return m_pluginToRootViewTransform.mapPoint(pointInPluginCoordinates);
}

bool NetscapePlugin::convertFromRootView(const IntPoint& pointInRootViewCoordinates, IntPoint& pointInPluginCoordinates)
{
    if (auto inverse = m_pluginToRootViewTransform.inverse()) {
        pointInPluginCoordinates = inverse.value().mapPoint(pointInRootViewCoordinates);
        return true;
    }
    return false;
}

void NetscapePlugin::mutedStateChanged(bool muted)
{
    NPBool value = muted;
    NPP_SetValue(NPNVmuteAudioBool, &value);
}

#if !PLATFORM(COCOA)
    
void NetscapePlugin::windowFocusChanged(bool)
{
}

void NetscapePlugin::windowVisibilityChanged(bool)
{
}

#endif

} // namespace WebKit

#endif // ENABLE(NETSCAPE_PLUGIN_API)
