/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/DebugOnly.h"

#include "mozilla/Logging.h"
#include "nscore.h"
#include "prenv.h"

#include "nsNPAPIPluginInstance.h"
#include "nsNPAPIPlugin.h"
#include "nsNPAPIPluginStreamListener.h"
#include "nsPluginHost.h"
#include "nsPluginLogging.h"
#include "nsContentUtils.h"
#include "nsPluginInstanceOwner.h"

#include "nsThreadUtils.h"
#include "nsIDOMElement.h"
#include "nsIDocument.h"
#include "nsIDocShell.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptContext.h"
#include "nsDirectoryServiceDefs.h"
#include "nsJSNPRuntime.h"
#include "nsPluginStreamListenerPeer.h"
#include "nsSize.h"
#include "nsNetCID.h"
#include "nsIContent.h"
#include "nsVersionComparator.h"
#include "mozilla/Preferences.h"
#include "mozilla/Unused.h"
#include "nsILoadContext.h"
#include "mozilla/dom/HTMLObjectElementBinding.h"
#include "AudioChannelService.h"

using namespace mozilla;
using namespace mozilla::dom;

using namespace mozilla;
using namespace mozilla::plugins::parent;
using namespace mozilla::layers;

NS_IMPL_ISUPPORTS(nsNPAPIPluginInstance, nsIAudioChannelAgentCallback)

nsNPAPIPluginInstance::nsNPAPIPluginInstance()
  : mDrawingModel(kDefaultDrawingModel)
  , mRunning(NOT_STARTED)
  , mWindowless(false)
  , mTransparent(false)
  , mCached(false)
  , mUsesDOMForCursor(false)
  , mInPluginInitCall(false)
  , mPlugin(nullptr)
  , mMIMEType(nullptr)
  , mOwner(nullptr)
#ifdef XP_MACOSX
  , mCurrentPluginEvent(nullptr)
#endif
  , mHaveJavaC2PJSObjectQuirk(false)
  , mCachedParamLength(0)
  , mCachedParamNames(nullptr)
  , mCachedParamValues(nullptr)
  , mMuted(false)
{
  mNPP.pdata = nullptr;
  mNPP.ndata = this;

  PLUGIN_LOG(PLUGIN_LOG_BASIC, ("nsNPAPIPluginInstance ctor: this=%p\n",this));
}

nsNPAPIPluginInstance::~nsNPAPIPluginInstance()
{
  PLUGIN_LOG(PLUGIN_LOG_BASIC, ("nsNPAPIPluginInstance dtor: this=%p\n",this));

  if (mMIMEType) {
    free(mMIMEType);
    mMIMEType = nullptr;
  }

  if (!mCachedParamValues || !mCachedParamNames) {
    return;
  }
  MOZ_ASSERT(mCachedParamValues && mCachedParamNames);

  for (uint32_t i = 0; i < mCachedParamLength; i++) {
    if (mCachedParamNames[i]) {
      free(mCachedParamNames[i]);
      mCachedParamNames[i] = nullptr;
    }
    if (mCachedParamValues[i]) {
      free(mCachedParamValues[i]);
      mCachedParamValues[i] = nullptr;
    }
  }

  free(mCachedParamNames);
  mCachedParamNames = nullptr;

  free(mCachedParamValues);
  mCachedParamValues = nullptr;
}

uint32_t nsNPAPIPluginInstance::gInUnsafePluginCalls = 0;

void
nsNPAPIPluginInstance::Destroy()
{
  Stop();
  mPlugin = nullptr;
  mAudioChannelAgent = nullptr;
}

TimeStamp
nsNPAPIPluginInstance::StopTime()
{
  return mStopTime;
}

nsresult nsNPAPIPluginInstance::Initialize(nsNPAPIPlugin *aPlugin, nsPluginInstanceOwner* aOwner, const nsACString& aMIMEType)
{
  AUTO_PROFILER_LABEL("nsNPAPIPlugin::Initialize", OTHER);
  PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::Initialize this=%p\n",this));

  NS_ENSURE_ARG_POINTER(aPlugin);
  NS_ENSURE_ARG_POINTER(aOwner);

  mPlugin = aPlugin;
  mOwner = aOwner;

  if (!aMIMEType.IsEmpty()) {
    mMIMEType = ToNewCString(aMIMEType);
  }

  return Start();
}

nsresult nsNPAPIPluginInstance::Stop()
{
  PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::Stop this=%p\n",this));

  // Make sure the plugin didn't leave popups enabled.
  if (mPopupStates.Length() > 0) {
    nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow();

    if (window) {
      window->PopPopupControlState(openAbused);
    }
  }

  if (RUNNING != mRunning) {
    return NS_OK;
  }

  // clean up all outstanding timers
  for (uint32_t i = mTimers.Length(); i > 0; i--)
    UnscheduleTimer(mTimers[i - 1]->id);

  // If there's code from this plugin instance on the stack, delay the
  // destroy.
  if (PluginDestructionGuard::DelayDestroy(this)) {
    return NS_OK;
  }

  mRunning = DESTROYING;
  mStopTime = TimeStamp::Now();

  // clean up open streams
  while (mStreamListeners.Length() > 0) {
    RefPtr<nsNPAPIPluginStreamListener> currentListener(mStreamListeners[0]);
    currentListener->CleanUpStream(NPRES_USER_BREAK);
    mStreamListeners.RemoveElement(currentListener);
  }

  if (!mPlugin || !mPlugin->GetLibrary())
    return NS_ERROR_FAILURE;

  NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();

  NPError error = NPERR_GENERIC_ERROR;
  if (pluginFunctions->destroy) {
    NPSavedData *sdata = 0;

    NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->destroy)(&mNPP, &sdata), this,
                            NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);

    NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
                   ("NPP Destroy called: this=%p, npp=%p, return=%d\n", this, &mNPP, error));
  }
  mRunning = DESTROYED;

  nsJSNPRuntime::OnPluginDestroy(&mNPP);

  if (error != NPERR_NO_ERROR)
    return NS_ERROR_FAILURE;
  else
    return NS_OK;
}

already_AddRefed<nsPIDOMWindowOuter>
nsNPAPIPluginInstance::GetDOMWindow()
{
  if (!mOwner)
    return nullptr;

  RefPtr<nsPluginInstanceOwner> kungFuDeathGrip(mOwner);

  nsCOMPtr<nsIDocument> doc;
  kungFuDeathGrip->GetDocument(getter_AddRefs(doc));
  if (!doc)
    return nullptr;

  RefPtr<nsPIDOMWindowOuter> window = doc->GetWindow();

  return window.forget();
}

nsresult
nsNPAPIPluginInstance::GetTagType(nsPluginTagType *result)
{
  if (!mOwner) {
    return NS_ERROR_FAILURE;
  }

  return mOwner->GetTagType(result);
}

nsTArray<nsNPAPIPluginStreamListener*>*
nsNPAPIPluginInstance::StreamListeners()
{
  return &mStreamListeners;
}

nsTArray<nsPluginStreamListenerPeer*>*
nsNPAPIPluginInstance::FileCachedStreamListeners()
{
  return &mFileCachedStreamListeners;
}

nsresult
nsNPAPIPluginInstance::Start()
{
  if (mRunning == RUNNING) {
    return NS_OK;
  }

  if (!mOwner) {
    MOZ_ASSERT(false, "Should not be calling Start() on unowned plugin.");
    return NS_ERROR_FAILURE;
  }

  PluginDestructionGuard guard(this);

  nsTArray<MozPluginParameter> attributes;
  nsTArray<MozPluginParameter> params;

  nsPluginTagType tagtype;
  nsresult rv = GetTagType(&tagtype);
  if (NS_SUCCEEDED(rv)) {
    mOwner->GetAttributes(attributes);
    mOwner->GetParameters(params);
  } else {
    MOZ_ASSERT(false, "Failed to get tag type.");
  }

  mCachedParamLength = attributes.Length() + 1 + params.Length();

  // We add an extra entry "PARAM" as a separator between the attribute
  // and param values, but we don't count it if there are no <param> entries.
  // Legacy behavior quirk.
  uint32_t quirkParamLength = params.Length() ?
                                mCachedParamLength : attributes.Length();

  mCachedParamNames = (char**)moz_xmalloc(sizeof(char*) * mCachedParamLength);
  mCachedParamValues = (char**)moz_xmalloc(sizeof(char*) * mCachedParamLength);

  for (uint32_t i = 0; i < attributes.Length(); i++) {
    mCachedParamNames[i] = ToNewUTF8String(attributes[i].mName);
    mCachedParamValues[i] = ToNewUTF8String(attributes[i].mValue);
  }

  mCachedParamNames[attributes.Length()] = ToNewUTF8String(NS_LITERAL_STRING("PARAM"));
  mCachedParamValues[attributes.Length()] = nullptr;

  for (uint32_t i = 0, pos = attributes.Length() + 1; i < params.Length(); i ++) {
    mCachedParamNames[pos] = ToNewUTF8String(params[i].mName);
    mCachedParamValues[pos] = ToNewUTF8String(params[i].mValue);
    pos++;
  }

  const char*   mimetype;
  NPError       error = NPERR_GENERIC_ERROR;

  GetMIMEType(&mimetype);

  bool oldVal = mInPluginInitCall;
  mInPluginInitCall = true;

  // Need this on the stack before calling NPP_New otherwise some callbacks that
  // the plugin may make could fail (NPN_HasProperty, for example).
  NPPAutoPusher autopush(&mNPP);

  if (!mPlugin)
    return NS_ERROR_FAILURE;

  PluginLibrary* library = mPlugin->GetLibrary();
  if (!library)
    return NS_ERROR_FAILURE;

  // Mark this instance as running before calling NPP_New because the plugin may
  // call other NPAPI functions, like NPN_GetURLNotify, that assume this is set
  // before returning. If the plugin returns failure, we'll clear it out below.
  mRunning = RUNNING;

  nsresult newResult = library->NPP_New((char*)mimetype, &mNPP,
                                        quirkParamLength, mCachedParamNames,
                                        mCachedParamValues, nullptr, &error);
  mInPluginInitCall = oldVal;

  NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
  ("NPP New called: this=%p, npp=%p, mime=%s, argc=%d, return=%d\n",
  this, &mNPP, mimetype, quirkParamLength, error));

  if (NS_FAILED(newResult) || error != NPERR_NO_ERROR) {
    mRunning = DESTROYED;
    nsJSNPRuntime::OnPluginDestroy(&mNPP);
    return NS_ERROR_FAILURE;
  }

  return newResult;
}

nsresult nsNPAPIPluginInstance::SetWindow(NPWindow* window)
{
  // NPAPI plugins don't want a SetWindow(nullptr).
  if (!window || RUNNING != mRunning)
    return NS_OK;

#if MOZ_WIDGET_GTK
  // bug 108347, flash plugin on linux doesn't like window->width <= 0
  return NS_OK;
#endif

  if (!mPlugin || !mPlugin->GetLibrary())
    return NS_ERROR_FAILURE;

  NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();

  if (pluginFunctions->setwindow) {
    PluginDestructionGuard guard(this);

    // XXX Turns out that NPPluginWindow and NPWindow are structurally
    // identical (on purpose!), so there's no need to make a copy.

    PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::SetWindow (about to call it) this=%p\n",this));

    bool oldVal = mInPluginInitCall;
    mInPluginInitCall = true;

    NPPAutoPusher nppPusher(&mNPP);

    NPError error;
    NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->setwindow)(&mNPP, (NPWindow*)window), this,
                            NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
    // 'error' is only used if this is a logging-enabled build.
    // That is somewhat complex to check, so we just use "unused"
    // to suppress any compiler warnings in build configurations
    // where the logging is a no-op.
    mozilla::Unused << error;

    mInPluginInitCall = oldVal;

    NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
    ("NPP SetWindow called: this=%p, [x=%d,y=%d,w=%d,h=%d], clip[t=%d,b=%d,l=%d,r=%d], return=%d\n",
    this, window->x, window->y, window->width, window->height,
    window->clipRect.top, window->clipRect.bottom, window->clipRect.left, window->clipRect.right, error));
  }
  return NS_OK;
}

nsresult
nsNPAPIPluginInstance::NewStreamListener(const char* aURL, void* notifyData,
                                         nsNPAPIPluginStreamListener** listener)
{
  RefPtr<nsNPAPIPluginStreamListener> sl = new nsNPAPIPluginStreamListener(this, notifyData, aURL);

  mStreamListeners.AppendElement(sl);

  sl.forget(listener);

  return NS_OK;
}

nsresult nsNPAPIPluginInstance::Print(NPPrint* platformPrint)
{
  NS_ENSURE_TRUE(platformPrint, NS_ERROR_NULL_POINTER);

  PluginDestructionGuard guard(this);

  if (!mPlugin || !mPlugin->GetLibrary())
    return NS_ERROR_FAILURE;

  NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();

  NPPrint* thePrint = (NPPrint *)platformPrint;

  // to be compatible with the older SDK versions and to match what
  // NPAPI and other browsers do, overwrite |window.type| field with one
  // more copy of |platformPrint|. See bug 113264
  uint16_t sdkmajorversion = (pluginFunctions->version & 0xff00)>>8;
  uint16_t sdkminorversion = pluginFunctions->version & 0x00ff;
  if ((sdkmajorversion == 0) && (sdkminorversion < 11)) {
    // Let's copy platformPrint bytes over to where it was supposed to be
    // in older versions -- four bytes towards the beginning of the struct
    // but we should be careful about possible misalignments
    if (sizeof(NPWindowType) >= sizeof(void *)) {
      void* source = thePrint->print.embedPrint.platformPrint;
      void** destination = (void **)&(thePrint->print.embedPrint.window.type);
      *destination = source;
    } else {
      NS_ERROR("Incompatible OS for assignment");
    }
  }

  if (pluginFunctions->print)
      NS_TRY_SAFE_CALL_VOID((*pluginFunctions->print)(&mNPP, thePrint), this,
                            NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);

  NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
  ("NPP PrintProc called: this=%p, pDC=%p, [x=%d,y=%d,w=%d,h=%d], clip[t=%d,b=%d,l=%d,r=%d]\n",
  this,
  platformPrint->print.embedPrint.platformPrint,
  platformPrint->print.embedPrint.window.x,
  platformPrint->print.embedPrint.window.y,
  platformPrint->print.embedPrint.window.width,
  platformPrint->print.embedPrint.window.height,
  platformPrint->print.embedPrint.window.clipRect.top,
  platformPrint->print.embedPrint.window.clipRect.bottom,
  platformPrint->print.embedPrint.window.clipRect.left,
  platformPrint->print.embedPrint.window.clipRect.right));

  return NS_OK;
}

nsresult nsNPAPIPluginInstance::HandleEvent(void* event, int16_t* result,
                                            NSPluginCallReentry aSafeToReenterGecko)
{
  if (RUNNING != mRunning)
    return NS_OK;

  AUTO_PROFILER_LABEL("nsNPAPIPluginInstance::HandleEvent", OTHER);

  if (!event)
    return NS_ERROR_FAILURE;

  PluginDestructionGuard guard(this);

  if (!mPlugin || !mPlugin->GetLibrary())
    return NS_ERROR_FAILURE;

  NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();

  int16_t tmpResult = kNPEventNotHandled;

  if (pluginFunctions->event) {
#ifdef XP_MACOSX
    mCurrentPluginEvent = event;
#endif
#if defined(XP_WIN)
    NS_TRY_SAFE_CALL_RETURN(tmpResult, (*pluginFunctions->event)(&mNPP, event), this,
                            aSafeToReenterGecko);
#else
    tmpResult = (*pluginFunctions->event)(&mNPP, event);
#endif
    NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY,
      ("NPP HandleEvent called: this=%p, npp=%p, event=%p, return=%d\n",
      this, &mNPP, event, tmpResult));

    if (result)
      *result = tmpResult;
#ifdef XP_MACOSX
    mCurrentPluginEvent = nullptr;
#endif
  }

  return NS_OK;
}

nsresult nsNPAPIPluginInstance::GetValueFromPlugin(NPPVariable variable, void* value)
{
  if (!mPlugin || !mPlugin->GetLibrary())
    return NS_ERROR_FAILURE;

  NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();

  nsresult rv = NS_ERROR_FAILURE;

  if (pluginFunctions->getvalue && RUNNING == mRunning) {
    PluginDestructionGuard guard(this);

    NPError pluginError = NPERR_GENERIC_ERROR;
    NS_TRY_SAFE_CALL_RETURN(pluginError, (*pluginFunctions->getvalue)(&mNPP, variable, value), this,
                            NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
    NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
    ("NPP GetValue called: this=%p, npp=%p, var=%d, value=%p, return=%d\n",
    this, &mNPP, variable, value, pluginError));

    if (pluginError == NPERR_NO_ERROR) {
      rv = NS_OK;
    }
  }

  return rv;
}

nsNPAPIPlugin* nsNPAPIPluginInstance::GetPlugin()
{
  return mPlugin;
}

nsresult nsNPAPIPluginInstance::GetNPP(NPP* aNPP)
{
  if (aNPP)
    *aNPP = &mNPP;
  else
    return NS_ERROR_NULL_POINTER;

  return NS_OK;
}

NPError nsNPAPIPluginInstance::SetWindowless(bool aWindowless)
{
  mWindowless = aWindowless;
  return NPERR_NO_ERROR;
}

NPError nsNPAPIPluginInstance::SetTransparent(bool aTransparent)
{
  mTransparent = aTransparent;
  return NPERR_NO_ERROR;
}

NPError nsNPAPIPluginInstance::SetUsesDOMForCursor(bool aUsesDOMForCursor)
{
  mUsesDOMForCursor = aUsesDOMForCursor;
  return NPERR_NO_ERROR;
}

bool
nsNPAPIPluginInstance::UsesDOMForCursor()
{
  return mUsesDOMForCursor;
}

void nsNPAPIPluginInstance::SetDrawingModel(NPDrawingModel aModel)
{
  mDrawingModel = aModel;
}

void nsNPAPIPluginInstance::RedrawPlugin()
{
  mOwner->RedrawPlugin();
}

#if defined(XP_MACOSX)
void nsNPAPIPluginInstance::SetEventModel(NPEventModel aModel)
{
  // the event model needs to be set for the object frame immediately
  if (!mOwner) {
    NS_WARNING("Trying to set event model without a plugin instance owner!");
    return;
  }

  mOwner->SetEventModel(aModel);
}
#endif

nsresult nsNPAPIPluginInstance::GetDrawingModel(int32_t* aModel)
{
  *aModel = (int32_t)mDrawingModel;
  return NS_OK;
}

nsresult nsNPAPIPluginInstance::IsRemoteDrawingCoreAnimation(bool* aDrawing)
{
#ifdef XP_MACOSX
  if (!mPlugin)
      return NS_ERROR_FAILURE;

  PluginLibrary* library = mPlugin->GetLibrary();
  if (!library)
      return NS_ERROR_FAILURE;

  return library->IsRemoteDrawingCoreAnimation(&mNPP, aDrawing);
#else
  return NS_ERROR_FAILURE;
#endif
}

nsresult
nsNPAPIPluginInstance::ContentsScaleFactorChanged(double aContentsScaleFactor)
{
#if defined(XP_MACOSX) || defined(XP_WIN)
  if (!mPlugin)
      return NS_ERROR_FAILURE;

  PluginLibrary* library = mPlugin->GetLibrary();
  if (!library)
      return NS_ERROR_FAILURE;

  // We only need to call this if the plugin is running OOP.
  if (!library->IsOOP())
      return NS_OK;

  return library->ContentsScaleFactorChanged(&mNPP, aContentsScaleFactor);
#else
  return NS_ERROR_FAILURE;
#endif
}

nsresult
nsNPAPIPluginInstance::CSSZoomFactorChanged(float aCSSZoomFactor)
{
  if (RUNNING != mRunning)
    return NS_OK;

  PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance informing plugin of CSS Zoom Factor change this=%p\n",this));

  if (!mPlugin || !mPlugin->GetLibrary())
    return NS_ERROR_FAILURE;

  NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();

  if (!pluginFunctions->setvalue)
    return NS_ERROR_FAILURE;

  PluginDestructionGuard guard(this);

  NPError error;
  double value = static_cast<double>(aCSSZoomFactor);
  NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->setvalue)(&mNPP, NPNVCSSZoomFactor, &value), this,
                          NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
  return (error == NPERR_NO_ERROR) ? NS_OK : NS_ERROR_FAILURE;
}

nsresult
nsNPAPIPluginInstance::GetJSObject(JSContext *cx, JSObject** outObject)
{
  NPObject *npobj = nullptr;
  nsresult rv = GetValueFromPlugin(NPPVpluginScriptableNPObject, &npobj);
  if (NS_FAILED(rv) || !npobj)
    return NS_ERROR_FAILURE;

  *outObject = nsNPObjWrapper::GetNewOrUsed(&mNPP, cx, npobj);

  _releaseobject(npobj);

  return NS_OK;
}

void
nsNPAPIPluginInstance::SetCached(bool aCache)
{
  mCached = aCache;
}

bool
nsNPAPIPluginInstance::ShouldCache()
{
  return mCached;
}

nsresult
nsNPAPIPluginInstance::IsWindowless(bool* isWindowless)
{
#if defined(XP_MACOSX)
  // All OS X plugins are windowless.
  *isWindowless = true;
#else
  *isWindowless = mWindowless;
#endif
  return NS_OK;
}

class MOZ_STACK_CLASS AutoPluginLibraryCall
{
public:
  explicit AutoPluginLibraryCall(nsNPAPIPluginInstance* aThis)
    : mThis(aThis), mGuard(aThis), mLibrary(nullptr)
  {
    nsNPAPIPlugin* plugin = mThis->GetPlugin();
    if (plugin)
      mLibrary = plugin->GetLibrary();
  }
  explicit operator bool() { return !!mLibrary; }
  PluginLibrary* operator->() { return mLibrary; }

private:
  nsNPAPIPluginInstance* mThis;
  PluginDestructionGuard mGuard;
  PluginLibrary* mLibrary;
};

nsresult
nsNPAPIPluginInstance::AsyncSetWindow(NPWindow* window)
{
  if (RUNNING != mRunning)
    return NS_OK;

  AutoPluginLibraryCall library(this);
  if (!library)
    return NS_ERROR_FAILURE;

  return library->AsyncSetWindow(&mNPP, window);
}

nsresult
nsNPAPIPluginInstance::GetImageContainer(ImageContainer**aContainer)
{
  *aContainer = nullptr;

  if (RUNNING != mRunning)
    return NS_OK;

  AutoPluginLibraryCall library(this);
  return !library ? NS_ERROR_FAILURE : library->GetImageContainer(&mNPP, aContainer);
}

nsresult
nsNPAPIPluginInstance::GetImageSize(nsIntSize* aSize)
{
  *aSize = nsIntSize(0, 0);

  if (RUNNING != mRunning)
    return NS_OK;

  AutoPluginLibraryCall library(this);
  return !library ? NS_ERROR_FAILURE : library->GetImageSize(&mNPP, aSize);
}

#if defined(XP_WIN)
nsresult
nsNPAPIPluginInstance::GetScrollCaptureContainer(ImageContainer**aContainer)
{
  *aContainer = nullptr;

  if (RUNNING != mRunning)
    return NS_OK;

  AutoPluginLibraryCall library(this);
  return !library ? NS_ERROR_FAILURE : library->GetScrollCaptureContainer(&mNPP, aContainer);
}
#endif

nsresult
nsNPAPIPluginInstance::HandledWindowedPluginKeyEvent(
                         const NativeEventData& aKeyEventData,
                         bool aIsConsumed)
{
  if (NS_WARN_IF(!mPlugin)) {
    return NS_ERROR_FAILURE;
  }

  PluginLibrary* library = mPlugin->GetLibrary();
  if (NS_WARN_IF(!library)) {
    return NS_ERROR_FAILURE;
  }
  return library->HandledWindowedPluginKeyEvent(&mNPP, aKeyEventData,
                                                aIsConsumed);
}

void
nsNPAPIPluginInstance::DidComposite()
{
  if (RUNNING != mRunning)
    return;

  AutoPluginLibraryCall library(this);
  library->DidComposite(&mNPP);
}

nsresult
nsNPAPIPluginInstance::NotifyPainted(void)
{
  NS_NOTREACHED("Dead code, shouldn't be called.");
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult
nsNPAPIPluginInstance::GetIsOOP(bool* aIsAsync)
{
  AutoPluginLibraryCall library(this);
  if (!library)
    return NS_ERROR_FAILURE;

  *aIsAsync = library->IsOOP();
  return NS_OK;
}

nsresult
nsNPAPIPluginInstance::SetBackgroundUnknown()
{
  if (RUNNING != mRunning)
    return NS_OK;

  AutoPluginLibraryCall library(this);
  if (!library)
    return NS_ERROR_FAILURE;

  return library->SetBackgroundUnknown(&mNPP);
}

nsresult
nsNPAPIPluginInstance::BeginUpdateBackground(nsIntRect* aRect,
                                             DrawTarget** aDrawTarget)
{
  if (RUNNING != mRunning)
    return NS_OK;

  AutoPluginLibraryCall library(this);
  if (!library)
    return NS_ERROR_FAILURE;

  return library->BeginUpdateBackground(&mNPP, *aRect, aDrawTarget);
}

nsresult
nsNPAPIPluginInstance::EndUpdateBackground(nsIntRect* aRect)
{
  if (RUNNING != mRunning)
    return NS_OK;

  AutoPluginLibraryCall library(this);
  if (!library)
    return NS_ERROR_FAILURE;

  return library->EndUpdateBackground(&mNPP, *aRect);
}

nsresult
nsNPAPIPluginInstance::IsTransparent(bool* isTransparent)
{
  *isTransparent = mTransparent;
  return NS_OK;
}

nsresult
nsNPAPIPluginInstance::GetFormValue(nsAString& aValue)
{
  aValue.Truncate();

  char *value = nullptr;
  nsresult rv = GetValueFromPlugin(NPPVformValue, &value);
  if (NS_FAILED(rv) || !value)
    return NS_ERROR_FAILURE;

  CopyUTF8toUTF16(value, aValue);

  // NPPVformValue allocates with NPN_MemAlloc(), which uses
  // nsMemory.
  free(value);

  return NS_OK;
}

nsresult
nsNPAPIPluginInstance::PushPopupsEnabledState(bool aEnabled)
{
  nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow();
  if (!window)
    return NS_ERROR_FAILURE;

  PopupControlState oldState =
    window->PushPopupControlState(aEnabled ? openAllowed : openAbused,
                                  true);

  if (!mPopupStates.AppendElement(oldState)) {
    // Appending to our state stack failed, pop what we just pushed.
    window->PopPopupControlState(oldState);
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

nsresult
nsNPAPIPluginInstance::PopPopupsEnabledState()
{
  int32_t last = mPopupStates.Length() - 1;

  if (last < 0) {
    // Nothing to pop.
    return NS_OK;
  }

  nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow();
  if (!window)
    return NS_ERROR_FAILURE;

  PopupControlState &oldState = mPopupStates[last];

  window->PopPopupControlState(oldState);

  mPopupStates.RemoveElementAt(last);

  return NS_OK;
}

nsresult
nsNPAPIPluginInstance::GetPluginAPIVersion(uint16_t* version)
{
  NS_ENSURE_ARG_POINTER(version);

  if (!mPlugin)
    return NS_ERROR_FAILURE;

  if (!mPlugin->GetLibrary())
    return NS_ERROR_FAILURE;

  NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();

  *version = pluginFunctions->version;

  return NS_OK;
}

nsresult
nsNPAPIPluginInstance::PrivateModeStateChanged(bool enabled)
{
  if (RUNNING != mRunning)
    return NS_OK;

  PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance informing plugin of private mode state change this=%p\n",this));

  if (!mPlugin || !mPlugin->GetLibrary())
    return NS_ERROR_FAILURE;

  NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();

  if (!pluginFunctions->setvalue)
    return NS_ERROR_FAILURE;

  PluginDestructionGuard guard(this);

  NPError error;
  NPBool value = static_cast<NPBool>(enabled);
  NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->setvalue)(&mNPP, NPNVprivateModeBool, &value), this,
                          NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
  return (error == NPERR_NO_ERROR) ? NS_OK : NS_ERROR_FAILURE;
}

nsresult
nsNPAPIPluginInstance::IsPrivateBrowsing(bool* aEnabled)
{
  if (!mOwner)
    return NS_ERROR_FAILURE;

  nsCOMPtr<nsIDocument> doc;
  mOwner->GetDocument(getter_AddRefs(doc));
  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);

  nsCOMPtr<nsPIDOMWindowOuter> domwindow = doc->GetWindow();
  NS_ENSURE_TRUE(domwindow, NS_ERROR_FAILURE);

  nsCOMPtr<nsIDocShell> docShell = domwindow->GetDocShell();
  nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
  *aEnabled = (loadContext && loadContext->UsePrivateBrowsing());
  return NS_OK;
}

static void
PluginTimerCallback(nsITimer *aTimer, void *aClosure)
{
  nsNPAPITimer* t = (nsNPAPITimer*)aClosure;
  NPP npp = t->npp;
  uint32_t id = t->id;

  PLUGIN_LOG(PLUGIN_LOG_NOISY, ("nsNPAPIPluginInstance running plugin timer callback this=%p\n", npp->ndata));

  // Some plugins (Flash on Android) calls unscheduletimer
  // from this callback.
  t->inCallback = true;
  (*(t->callback))(npp, id);
  t->inCallback = false;

  // Make sure we still have an instance and the timer is still alive
  // after the callback.
  nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance*)npp->ndata;
  if (!inst || !inst->TimerWithID(id, nullptr))
    return;

  // use UnscheduleTimer to clean up if this is a one-shot timer
  uint32_t timerType;
  t->timer->GetType(&timerType);
  if (t->needUnschedule || timerType == nsITimer::TYPE_ONE_SHOT)
    inst->UnscheduleTimer(id);
}

nsNPAPITimer*
nsNPAPIPluginInstance::TimerWithID(uint32_t id, uint32_t* index)
{
  uint32_t len = mTimers.Length();
  for (uint32_t i = 0; i < len; i++) {
    if (mTimers[i]->id == id) {
      if (index)
        *index = i;
      return mTimers[i];
    }
  }
  return nullptr;
}

uint32_t
nsNPAPIPluginInstance::ScheduleTimer(uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID))
{
  if (RUNNING != mRunning)
    return 0;

  nsNPAPITimer *newTimer = new nsNPAPITimer();

  newTimer->inCallback = newTimer->needUnschedule = false;
  newTimer->npp = &mNPP;

  // generate ID that is unique to this instance
  uint32_t uniqueID = mTimers.Length();
  while ((uniqueID == 0) || TimerWithID(uniqueID, nullptr))
    uniqueID++;
  newTimer->id = uniqueID;

  // create new xpcom timer, scheduled correctly
  nsresult rv;
  const short timerType = (repeat ? (short)nsITimer::TYPE_REPEATING_SLACK : (short)nsITimer::TYPE_ONE_SHOT);
  rv = NS_NewTimerWithFuncCallback(getter_AddRefs(newTimer->timer),
                                   PluginTimerCallback,
                                   newTimer,
                                   interval,
                                   timerType,
                                   "nsNPAPIPluginInstance::ScheduleTimer");
  if (NS_FAILED(rv)) {
    delete newTimer;
    return 0;
  }

  // save callback function
  newTimer->callback = timerFunc;

  // add timer to timers array
  mTimers.AppendElement(newTimer);

  return newTimer->id;
}

void
nsNPAPIPluginInstance::UnscheduleTimer(uint32_t timerID)
{
  // find the timer struct by ID
  uint32_t index;
  nsNPAPITimer* t = TimerWithID(timerID, &index);
  if (!t)
    return;

  if (t->inCallback) {
    t->needUnschedule = true;
    return;
  }

  // cancel the timer
  t->timer->Cancel();

  // remove timer struct from array
  mTimers.RemoveElementAt(index);

  // delete timer
  delete t;
}

NPBool
nsNPAPIPluginInstance::ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
                                    double *destX, double *destY, NPCoordinateSpace destSpace)
{
  if (mOwner) {
    return mOwner->ConvertPoint(sourceX, sourceY, sourceSpace, destX, destY, destSpace);
  }

  return false;
}

nsresult
nsNPAPIPluginInstance::GetDOMElement(nsIDOMElement* *result)
{
  if (!mOwner) {
    *result = nullptr;
    return NS_ERROR_FAILURE;
  }

  return mOwner->GetDOMElement(result);
}

nsresult
nsNPAPIPluginInstance::InvalidateRect(NPRect *invalidRect)
{
  if (RUNNING != mRunning)
    return NS_OK;

  if (!mOwner)
    return NS_ERROR_FAILURE;

  return mOwner->InvalidateRect(invalidRect);
}

nsresult
nsNPAPIPluginInstance::InvalidateRegion(NPRegion invalidRegion)
{
  if (RUNNING != mRunning)
    return NS_OK;

  if (!mOwner)
    return NS_ERROR_FAILURE;

  return mOwner->InvalidateRegion(invalidRegion);
}

nsresult
nsNPAPIPluginInstance::GetMIMEType(const char* *result)
{
  if (!mMIMEType)
    *result = "";
  else
    *result = mMIMEType;

  return NS_OK;
}

nsPluginInstanceOwner*
nsNPAPIPluginInstance::GetOwner()
{
  return mOwner;
}

void
nsNPAPIPluginInstance::SetOwner(nsPluginInstanceOwner *aOwner)
{
  mOwner = aOwner;
}

nsresult
nsNPAPIPluginInstance::AsyncSetWindow(NPWindow& window)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

void
nsNPAPIPluginInstance::URLRedirectResponse(void* notifyData, NPBool allow)
{
  if (!notifyData) {
    return;
  }

  uint32_t listenerCount = mStreamListeners.Length();
  for (uint32_t i = 0; i < listenerCount; i++) {
    nsNPAPIPluginStreamListener* currentListener = mStreamListeners[i];
    if (currentListener->GetNotifyData() == notifyData) {
      currentListener->URLRedirectResponse(allow);
    }
  }
}

NPError
nsNPAPIPluginInstance::InitAsyncSurface(NPSize *size, NPImageFormat format,
                                        void *initData, NPAsyncSurface *surface)
{
  if (mOwner) {
    return mOwner->InitAsyncSurface(size, format, initData, surface);
  }

  return NPERR_GENERIC_ERROR;
}

NPError
nsNPAPIPluginInstance::FinalizeAsyncSurface(NPAsyncSurface *surface)
{
  if (mOwner) {
    return mOwner->FinalizeAsyncSurface(surface);
  }

  return NPERR_GENERIC_ERROR;
}

void
nsNPAPIPluginInstance::SetCurrentAsyncSurface(NPAsyncSurface *surface, NPRect *changed)
{
  if (mOwner) {
    mOwner->SetCurrentAsyncSurface(surface, changed);
  }
}

double
nsNPAPIPluginInstance::GetContentsScaleFactor()
{
  double scaleFactor = 1.0;
  if (mOwner) {
    mOwner->GetContentsScaleFactor(&scaleFactor);
  }
  return scaleFactor;
}

float
nsNPAPIPluginInstance::GetCSSZoomFactor()
{
  float zoomFactor = 1.0;
  if (mOwner) {
    mOwner->GetCSSZoomFactor(&zoomFactor);
  }
  return zoomFactor;
}

nsresult
nsNPAPIPluginInstance::GetRunID(uint32_t* aRunID)
{
  if (NS_WARN_IF(!aRunID)) {
    return NS_ERROR_INVALID_POINTER;
  }

  if (NS_WARN_IF(!mPlugin)) {
    return NS_ERROR_FAILURE;
  }

  PluginLibrary* library = mPlugin->GetLibrary();
  if (!library) {
    return NS_ERROR_FAILURE;
  }

  return library->GetRunID(aRunID);
}

nsresult
nsNPAPIPluginInstance::CreateAudioChannelAgentIfNeeded()
{
  if (mAudioChannelAgent) {
    return NS_OK;
  }

  nsresult rv;
  mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
  if (NS_WARN_IF(!mAudioChannelAgent)) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow();
  if (NS_WARN_IF(!window)) {
    return NS_ERROR_FAILURE;
  }

  rv = mAudioChannelAgent->Init(window->GetCurrentInnerWindow(), this);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
}

void
nsNPAPIPluginInstance::NotifyStartedPlaying()
{
  nsresult rv = CreateAudioChannelAgentIfNeeded();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  MOZ_ASSERT(mAudioChannelAgent);
  dom::AudioPlaybackConfig config;
  rv = mAudioChannelAgent->NotifyStartedPlaying(&config,
                                                dom::AudioChannelService::AudibleState::eAudible);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  rv = WindowVolumeChanged(config.mVolume, config.mMuted);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  // Since we only support muting for now, the implementation of suspend
  // is equal to muting. Therefore, if we have already muted the plugin,
  // then we don't need to call WindowSuspendChanged() again.
  if (config.mMuted) {
    return;
  }

  rv = WindowSuspendChanged(config.mSuspend);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }
}

void
nsNPAPIPluginInstance::NotifyStoppedPlaying()
{
  MOZ_ASSERT(mAudioChannelAgent);

  // Reset the attribute.
  mMuted = false;
  nsresult rv = mAudioChannelAgent->NotifyStoppedPlaying();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }
}

NS_IMETHODIMP
nsNPAPIPluginInstance::WindowVolumeChanged(float aVolume, bool aMuted)
{
  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
         ("nsNPAPIPluginInstance, WindowVolumeChanged, "
          "this = %p, aVolume = %f, aMuted = %s\n",
          this, aVolume, aMuted ? "true" : "false"));

  // We just support mute/unmute
  nsresult rv = SetMuted(aMuted);
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetMuted failed");
  if (mMuted != aMuted) {
    mMuted = aMuted;
    if (mAudioChannelAgent) {
      AudioChannelService::AudibleState audible = aMuted ?
        AudioChannelService::AudibleState::eNotAudible :
        AudioChannelService::AudibleState::eAudible;
      mAudioChannelAgent->NotifyStartedAudible(audible,
                                               AudioChannelService::AudibleChangedReasons::eVolumeChanged);
    }
  }
  return rv;
}

NS_IMETHODIMP
nsNPAPIPluginInstance::WindowSuspendChanged(nsSuspendedTypes aSuspend)
{
  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
         ("nsNPAPIPluginInstance, WindowSuspendChanged, "
          "this = %p, aSuspend = %s\n", this, SuspendTypeToStr(aSuspend)));

  // It doesn't support suspended, so we just do something like mute/unmute.
  WindowVolumeChanged(1.0, /* useless */
                      aSuspend != nsISuspendedTypes::NONE_SUSPENDED);
  return NS_OK;
}

NS_IMETHODIMP
nsNPAPIPluginInstance::WindowAudioCaptureChanged(bool aCapture)
{
  return NS_OK;
}

nsresult
nsNPAPIPluginInstance::SetMuted(bool aIsMuted)
{
  if (RUNNING != mRunning)
    return NS_OK;

  PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance informing plugin of mute state change this=%p\n",this));

  if (!mPlugin || !mPlugin->GetLibrary())
    return NS_ERROR_FAILURE;

  NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();

  if (!pluginFunctions->setvalue)
    return NS_ERROR_FAILURE;

  PluginDestructionGuard guard(this);

  NPError error;
  NPBool value = static_cast<NPBool>(aIsMuted);
  NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->setvalue)(&mNPP, NPNVmuteAudioBool, &value), this,
                          NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
  return (error == NPERR_NO_ERROR) ? NS_OK : NS_ERROR_FAILURE;
}
