diff options
Diffstat (limited to 'dom/plugins/base')
58 files changed, 29704 insertions, 0 deletions
diff --git a/dom/plugins/base/PluginPRLibrary.cpp b/dom/plugins/base/PluginPRLibrary.cpp new file mode 100644 index 000000000..a7ad76ea4 --- /dev/null +++ b/dom/plugins/base/PluginPRLibrary.cpp @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + * 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/PluginPRLibrary.h" +#include "nsNPAPIPluginInstance.h" + +// Some plugins on Windows, notably Quake Live, implement NP_Initialize using +// cdecl instead of the documented stdcall. In order to work around this, +// we force the caller to use a frame pointer. +#if defined(XP_WIN) && defined(_M_IX86) +#include <malloc.h> + +// gNotOptimized exists so that the compiler will not optimize the alloca +// below. +static int gNotOptimized; +#define CALLING_CONVENTION_HACK void* foo = _alloca(gNotOptimized); +#else +#define CALLING_CONVENTION_HACK +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidBridge.h" +#include "android_npapi.h" +#include <android/log.h> +#undef ALOG +#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoJavaEnv", ## args) +#endif + +using namespace mozilla::layers; + +namespace mozilla { +#ifdef MOZ_WIDGET_ANDROID +nsresult +PluginPRLibrary::NP_Initialize(NPNetscapeFuncs* bFuncs, + NPPluginFuncs* pFuncs, NPError* error) +{ + JNIEnv* env = jni::GetEnvForThread(); + + mozilla::AutoLocalJNIFrame jniFrame(env); + + if (mNP_Initialize) { + *error = mNP_Initialize(bFuncs, pFuncs, env); + } else { + NP_InitializeFunc pfNP_Initialize = (NP_InitializeFunc) + PR_FindFunctionSymbol(mLibrary, "NP_Initialize"); + if (!pfNP_Initialize) + return NS_ERROR_FAILURE; + *error = pfNP_Initialize(bFuncs, pFuncs, env); + } + + // Save pointers to functions that get called through PluginLibrary itself. + mNPP_New = pFuncs->newp; + mNPP_ClearSiteData = pFuncs->clearsitedata; + mNPP_GetSitesWithData = pFuncs->getsiteswithdata; + return NS_OK; +} +#elif defined(MOZ_WIDGET_GONK) +nsresult +PluginPRLibrary::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) +{ + return NS_OK; +} +#elif defined(XP_UNIX) && !defined(XP_MACOSX) +nsresult +PluginPRLibrary::NP_Initialize(NPNetscapeFuncs* bFuncs, + NPPluginFuncs* pFuncs, NPError* error) +{ + if (mNP_Initialize) { + *error = mNP_Initialize(bFuncs, pFuncs); + } else { + NP_InitializeFunc pfNP_Initialize = (NP_InitializeFunc) + PR_FindFunctionSymbol(mLibrary, "NP_Initialize"); + if (!pfNP_Initialize) + return NS_ERROR_FAILURE; + *error = pfNP_Initialize(bFuncs, pFuncs); + } + + + // Save pointers to functions that get called through PluginLibrary itself. + mNPP_New = pFuncs->newp; + mNPP_ClearSiteData = pFuncs->clearsitedata; + mNPP_GetSitesWithData = pFuncs->getsiteswithdata; + return NS_OK; +} +#else +nsresult +PluginPRLibrary::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) +{ + CALLING_CONVENTION_HACK + + if (mNP_Initialize) { + *error = mNP_Initialize(bFuncs); + } else { + NP_InitializeFunc pfNP_Initialize = (NP_InitializeFunc) + PR_FindFunctionSymbol(mLibrary, "NP_Initialize"); + if (!pfNP_Initialize) + return NS_ERROR_FAILURE; + *error = pfNP_Initialize(bFuncs); + } + + return NS_OK; +} +#endif + +nsresult +PluginPRLibrary::NP_Shutdown(NPError* error) +{ + CALLING_CONVENTION_HACK + + if (mNP_Shutdown) { + *error = mNP_Shutdown(); + } else { + NP_ShutdownFunc pfNP_Shutdown = (NP_ShutdownFunc) + PR_FindFunctionSymbol(mLibrary, "NP_Shutdown"); + if (!pfNP_Shutdown) + return NS_ERROR_FAILURE; + *error = pfNP_Shutdown(); + } + + return NS_OK; +} + +nsresult +PluginPRLibrary::NP_GetMIMEDescription(const char** mimeDesc) +{ + CALLING_CONVENTION_HACK + + if (mNP_GetMIMEDescription) { + *mimeDesc = mNP_GetMIMEDescription(); + } + else { + NP_GetMIMEDescriptionFunc pfNP_GetMIMEDescription = + (NP_GetMIMEDescriptionFunc) + PR_FindFunctionSymbol(mLibrary, "NP_GetMIMEDescription"); + if (!pfNP_GetMIMEDescription) { + *mimeDesc = ""; + return NS_ERROR_FAILURE; + } + *mimeDesc = pfNP_GetMIMEDescription(); + } + + return NS_OK; +} + +nsresult +PluginPRLibrary::NP_GetValue(void *future, NPPVariable aVariable, + void *aValue, NPError* error) +{ +#if defined(XP_UNIX) && !defined(XP_MACOSX) + if (mNP_GetValue) { + *error = mNP_GetValue(future, aVariable, aValue); + } else { + NP_GetValueFunc pfNP_GetValue = (NP_GetValueFunc)PR_FindFunctionSymbol(mLibrary, "NP_GetValue"); + if (!pfNP_GetValue) + return NS_ERROR_FAILURE; + *error = pfNP_GetValue(future, aVariable, aValue); + } + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#if defined(XP_WIN) || defined(XP_MACOSX) +nsresult +PluginPRLibrary::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) +{ + CALLING_CONVENTION_HACK + + if (mNP_GetEntryPoints) { + *error = mNP_GetEntryPoints(pFuncs); + } else { + NP_GetEntryPointsFunc pfNP_GetEntryPoints = (NP_GetEntryPointsFunc) + PR_FindFunctionSymbol(mLibrary, "NP_GetEntryPoints"); + if (!pfNP_GetEntryPoints) + return NS_ERROR_FAILURE; + *error = pfNP_GetEntryPoints(pFuncs); + } + + // Save pointers to functions that get called through PluginLibrary itself. + mNPP_New = pFuncs->newp; + mNPP_ClearSiteData = pFuncs->clearsitedata; + mNPP_GetSitesWithData = pFuncs->getsiteswithdata; + return NS_OK; +} +#endif + +nsresult +PluginPRLibrary::NPP_New(NPMIMEType pluginType, NPP instance, + uint16_t mode, int16_t argc, char* argn[], + char* argv[], NPSavedData* saved, + NPError* error) +{ + if (!mNPP_New) + return NS_ERROR_FAILURE; + + MAIN_THREAD_JNI_REF_GUARD; + *error = mNPP_New(pluginType, instance, mode, argc, argn, argv, saved); + return NS_OK; +} + +nsresult +PluginPRLibrary::NPP_ClearSiteData(const char* site, uint64_t flags, + uint64_t maxAge, nsCOMPtr<nsIClearSiteDataCallback> callback) +{ + if (!mNPP_ClearSiteData) { + return NS_ERROR_NOT_AVAILABLE; + } + + MAIN_THREAD_JNI_REF_GUARD; + NPError result = mNPP_ClearSiteData(site, flags, maxAge); + + nsresult rv; + switch (result) { + case NPERR_NO_ERROR: + rv = NS_OK; + break; + case NPERR_TIME_RANGE_NOT_SUPPORTED: + rv = NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED; + break; + case NPERR_MALFORMED_SITE: + rv = NS_ERROR_INVALID_ARG; + break; + default: + rv = NS_ERROR_FAILURE; + } + callback->Callback(rv); + return NS_OK; +} + +nsresult +PluginPRLibrary::NPP_GetSitesWithData(nsCOMPtr<nsIGetSitesWithDataCallback> callback) +{ + if (!mNPP_GetSitesWithData) { + return NS_ERROR_NOT_AVAILABLE; + } + + MAIN_THREAD_JNI_REF_GUARD; + char** sites = mNPP_GetSitesWithData(); + if (!sites) { + return NS_OK; + } + InfallibleTArray<nsCString> result; + char** iterator = sites; + while (*iterator) { + result.AppendElement(*iterator); + free(*iterator); + ++iterator; + } + callback->SitesWithData(result); + free(sites); + + return NS_OK; +} + +nsresult +PluginPRLibrary::AsyncSetWindow(NPP instance, NPWindow* window) +{ + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + NS_ENSURE_TRUE(inst, NS_ERROR_NULL_POINTER); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +PluginPRLibrary::GetImageContainer(NPP instance, ImageContainer** aContainer) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +#if defined(XP_MACOSX) +nsresult +PluginPRLibrary::IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) +{ + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + NS_ENSURE_TRUE(inst, NS_ERROR_NULL_POINTER); + *aDrawing = false; + return NS_OK; +} +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +nsresult +PluginPRLibrary::ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) +{ + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + NS_ENSURE_TRUE(inst, NS_ERROR_NULL_POINTER); + return NS_OK; +} +#endif + +nsresult +PluginPRLibrary::GetImageSize(NPP instance, nsIntSize* aSize) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +PluginPRLibrary::SetBackgroundUnknown(NPP instance) +{ + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + NS_ENSURE_TRUE(inst, NS_ERROR_NULL_POINTER); + NS_ERROR("Unexpected use of async APIs for in-process plugin."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +PluginPRLibrary::BeginUpdateBackground(NPP instance, const nsIntRect&, + DrawTarget** aDrawTarget) +{ + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + NS_ENSURE_TRUE(inst, NS_ERROR_NULL_POINTER); + NS_ERROR("Unexpected use of async APIs for in-process plugin."); + *aDrawTarget = nullptr; + return NS_OK; +} + +nsresult +PluginPRLibrary::EndUpdateBackground(NPP instance, const nsIntRect&) +{ + NS_RUNTIMEABORT("This should never be called"); + return NS_ERROR_NOT_AVAILABLE; +} + +#if defined(XP_WIN) +nsresult +PluginPRLibrary::GetScrollCaptureContainer(NPP aInstance, ImageContainer** aContainer) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} +#endif + +nsresult +PluginPRLibrary::HandledWindowedPluginKeyEvent( + NPP aInstance, + const NativeEventData& aNativeKeyData, + bool aIsConsumed) +{ + nsNPAPIPluginInstance* instance = (nsNPAPIPluginInstance*)aInstance->ndata; + if (NS_WARN_IF(!instance)) { + return NS_ERROR_NULL_POINTER; + } + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/plugins/base/PluginPRLibrary.h b/dom/plugins/base/PluginPRLibrary.h new file mode 100644 index 000000000..602ad8cf8 --- /dev/null +++ b/dom/plugins/base/PluginPRLibrary.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 ts=4 et : + * 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/. */ + +#ifndef PluginPRLibrary_h +#define PluginPRLibrary_h 1 + +#include "mozilla/PluginLibrary.h" +#include "nsNPAPIPlugin.h" +#include "npfunctions.h" + +namespace mozilla { + +class PluginPRLibrary : public PluginLibrary +{ +public: + PluginPRLibrary(const char* aFilePath, PRLibrary* aLibrary) : +#if defined(XP_UNIX) && !defined(XP_MACOSX) + mNP_Initialize(nullptr), +#else + mNP_Initialize(nullptr), +#endif + mNP_Shutdown(nullptr), + mNP_GetMIMEDescription(nullptr), +#if defined(XP_UNIX) && !defined(XP_MACOSX) + mNP_GetValue(nullptr), +#endif +#if defined(XP_WIN) || defined(XP_MACOSX) + mNP_GetEntryPoints(nullptr), +#endif + mNPP_New(nullptr), + mNPP_ClearSiteData(nullptr), + mNPP_GetSitesWithData(nullptr), + mLibrary(aLibrary), + mFilePath(aFilePath) + { + NS_ASSERTION(mLibrary, "need non-null lib"); + // addref here?? + } + + virtual ~PluginPRLibrary() + { + // unref here?? + } + + virtual void SetPlugin(nsNPAPIPlugin*) override { } + + virtual bool HasRequiredFunctions() override { + mNP_Initialize = (NP_InitializeFunc) + PR_FindFunctionSymbol(mLibrary, "NP_Initialize"); + if (!mNP_Initialize) + return false; + + mNP_Shutdown = (NP_ShutdownFunc) + PR_FindFunctionSymbol(mLibrary, "NP_Shutdown"); + if (!mNP_Shutdown) + return false; + + mNP_GetMIMEDescription = (NP_GetMIMEDescriptionFunc) + PR_FindFunctionSymbol(mLibrary, "NP_GetMIMEDescription"); +#ifndef XP_MACOSX + if (!mNP_GetMIMEDescription) + return false; +#endif + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + mNP_GetValue = (NP_GetValueFunc) + PR_FindFunctionSymbol(mLibrary, "NP_GetValue"); + if (!mNP_GetValue) + return false; +#endif + +#if defined(XP_WIN) || defined(XP_MACOSX) + mNP_GetEntryPoints = (NP_GetEntryPointsFunc) + PR_FindFunctionSymbol(mLibrary, "NP_GetEntryPoints"); + if (!mNP_GetEntryPoints) + return false; +#endif + return true; + } + +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + virtual nsresult NP_Initialize(NPNetscapeFuncs* aNetscapeFuncs, + NPPluginFuncs* aFuncs, NPError* aError) override; +#else + virtual nsresult NP_Initialize(NPNetscapeFuncs* aNetscapeFuncs, + NPError* aError) override; +#endif + + virtual nsresult NP_Shutdown(NPError* aError) override; + virtual nsresult NP_GetMIMEDescription(const char** aMimeDesc) override; + + virtual nsresult NP_GetValue(void* aFuture, NPPVariable aVariable, + void* aValue, NPError* aError) override; + +#if defined(XP_WIN) || defined(XP_MACOSX) + virtual nsresult NP_GetEntryPoints(NPPluginFuncs* aFuncs, NPError* aError) override; +#endif + + virtual nsresult NPP_New(NPMIMEType aPluginType, NPP aInstance, + uint16_t aMode, int16_t aArgc, char* aArgn[], + char* aArgv[], NPSavedData* aSaved, + NPError* aError) override; + + virtual nsresult NPP_ClearSiteData(const char* aSite, uint64_t aFlags, + uint64_t aMaxAge, nsCOMPtr<nsIClearSiteDataCallback> callback) override; + virtual nsresult NPP_GetSitesWithData(nsCOMPtr<nsIGetSitesWithDataCallback> callback) override; + + virtual nsresult AsyncSetWindow(NPP aInstance, NPWindow* aWindow) override; + virtual nsresult GetImageContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) override; + virtual nsresult GetImageSize(NPP aInstance, nsIntSize* aSize) override; + virtual bool IsOOP() override { return false; } +#if defined(XP_MACOSX) + virtual nsresult IsRemoteDrawingCoreAnimation(NPP aInstance, bool* aDrawing) override; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + virtual nsresult ContentsScaleFactorChanged(NPP aInstance, double aContentsScaleFactor) override; +#endif + virtual nsresult SetBackgroundUnknown(NPP instance) override; + virtual nsresult BeginUpdateBackground(NPP instance, const nsIntRect&, + DrawTarget** aDrawTarget) override; + virtual nsresult EndUpdateBackground(NPP instance, + const nsIntRect&) override; + virtual void DidComposite(NPP aInstance) override { } + virtual void GetLibraryPath(nsACString& aPath) { aPath.Assign(mFilePath); } + virtual nsresult GetRunID(uint32_t* aRunID) override { return NS_ERROR_NOT_IMPLEMENTED; } + virtual void SetHasLocalInstance() override { } +#if defined(XP_WIN) + virtual nsresult GetScrollCaptureContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) override; +#endif + virtual nsresult HandledWindowedPluginKeyEvent( + NPP aInstance, + const mozilla::NativeEventData& aNativeKeyData, + bool aIsCOnsumed) override; + +private: + NP_InitializeFunc mNP_Initialize; + NP_ShutdownFunc mNP_Shutdown; + NP_GetMIMEDescriptionFunc mNP_GetMIMEDescription; +#if defined(XP_UNIX) && !defined(XP_MACOSX) + NP_GetValueFunc mNP_GetValue; +#endif +#if defined(XP_WIN) || defined(XP_MACOSX) + NP_GetEntryPointsFunc mNP_GetEntryPoints; +#endif + NPP_NewProcPtr mNPP_New; + NPP_ClearSiteDataPtr mNPP_ClearSiteData; + NPP_GetSitesWithDataPtr mNPP_GetSitesWithData; + PRLibrary* mLibrary; + nsCString mFilePath; +}; + + +} // namespace mozilla + +#endif // ifndef PluginPRLibrary_h diff --git a/dom/plugins/base/android/ANPAudio.cpp b/dom/plugins/base/android/ANPAudio.cpp new file mode 100644 index 000000000..bc47e8999 --- /dev/null +++ b/dom/plugins/base/android/ANPAudio.cpp @@ -0,0 +1,390 @@ +/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- + * 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 "base/basictypes.h" +#include "AndroidBridge.h" + +#include <android/log.h> +#include <stdlib.h> +#include <time.h> + +#include "assert.h" +#include "ANPBase.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "mozilla/Mutex.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPluginsAudio" , ## args) +#define ASSIGN(obj, name) (obj)->name = anp_audio_##name + +/* android.media.AudioTrack */ +struct AudioTrack { + jclass at_class; + jmethodID constructor; + jmethodID flush; + jmethodID pause; + jmethodID play; + jmethodID setvol; + jmethodID stop; + jmethodID write; + jmethodID getpos; + jmethodID getstate; + jmethodID release; +}; + +enum AudioTrackMode { + MODE_STATIC = 0, + MODE_STREAM = 1 +}; + +/* android.media.AudioManager */ +enum AudioManagerStream { + STREAM_VOICE_CALL = 0, + STREAM_SYSTEM = 1, + STREAM_RING = 2, + STREAM_MUSIC = 3, + STREAM_ALARM = 4, + STREAM_NOTIFICATION = 5, + STREAM_DTMF = 8 +}; + +/* android.media.AudioFormat */ +enum AudioFormatChannel { + CHANNEL_OUT_MONO = 4, + CHANNEL_OUT_STEREO = 12 +}; + +enum AudioFormatEncoding { + ENCODING_PCM_16BIT = 2, + ENCODING_PCM_8BIT = 3 +}; + +enum AudioFormatState { + STATE_UNINITIALIZED = 0, + STATE_INITIALIZED = 1, + STATE_NO_STATIC_DATA = 2 +}; + +static struct AudioTrack at; + +static jclass +init_jni_bindings(JNIEnv *jenv) { + jclass jc = + (jclass)jenv->NewGlobalRef(jenv->FindClass("android/media/AudioTrack")); + + at.constructor = jenv->GetMethodID(jc, "<init>", "(IIIIII)V"); + at.flush = jenv->GetMethodID(jc, "flush", "()V"); + at.pause = jenv->GetMethodID(jc, "pause", "()V"); + at.play = jenv->GetMethodID(jc, "play", "()V"); + at.setvol = jenv->GetMethodID(jc, "setStereoVolume", "(FF)I"); + at.stop = jenv->GetMethodID(jc, "stop", "()V"); + at.write = jenv->GetMethodID(jc, "write", "([BII)I"); + at.getpos = jenv->GetMethodID(jc, "getPlaybackHeadPosition", "()I"); + at.getstate = jenv->GetMethodID(jc, "getState", "()I"); + at.release = jenv->GetMethodID(jc, "release", "()V"); + + return jc; +} + +struct ANPAudioTrack { + jobject output_unit; + jclass at_class; + + unsigned int rate; + unsigned int channels; + unsigned int bufferSize; + unsigned int isStopped; + unsigned int keepGoing; + + mozilla::Mutex lock; + + void* user; + ANPAudioCallbackProc proc; + ANPSampleFormat format; + + ANPAudioTrack() : lock("ANPAudioTrack") { } +}; + +class AudioRunnable : public mozilla::Runnable +{ +public: + NS_DECL_NSIRUNNABLE + + AudioRunnable(ANPAudioTrack* aAudioTrack) { + mTrack = aAudioTrack; + } + + ANPAudioTrack* mTrack; +}; + +NS_IMETHODIMP +AudioRunnable::Run() +{ + PR_SetCurrentThreadName("Android Audio"); + + JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); + + mozilla::AutoLocalJNIFrame autoFrame(jenv, 2); + + jbyteArray bytearray = jenv->NewByteArray(mTrack->bufferSize); + if (!bytearray) { + LOG("AudioRunnable:: Run. Could not create bytearray"); + return NS_ERROR_FAILURE; + } + + jbyte *byte = jenv->GetByteArrayElements(bytearray, nullptr); + if (!byte) { + LOG("AudioRunnable:: Run. Could not create bytearray"); + return NS_ERROR_FAILURE; + } + + ANPAudioBuffer buffer; + buffer.channelCount = mTrack->channels; + buffer.format = mTrack->format; + buffer.bufferData = (void*) byte; + + while (true) + { + // reset the buffer size + buffer.size = mTrack->bufferSize; + + { + mozilla::MutexAutoLock lock(mTrack->lock); + + if (!mTrack->keepGoing) + break; + + // Get data from the plugin + mTrack->proc(kMoreData_ANPAudioEvent, mTrack->user, &buffer); + } + + if (buffer.size == 0) { + LOG("%p - kMoreData_ANPAudioEvent", mTrack); + continue; + } + + size_t wroteSoFar = 0; + jint retval; + do { + retval = jenv->CallIntMethod(mTrack->output_unit, + at.write, + bytearray, + wroteSoFar, + buffer.size - wroteSoFar); + if (retval < 0) { + LOG("%p - Write failed %d", mTrack, retval); + break; + } + + wroteSoFar += retval; + + } while(wroteSoFar < buffer.size); + } + + jenv->CallVoidMethod(mTrack->output_unit, at.release); + + jenv->DeleteGlobalRef(mTrack->output_unit); + jenv->DeleteGlobalRef(mTrack->at_class); + + delete mTrack; + + jenv->ReleaseByteArrayElements(bytearray, byte, 0); + + return NS_OK; +} + +ANPAudioTrack* +anp_audio_newTrack(uint32_t sampleRate, // sampling rate in Hz + ANPSampleFormat format, + int channelCount, // MONO=1, STEREO=2 + ANPAudioCallbackProc proc, + void* user) +{ + ANPAudioTrack *s = new ANPAudioTrack(); + if (s == nullptr) { + return nullptr; + } + + JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); + + s->at_class = init_jni_bindings(jenv); + s->rate = sampleRate; + s->channels = channelCount; + s->bufferSize = s->rate * s->channels; + s->isStopped = true; + s->keepGoing = false; + s->user = user; + s->proc = proc; + s->format = format; + + int jformat; + switch (format) { + case kPCM16Bit_ANPSampleFormat: + jformat = ENCODING_PCM_16BIT; + break; + case kPCM8Bit_ANPSampleFormat: + jformat = ENCODING_PCM_8BIT; + break; + default: + LOG("Unknown audio format. defaulting to 16bit."); + jformat = ENCODING_PCM_16BIT; + break; + } + + int jChannels; + switch (channelCount) { + case 1: + jChannels = CHANNEL_OUT_MONO; + break; + case 2: + jChannels = CHANNEL_OUT_STEREO; + break; + default: + LOG("Unknown channel count. defaulting to mono."); + jChannels = CHANNEL_OUT_MONO; + break; + } + + mozilla::AutoLocalJNIFrame autoFrame(jenv); + + jobject obj = jenv->NewObject(s->at_class, + at.constructor, + STREAM_MUSIC, + s->rate, + jChannels, + jformat, + s->bufferSize, + MODE_STREAM); + + if (autoFrame.CheckForException() || obj == nullptr) { + jenv->DeleteGlobalRef(s->at_class); + delete s; + return nullptr; + } + + jint state = jenv->CallIntMethod(obj, at.getstate); + + if (autoFrame.CheckForException() || state == STATE_UNINITIALIZED) { + jenv->DeleteGlobalRef(s->at_class); + delete s; + return nullptr; + } + + s->output_unit = jenv->NewGlobalRef(obj); + return s; +} + +void +anp_audio_deleteTrack(ANPAudioTrack* s) +{ + if (s == nullptr) { + return; + } + + mozilla::MutexAutoLock lock(s->lock); + s->keepGoing = false; + + // deallocation happens in the AudioThread. There is a + // potential leak if anp_audio_start is never called, but + // we do not see that from flash. +} + +void +anp_audio_start(ANPAudioTrack* s) +{ + if (s == nullptr || s->output_unit == nullptr) { + return; + } + + if (s->keepGoing) { + // we are already playing. Ignore. + return; + } + + JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); + + mozilla::AutoLocalJNIFrame autoFrame(jenv, 0); + jenv->CallVoidMethod(s->output_unit, at.play); + + if (autoFrame.CheckForException()) { + jenv->DeleteGlobalRef(s->at_class); + delete s; + return; + } + + s->isStopped = false; + s->keepGoing = true; + + // AudioRunnable now owns the ANPAudioTrack + RefPtr<AudioRunnable> runnable = new AudioRunnable(s); + + nsCOMPtr<nsIThread> thread; + NS_NewThread(getter_AddRefs(thread), runnable); +} + +void +anp_audio_pause(ANPAudioTrack* s) +{ + if (s == nullptr || s->output_unit == nullptr) { + return; + } + + JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); + + mozilla::AutoLocalJNIFrame autoFrame(jenv, 0); + jenv->CallVoidMethod(s->output_unit, at.pause); +} + +void +anp_audio_stop(ANPAudioTrack* s) +{ + if (s == nullptr || s->output_unit == nullptr) { + return; + } + + s->isStopped = true; + JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); + + mozilla::AutoLocalJNIFrame autoFrame(jenv, 0); + jenv->CallVoidMethod(s->output_unit, at.stop); +} + +bool +anp_audio_isStopped(ANPAudioTrack* s) +{ + return s->isStopped; +} + +uint32_t +anp_audio_trackLatency(ANPAudioTrack* s) { + // Hardcode an estimate of the system's audio latency. Flash hardcodes + // similar latency estimates for pre-Honeycomb devices that do not support + // ANPAudioTrackInterfaceV1's trackLatency(). The Android stock browser + // calls android::AudioTrack::latency(), an internal Android API that is + // not available in the public NDK: + // https://github.com/android/platform_external_webkit/commit/49bf866973cb3b2a6c74c0eab864e9562e4cbab1 + return 100; // milliseconds +} + +void InitAudioTrackInterfaceV0(ANPAudioTrackInterfaceV0 *i) { + _assert(i->inSize == sizeof(*i)); + ASSIGN(i, newTrack); + ASSIGN(i, deleteTrack); + ASSIGN(i, start); + ASSIGN(i, pause); + ASSIGN(i, stop); + ASSIGN(i, isStopped); +} + +void InitAudioTrackInterfaceV1(ANPAudioTrackInterfaceV1 *i) { + _assert(i->inSize == sizeof(*i)); + ASSIGN(i, newTrack); + ASSIGN(i, deleteTrack); + ASSIGN(i, start); + ASSIGN(i, pause); + ASSIGN(i, stop); + ASSIGN(i, isStopped); + ASSIGN(i, trackLatency); +} diff --git a/dom/plugins/base/android/ANPBase.h b/dom/plugins/base/android/ANPBase.h new file mode 100644 index 000000000..f9712dc79 --- /dev/null +++ b/dom/plugins/base/android/ANPBase.h @@ -0,0 +1,36 @@ +/* -*- Mode: IDL; 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 <stdlib.h> +#include "android_npapi.h" +#include "nsISupportsImpl.h" + +#define NOT_IMPLEMENTED_FATAL() do { \ + __android_log_print(ANDROID_LOG_ERROR, "GeckoPlugins", \ + "%s not implemented %s, %d", \ + __PRETTY_FUNCTION__, __FILE__, __LINE__); \ + abort(); \ + } while(0) + +#define NOT_IMPLEMENTED() \ + __android_log_print(ANDROID_LOG_ERROR, "GeckoPlugins", \ + "!!!!!!!!!!!!!! %s not implemented %s, %d", \ + __PRETTY_FUNCTION__, __FILE__, __LINE__); \ + +void InitAudioTrackInterfaceV0(ANPAudioTrackInterfaceV0 *i); +void InitAudioTrackInterfaceV1(ANPAudioTrackInterfaceV1* i); +void InitCanvasInterface(ANPCanvasInterfaceV0 *i); +void InitEventInterface(ANPEventInterfaceV0 *i); +void InitLogInterface(ANPLogInterfaceV0 *i); +void InitPaintInterface(ANPPaintInterfaceV0 *i); +void InitSurfaceInterface(ANPSurfaceInterfaceV0 *i); +void InitSystemInterfaceV1(ANPSystemInterfaceV1 *i); +void InitSystemInterfaceV2(ANPSystemInterfaceV2 *i); +void InitTypeFaceInterface(ANPTypefaceInterfaceV0 *i); +void InitWindowInterface(ANPWindowInterfaceV0 *i); +void InitWindowInterfaceV2(ANPWindowInterfaceV2 *i); +void InitVideoInterfaceV1(ANPVideoInterfaceV1 *i); +void InitOpenGLInterface(ANPOpenGLInterfaceV0 *i); +void InitNativeWindowInterface(ANPNativeWindowInterfaceV0 *i); diff --git a/dom/plugins/base/android/ANPEvent.cpp b/dom/plugins/base/android/ANPEvent.cpp new file mode 100644 index 000000000..cac8c94f0 --- /dev/null +++ b/dom/plugins/base/android/ANPEvent.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: IDL; 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 "assert.h" +#include "ANPBase.h" +#include <android/log.h> +#include "nsThreadUtils.h" +#include "nsNPAPIPluginInstance.h" +#include "nsNPAPIPlugin.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#define ASSIGN(obj, name) (obj)->name = anp_event_##name + +void +anp_event_postEvent(NPP instance, const ANPEvent* event) +{ + LOG("%s", __PRETTY_FUNCTION__); + + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + pinst->PostEvent((void*) event); + + LOG("returning from %s", __PRETTY_FUNCTION__); +} + + +void InitEventInterface(ANPEventInterfaceV0 *i) { + _assert(i->inSize == sizeof(*i)); + ASSIGN(i, postEvent); +} diff --git a/dom/plugins/base/android/ANPKeyCodes.h b/dom/plugins/base/android/ANPKeyCodes.h new file mode 100644 index 000000000..edfe2b95c --- /dev/null +++ b/dom/plugins/base/android/ANPKeyCodes.h @@ -0,0 +1,152 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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. + */ + +#ifndef ANPKeyCodes_DEFINED +#define ANPKeyCodes_DEFINED + +/* List the key codes that are set to a plugin in the ANPKeyEvent. + + These exactly match the values in android/view/KeyEvent.java and the + corresponding .h file android/keycodes.h. +*/ +enum ANPKeyCodes { + kUnknown_ANPKeyCode = 0, + + kSoftLeft_ANPKeyCode = 1, + kSoftRight_ANPKeyCode = 2, + kHome_ANPKeyCode = 3, + kBack_ANPKeyCode = 4, + kCall_ANPKeyCode = 5, + kEndCall_ANPKeyCode = 6, + k0_ANPKeyCode = 7, + k1_ANPKeyCode = 8, + k2_ANPKeyCode = 9, + k3_ANPKeyCode = 10, + k4_ANPKeyCode = 11, + k5_ANPKeyCode = 12, + k6_ANPKeyCode = 13, + k7_ANPKeyCode = 14, + k8_ANPKeyCode = 15, + k9_ANPKeyCode = 16, + kStar_ANPKeyCode = 17, + kPound_ANPKeyCode = 18, + kDpadUp_ANPKeyCode = 19, + kDpadDown_ANPKeyCode = 20, + kDpadLeft_ANPKeyCode = 21, + kDpadRight_ANPKeyCode = 22, + kDpadCenter_ANPKeyCode = 23, + kVolumeUp_ANPKeyCode = 24, + kVolumeDown_ANPKeyCode = 25, + kPower_ANPKeyCode = 26, + kCamera_ANPKeyCode = 27, + kClear_ANPKeyCode = 28, + kA_ANPKeyCode = 29, + kB_ANPKeyCode = 30, + kC_ANPKeyCode = 31, + kD_ANPKeyCode = 32, + kE_ANPKeyCode = 33, + kF_ANPKeyCode = 34, + kG_ANPKeyCode = 35, + kH_ANPKeyCode = 36, + kI_ANPKeyCode = 37, + kJ_ANPKeyCode = 38, + kK_ANPKeyCode = 39, + kL_ANPKeyCode = 40, + kM_ANPKeyCode = 41, + kN_ANPKeyCode = 42, + kO_ANPKeyCode = 43, + kP_ANPKeyCode = 44, + kQ_ANPKeyCode = 45, + kR_ANPKeyCode = 46, + kS_ANPKeyCode = 47, + kT_ANPKeyCode = 48, + kU_ANPKeyCode = 49, + kV_ANPKeyCode = 50, + kW_ANPKeyCode = 51, + kX_ANPKeyCode = 52, + kY_ANPKeyCode = 53, + kZ_ANPKeyCode = 54, + kComma_ANPKeyCode = 55, + kPeriod_ANPKeyCode = 56, + kAltLeft_ANPKeyCode = 57, + kAltRight_ANPKeyCode = 58, + kShiftLeft_ANPKeyCode = 59, + kShiftRight_ANPKeyCode = 60, + kTab_ANPKeyCode = 61, + kSpace_ANPKeyCode = 62, + kSym_ANPKeyCode = 63, + kExplorer_ANPKeyCode = 64, + kEnvelope_ANPKeyCode = 65, + kNewline_ANPKeyCode = 66, + kDel_ANPKeyCode = 67, + kGrave_ANPKeyCode = 68, + kMinus_ANPKeyCode = 69, + kEquals_ANPKeyCode = 70, + kLeftBracket_ANPKeyCode = 71, + kRightBracket_ANPKeyCode = 72, + kBackslash_ANPKeyCode = 73, + kSemicolon_ANPKeyCode = 74, + kApostrophe_ANPKeyCode = 75, + kSlash_ANPKeyCode = 76, + kAt_ANPKeyCode = 77, + kNum_ANPKeyCode = 78, + kHeadSetHook_ANPKeyCode = 79, + kFocus_ANPKeyCode = 80, + kPlus_ANPKeyCode = 81, + kMenu_ANPKeyCode = 82, + kNotification_ANPKeyCode = 83, + kSearch_ANPKeyCode = 84, + kMediaPlayPause_ANPKeyCode = 85, + kMediaStop_ANPKeyCode = 86, + kMediaNext_ANPKeyCode = 87, + kMediaPrevious_ANPKeyCode = 88, + kMediaRewind_ANPKeyCode = 89, + kMediaFastForward_ANPKeyCode = 90, + kMute_ANPKeyCode = 91, + kPageUp_ANPKeyCode = 92, + kPageDown_ANPKeyCode = 93, + kPictsymbols_ANPKeyCode = 94, + kSwitchCharset_ANPKeyCode = 95, + kButtonA_ANPKeyCode = 96, + kButtonB_ANPKeyCode = 97, + kButtonC_ANPKeyCode = 98, + kButtonX_ANPKeyCode = 99, + kButtonY_ANPKeyCode = 100, + kButtonZ_ANPKeyCode = 101, + kButtonL1_ANPKeyCode = 102, + kButtonR1_ANPKeyCode = 103, + kButtonL2_ANPKeyCode = 104, + kButtonR2_ANPKeyCode = 105, + kButtonThumbL_ANPKeyCode = 106, + kButtonThumbR_ANPKeyCode = 107, + kButtonStart_ANPKeyCode = 108, + kButtonSelect_ANPKeyCode = 109, + kButtonMode_ANPKeyCode = 110, + + // NOTE: If you add a new keycode here you must also add it to several other files. + // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. +}; + +#endif diff --git a/dom/plugins/base/android/ANPLog.cpp b/dom/plugins/base/android/ANPLog.cpp new file mode 100644 index 000000000..7ce13107b --- /dev/null +++ b/dom/plugins/base/android/ANPLog.cpp @@ -0,0 +1,26 @@ +/* -*- Mode: IDL; 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 "assert.h" +#include "ANPBase.h" +#include <android/log.h> + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#define ASSIGN(obj, name) (obj)->name = anp_log_##name + +void +anp_log_log(ANPLogType type, const char format[], ...) { + + va_list argp; + va_start(argp,format); + __android_log_vprint(type == kError_ANPLogType ? ANDROID_LOG_ERROR : type == kWarning_ANPLogType ? + ANDROID_LOG_WARN : ANDROID_LOG_INFO, "GeckoPluginLog", format, argp); + va_end(argp); +} + +void InitLogInterface(ANPLogInterfaceV0 *i) { + _assert(i->inSize == sizeof(*i)); + ASSIGN(i, log); +} diff --git a/dom/plugins/base/android/ANPNativeWindow.cpp b/dom/plugins/base/android/ANPNativeWindow.cpp new file mode 100644 index 000000000..88b43bd4a --- /dev/null +++ b/dom/plugins/base/android/ANPNativeWindow.cpp @@ -0,0 +1,39 @@ +/* -*- Mode: IDL; 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/. */ + +// must include config.h first for webkit to fiddle with new/delete +#include <android/log.h> +#include "ANPBase.h" +#include "nsIPluginInstanceOwner.h" +#include "nsPluginInstanceOwner.h" +#include "nsNPAPIPluginInstance.h" +#include "gfxRect.h" + +using namespace mozilla; + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#define ASSIGN(obj, name) (obj)->name = anp_native_window_##name + +static ANPNativeWindow anp_native_window_acquireNativeWindow(NPP instance) { + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + return pinst->AcquireContentWindow(); +} + +static void anp_native_window_invertPluginContent(NPP instance, bool isContentInverted) { + // NativeWindow is TopLeft if uninverted. + gl::OriginPos newOriginPos = gl::OriginPos::TopLeft; + if (isContentInverted) + newOriginPos = gl::OriginPos::BottomLeft; + + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + pinst->SetOriginPos(newOriginPos); + pinst->RedrawPlugin(); +} + + +void InitNativeWindowInterface(ANPNativeWindowInterfaceV0* i) { + ASSIGN(i, acquireNativeWindow); + ASSIGN(i, invertPluginContent); +} diff --git a/dom/plugins/base/android/ANPSurface.cpp b/dom/plugins/base/android/ANPSurface.cpp new file mode 100644 index 000000000..b6a699f28 --- /dev/null +++ b/dom/plugins/base/android/ANPSurface.cpp @@ -0,0 +1,266 @@ +/* -*- Mode: IDL; 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 <dlfcn.h> +#include <android/log.h> +#include "ANPBase.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#define ASSIGN(obj, name) (obj)->name = anp_surface_##name + +#define CLEAR_EXCEPTION(env) if (env->ExceptionOccurred()) env->ExceptionClear(); + +#define ANDROID_REGION_SIZE 512 + +enum { + PIXEL_FORMAT_RGBA_8888 = 1, + PIXEL_FORMAT_RGB_565 = 4, +}; + +struct SurfaceInfo { + uint32_t w; + uint32_t h; + uint32_t s; + uint32_t usage; + uint32_t format; + unsigned char* bits; + uint32_t reserved[2]; +}; + +typedef struct ARect { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +} ARect; + + +// used to cache JNI method and field IDs for Surface Objects +static struct ANPSurfaceInterfaceJavaGlue { + bool initialized; + jmethodID getSurfaceHolder; + jmethodID getSurface; + jfieldID surfacePointer; +} gSurfaceJavaGlue; + +static struct ANPSurfaceFunctions { + bool initialized; + + int (* lock)(void*, SurfaceInfo*, void*); + int (* unlockAndPost)(void*); + + void* (* regionConstructor)(void*); + void (* setRegion)(void*, ARect const&); +} gSurfaceFunctions; + + +static inline void* getSurface(JNIEnv* env, jobject view) { + if (!env || !view) { + return nullptr; + } + + if (!gSurfaceJavaGlue.initialized) { + + jclass surfaceViewClass = env->FindClass("android/view/SurfaceView"); + gSurfaceJavaGlue.getSurfaceHolder = env->GetMethodID(surfaceViewClass, "getHolder", "()Landroid/view/SurfaceHolder;"); + + jclass surfaceHolderClass = env->FindClass("android/view/SurfaceHolder"); + gSurfaceJavaGlue.getSurface = env->GetMethodID(surfaceHolderClass, "getSurface", "()Landroid/view/Surface;"); + + jclass surfaceClass = env->FindClass("android/view/Surface"); + gSurfaceJavaGlue.surfacePointer = env->GetFieldID(surfaceClass, + "mSurfacePointer", "I"); + + if (!gSurfaceJavaGlue.surfacePointer) { + CLEAR_EXCEPTION(env); + + // It was something else in 2.2. + gSurfaceJavaGlue.surfacePointer = env->GetFieldID(surfaceClass, + "mSurface", "I"); + + if (!gSurfaceJavaGlue.surfacePointer) { + CLEAR_EXCEPTION(env); + + // And something else in 2.3+ + gSurfaceJavaGlue.surfacePointer = env->GetFieldID(surfaceClass, + "mNativeSurface", "I"); + + CLEAR_EXCEPTION(env); + } + } + + if (!gSurfaceJavaGlue.surfacePointer) { + LOG("Failed to acquire surface pointer"); + return nullptr; + } + + env->DeleteLocalRef(surfaceClass); + env->DeleteLocalRef(surfaceViewClass); + env->DeleteLocalRef(surfaceHolderClass); + + gSurfaceJavaGlue.initialized = (gSurfaceJavaGlue.surfacePointer != nullptr); + } + + jobject holder = env->CallObjectMethod(view, gSurfaceJavaGlue.getSurfaceHolder); + jobject surface = env->CallObjectMethod(holder, gSurfaceJavaGlue.getSurface); + jint surfacePointer = env->GetIntField(surface, gSurfaceJavaGlue.surfacePointer); + + env->DeleteLocalRef(holder); + env->DeleteLocalRef(surface); + + return (void*)surfacePointer; +} + +static ANPBitmapFormat convertPixelFormat(int32_t format) { + switch (format) { + case PIXEL_FORMAT_RGBA_8888: return kRGBA_8888_ANPBitmapFormat; + case PIXEL_FORMAT_RGB_565: return kRGB_565_ANPBitmapFormat; + default: return kUnknown_ANPBitmapFormat; + } +} + +static int bytesPerPixel(int32_t format) { + switch (format) { + case PIXEL_FORMAT_RGBA_8888: return 4; + case PIXEL_FORMAT_RGB_565: return 2; + default: return -1; + } +} + +static bool init() { + if (gSurfaceFunctions.initialized) + return true; + + void* handle = dlopen("libsurfaceflinger_client.so", RTLD_LAZY); + + if (!handle) { + LOG("Failed to open libsurfaceflinger_client.so"); + return false; + } + + gSurfaceFunctions.lock = (int (*)(void*, SurfaceInfo*, void*))dlsym(handle, "_ZN7android7Surface4lockEPNS0_11SurfaceInfoEPNS_6RegionEb"); + gSurfaceFunctions.unlockAndPost = (int (*)(void*))dlsym(handle, "_ZN7android7Surface13unlockAndPostEv"); + + + if (!gSurfaceFunctions.lock) { + // Stuff changed in 3.0/4.0 + handle = dlopen("libgui.so", RTLD_LAZY); + gSurfaceFunctions.lock = (int (*)(void*, SurfaceInfo*, void*))dlsym(handle, "_ZN7android7Surface4lockEPNS0_11SurfaceInfoEPNS_6RegionE"); + gSurfaceFunctions.unlockAndPost = (int (*)(void*))dlsym(handle, "_ZN7android7Surface13unlockAndPostEv"); + } + + handle = dlopen("libui.so", RTLD_LAZY); + if (!handle) { + LOG("Failed to open libui.so"); + return false; + } + + gSurfaceFunctions.regionConstructor = (void* (*)(void*))dlsym(handle, "_ZN7android6RegionC1Ev"); + gSurfaceFunctions.setRegion = (void (*)(void*, ARect const&))dlsym(handle, "_ZN7android6Region3setERKNS_4RectE"); + + gSurfaceFunctions.initialized = (gSurfaceFunctions.lock && gSurfaceFunctions.unlockAndPost && + gSurfaceFunctions.regionConstructor && gSurfaceFunctions.setRegion); + LOG("Initialized? %d\n", gSurfaceFunctions.initialized); + return gSurfaceFunctions.initialized; +} + +// FIXME: All of this should be changed to use the equivalent things in AndroidBridge, bug 758612 +static bool anp_surface_lock(JNIEnv* env, jobject surfaceView, ANPBitmap* bitmap, ANPRectI* dirtyRect) { + if (!bitmap || !surfaceView) { + return false; + } + + void* surface = getSurface(env, surfaceView); + + if (!bitmap || !surface) { + return false; + } + + if (!init()) { + return false; + } + + void* region = nullptr; + if (dirtyRect) { + region = malloc(ANDROID_REGION_SIZE); + gSurfaceFunctions.regionConstructor(region); + + ARect rect; + rect.left = dirtyRect->left; + rect.top = dirtyRect->top; + rect.right = dirtyRect->right; + rect.bottom = dirtyRect->bottom; + + gSurfaceFunctions.setRegion(region, rect); + } + + SurfaceInfo info; + int err = gSurfaceFunctions.lock(surface, &info, region); + if (err < 0) { + LOG("Failed to lock surface"); + return false; + } + + // the surface may have expanded the dirty region so we must to pass that + // information back to the plugin. + if (dirtyRect) { + ARect* dirtyBounds = (ARect*)region; // The bounds are the first member, so this should work! + + dirtyRect->left = dirtyBounds->left; + dirtyRect->right = dirtyBounds->right; + dirtyRect->top = dirtyBounds->top; + dirtyRect->bottom = dirtyBounds->bottom; + } + + if (region) + free(region); + + int bpr = info.s * bytesPerPixel(info.format); + + bitmap->format = convertPixelFormat(info.format); + bitmap->width = info.w; + bitmap->height = info.h; + bitmap->rowBytes = bpr; + + if (info.w > 0 && info.h > 0) { + bitmap->baseAddr = info.bits; + } else { + bitmap->baseAddr = nullptr; + return false; + } + + return true; +} + +static void anp_surface_unlock(JNIEnv* env, jobject surfaceView) { + if (!surfaceView) { + return; + } + + if (!init()) { + return; + } + + void* surface = getSurface(env, surfaceView); + + if (!surface) { + return; + } + + gSurfaceFunctions.unlockAndPost(surface); +} + +/////////////////////////////////////////////////////////////////////////////// + +void InitSurfaceInterface(ANPSurfaceInterfaceV0* i) { + ASSIGN(i, lock); + ASSIGN(i, unlock); + + // setup the java glue struct + gSurfaceJavaGlue.initialized = false; + + // setup the function struct + gSurfaceFunctions.initialized = false; +} diff --git a/dom/plugins/base/android/ANPSystem.cpp b/dom/plugins/base/android/ANPSystem.cpp new file mode 100644 index 000000000..d5b2a7710 --- /dev/null +++ b/dom/plugins/base/android/ANPSystem.cpp @@ -0,0 +1,88 @@ +/* -*- Mode: IDL; 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 "base/basictypes.h" + +#include "ANPBase.h" +#include "GeneratedJNIWrappers.h" +#include "PluginPRLibrary.h" +#include "assert.h" +#include "nsNPAPIPluginInstance.h" +#include "nsNPAPIPlugin.h" + +#include <android/log.h> + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#define ASSIGN(obj, name) (obj)->name = anp_system_##name + +const char* +anp_system_getApplicationDataDirectory(NPP instance) +{ + static const char *dir = nullptr; + static const char *privateDir = nullptr; + + bool isPrivate = false; + + if (!dir) { + dir = getenv("ANDROID_PLUGIN_DATADIR"); + } + + if (!privateDir) { + privateDir = getenv("ANDROID_PLUGIN_DATADIR_PRIVATE"); + } + + if (!instance) { + return dir; + } + + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + if (pinst && NS_SUCCEEDED(pinst->IsPrivateBrowsing(&isPrivate)) && isPrivate) { + return privateDir; + } + + return dir; +} + +const char* +anp_system_getApplicationDataDirectory() +{ + return anp_system_getApplicationDataDirectory(nullptr); +} + +jclass anp_system_loadJavaClass(NPP instance, const char* className) +{ + LOG("%s", __PRETTY_FUNCTION__); + + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + mozilla::PluginPRLibrary* lib = static_cast<mozilla::PluginPRLibrary*>(pinst->GetPlugin()->GetLibrary()); + + nsCString libName; + lib->GetLibraryPath(libName); + + return mozilla::java::GeckoAppShell::LoadPluginClass(className, libName).Forget(); +} + +void anp_system_setPowerState(NPP instance, ANPPowerState powerState) +{ + nsNPAPIPluginInstance* pinst = nsNPAPIPluginInstance::GetFromNPP(instance); + + if (pinst) { + pinst->SetWakeLock(powerState == kScreenOn_ANPPowerState); + } +} + +void InitSystemInterfaceV1(ANPSystemInterfaceV1 *i) { + _assert(i->inSize == sizeof(*i)); + ASSIGN(i, getApplicationDataDirectory); + ASSIGN(i, loadJavaClass); + ASSIGN(i, setPowerState); +} + +void InitSystemInterfaceV2(ANPSystemInterfaceV2 *i) { + _assert(i->inSize == sizeof(*i)); + ASSIGN(i, getApplicationDataDirectory); + ASSIGN(i, loadJavaClass); + ASSIGN(i, setPowerState); +} diff --git a/dom/plugins/base/android/ANPVideo.cpp b/dom/plugins/base/android/ANPVideo.cpp new file mode 100644 index 000000000..185ab1194 --- /dev/null +++ b/dom/plugins/base/android/ANPVideo.cpp @@ -0,0 +1,55 @@ +/* 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 <android/log.h> +#include "ANPBase.h" +#include "nsIPluginInstanceOwner.h" +#include "nsPluginInstanceOwner.h" +#include "nsNPAPIPluginInstance.h" +#include "gfxRect.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#define ASSIGN(obj, name) (obj)->name = anp_video_##name + +using namespace mozilla; + +typedef nsNPAPIPluginInstance::VideoInfo VideoInfo; + +static ANPNativeWindow anp_video_acquireNativeWindow(NPP instance) { + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + + return pinst->AcquireVideoWindow(); +} + +static void anp_video_setWindowDimensions(NPP instance, const ANPNativeWindow window, + const ANPRectF* dimensions) { + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + + gfxRect rect(dimensions->left, dimensions->top, + dimensions->right - dimensions->left, + dimensions->bottom - dimensions->top); + + pinst->SetVideoDimensions(window, rect); + pinst->RedrawPlugin(); +} + +static void anp_video_releaseNativeWindow(NPP instance, ANPNativeWindow window) { + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + pinst->ReleaseVideoWindow(window); + pinst->RedrawPlugin(); +} + +static void anp_video_setFramerateCallback(NPP instance, const ANPNativeWindow window, ANPVideoFrameCallbackProc callback) { + // Bug 722682 + NOT_IMPLEMENTED(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void InitVideoInterfaceV1(ANPVideoInterfaceV1* i) { + ASSIGN(i, acquireNativeWindow); + ASSIGN(i, setWindowDimensions); + ASSIGN(i, releaseNativeWindow); + ASSIGN(i, setFramerateCallback); +} diff --git a/dom/plugins/base/android/ANPWindow.cpp b/dom/plugins/base/android/ANPWindow.cpp new file mode 100644 index 000000000..e9003aff5 --- /dev/null +++ b/dom/plugins/base/android/ANPWindow.cpp @@ -0,0 +1,152 @@ +/* -*- 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 "base/basictypes.h" +#include "assert.h" +#include "ANPBase.h" +#include <android/log.h> +#include "nsNPAPIPluginInstance.h" +#include "nsPluginInstanceOwner.h" +#include "nsWindow.h" +#include "mozilla/dom/ScreenOrientation.h" + +#undef LOG +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#define ASSIGN(obj, name) (obj)->name = anp_window_##name + +using namespace mozilla; +using namespace mozilla::widget; +using namespace mozilla::dom; + +void +anp_window_setVisibleRects(NPP instance, const ANPRectI rects[], int32_t count) +{ + NOT_IMPLEMENTED(); +} + +void +anp_window_clearVisibleRects(NPP instance) +{ + NOT_IMPLEMENTED(); +} + +void +anp_window_showKeyboard(NPP instance, bool value) +{ + InputContext context; + context.mIMEState.mEnabled = value ? IMEState::PLUGIN : IMEState::DISABLED; + context.mIMEState.mOpen = value ? IMEState::OPEN : IMEState::CLOSED; + context.mActionHint.Assign(EmptyString()); + + InputContextAction action; + action.mCause = InputContextAction::CAUSE_UNKNOWN; + action.mFocusChange = InputContextAction::FOCUS_NOT_CHANGED; + + nsWindow* window = nsWindow::TopWindow(); + if (!window) { + LOG("Couldn't get top window?"); + return; + } + + window->SetInputContext(context, action); +} + +void +anp_window_requestFullScreen(NPP instance) +{ + nsNPAPIPluginInstance* inst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + + RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner(); + if (!owner) { + return; + } + + owner->RequestFullScreen(); +} + +void +anp_window_exitFullScreen(NPP instance) +{ + nsNPAPIPluginInstance* inst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + + RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner(); + if (!owner) { + return; + } + + owner->ExitFullScreen(); +} + +void +anp_window_requestCenterFitZoom(NPP instance) +{ + NOT_IMPLEMENTED(); +} + +ANPRectI +anp_window_visibleRect(NPP instance) +{ + ANPRectI rect = { 0, 0, 0, 0 }; + + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + + nsIntSize currentSize = pinst->CurrentSize(); + rect.left = rect.top = 0; + rect.right = currentSize.width; + rect.bottom = currentSize.height; + + return rect; +} + +void anp_window_requestFullScreenOrientation(NPP instance, ANPScreenOrientation orientation) +{ + short newOrientation; + + // Convert to the ActivityInfo equivalent + switch (orientation) { + case kFixedLandscape_ANPScreenOrientation: + newOrientation = eScreenOrientation_LandscapePrimary; + break; + case kFixedPortrait_ANPScreenOrientation: + newOrientation = eScreenOrientation_PortraitPrimary; + break; + case kLandscape_ANPScreenOrientation: + newOrientation = eScreenOrientation_LandscapePrimary | + eScreenOrientation_LandscapeSecondary; + break; + case kPortrait_ANPScreenOrientation: + newOrientation = eScreenOrientation_PortraitPrimary | + eScreenOrientation_PortraitSecondary; + break; + default: + newOrientation = eScreenOrientation_None; + break; + } + + nsNPAPIPluginInstance* pinst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + pinst->SetFullScreenOrientation(newOrientation); +} + +void InitWindowInterface(ANPWindowInterfaceV0 *i) { + _assert(i->inSize == sizeof(*i)); + ASSIGN(i, setVisibleRects); + ASSIGN(i, clearVisibleRects); + ASSIGN(i, showKeyboard); + ASSIGN(i, requestFullScreen); + ASSIGN(i, exitFullScreen); + ASSIGN(i, requestCenterFitZoom); +} + +void InitWindowInterfaceV2(ANPWindowInterfaceV2 *i) { + _assert(i->inSize == sizeof(*i)); + ASSIGN(i, setVisibleRects); + ASSIGN(i, clearVisibleRects); + ASSIGN(i, showKeyboard); + ASSIGN(i, requestFullScreen); + ASSIGN(i, exitFullScreen); + ASSIGN(i, requestCenterFitZoom); + ASSIGN(i, visibleRect); + ASSIGN(i, requestFullScreenOrientation); +} diff --git a/dom/plugins/base/android/android_npapi.h b/dom/plugins/base/android/android_npapi.h new file mode 100644 index 000000000..16463d799 --- /dev/null +++ b/dom/plugins/base/android/android_npapi.h @@ -0,0 +1,1027 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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. + */ + +/* Defines the android-specific types and functions as part of npapi + + In particular, defines the window and event types that are passed to + NPN_GetValue, NPP_SetWindow and NPP_HandleEvent. + + To minimize what native libraries the plugin links against, some + functionality is provided via function-ptrs (e.g. time, sound) + */ + +#ifndef android_npapi_H +#define android_npapi_H + +#include <stdint.h> +#include <jni.h> +#include "npapi.h" +#include "GLDefs.h" + +/////////////////////////////////////////////////////////////////////////////// +// General types + +enum ANPBitmapFormats { + kUnknown_ANPBitmapFormat = 0, + kRGBA_8888_ANPBitmapFormat = 1, + kRGB_565_ANPBitmapFormat = 2 +}; +typedef int32_t ANPBitmapFormat; + +struct ANPPixelPacking { + uint8_t AShift; + uint8_t ABits; + uint8_t RShift; + uint8_t RBits; + uint8_t GShift; + uint8_t GBits; + uint8_t BShift; + uint8_t BBits; +}; + +struct ANPBitmap { + void* baseAddr; + ANPBitmapFormat format; + int32_t width; + int32_t height; + int32_t rowBytes; +}; + +struct ANPRectF { + float left; + float top; + float right; + float bottom; +}; + +struct ANPRectI { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +}; + +struct ANPCanvas; +struct ANPMatrix; +struct ANPPaint; +struct ANPPath; +struct ANPTypeface; + +/////////////////////////////////////////////////////////////////////////////// +// NPN_GetValue + +/** queries for a specific ANPInterface. + + Maybe called with NULL for the NPP instance + + NPN_GetValue(inst, interface_enum, ANPInterface*) + */ +#define kLogInterfaceV0_ANPGetValue ((NPNVariable)1000) +#define kAudioTrackInterfaceV0_ANPGetValue ((NPNVariable)1001) +#define kCanvasInterfaceV0_ANPGetValue ((NPNVariable)1002) +#define kMatrixInterfaceV0_ANPGetValue ((NPNVariable)1003) +#define kPaintInterfaceV0_ANPGetValue ((NPNVariable)1004) +#define kPathInterfaceV0_ANPGetValue ((NPNVariable)1005) +#define kTypefaceInterfaceV0_ANPGetValue ((NPNVariable)1006) +#define kWindowInterfaceV0_ANPGetValue ((NPNVariable)1007) +#define kBitmapInterfaceV0_ANPGetValue ((NPNVariable)1008) +#define kSurfaceInterfaceV0_ANPGetValue ((NPNVariable)1009) +#define kSystemInterfaceV0_ANPGetValue ((NPNVariable)1010) +#define kEventInterfaceV0_ANPGetValue ((NPNVariable)1011) + +#define kAudioTrackInterfaceV1_ANPGetValue ((NPNVariable)1012) +#define kOpenGLInterfaceV0_ANPGetValue ((NPNVariable)1013) +#define kWindowInterfaceV1_ANPGetValue ((NPNVariable)1014) +#define kVideoInterfaceV0_ANPGetValue ((NPNVariable)1015) +#define kSystemInterfaceV1_ANPGetValue ((NPNVariable)1016) +#define kSystemInterfaceV2_ANPGetValue ((NPNVariable)1017) +#define kWindowInterfaceV2_ANPGetValue ((NPNVariable)1018) +#define kNativeWindowInterfaceV0_ANPGetValue ((NPNVariable)1019) +#define kVideoInterfaceV1_ANPGetValue ((NPNVariable)1020) + +/** queries for the drawing models supported on this device. + + NPN_GetValue(inst, kSupportedDrawingModel_ANPGetValue, uint32_t* bits) + */ +#define kSupportedDrawingModel_ANPGetValue ((NPNVariable)2000) + +/** queries for the context (android.content.Context) of the plugin. If no + instance is specified the application's context is returned. If the instance + is given then the context returned is identical to the context used to + create the webview in which that instance resides. + + NOTE: Holding onto a non-application context after your instance has been + destroyed will cause a memory leak. Refer to the android documentation to + determine what context is best suited for your particular scenario. + + NPN_GetValue(inst, kJavaContext_ANPGetValue, jobject context) + */ +#define kJavaContext_ANPGetValue ((NPNVariable)2001) + +/////////////////////////////////////////////////////////////////////////////// +// NPN_SetValue + +/** Request to set the drawing model. SetValue will return false if the drawing + model is not supported or has insufficient information for configuration. + + NPN_SetValue(inst, kRequestDrawingModel_ANPSetValue, (void*)foo_ANPDrawingModel) + */ +#define kRequestDrawingModel_ANPSetValue ((NPPVariable)1000) + +/** These are used as bitfields in ANPSupportedDrawingModels_EnumValue, + and as-is in ANPRequestDrawingModel_EnumValue. The drawing model determines + how to interpret the ANPDrawingContext provided in the Draw event and how + to interpret the NPWindow->window field. + */ +enum ANPDrawingModels { + /** Draw into a bitmap from the browser thread in response to a Draw event. + NPWindow->window is reserved (ignore) + */ + kBitmap_ANPDrawingModel = 1 << 0, + /** Draw into a surface (e.g. raster, openGL, etc.) using the Java surface + interface. When this model is used the browser will invoke the Java + class specified in the plugin's apk manifest. From that class the browser + will invoke the appropriate method to return an an instance of a android + Java View. The instance is then embedded in the html. The plugin can then + manipulate the view as it would any normal Java View in android. + + Unlike the bitmap model, a surface model is opaque so no html content + behind the plugin will be visible. Unless the plugin needs to be + transparent the surface model should be chosen over the bitmap model as + it will have better performance. + + Further, a plugin can manipulate some surfaces in native code using the + ANPSurfaceInterface. This interface can be used to manipulate Java + objects that extend Surface.class by allowing them to access the + surface's underlying bitmap in native code. For instance, if a raster + surface is used the plugin can lock, draw directly into the bitmap, and + unlock the surface in native code without making JNI calls to the Java + surface object. + */ + kSurface_ANPDrawingModel = 1 << 1, + kOpenGL_ANPDrawingModel = 1 << 2, +}; +typedef int32_t ANPDrawingModel; + +/** Request to receive/disable events. If the pointer is NULL then all flags will + be disabled. Otherwise, the event type will be enabled iff its corresponding + bit in the EventFlags bit field is set. + + NPN_SetValue(inst, ANPAcceptEvents, (void*)EventFlags) + */ +#define kAcceptEvents_ANPSetValue ((NPPVariable)1001) + +/** The EventFlags are a set of bits used to determine which types of events the + plugin wishes to receive. For example, if the value is 0x03 then both key + and touch events will be provided to the plugin. + */ +enum ANPEventFlag { + kKey_ANPEventFlag = 0x01, + kTouch_ANPEventFlag = 0x02, +}; +typedef uint32_t ANPEventFlags; + +/////////////////////////////////////////////////////////////////////////////// +// NPP_GetValue + +/** Requests that the plugin return a java surface to be displayed. This will + only be used if the plugin has choosen the kSurface_ANPDrawingModel. + + NPP_GetValue(inst, kJavaSurface_ANPGetValue, jobject surface) + */ +#define kJavaSurface_ANPGetValue ((NPPVariable)2000) + + +/////////////////////////////////////////////////////////////////////////////// +// ANDROID INTERFACE DEFINITIONS + +/** Interfaces provide additional functionality to the plugin via function ptrs. + Once an interface is retrieved, it is valid for the lifetime of the plugin + (just like browserfuncs). + + All ANPInterfaces begin with an inSize field, which must be set by the + caller (plugin) with the number of bytes allocated for the interface. + e.g. SomeInterface si; si.inSize = sizeof(si); browser->getvalue(..., &si); + */ +struct ANPInterface { + uint32_t inSize; // size (in bytes) of this struct +}; + +enum ANPLogTypes { + kError_ANPLogType = 0, // error + kWarning_ANPLogType = 1, // warning + kDebug_ANPLogType = 2 // debug only (informational) +}; +typedef int32_t ANPLogType; + +struct ANPLogInterfaceV0 : ANPInterface { + /** dumps printf messages to the log file + e.g. interface->log(instance, kWarning_ANPLogType, "value is %d", value); + */ + void (*log)(ANPLogType, const char format[], ...); +}; + +/** ANPColor is always defined to have the same packing on all platforms, and + it is always unpremultiplied. + + This is in contrast to 32bit format(s) in bitmaps, which are premultiplied, + and their packing may vary depending on the platform, hence the need for + ANPBitmapInterface::getPixelPacking() + */ +typedef uint32_t ANPColor; +#define ANPColor_ASHIFT 24 +#define ANPColor_RSHIFT 16 +#define ANPColor_GSHIFT 8 +#define ANPColor_BSHIFT 0 +#define ANP_MAKE_COLOR(a, r, g, b) \ + (((a) << ANPColor_ASHIFT) | \ + ((r) << ANPColor_RSHIFT) | \ + ((g) << ANPColor_GSHIFT) | \ + ((b) << ANPColor_BSHIFT)) + +enum ANPPaintFlag { + kAntiAlias_ANPPaintFlag = 1 << 0, + kFilterBitmap_ANPPaintFlag = 1 << 1, + kDither_ANPPaintFlag = 1 << 2, + kUnderlineText_ANPPaintFlag = 1 << 3, + kStrikeThruText_ANPPaintFlag = 1 << 4, + kFakeBoldText_ANPPaintFlag = 1 << 5, +}; +typedef uint32_t ANPPaintFlags; + +enum ANPPaintStyles { + kFill_ANPPaintStyle = 0, + kStroke_ANPPaintStyle = 1, + kFillAndStroke_ANPPaintStyle = 2 +}; +typedef int32_t ANPPaintStyle; + +enum ANPPaintCaps { + kButt_ANPPaintCap = 0, + kRound_ANPPaintCap = 1, + kSquare_ANPPaintCap = 2 +}; +typedef int32_t ANPPaintCap; + +enum ANPPaintJoins { + kMiter_ANPPaintJoin = 0, + kRound_ANPPaintJoin = 1, + kBevel_ANPPaintJoin = 2 +}; +typedef int32_t ANPPaintJoin; + +enum ANPPaintAligns { + kLeft_ANPPaintAlign = 0, + kCenter_ANPPaintAlign = 1, + kRight_ANPPaintAlign = 2 +}; +typedef int32_t ANPPaintAlign; + +enum ANPTextEncodings { + kUTF8_ANPTextEncoding = 0, + kUTF16_ANPTextEncoding = 1, +}; +typedef int32_t ANPTextEncoding; + +enum ANPTypefaceStyles { + kBold_ANPTypefaceStyle = 1 << 0, + kItalic_ANPTypefaceStyle = 1 << 1 +}; +typedef uint32_t ANPTypefaceStyle; + +typedef uint32_t ANPFontTableTag; + +struct ANPFontMetrics { + /** The greatest distance above the baseline for any glyph (will be <= 0) */ + float fTop; + /** The recommended distance above the baseline (will be <= 0) */ + float fAscent; + /** The recommended distance below the baseline (will be >= 0) */ + float fDescent; + /** The greatest distance below the baseline for any glyph (will be >= 0) */ + float fBottom; + /** The recommended distance to add between lines of text (will be >= 0) */ + float fLeading; +}; + +struct ANPTypefaceInterfaceV0 : ANPInterface { + /** Return a new reference to the typeface that most closely matches the + requested name and style. Pass null as the name to return + the default font for the requested style. Will never return null + + The 5 generic font names "serif", "sans-serif", "monospace", "cursive", + "fantasy" are recognized, and will be mapped to their logical font + automatically by this call. + + @param name May be NULL. The name of the font family. + @param style The style (normal, bold, italic) of the typeface. + @return reference to the closest-matching typeface. Caller must call + unref() when they are done with the typeface. + */ + ANPTypeface* (*createFromName)(const char name[], ANPTypefaceStyle); + + /** Return a new reference to the typeface that most closely matches the + requested typeface and specified Style. Use this call if you want to + pick a new style from the same family of the existing typeface. + If family is NULL, this selects from the default font's family. + + @param family May be NULL. The name of the existing type face. + @param s The style (normal, bold, italic) of the type face. + @return reference to the closest-matching typeface. Call must call + unref() when they are done. + */ + ANPTypeface* (*createFromTypeface)(const ANPTypeface* family, + ANPTypefaceStyle); + + /** Return the owner count of the typeface. A newly created typeface has an + owner count of 1. When the owner count is reaches 0, the typeface is + deleted. + */ + int32_t (*getRefCount)(const ANPTypeface*); + + /** Increment the owner count on the typeface + */ + void (*ref)(ANPTypeface*); + + /** Decrement the owner count on the typeface. When the count goes to 0, + the typeface is deleted. + */ + void (*unref)(ANPTypeface*); + + /** Return the style bits for the specified typeface + */ + ANPTypefaceStyle (*getStyle)(const ANPTypeface*); + + /** Some fonts are stored in files. If that is true for the fontID, then + this returns the byte length of the full file path. If path is not null, + then the full path is copied into path (allocated by the caller), up to + length bytes. If index is not null, then it is set to the truetype + collection index for this font, or 0 if the font is not in a collection. + + Note: getFontPath does not assume that path is a null-terminated string, + so when it succeeds, it only copies the bytes of the file name and + nothing else (i.e. it copies exactly the number of bytes returned by the + function. If the caller wants to treat path[] as a C string, it must be + sure that it is allocated at least 1 byte larger than the returned size, + and it must copy in the terminating 0. + + If the fontID does not correspond to a file, then the function returns + 0, and the path and index parameters are ignored. + + @param fontID The font whose file name is being queried + @param path Either NULL, or storage for receiving up to length bytes + of the font's file name. Allocated by the caller. + @param length The maximum space allocated in path (by the caller). + Ignored if path is NULL. + @param index Either NULL, or receives the TTC index for this font. + If the font is not a TTC, then will be set to 0. + @return The byte length of th font's file name, or 0 if the font is not + baked by a file. + */ + int32_t (*getFontPath)(const ANPTypeface*, char path[], int32_t length, + int32_t* index); + + /** Return a UTF8 encoded path name for the font directory, or NULL if not + supported. If returned, this string address will be valid for the life + of the plugin instance. It will always end with a '/' character. + */ + const char* (*getFontDirectoryPath)(); +}; + +struct ANPPaintInterfaceV0 : ANPInterface { + /** Return a new paint object, which holds all of the color and style + attributes that affect how things (geometry, text, bitmaps) are drawn + in a ANPCanvas. + + The paint that is returned is not tied to any particular plugin + instance, but it must only be accessed from one thread at a time. + */ + ANPPaint* (*newPaint)(); + void (*deletePaint)(ANPPaint*); + + ANPPaintFlags (*getFlags)(const ANPPaint*); + void (*setFlags)(ANPPaint*, ANPPaintFlags); + + ANPColor (*getColor)(const ANPPaint*); + void (*setColor)(ANPPaint*, ANPColor); + + ANPPaintStyle (*getStyle)(const ANPPaint*); + void (*setStyle)(ANPPaint*, ANPPaintStyle); + + float (*getStrokeWidth)(const ANPPaint*); + float (*getStrokeMiter)(const ANPPaint*); + ANPPaintCap (*getStrokeCap)(const ANPPaint*); + ANPPaintJoin (*getStrokeJoin)(const ANPPaint*); + void (*setStrokeWidth)(ANPPaint*, float); + void (*setStrokeMiter)(ANPPaint*, float); + void (*setStrokeCap)(ANPPaint*, ANPPaintCap); + void (*setStrokeJoin)(ANPPaint*, ANPPaintJoin); + + ANPTextEncoding (*getTextEncoding)(const ANPPaint*); + ANPPaintAlign (*getTextAlign)(const ANPPaint*); + float (*getTextSize)(const ANPPaint*); + float (*getTextScaleX)(const ANPPaint*); + float (*getTextSkewX)(const ANPPaint*); + void (*setTextEncoding)(ANPPaint*, ANPTextEncoding); + void (*setTextAlign)(ANPPaint*, ANPPaintAlign); + void (*setTextSize)(ANPPaint*, float); + void (*setTextScaleX)(ANPPaint*, float); + void (*setTextSkewX)(ANPPaint*, float); + + /** Return the typeface ine paint, or null if there is none. This does not + modify the owner count of the returned typeface. + */ + ANPTypeface* (*getTypeface)(const ANPPaint*); + + /** Set the paint's typeface. If the paint already had a non-null typeface, + its owner count is decremented. If the new typeface is non-null, its + owner count is incremented. + */ + void (*setTypeface)(ANPPaint*, ANPTypeface*); + + /** Return the width of the text. If bounds is not null, return the bounds + of the text in that rectangle. + */ + float (*measureText)(ANPPaint*, const void* text, uint32_t byteLength, + ANPRectF* bounds); + + /** Return the number of unichars specifed by the text. + If widths is not null, returns the array of advance widths for each + unichar. + If bounds is not null, returns the array of bounds for each unichar. + */ + int (*getTextWidths)(ANPPaint*, const void* text, uint32_t byteLength, + float widths[], ANPRectF bounds[]); + + /** Return in metrics the spacing values for text, respecting the paint's + typeface and pointsize, and return the spacing between lines + (descent - ascent + leading). If metrics is NULL, it will be ignored. + */ + float (*getFontMetrics)(ANPPaint*, ANPFontMetrics* metrics); +}; + +struct ANPCanvasInterfaceV0 : ANPInterface { + /** Return a canvas that will draw into the specified bitmap. Note: the + canvas copies the fields of the bitmap, so it need not persist after + this call, but the canvas DOES point to the same pixel memory that the + bitmap did, so the canvas should not be used after that pixel memory + goes out of scope. In the case of creating a canvas to draw into the + pixels provided by kDraw_ANPEventType, those pixels are only while + handling that event. + + The canvas that is returned is not tied to any particular plugin + instance, but it must only be accessed from one thread at a time. + */ + ANPCanvas* (*newCanvas)(const ANPBitmap*); + void (*deleteCanvas)(ANPCanvas*); + + void (*save)(ANPCanvas*); + void (*restore)(ANPCanvas*); + void (*translate)(ANPCanvas*, float tx, float ty); + void (*scale)(ANPCanvas*, float sx, float sy); + void (*rotate)(ANPCanvas*, float degrees); + void (*skew)(ANPCanvas*, float kx, float ky); + void (*concat)(ANPCanvas*, const ANPMatrix*); + void (*clipRect)(ANPCanvas*, const ANPRectF*); + void (*clipPath)(ANPCanvas*, const ANPPath*); + + /** Return the current matrix on the canvas + */ + void (*getTotalMatrix)(ANPCanvas*, ANPMatrix*); + /** Return the current clip bounds in local coordinates, expanding it to + account for antialiasing edge effects if aa is true. If the + current clip is empty, return false and ignore the bounds argument. + */ + bool (*getLocalClipBounds)(ANPCanvas*, ANPRectF* bounds, bool aa); + /** Return the current clip bounds in device coordinates in bounds. If the + current clip is empty, return false and ignore the bounds argument. + */ + bool (*getDeviceClipBounds)(ANPCanvas*, ANPRectI* bounds); + + void (*drawColor)(ANPCanvas*, ANPColor); + void (*drawPaint)(ANPCanvas*, const ANPPaint*); + void (*drawLine)(ANPCanvas*, float x0, float y0, float x1, float y1, + const ANPPaint*); + void (*drawRect)(ANPCanvas*, const ANPRectF*, const ANPPaint*); + void (*drawOval)(ANPCanvas*, const ANPRectF*, const ANPPaint*); + void (*drawPath)(ANPCanvas*, const ANPPath*, const ANPPaint*); + void (*drawText)(ANPCanvas*, const void* text, uint32_t byteLength, + float x, float y, const ANPPaint*); + void (*drawPosText)(ANPCanvas*, const void* text, uint32_t byteLength, + const float xy[], const ANPPaint*); + void (*drawBitmap)(ANPCanvas*, const ANPBitmap*, float x, float y, + const ANPPaint*); + void (*drawBitmapRect)(ANPCanvas*, const ANPBitmap*, + const ANPRectI* src, const ANPRectF* dst, + const ANPPaint*); +}; + +struct ANPWindowInterfaceV0 : ANPInterface { + /** Registers a set of rectangles that the plugin would like to keep on + screen. The rectangles are listed in order of priority with the highest + priority rectangle in location rects[0]. The browser will attempt to keep + as many of the rectangles on screen as possible and will scroll them into + view in response to the invocation of this method and other various events. + The count specifies how many rectangles are in the array. If the count is + zero it signals the browser that any existing rectangles should be cleared + and no rectangles will be tracked. + */ + void (*setVisibleRects)(NPP instance, const ANPRectI rects[], int32_t count); + /** Clears any rectangles that are being tracked as a result of a call to + setVisibleRects. This call is equivalent to setVisibleRect(inst, NULL, 0). + */ + void (*clearVisibleRects)(NPP instance); + /** Given a boolean value of true the device will be requested to provide + a keyboard. A value of false will result in a request to hide the + keyboard. Further, the on-screen keyboard will not be displayed if a + physical keyboard is active. + */ + void (*showKeyboard)(NPP instance, bool value); + /** Called when a plugin wishes to enter into full screen mode. The plugin's + Java class (defined in the plugin's apk manifest) will be called + asynchronously to provide a View object to be displayed full screen. + */ + void (*requestFullScreen)(NPP instance); + /** Called when a plugin wishes to exit from full screen mode. As a result, + the plugin's full screen view will be discarded by the view system. + */ + void (*exitFullScreen)(NPP instance); + /** Called when a plugin wishes to be zoomed and centered in the current view. + */ + void (*requestCenterFitZoom)(NPP instance); +}; + +enum ANPScreenOrientations { + /** No preference specified: let the system decide the best orientation. + */ + kDefault_ANPScreenOrientation = 0, + /** Would like to have the screen in a landscape orientation, but it will + not allow for 180 degree rotations. + */ + kFixedLandscape_ANPScreenOrientation = 1, + /** Would like to have the screen in a portrait orientation, but it will + not allow for 180 degree rotations. + */ + kFixedPortrait_ANPScreenOrientation = 2, + /** Would like to have the screen in landscape orientation, but can use the + sensor to change which direction the screen is facing. + */ + kLandscape_ANPScreenOrientation = 3, + /** Would like to have the screen in portrait orientation, but can use the + sensor to change which direction the screen is facing. + */ + kPortrait_ANPScreenOrientation = 4 +}; + +typedef int32_t ANPScreenOrientation; + +struct ANPWindowInterfaceV2 : ANPWindowInterfaceV0 { + /** Returns a rectangle representing the visible area of the plugin on + screen. The coordinates are relative to the size of the plugin in the + document and therefore will never be negative or exceed the plugin's size. + */ + ANPRectI (*visibleRect)(NPP instance); + + /** Called when the plugin wants to specify a particular screen orientation + when entering into full screen mode. The orientation must be set prior + to entering into full screen. After entering full screen any subsequent + changes will be updated the next time the plugin goes full screen. + */ + void (*requestFullScreenOrientation)(NPP instance, ANPScreenOrientation orientation); +}; + +/////////////////////////////////////////////////////////////////////////////// + +enum ANPSampleFormats { + kUnknown_ANPSamleFormat = 0, + kPCM16Bit_ANPSampleFormat = 1, + kPCM8Bit_ANPSampleFormat = 2 +}; +typedef int32_t ANPSampleFormat; + +/** The audio buffer is passed to the callback proc to request more samples. + It is owned by the system, and the callback may read it, but should not + maintain a pointer to it outside of the scope of the callback proc. + */ +struct ANPAudioBuffer { + // RO - repeat what was specified in newTrack() + int32_t channelCount; + // RO - repeat what was specified in newTrack() + ANPSampleFormat format; + /** This buffer is owned by the caller. Inside the callback proc, up to + "size" bytes of sample data should be written into this buffer. The + address is only valid for the scope of a single invocation of the + callback proc. + */ + void* bufferData; + /** On input, specifies the maximum number of bytes that can be written + to "bufferData". On output, specifies the actual number of bytes that + the callback proc wrote into "bufferData". + */ + uint32_t size; +}; + +enum ANPAudioEvents { + /** This event is passed to the callback proc when the audio-track needs + more sample data written to the provided buffer parameter. + */ + kMoreData_ANPAudioEvent = 0, + /** This event is passed to the callback proc if the audio system runs out + of sample data. In this event, no buffer parameter will be specified + (i.e. NULL will be passed to the 3rd parameter). + */ + kUnderRun_ANPAudioEvent = 1 +}; +typedef int32_t ANPAudioEvent; + +/** Called to feed sample data to the track. This will be called in a separate + thread. However, you may call trackStop() from the callback (but you + cannot delete the track). + + For example, when you have written the last chunk of sample data, you can + immediately call trackStop(). This will take effect after the current + buffer has been played. + + The "user" parameter is the same value that was passed to newTrack() + */ +typedef void (*ANPAudioCallbackProc)(ANPAudioEvent event, void* user, + ANPAudioBuffer* buffer); + +struct ANPAudioTrack; // abstract type for audio tracks + +struct ANPAudioTrackInterfaceV0 : ANPInterface { + /** Create a new audio track, or NULL on failure. The track is initially in + the stopped state and therefore ANPAudioCallbackProc will not be called + until the track is started. + */ + ANPAudioTrack* (*newTrack)(uint32_t sampleRate, // sampling rate in Hz + ANPSampleFormat, + int channelCount, // MONO=1, STEREO=2 + ANPAudioCallbackProc, + void* user); + /** Deletes a track that was created using newTrack. The track can be + deleted in any state and it waits for the ANPAudioCallbackProc thread + to exit before returning. + */ + void (*deleteTrack)(ANPAudioTrack*); + + void (*start)(ANPAudioTrack*); + void (*pause)(ANPAudioTrack*); + void (*stop)(ANPAudioTrack*); + /** Returns true if the track is not playing (e.g. pause or stop was called, + or start was never called. + */ + bool (*isStopped)(ANPAudioTrack*); +}; + +struct ANPAudioTrackInterfaceV1 : ANPAudioTrackInterfaceV0 { + /** Returns the track's latency in milliseconds. */ + uint32_t (*trackLatency)(ANPAudioTrack*); +}; + + +/////////////////////////////////////////////////////////////////////////////// +// DEFINITION OF VALUES PASSED THROUGH NPP_HandleEvent + +enum ANPEventTypes { + kNull_ANPEventType = 0, + kKey_ANPEventType = 1, + /** Mouse events are triggered by either clicking with the navigational pad + or by tapping the touchscreen (if the kDown_ANPTouchAction is handled by + the plugin then no mouse event is generated). The kKey_ANPEventFlag has + to be set to true in order to receive these events. + */ + kMouse_ANPEventType = 2, + /** Touch events are generated when the user touches on the screen. The + kTouch_ANPEventFlag has to be set to true in order to receive these + events. + */ + kTouch_ANPEventType = 3, + /** Only triggered by a plugin using the kBitmap_ANPDrawingModel. This event + signals that the plugin needs to redraw itself into the provided bitmap. + */ + kDraw_ANPEventType = 4, + kLifecycle_ANPEventType = 5, + + /** This event type is completely defined by the plugin. + When creating an event, the caller must always set the first + two fields, the remaining data is optional. + ANPEvent evt; + evt.inSize = sizeof(ANPEvent); + evt.eventType = kCustom_ANPEventType + // other data slots are optional + evt.other[] = ...; + To post a copy of the event, call + eventInterface->postEvent(myNPPInstance, &evt); + That call makes a copy of the event struct, and post that on the event + queue for the plugin. + */ + kCustom_ANPEventType = 6, +}; +typedef int32_t ANPEventType; + +enum ANPKeyActions { + kDown_ANPKeyAction = 0, + kUp_ANPKeyAction = 1, +}; +typedef int32_t ANPKeyAction; + +#include "ANPKeyCodes.h" +typedef int32_t ANPKeyCode; + +enum ANPKeyModifiers { + kAlt_ANPKeyModifier = 1 << 0, + kShift_ANPKeyModifier = 1 << 1, +}; +// bit-field containing some number of ANPKeyModifier bits +typedef uint32_t ANPKeyModifier; + +enum ANPMouseActions { + kDown_ANPMouseAction = 0, + kUp_ANPMouseAction = 1, +}; +typedef int32_t ANPMouseAction; + +enum ANPTouchActions { + /** This occurs when the user first touches on the screen. As such, this + action will always occur prior to any of the other touch actions. If + the plugin chooses to not handle this action then no other events + related to that particular touch gesture will be generated. + */ + kDown_ANPTouchAction = 0, + kUp_ANPTouchAction = 1, + kMove_ANPTouchAction = 2, + kCancel_ANPTouchAction = 3, + // The web view will ignore the return value from the following actions + kLongPress_ANPTouchAction = 4, + kDoubleTap_ANPTouchAction = 5, +}; +typedef int32_t ANPTouchAction; + +enum ANPLifecycleActions { + /** The web view containing this plugin has been paused. See documentation + on the android activity lifecycle for more information. + */ + kPause_ANPLifecycleAction = 0, + /** The web view containing this plugin has been resumed. See documentation + on the android activity lifecycle for more information. + */ + kResume_ANPLifecycleAction = 1, + /** The plugin has focus and is now the recipient of input events (e.g. key, + touch, etc.) + */ + kGainFocus_ANPLifecycleAction = 2, + /** The plugin has lost focus and will not receive any input events until it + regains focus. This event is always preceded by a GainFocus action. + */ + kLoseFocus_ANPLifecycleAction = 3, + /** The browser is running low on available memory and is requesting that + the plugin free any unused/inactive resources to prevent a performance + degradation. + */ + kFreeMemory_ANPLifecycleAction = 4, + /** The page has finished loading. This happens when the page's top level + frame reports that it has completed loading. + */ + kOnLoad_ANPLifecycleAction = 5, + /** The browser is honoring the plugin's request to go full screen. Upon + returning from this event the browser will resize the plugin's java + surface to full-screen coordinates. + */ + kEnterFullScreen_ANPLifecycleAction = 6, + /** The browser has exited from full screen mode. Immediately prior to + sending this event the browser has resized the plugin's java surface to + its original coordinates. + */ + kExitFullScreen_ANPLifecycleAction = 7, + /** The plugin is visible to the user on the screen. This event will always + occur after a kOffScreen_ANPLifecycleAction event. + */ + kOnScreen_ANPLifecycleAction = 8, + /** The plugin is no longer visible to the user on the screen. This event + will always occur prior to an kOnScreen_ANPLifecycleAction event. + */ + kOffScreen_ANPLifecycleAction = 9, +}; +typedef uint32_t ANPLifecycleAction; + +/* This is what is passed to NPP_HandleEvent() */ +struct ANPEvent { + uint32_t inSize; // size of this struct in bytes + ANPEventType eventType; + // use based on the value in eventType + union { + struct { + ANPKeyAction action; + ANPKeyCode nativeCode; + int32_t virtualCode; // windows virtual key code + ANPKeyModifier modifiers; + int32_t repeatCount; // 0 for initial down (or up) + int32_t unichar; // 0 if there is no value + } key; + struct { + ANPMouseAction action; + int32_t x; // relative to your "window" (0...width) + int32_t y; // relative to your "window" (0...height) + } mouse; + struct { + ANPTouchAction action; + ANPKeyModifier modifiers; + int32_t x; // relative to your "window" (0...width) + int32_t y; // relative to your "window" (0...height) + } touch; + struct { + ANPLifecycleAction action; + } lifecycle; + struct { + ANPDrawingModel model; + // relative to (0,0) in top-left of your plugin + ANPRectI clip; + // use based on the value in model + union { + ANPBitmap bitmap; + struct { + int32_t width; + int32_t height; + } surfaceSize; + } data; + } draw; + } data; +}; + + +struct ANPEventInterfaceV0 : ANPInterface { + /** Post a copy of the specified event to the plugin. The event will be + delivered to the plugin in its main thread (the thread that receives + other ANPEvents). If, after posting before delivery, the NPP instance + is torn down, the event will be discarded. + */ + void (*postEvent)(NPP inst, const ANPEvent* event); +}; + +struct ANPSurfaceInterfaceV0 : ANPInterface { + /** Locks the surface from manipulation by other threads and provides a bitmap + to be written to. The dirtyRect param specifies which portion of the + bitmap will be written to. If the dirtyRect is NULL then the entire + surface will be considered dirty. If the lock was successful the function + will return true and the bitmap will be set to point to a valid bitmap. + If not the function will return false and the bitmap will be set to NULL. + */ + bool (*lock)(JNIEnv* env, jobject surface, ANPBitmap* bitmap, ANPRectI* dirtyRect); + /** Given a locked surface handle (i.e. result of a successful call to lock) + the surface is unlocked and the contents of the bitmap, specifically + those inside the dirtyRect are written to the screen. + */ + void (*unlock)(JNIEnv* env, jobject surface); +}; + +/** + * TODO should we not use EGL and GL data types for ABI safety? + */ +struct ANPTextureInfo { + GLuint textureId; + uint32_t width; + uint32_t height; + GLenum internalFormat; +}; + +typedef void* ANPEGLContext; + +struct ANPOpenGLInterfaceV0 : ANPInterface { + ANPEGLContext (*acquireContext)(NPP instance); + + ANPTextureInfo (*lockTexture)(NPP instance); + + void (*releaseTexture)(NPP instance, const ANPTextureInfo*); + + /** + * Invert the contents of the plugin on the y-axis. + * default is to not be inverted (i.e. use OpenGL coordinates) + */ + void (*invertPluginContent)(NPP instance, bool isContentInverted); +}; + +enum ANPPowerStates { + kDefault_ANPPowerState = 0, + kScreenOn_ANPPowerState = 1 +}; +typedef int32_t ANPPowerState; + +struct ANPSystemInterfaceV1 : ANPInterface { + /** Return the path name for the current Application's plugin data directory, + or NULL if not supported + */ + const char* (*getApplicationDataDirectory)(); + + /** A helper function to load java classes from the plugin's apk. The + function looks for a class given the fully qualified and null terminated + string representing the className. For example, + + const char* className = "com.android.mypackage.MyClass"; + + If the class cannot be found or there is a problem loading the class + NULL will be returned. + */ + jclass (*loadJavaClass)(NPP instance, const char* className); + + void (*setPowerState)(NPP instance, ANPPowerState powerState); +}; + +struct ANPSystemInterfaceV2 : ANPInterface { + /** Return the path name for the current Application's plugin data directory, + or NULL if not supported. This directory will change depending on whether + or not the plugin is found within an incognito tab. + */ + const char* (*getApplicationDataDirectory)(NPP instance); + + // redeclaration of existing features + jclass (*loadJavaClass)(NPP instance, const char* className); + void (*setPowerState)(NPP instance, ANPPowerState powerState); +}; + +typedef void* ANPNativeWindow; + +/** Called to notify the plugin that a video frame has been composited by the +* browser for display. This will be called in a separate thread and as such +* you cannot call releaseNativeWindow from the callback. +* +* The timestamp is in nanoseconds, and is monotonically increasing. +*/ +typedef void (*ANPVideoFrameCallbackProc)(ANPNativeWindow* window, int64_t timestamp); + +struct ANPVideoInterfaceV1 : ANPInterface { + + /** + * Constructs a new native window to be used for rendering video content. + * + * Subsequent calls will produce new windows, but may also return NULL after + * n attempts if the browser has reached it's limit. Further, if the browser + * is unable to acquire the window quickly it may also return NULL in order + * to not prevent the plugin from executing. A subsequent call will then + * return the window if it is avaiable. + * + * NOTE: The hardware may fail if you try to decode more than the allowable + * number of videos supported on that device. + */ + ANPNativeWindow (*acquireNativeWindow)(NPP instance); + + /** + * Sets the rectangle that specifies where the video content is to be drawn. + * The dimensions are in document space. Further, if the rect is NULL the + * browser will not attempt to draw the window, therefore do not set the + * dimensions until you queue the first buffer in the window. + */ + void (*setWindowDimensions)(NPP instance, const ANPNativeWindow window, const ANPRectF* dimensions); + + /** + */ + void (*releaseNativeWindow)(NPP instance, ANPNativeWindow window); + + /** Set a callback to be notified when an ANPNativeWindow is composited by + * the browser. + */ + void (*setFramerateCallback)(NPP instance, const ANPNativeWindow window, ANPVideoFrameCallbackProc); +}; + +struct ANPNativeWindowInterfaceV0 : ANPInterface { + /** + * Constructs a new native window to be used for rendering plugin content. + * + * Subsequent calls will return the original constructed window. Further, if + * the browser is unable to acquire the window quickly it may return NULL in + * order to not block the plugin indefinitely. A subsequent call will then + * return the window if it is available. + */ + ANPNativeWindow (*acquireNativeWindow)(NPP instance); + + /** + * Invert the contents of the plugin on the y-axis. + * default is to not be inverted (e.g. use OpenGL coordinates) + */ + void (*invertPluginContent)(NPP instance, bool isContentInverted); +}; + + +#endif diff --git a/dom/plugins/base/android/moz.build b/dom/plugins/base/android/moz.build new file mode 100644 index 000000000..d95d20d5b --- /dev/null +++ b/dom/plugins/base/android/moz.build @@ -0,0 +1,35 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + 'android_npapi.h', + 'ANPKeyCodes.h', +] + +SOURCES += [ + 'ANPAudio.cpp', + 'ANPEvent.cpp', + 'ANPLog.cpp', + 'ANPNativeWindow.cpp', + 'ANPSurface.cpp', + 'ANPSystem.cpp', + 'ANPVideo.cpp', + 'ANPWindow.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/dom/plugins/base', + '/gfx/gl', + '/widget', + '/widget/android', +] + +DEFINES['MOZ_APP_NAME'] = '"%s"' % CONFIG['MOZ_APP_NAME'] + +CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] diff --git a/dom/plugins/base/moz.build b/dom/plugins/base/moz.build new file mode 100644 index 000000000..f43f75f79 --- /dev/null +++ b/dom/plugins/base/moz.build @@ -0,0 +1,117 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': + DIRS += ['android'] + +XPIDL_SOURCES += [ + 'nsIHTTPHeaderListener.idl', + 'nsIPluginDocument.idl', + 'nsIPluginHost.idl', + 'nsIPluginInputStream.idl', + 'nsIPluginInstanceOwner.idl', + 'nsIPluginTag.idl', + 'nspluginroot.idl', +] + +XPIDL_MODULE = 'plugin' + +EXPORTS += [ + 'npapi.h', + 'npfunctions.h', + 'npruntime.h', + 'nptypes.h', + 'nsJSNPRuntime.h', + 'nsNPAPIPluginInstance.h', + 'nsPluginDirServiceProvider.h', + 'nsPluginHost.h', + 'nsPluginInstanceOwner.h', + 'nsPluginLogging.h', + 'nsPluginNativeWindow.h', + 'nsPluginNativeWindowGtk.h', + 'nsPluginsCID.h', + 'nsPluginsDir.h', + 'nsPluginTags.h', +] + +EXPORTS.mozilla += [ + 'PluginPRLibrary.h', +] + +UNIFIED_SOURCES += [ + 'nsJSNPRuntime.cpp', + 'nsNPAPIPluginInstance.cpp', + 'nsNPAPIPluginStreamListener.cpp', + 'nsPluginInstanceOwner.cpp', + 'nsPluginModule.cpp', + 'nsPluginStreamListenerPeer.cpp', + 'nsPluginTags.cpp', + 'PluginPRLibrary.cpp', +] + +SOURCES += [ + 'nsNPAPIPlugin.cpp', # Conflict with X11 headers + 'nsPluginHost.cpp', # Conflict with NS_NPAPIPLUGIN_CALLBACK +] + +if CONFIG['OS_ARCH'] == 'WINNT': + UNIFIED_SOURCES += [ + 'nsPluginDirServiceProvider.cpp', + 'nsPluginNativeWindowWin.cpp', + 'nsPluginsDirWin.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + UNIFIED_SOURCES += [ + 'nsPluginNativeWindow.cpp', + 'nsPluginsDirDarwin.cpp', + ] +else: + UNIFIED_SOURCES += [ + 'nsPluginsDirUnix.cpp', + ] + if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + # This file cannot be built in unified mode because of name clashes in X11 headers. + SOURCES += [ + 'nsPluginNativeWindowGtk.cpp', + ] + else: + UNIFIED_SOURCES += [ + 'nsPluginNativeWindow.cpp', + ] + +LOCAL_INCLUDES += [ + '/dom/base', + '/dom/plugins/ipc', + '/layout/generic', + '/layout/xul', + '/netwerk/base', + '/widget', + '/widget/android', + '/widget/cocoa', + '/xpcom/base', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': + LOCAL_INCLUDES += [ + '/dom/plugins/base/android', + ] + +if CONFIG['OS_ARCH'] == 'WINNT': + LOCAL_INCLUDES += [ + '/xpcom/base', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +DEFINES['SK_BUILD_FOR_ANDROID_NDK'] = True + +FINAL_LIBRARY = 'xul' + +CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] +CXXFLAGS += CONFIG['TK_CFLAGS'] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/plugins/base/npapi.h b/dom/plugins/base/npapi.h new file mode 100644 index 000000000..12ac635c7 --- /dev/null +++ b/dom/plugins/base/npapi.h @@ -0,0 +1,921 @@ +/* -*- Mode: C; tab-width: 4; 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/. */ + +#ifndef npapi_h_ +#define npapi_h_ + +#include "nptypes.h" + +#if defined(_WIN32) && !defined(__SYMBIAN32__) +#include <windef.h> +#ifndef XP_WIN +#define XP_WIN 1 +#endif +#endif + +#if defined(__SYMBIAN32__) +#ifndef XP_SYMBIAN +#define XP_SYMBIAN 1 +#undef XP_WIN +#endif +#endif + +#if defined(__APPLE_CC__) && !defined(XP_UNIX) +#ifndef XP_MACOSX +#define XP_MACOSX 1 +#endif +#endif + +#if defined(XP_MACOSX) && defined(__LP64__) +#define NP_NO_QUICKDRAW +#define NP_NO_CARBON +#endif + +#if defined(XP_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#include <OpenGL/OpenGL.h> +#ifndef NP_NO_CARBON +#include <Carbon/Carbon.h> +#endif +#endif + +#if defined(XP_UNIX) +#include <stdio.h> +#if defined(MOZ_X11) +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "X11UndefineNone.h" +#endif +#endif + +#if defined(XP_SYMBIAN) +#include <QEvent> +#include <QRegion> +#endif + +/*----------------------------------------------------------------------*/ +/* Plugin Version Constants */ +/*----------------------------------------------------------------------*/ + +#define NP_VERSION_MAJOR 0 +#define NP_VERSION_MINOR 29 + + +/* The OS/2 version of Netscape uses RC_DATA to define the + mime types, file extensions, etc that are required. + Use a vertical bar to separate types, end types with \0. + FileVersion and ProductVersion are 32bit ints, all other + entries are strings that MUST be terminated with a \0. + +AN EXAMPLE: + +RCDATA NP_INFO_ProductVersion { 1,0,0,1,} + +RCDATA NP_INFO_MIMEType { "video/x-video|", + "video/x-flick\0" } +RCDATA NP_INFO_FileExtents { "avi|", + "flc\0" } +RCDATA NP_INFO_FileOpenName{ "MMOS2 video player(*.avi)|", + "MMOS2 Flc/Fli player(*.flc)\0" } + +RCDATA NP_INFO_FileVersion { 1,0,0,1 } +RCDATA NP_INFO_CompanyName { "Netscape Communications\0" } +RCDATA NP_INFO_FileDescription { "NPAVI32 Extension DLL\0" +RCDATA NP_INFO_InternalName { "NPAVI32\0" ) +RCDATA NP_INFO_LegalCopyright { "Copyright Netscape Communications \251 1996\0" +RCDATA NP_INFO_OriginalFilename { "NVAPI32.DLL" } +RCDATA NP_INFO_ProductName { "NPAVI32 Dynamic Link Library\0" } +*/ +/* RC_DATA types for version info - required */ +#define NP_INFO_ProductVersion 1 +#define NP_INFO_MIMEType 2 +#define NP_INFO_FileOpenName 3 +#define NP_INFO_FileExtents 4 +/* RC_DATA types for version info - used if found */ +#define NP_INFO_FileDescription 5 +#define NP_INFO_ProductName 6 +/* RC_DATA types for version info - optional */ +#define NP_INFO_CompanyName 7 +#define NP_INFO_FileVersion 8 +#define NP_INFO_InternalName 9 +#define NP_INFO_LegalCopyright 10 +#define NP_INFO_OriginalFilename 11 + +#ifndef RC_INVOKED + +/*----------------------------------------------------------------------*/ +/* Definition of Basic Types */ +/*----------------------------------------------------------------------*/ + +typedef unsigned char NPBool; +typedef int16_t NPError; +typedef int16_t NPReason; +typedef char* NPMIMEType; + +/*----------------------------------------------------------------------*/ +/* Structures and definitions */ +/*----------------------------------------------------------------------*/ + +#if !defined(__LP64__) +#if defined(XP_MACOSX) +#pragma options align=mac68k +#endif +#endif /* __LP64__ */ + +/* + * NPP is a plug-in's opaque instance handle + */ +typedef struct _NPP +{ + void* pdata; /* plug-in private data */ + void* ndata; /* netscape private data */ +} NPP_t; + +typedef NPP_t* NPP; + +typedef struct _NPStream +{ + void* pdata; /* plug-in private data */ + void* ndata; /* netscape private data */ + const char* url; + uint32_t end; + uint32_t lastmodified; + void* notifyData; + const char* headers; /* Response headers from host. + * Exists only for >= NPVERS_HAS_RESPONSE_HEADERS. + * Used for HTTP only; nullptr for non-HTTP. + * Available from NPP_NewStream onwards. + * Plugin should copy this data before storing it. + * Includes HTTP status line and all headers, + * preferably verbatim as received from server, + * headers formatted as in HTTP ("Header: Value"), + * and newlines (\n, NOT \r\n) separating lines. + * Terminated by \n\0 (NOT \n\n\0). */ +} NPStream; + +typedef struct _NPByteRange +{ + int32_t offset; /* negative offset means from the end */ + uint32_t length; + struct _NPByteRange* next; +} NPByteRange; + +typedef struct _NPSavedData +{ + int32_t len; + void* buf; +} NPSavedData; + +typedef struct _NPRect +{ + uint16_t top; + uint16_t left; + uint16_t bottom; + uint16_t right; +} NPRect; + +typedef struct _NPSize +{ + int32_t width; + int32_t height; +} NPSize; + +typedef enum { + NPFocusNext = 0, + NPFocusPrevious = 1 +} NPFocusDirection; + +/* These formats describe the format in the memory byte-order. This means if + * a 32-bit value of a pixel is viewed on a little-endian system the layout will + * be 0xAARRGGBB. The Alpha channel will be stored in the most significant + * bits. */ +typedef enum { + /* 32-bit per pixel 8-bit per channel - premultiplied alpha */ + NPImageFormatBGRA32 = 0x1, + /* 32-bit per pixel 8-bit per channel - 1 unused channel */ + NPImageFormatBGRX32 = 0x2 +} NPImageFormat; + +typedef struct _NPAsyncSurface +{ + uint32_t version; + NPSize size; + NPImageFormat format; + union { + struct { + uint32_t stride; + void *data; + } bitmap; +#if defined(XP_WIN) + HANDLE sharedHandle; +#endif + }; +} NPAsyncSurface; + +/* Return values for NPP_HandleEvent */ +#define kNPEventNotHandled 0 +#define kNPEventHandled 1 +/* Exact meaning must be spec'd in event model. */ +#define kNPEventStartIME 2 + +#if defined(XP_UNIX) +/* + * Unix specific structures and definitions + */ + +/* + * Callback Structures. + * + * These are used to pass additional platform specific information. + */ +enum { + NP_SETWINDOW = 1, + NP_PRINT +}; + +typedef struct +{ + int32_t type; +} NPAnyCallbackStruct; + +typedef struct +{ + int32_t type; +#if defined(MOZ_X11) + Display* display; + Visual* visual; + Colormap colormap; + unsigned int depth; +#endif +} NPSetWindowCallbackStruct; + +typedef struct +{ + int32_t type; + FILE* fp; +} NPPrintCallbackStruct; + +#endif /* XP_UNIX */ + +#if defined(XP_WIN) +/* + * Windows specific structures and definitions + */ + +/* + * Information about the default audio device. These values share meaning with + * the parameters to the Windows API IMMNotificationClient object. + * This is the value of the NPNVaudioDeviceChangeDetails variable. + */ +typedef struct _NPAudioDeviceChangeDetails
+{
+ int32_t flow;
+ int32_t role;
+ const wchar_t* defaultDevice; // this pointer is only valid during the call
+ // to NPPSetValue.
+} NPAudioDeviceChangeDetails; + +#endif /* XP_WIN */ + +typedef enum { + NPDrawingModelDUMMY +#if defined(XP_MACOSX) +#ifndef NP_NO_QUICKDRAW + , NPDrawingModelQuickDraw = 0 +#endif + , NPDrawingModelCoreGraphics = 1 + , NPDrawingModelOpenGL = 2 + , NPDrawingModelCoreAnimation = 3 + , NPDrawingModelInvalidatingCoreAnimation = 4 +#endif +#if defined(XP_WIN) + , NPDrawingModelSyncWin = 5 +#endif +#if defined(MOZ_X11) + , NPDrawingModelSyncX = 6 +#endif + , NPDrawingModelAsyncBitmapSurface = 7 +#if defined(XP_WIN) + , NPDrawingModelAsyncWindowsDXGISurface = 8 +#endif +} NPDrawingModel; + +#ifdef XP_MACOSX +typedef enum { +#ifndef NP_NO_CARBON + NPEventModelCarbon = 0, +#endif + NPEventModelCocoa = 1 +} NPEventModel; +#endif + +/* + * The following masks are applied on certain platforms to NPNV and + * NPPV selectors that pass around pointers to COM interfaces. Newer + * compilers on some platforms may generate vtables that are not + * compatible with older compilers. To prevent older plugins from + * not understanding a new browser's ABI, these masks change the + * values of those selectors on those platforms. To remain backwards + * compatible with different versions of the browser, plugins can + * use these masks to dynamically determine and use the correct C++ + * ABI that the browser is expecting. This does not apply to Windows + * as Microsoft's COM ABI will likely not change. + */ + +#define NP_ABI_GCC3_MASK 0x10000000 +/* + * gcc 3.x generated vtables on UNIX and OSX are incompatible with + * previous compilers. + */ +#if (defined(XP_UNIX) && defined(__GNUC__) && (__GNUC__ >= 3)) +#define _NP_ABI_MIXIN_FOR_GCC3 NP_ABI_GCC3_MASK +#else +#define _NP_ABI_MIXIN_FOR_GCC3 0 +#endif + +#if defined(XP_MACOSX) +#define NP_ABI_MACHO_MASK 0x01000000 +#define _NP_ABI_MIXIN_FOR_MACHO NP_ABI_MACHO_MASK +#else +#define _NP_ABI_MIXIN_FOR_MACHO 0 +#endif + +#define NP_ABI_MASK (_NP_ABI_MIXIN_FOR_GCC3 | _NP_ABI_MIXIN_FOR_MACHO) + +/* + * List of variable names for which NPP_GetValue shall be implemented + */ +typedef enum { + NPPVpluginNameString = 1, + NPPVpluginDescriptionString, + NPPVpluginWindowBool, + NPPVpluginTransparentBool, + NPPVjavaClass, + NPPVpluginWindowSize, + NPPVpluginTimerInterval, + NPPVpluginScriptableInstance = (10 | NP_ABI_MASK), + NPPVpluginScriptableIID = 11, + NPPVjavascriptPushCallerBool = 12, + NPPVpluginKeepLibraryInMemory = 13, + NPPVpluginNeedsXEmbed = 14, + + /* Get the NPObject for scripting the plugin. Introduced in NPAPI minor version 14. + */ + NPPVpluginScriptableNPObject = 15, + + /* Get the plugin value (as \0-terminated UTF-8 string data) for + * form submission if the plugin is part of a form. Use + * NPN_MemAlloc() to allocate memory for the string data. Introduced + * in NPAPI minor version 15. + */ + NPPVformValue = 16, + + NPPVpluginUrlRequestsDisplayedBool = 17, + + /* Checks if the plugin is interested in receiving the http body of + * all http requests (including failed ones, http status != 200). + */ + NPPVpluginWantsAllNetworkStreams = 18, + + /* Browsers can retrieve a native ATK accessibility plug ID via this variable. */ + NPPVpluginNativeAccessibleAtkPlugId = 19, + + /* Checks to see if the plug-in would like the browser to load the "src" attribute. */ + NPPVpluginCancelSrcStream = 20, + + NPPVsupportsAdvancedKeyHandling = 21, + + NPPVpluginUsesDOMForCursorBool = 22, + + /* Used for negotiating drawing models */ + NPPVpluginDrawingModel = 1000 +#if defined(XP_MACOSX) + /* Used for negotiating event models */ + , NPPVpluginEventModel = 1001 + /* In the NPDrawingModelCoreAnimation drawing model, the browser asks the plug-in for a Core Animation layer. */ + , NPPVpluginCoreAnimationLayer = 1003 +#endif + /* Notification that the plugin just started or stopped playing audio */ + , NPPVpluginIsPlayingAudio = 4000 +#if defined(XP_WIN) + /* Notification that the plugin requests notification when the default audio device has changed */ + , NPPVpluginRequiresAudioDeviceChanges = 4001 +#endif + +} NPPVariable; + +/* + * List of variable names for which NPN_GetValue should be implemented. + */ +typedef enum { + NPNVxDisplay = 1, + NPNVxtAppContext, + NPNVnetscapeWindow, + NPNVjavascriptEnabledBool, + NPNVasdEnabledBool, + NPNVisOfflineBool, + + NPNVserviceManager = (10 | NP_ABI_MASK), + NPNVDOMElement = (11 | NP_ABI_MASK), + NPNVDOMWindow = (12 | NP_ABI_MASK), + NPNVToolkit = (13 | NP_ABI_MASK), + NPNVSupportsXEmbedBool = 14, + + /* Get the NPObject wrapper for the browser window. */ + NPNVWindowNPObject = 15, + + /* Get the NPObject wrapper for the plugins DOM element. */ + NPNVPluginElementNPObject = 16, + + NPNVSupportsWindowless = 17, + + NPNVprivateModeBool = 18, + + NPNVsupportsAdvancedKeyHandling = 21, + + NPNVdocumentOrigin = 22, + + NPNVCSSZoomFactor = 23, + + NPNVpluginDrawingModel = 1000 /* Get the current drawing model (NPDrawingModel) */ +#if defined(XP_MACOSX) || defined(XP_WIN) + , NPNVcontentsScaleFactor = 1001 +#endif +#if defined(XP_MACOSX) +#ifndef NP_NO_QUICKDRAW + , NPNVsupportsQuickDrawBool = 2000 +#endif + , NPNVsupportsCoreGraphicsBool = 2001 + , NPNVsupportsOpenGLBool = 2002 + , NPNVsupportsCoreAnimationBool = 2003 + , NPNVsupportsInvalidatingCoreAnimationBool = 2004 +#endif + , NPNVsupportsAsyncBitmapSurfaceBool = 2007 +#if defined(XP_WIN) + , NPNVsupportsAsyncWindowsDXGISurfaceBool = 2008 + , NPNVpreferredDXGIAdapter = 2009 +#endif +#if defined(XP_MACOSX) +#ifndef NP_NO_CARBON + , NPNVsupportsCarbonBool = 3000 /* TRUE if the browser supports the Carbon event model */ +#endif + , NPNVsupportsCocoaBool = 3001 /* TRUE if the browser supports the Cocoa event model */ + , NPNVsupportsUpdatedCocoaTextInputBool = 3002 /* TRUE if the browser supports the updated + Cocoa text input specification. */ +#endif + , NPNVmuteAudioBool = 4000 /* Request that the browser wants to mute or unmute the plugin */ +#if defined(XP_WIN) + , NPNVaudioDeviceChangeDetails = 4001 /* Provides information about the new default audio device */ +#endif +#if defined(XP_MACOSX) + , NPNVsupportsCompositingCoreAnimationPluginsBool = 74656 /* TRUE if the browser supports + CA model compositing */ +#endif +} NPNVariable; + +typedef enum { + NPNURLVCookie = 501, + NPNURLVProxy +} NPNURLVariable; + +/* + * The type of Toolkit the widgets use + */ +typedef enum { + NPNVGtk12 = 1, + NPNVGtk2 +} NPNToolkitType; + +/* + * The type of a NPWindow - it specifies the type of the data structure + * returned in the window field. + */ +typedef enum { + NPWindowTypeWindow = 1, + NPWindowTypeDrawable +} NPWindowType; + +typedef struct _NPWindow +{ + void* window; /* Platform specific window handle */ + /* OS/2: x - Position of bottom left corner */ + /* OS/2: y - relative to visible netscape window */ + int32_t x; /* Position of top left corner relative */ + int32_t y; /* to a netscape page. */ + uint32_t width; /* Maximum window size */ + uint32_t height; + NPRect clipRect; /* Clipping rectangle in port coordinates */ +#if (defined(XP_UNIX) || defined(XP_SYMBIAN)) && !defined(XP_MACOSX) + void * ws_info; /* Platform-dependent additional data */ +#endif /* XP_UNIX */ + NPWindowType type; /* Is this a window or a drawable? */ +} NPWindow; + +typedef struct _NPImageExpose +{ + char* data; /* image pointer */ + int32_t stride; /* Stride of data image pointer */ + int32_t depth; /* Depth of image pointer */ + int32_t x; /* Expose x */ + int32_t y; /* Expose y */ + uint32_t width; /* Expose width */ + uint32_t height; /* Expose height */ + NPSize dataSize; /* Data buffer size */ + float translateX; /* translate X matrix value */ + float translateY; /* translate Y matrix value */ + float scaleX; /* scale X matrix value */ + float scaleY; /* scale Y matrix value */ +} NPImageExpose; + +typedef struct _NPFullPrint +{ + NPBool pluginPrinted;/* Set TRUE if plugin handled fullscreen printing */ + NPBool printOne; /* TRUE if plugin should print one copy to default + printer */ + void* platformPrint; /* Platform-specific printing info */ +} NPFullPrint; + +typedef struct _NPEmbedPrint +{ + NPWindow window; + void* platformPrint; /* Platform-specific printing info */ +} NPEmbedPrint; + +typedef struct _NPPrint +{ + uint16_t mode; /* NP_FULL or NP_EMBED */ + union + { + NPFullPrint fullPrint; /* if mode is NP_FULL */ + NPEmbedPrint embedPrint; /* if mode is NP_EMBED */ + } print; +} NPPrint; + +#if defined(XP_MACOSX) +#ifndef NP_NO_CARBON +typedef EventRecord NPEvent; +#endif +#elif defined(XP_SYMBIAN) +typedef QEvent NPEvent; +#elif defined(XP_WIN) +typedef struct _NPEvent +{ + uint16_t event; + uintptr_t wParam; + uintptr_t lParam; +} NPEvent; +#elif defined(XP_UNIX) && defined(MOZ_X11) +typedef XEvent NPEvent; +#else +typedef void* NPEvent; +#endif + +#if defined(XP_MACOSX) +typedef void* NPRegion; +#ifndef NP_NO_QUICKDRAW +typedef RgnHandle NPQDRegion; +#endif +typedef CGPathRef NPCGRegion; +#elif defined(XP_WIN) +typedef HRGN NPRegion; +#elif defined(XP_UNIX) && defined(MOZ_X11) +typedef Region NPRegion; +#elif defined(XP_SYMBIAN) +typedef QRegion* NPRegion; +#else +typedef void *NPRegion; +#endif + +typedef struct _NPNSString NPNSString; +typedef struct _NPNSWindow NPNSWindow; +typedef struct _NPNSMenu NPNSMenu; + +#if defined(XP_MACOSX) +typedef NPNSMenu NPMenu; +#else +typedef void *NPMenu; +#endif + +typedef enum { + NPCoordinateSpacePlugin = 1, + NPCoordinateSpaceWindow, + NPCoordinateSpaceFlippedWindow, + NPCoordinateSpaceScreen, + NPCoordinateSpaceFlippedScreen +} NPCoordinateSpace; + +#if defined(XP_MACOSX) + +#ifndef NP_NO_QUICKDRAW +typedef struct NP_Port +{ + CGrafPtr port; + int32_t portx; /* position inside the topmost window */ + int32_t porty; +} NP_Port; +#endif /* NP_NO_QUICKDRAW */ + +/* + * NP_CGContext is the type of the NPWindow's 'window' when the plugin specifies NPDrawingModelCoreGraphics + * as its drawing model. + */ + +typedef struct NP_CGContext +{ + CGContextRef context; + void *window; /* A WindowRef under the Carbon event model. */ +} NP_CGContext; + +/* + * NP_GLContext is the type of the NPWindow's 'window' when the plugin specifies NPDrawingModelOpenGL as its + * drawing model. + */ + +typedef struct NP_GLContext +{ + CGLContextObj context; +#ifdef NP_NO_CARBON + NPNSWindow *window; +#else + void *window; /* Can be either an NSWindow or a WindowRef depending on the event model */ +#endif +} NP_GLContext; + +typedef enum { + NPCocoaEventDrawRect = 1, + NPCocoaEventMouseDown, + NPCocoaEventMouseUp, + NPCocoaEventMouseMoved, + NPCocoaEventMouseEntered, + NPCocoaEventMouseExited, + NPCocoaEventMouseDragged, + NPCocoaEventKeyDown, + NPCocoaEventKeyUp, + NPCocoaEventFlagsChanged, + NPCocoaEventFocusChanged, + NPCocoaEventWindowFocusChanged, + NPCocoaEventScrollWheel, + NPCocoaEventTextInput +} NPCocoaEventType; + +typedef struct _NPCocoaEvent { + NPCocoaEventType type; + uint32_t version; + union { + struct { + uint32_t modifierFlags; + double pluginX; + double pluginY; + int32_t buttonNumber; + int32_t clickCount; + double deltaX; + double deltaY; + double deltaZ; + } mouse; + struct { + uint32_t modifierFlags; + NPNSString *characters; + NPNSString *charactersIgnoringModifiers; + NPBool isARepeat; + uint16_t keyCode; + } key; + struct { + CGContextRef context; + double x; + double y; + double width; + double height; + } draw; + struct { + NPBool hasFocus; + } focus; + struct { + NPNSString *text; + } text; + } data; +} NPCocoaEvent; + +#ifndef NP_NO_CARBON +/* Non-standard event types that can be passed to HandleEvent */ +enum NPEventType { + NPEventType_GetFocusEvent = (osEvt + 16), + NPEventType_LoseFocusEvent, + NPEventType_AdjustCursorEvent, + NPEventType_MenuCommandEvent, + NPEventType_ClippingChangedEvent, + NPEventType_ScrollingBeginsEvent = 1000, + NPEventType_ScrollingEndsEvent +}; +#endif /* NP_NO_CARBON */ + +#endif /* XP_MACOSX */ + +/* + * Values for mode passed to NPP_New: + */ +#define NP_EMBED 1 +#define NP_FULL 2 + +/* + * Values for stream type passed to NPP_NewStream: + */ +#define NP_NORMAL 1 +#define NP_SEEK 2 +#define NP_ASFILE 3 +#define NP_ASFILEONLY 4 + +#define NP_MAXREADY (((unsigned)(~0)<<1)>>1) + +/* + * Flags for NPP_ClearSiteData. + */ +#define NP_CLEAR_ALL 0 +#define NP_CLEAR_CACHE (1 << 0) + +#if !defined(__LP64__) +#if defined(XP_MACOSX) +#pragma options align=reset +#endif +#endif /* __LP64__ */ + +/*----------------------------------------------------------------------*/ +/* Error and Reason Code definitions */ +/*----------------------------------------------------------------------*/ + +/* + * Values of type NPError: + */ +#define NPERR_BASE 0 +#define NPERR_NO_ERROR (NPERR_BASE + 0) +#define NPERR_GENERIC_ERROR (NPERR_BASE + 1) +#define NPERR_INVALID_INSTANCE_ERROR (NPERR_BASE + 2) +#define NPERR_INVALID_FUNCTABLE_ERROR (NPERR_BASE + 3) +#define NPERR_MODULE_LOAD_FAILED_ERROR (NPERR_BASE + 4) +#define NPERR_OUT_OF_MEMORY_ERROR (NPERR_BASE + 5) +#define NPERR_INVALID_PLUGIN_ERROR (NPERR_BASE + 6) +#define NPERR_INVALID_PLUGIN_DIR_ERROR (NPERR_BASE + 7) +#define NPERR_INCOMPATIBLE_VERSION_ERROR (NPERR_BASE + 8) +#define NPERR_INVALID_PARAM (NPERR_BASE + 9) +#define NPERR_INVALID_URL (NPERR_BASE + 10) +#define NPERR_FILE_NOT_FOUND (NPERR_BASE + 11) +#define NPERR_NO_DATA (NPERR_BASE + 12) +#define NPERR_STREAM_NOT_SEEKABLE (NPERR_BASE + 13) +#define NPERR_TIME_RANGE_NOT_SUPPORTED (NPERR_BASE + 14) +#define NPERR_MALFORMED_SITE (NPERR_BASE + 15) + +/* + * Values of type NPReason: + */ +#define NPRES_BASE 0 +#define NPRES_DONE (NPRES_BASE + 0) +#define NPRES_NETWORK_ERR (NPRES_BASE + 1) +#define NPRES_USER_BREAK (NPRES_BASE + 2) + +/* + * Don't use these obsolete error codes any more. + */ +#define NP_NOERR NP_NOERR_is_obsolete_use_NPERR_NO_ERROR +#define NP_EINVAL NP_EINVAL_is_obsolete_use_NPERR_GENERIC_ERROR +#define NP_EABORT NP_EABORT_is_obsolete_use_NPRES_USER_BREAK + +/* + * Version feature information + */ +#define NPVERS_HAS_STREAMOUTPUT 8 +#define NPVERS_HAS_NOTIFICATION 9 +#define NPVERS_HAS_LIVECONNECT 9 +#define NPVERS_68K_HAS_LIVECONNECT 11 +#define NPVERS_HAS_WINDOWLESS 11 +#define NPVERS_HAS_XPCONNECT_SCRIPTING 13 +#define NPVERS_HAS_NPRUNTIME_SCRIPTING 14 +#define NPVERS_HAS_FORM_VALUES 15 +#define NPVERS_HAS_POPUPS_ENABLED_STATE 16 +#define NPVERS_HAS_RESPONSE_HEADERS 17 +#define NPVERS_HAS_NPOBJECT_ENUM 18 +#define NPVERS_HAS_PLUGIN_THREAD_ASYNC_CALL 19 +#define NPVERS_HAS_ALL_NETWORK_STREAMS 20 +#define NPVERS_HAS_URL_AND_AUTH_INFO 21 +#define NPVERS_HAS_PRIVATE_MODE 22 +#define NPVERS_MACOSX_HAS_COCOA_EVENTS 23 +#define NPVERS_HAS_ADVANCED_KEY_HANDLING 25 +#define NPVERS_HAS_URL_REDIRECT_HANDLING 26 +#define NPVERS_HAS_CLEAR_SITE_DATA 27 + +/*----------------------------------------------------------------------*/ +/* Function Prototypes */ +/*----------------------------------------------------------------------*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* NPP_* functions are provided by the plugin and called by the navigator. */ + +#if defined(XP_UNIX) +const char* NPP_GetMIMEDescription(void); +#endif + +NPError NPP_New(NPMIMEType pluginType, NPP instance, + uint16_t mode, int16_t argc, char* argn[], + char* argv[], NPSavedData* saved); +NPError NPP_Destroy(NPP instance, NPSavedData** save); +NPError NPP_SetWindow(NPP instance, NPWindow* window); +NPError NPP_NewStream(NPP instance, NPMIMEType type, + NPStream* stream, NPBool seekable, + uint16_t* stype); +NPError NPP_DestroyStream(NPP instance, NPStream* stream, + NPReason reason); +int32_t NPP_WriteReady(NPP instance, NPStream* stream); +int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, + int32_t len, void* buffer); +void NPP_StreamAsFile(NPP instance, NPStream* stream, + const char* fname); +void NPP_Print(NPP instance, NPPrint* platformPrint); +int16_t NPP_HandleEvent(NPP instance, void* event); +void NPP_URLNotify(NPP instance, const char* url, + NPReason reason, void* notifyData); +NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value); +NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value); +NPBool NPP_GotFocus(NPP instance, NPFocusDirection direction); +void NPP_LostFocus(NPP instance); +void NPP_URLRedirectNotify(NPP instance, const char* url, int32_t status, void* notifyData); +NPError NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge); +char** NPP_GetSitesWithData(void); +void NPP_DidComposite(NPP instance); + +/* NPN_* functions are provided by the navigator and called by the plugin. */ +void NPN_Version(int* plugin_major, int* plugin_minor, + int* netscape_major, int* netscape_minor); +NPError NPN_GetURLNotify(NPP instance, const char* url, + const char* target, void* notifyData); +NPError NPN_GetURL(NPP instance, const char* url, + const char* target); +NPError NPN_PostURLNotify(NPP instance, const char* url, + const char* target, uint32_t len, + const char* buf, NPBool file, + void* notifyData); +NPError NPN_PostURL(NPP instance, const char* url, + const char* target, uint32_t len, + const char* buf, NPBool file); +NPError NPN_RequestRead(NPStream* stream, NPByteRange* rangeList); +NPError NPN_NewStream(NPP instance, NPMIMEType type, + const char* target, NPStream** stream); +int32_t NPN_Write(NPP instance, NPStream* stream, int32_t len, + void* buffer); +NPError NPN_DestroyStream(NPP instance, NPStream* stream, + NPReason reason); +void NPN_Status(NPP instance, const char* message); +const char* NPN_UserAgent(NPP instance); +void* NPN_MemAlloc(uint32_t size); +void NPN_MemFree(void* ptr); +uint32_t NPN_MemFlush(uint32_t size); +void NPN_ReloadPlugins(NPBool reloadPages); +NPError NPN_GetValue(NPP instance, NPNVariable variable, + void *value); +NPError NPN_SetValue(NPP instance, NPPVariable variable, + void *value); +void NPN_InvalidateRect(NPP instance, NPRect *invalidRect); +void NPN_InvalidateRegion(NPP instance, + NPRegion invalidRegion); +void NPN_ForceRedraw(NPP instance); +void NPN_PushPopupsEnabledState(NPP instance, NPBool enabled); +void NPN_PopPopupsEnabledState(NPP instance); +void NPN_PluginThreadAsyncCall(NPP instance, + void (*func) (void *), + void *userData); +NPError NPN_GetValueForURL(NPP instance, NPNURLVariable variable, + const char *url, char **value, + uint32_t *len); +NPError NPN_SetValueForURL(NPP instance, NPNURLVariable variable, + const char *url, const char *value, + uint32_t len); +NPError NPN_GetAuthenticationInfo(NPP instance, + const char *protocol, + const char *host, int32_t port, + const char *scheme, + const char *realm, + char **username, uint32_t *ulen, + char **password, + uint32_t *plen); +uint32_t NPN_ScheduleTimer(NPP instance, uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID)); +void NPN_UnscheduleTimer(NPP instance, uint32_t timerID); +NPError NPN_PopUpContextMenu(NPP instance, NPMenu* menu); +NPBool NPN_ConvertPoint(NPP instance, double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double *destX, double *destY, NPCoordinateSpace destSpace); +NPBool NPN_HandleEvent(NPP instance, void *event, NPBool handled); +NPBool NPN_UnfocusInstance(NPP instance, NPFocusDirection direction); +void NPN_URLRedirectResponse(NPP instance, void* notifyData, NPBool allow); +NPError NPN_InitAsyncSurface(NPP instance, NPSize *size, + NPImageFormat format, void *initData, + NPAsyncSurface *surface); +NPError NPN_FinalizeAsyncSurface(NPP instance, NPAsyncSurface *surface); +void NPN_SetCurrentAsyncSurface(NPP instance, NPAsyncSurface *surface, NPRect *changed); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* RC_INVOKED */ + +#endif /* npapi_h_ */ diff --git a/dom/plugins/base/npfunctions.h b/dom/plugins/base/npfunctions.h new file mode 100644 index 000000000..4c9d66ea0 --- /dev/null +++ b/dom/plugins/base/npfunctions.h @@ -0,0 +1,281 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef npfunctions_h_ +#define npfunctions_h_ + +#include "npapi.h" +#include "npruntime.h" + +#ifdef MOZ_WIDGET_ANDROID +#include <jni.h> +#endif + +typedef NPError (* NPP_NewProcPtr)(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved); +typedef NPError (* NPP_DestroyProcPtr)(NPP instance, NPSavedData** save); +typedef NPError (* NPP_SetWindowProcPtr)(NPP instance, NPWindow* window); +typedef NPError (* NPP_NewStreamProcPtr)(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype); +typedef NPError (* NPP_DestroyStreamProcPtr)(NPP instance, NPStream* stream, NPReason reason); +typedef int32_t (* NPP_WriteReadyProcPtr)(NPP instance, NPStream* stream); +typedef int32_t (* NPP_WriteProcPtr)(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer); +typedef void (* NPP_StreamAsFileProcPtr)(NPP instance, NPStream* stream, const char* fname); +typedef void (* NPP_PrintProcPtr)(NPP instance, NPPrint* platformPrint); +typedef int16_t (* NPP_HandleEventProcPtr)(NPP instance, void* event); +typedef void (* NPP_URLNotifyProcPtr)(NPP instance, const char* url, NPReason reason, void* notifyData); +/* Any NPObjects returned to the browser via NPP_GetValue should be retained + by the plugin on the way out. The browser is responsible for releasing. */ +typedef NPError (* NPP_GetValueProcPtr)(NPP instance, NPPVariable variable, void *ret_value); +typedef NPError (* NPP_SetValueProcPtr)(NPP instance, NPNVariable variable, void *value); +typedef NPBool (* NPP_GotFocusPtr)(NPP instance, NPFocusDirection direction); +typedef void (* NPP_LostFocusPtr)(NPP instance); +typedef void (* NPP_URLRedirectNotifyPtr)(NPP instance, const char* url, int32_t status, void* notifyData); +typedef NPError (* NPP_ClearSiteDataPtr)(const char* site, uint64_t flags, uint64_t maxAge); +typedef char** (* NPP_GetSitesWithDataPtr)(void); +typedef void (* NPP_DidCompositePtr)(NPP instance); + +typedef NPError (*NPN_GetValueProcPtr)(NPP instance, NPNVariable variable, void *ret_value); +typedef NPError (*NPN_SetValueProcPtr)(NPP instance, NPPVariable variable, void *value); +typedef NPError (*NPN_GetURLNotifyProcPtr)(NPP instance, const char* url, const char* window, void* notifyData); +typedef NPError (*NPN_PostURLNotifyProcPtr)(NPP instance, const char* url, const char* window, uint32_t len, const char* buf, NPBool file, void* notifyData); +typedef NPError (*NPN_GetURLProcPtr)(NPP instance, const char* url, const char* window); +typedef NPError (*NPN_PostURLProcPtr)(NPP instance, const char* url, const char* window, uint32_t len, const char* buf, NPBool file); +typedef NPError (*NPN_RequestReadProcPtr)(NPStream* stream, NPByteRange* rangeList); +typedef NPError (*NPN_NewStreamProcPtr)(NPP instance, NPMIMEType type, const char* window, NPStream** stream); +typedef int32_t (*NPN_WriteProcPtr)(NPP instance, NPStream* stream, int32_t len, void* buffer); +typedef NPError (*NPN_DestroyStreamProcPtr)(NPP instance, NPStream* stream, NPReason reason); +typedef void (*NPN_StatusProcPtr)(NPP instance, const char* message); +/* Browser manages the lifetime of the buffer returned by NPN_UserAgent, don't + depend on it sticking around and don't free it. */ +typedef const char* (*NPN_UserAgentProcPtr)(NPP instance); +typedef void* (*NPN_MemAllocProcPtr)(uint32_t size); +typedef void (*NPN_MemFreeProcPtr)(void* ptr); +typedef uint32_t (*NPN_MemFlushProcPtr)(uint32_t size); +typedef void (*NPN_ReloadPluginsProcPtr)(NPBool reloadPages); +typedef void* (*NPN_GetJavaEnvProcPtr)(void); +typedef void* (*NPN_GetJavaPeerProcPtr)(NPP instance); +typedef void (*NPN_InvalidateRectProcPtr)(NPP instance, NPRect *rect); +typedef void (*NPN_InvalidateRegionProcPtr)(NPP instance, NPRegion region); +typedef void (*NPN_ForceRedrawProcPtr)(NPP instance); +typedef NPIdentifier (*NPN_GetStringIdentifierProcPtr)(const NPUTF8* name); +typedef void (*NPN_GetStringIdentifiersProcPtr)(const NPUTF8** names, int32_t nameCount, NPIdentifier* identifiers); +typedef NPIdentifier (*NPN_GetIntIdentifierProcPtr)(int32_t intid); +typedef bool (*NPN_IdentifierIsStringProcPtr)(NPIdentifier identifier); +typedef NPUTF8* (*NPN_UTF8FromIdentifierProcPtr)(NPIdentifier identifier); +typedef int32_t (*NPN_IntFromIdentifierProcPtr)(NPIdentifier identifier); +typedef NPObject* (*NPN_CreateObjectProcPtr)(NPP npp, NPClass *aClass); +typedef NPObject* (*NPN_RetainObjectProcPtr)(NPObject *obj); +typedef void (*NPN_ReleaseObjectProcPtr)(NPObject *obj); +typedef bool (*NPN_InvokeProcPtr)(NPP npp, NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result); +typedef bool (*NPN_InvokeDefaultProcPtr)(NPP npp, NPObject* obj, const NPVariant *args, uint32_t argCount, NPVariant *result); +typedef bool (*NPN_EvaluateProcPtr)(NPP npp, NPObject *obj, NPString *script, NPVariant *result); +typedef bool (*NPN_GetPropertyProcPtr)(NPP npp, NPObject *obj, NPIdentifier propertyName, NPVariant *result); +typedef bool (*NPN_SetPropertyProcPtr)(NPP npp, NPObject *obj, NPIdentifier propertyName, const NPVariant *value); +typedef bool (*NPN_RemovePropertyProcPtr)(NPP npp, NPObject *obj, NPIdentifier propertyName); +typedef bool (*NPN_HasPropertyProcPtr)(NPP npp, NPObject *obj, NPIdentifier propertyName); +typedef bool (*NPN_HasMethodProcPtr)(NPP npp, NPObject *obj, NPIdentifier propertyName); +typedef void (*NPN_ReleaseVariantValueProcPtr)(NPVariant *variant); +typedef void (*NPN_SetExceptionProcPtr)(NPObject *obj, const NPUTF8 *message); +typedef void (*NPN_PushPopupsEnabledStateProcPtr)(NPP npp, NPBool enabled); +typedef void (*NPN_PopPopupsEnabledStateProcPtr)(NPP npp); +typedef bool (*NPN_EnumerateProcPtr)(NPP npp, NPObject *obj, NPIdentifier **identifier, uint32_t *count); +typedef void (*NPN_PluginThreadAsyncCallProcPtr)(NPP instance, void (*func)(void *), void *userData); +typedef bool (*NPN_ConstructProcPtr)(NPP npp, NPObject* obj, const NPVariant *args, uint32_t argCount, NPVariant *result); +typedef NPError (*NPN_GetValueForURLPtr)(NPP npp, NPNURLVariable variable, const char *url, char **value, uint32_t *len); +typedef NPError (*NPN_SetValueForURLPtr)(NPP npp, NPNURLVariable variable, const char *url, const char *value, uint32_t len); +typedef NPError (*NPN_GetAuthenticationInfoPtr)(NPP npp, const char *protocol, const char *host, int32_t port, const char *scheme, const char *realm, char **username, uint32_t *ulen, char **password, uint32_t *plen); +typedef uint32_t (*NPN_ScheduleTimerPtr)(NPP instance, uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID)); +typedef void (*NPN_UnscheduleTimerPtr)(NPP instance, uint32_t timerID); +typedef NPError (*NPN_PopUpContextMenuPtr)(NPP instance, NPMenu* menu); +typedef NPBool (*NPN_ConvertPointPtr)(NPP instance, double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double *destX, double *destY, NPCoordinateSpace destSpace); +typedef NPBool (*NPN_HandleEventPtr)(NPP instance, void *event, NPBool handled); +typedef NPBool (*NPN_UnfocusInstancePtr)(NPP instance, NPFocusDirection direction); +typedef void (*NPN_URLRedirectResponsePtr)(NPP instance, void* notifyData, NPBool allow); +typedef NPError (*NPN_InitAsyncSurfacePtr)(NPP instance, NPSize *size, NPImageFormat format, void *initData, NPAsyncSurface *surface); +typedef NPError (*NPN_FinalizeAsyncSurfacePtr)(NPP instance, NPAsyncSurface *surface); +typedef void (*NPN_SetCurrentAsyncSurfacePtr)(NPP instance, NPAsyncSurface *surface, NPRect *changed); + +typedef void (*NPN_DummyPtr)(void); + +typedef struct _NPPluginFuncs { + uint16_t size; + uint16_t version; + NPP_NewProcPtr newp; + NPP_DestroyProcPtr destroy; + NPP_SetWindowProcPtr setwindow; + NPP_NewStreamProcPtr newstream; + NPP_DestroyStreamProcPtr destroystream; + NPP_StreamAsFileProcPtr asfile; + NPP_WriteReadyProcPtr writeready; + NPP_WriteProcPtr write; + NPP_PrintProcPtr print; + NPP_HandleEventProcPtr event; + NPP_URLNotifyProcPtr urlnotify; + void* javaClass; + NPP_GetValueProcPtr getvalue; + NPP_SetValueProcPtr setvalue; + NPP_GotFocusPtr gotfocus; + NPP_LostFocusPtr lostfocus; + NPP_URLRedirectNotifyPtr urlredirectnotify; + NPP_ClearSiteDataPtr clearsitedata; + NPP_GetSitesWithDataPtr getsiteswithdata; + NPP_DidCompositePtr didComposite; +} NPPluginFuncs; + +typedef struct _NPNetscapeFuncs { + uint16_t size; + uint16_t version; + NPN_GetURLProcPtr geturl; + NPN_PostURLProcPtr posturl; + NPN_RequestReadProcPtr requestread; + NPN_NewStreamProcPtr newstream; + NPN_WriteProcPtr write; + NPN_DestroyStreamProcPtr destroystream; + NPN_StatusProcPtr status; + NPN_UserAgentProcPtr uagent; + NPN_MemAllocProcPtr memalloc; + NPN_MemFreeProcPtr memfree; + NPN_MemFlushProcPtr memflush; + NPN_ReloadPluginsProcPtr reloadplugins; + NPN_GetJavaEnvProcPtr getJavaEnv; + NPN_GetJavaPeerProcPtr getJavaPeer; + NPN_GetURLNotifyProcPtr geturlnotify; + NPN_PostURLNotifyProcPtr posturlnotify; + NPN_GetValueProcPtr getvalue; + NPN_SetValueProcPtr setvalue; + NPN_InvalidateRectProcPtr invalidaterect; + NPN_InvalidateRegionProcPtr invalidateregion; + NPN_ForceRedrawProcPtr forceredraw; + NPN_GetStringIdentifierProcPtr getstringidentifier; + NPN_GetStringIdentifiersProcPtr getstringidentifiers; + NPN_GetIntIdentifierProcPtr getintidentifier; + NPN_IdentifierIsStringProcPtr identifierisstring; + NPN_UTF8FromIdentifierProcPtr utf8fromidentifier; + NPN_IntFromIdentifierProcPtr intfromidentifier; + NPN_CreateObjectProcPtr createobject; + NPN_RetainObjectProcPtr retainobject; + NPN_ReleaseObjectProcPtr releaseobject; + NPN_InvokeProcPtr invoke; + NPN_InvokeDefaultProcPtr invokeDefault; + NPN_EvaluateProcPtr evaluate; + NPN_GetPropertyProcPtr getproperty; + NPN_SetPropertyProcPtr setproperty; + NPN_RemovePropertyProcPtr removeproperty; + NPN_HasPropertyProcPtr hasproperty; + NPN_HasMethodProcPtr hasmethod; + NPN_ReleaseVariantValueProcPtr releasevariantvalue; + NPN_SetExceptionProcPtr setexception; + NPN_PushPopupsEnabledStateProcPtr pushpopupsenabledstate; + NPN_PopPopupsEnabledStateProcPtr poppopupsenabledstate; + NPN_EnumerateProcPtr enumerate; + NPN_PluginThreadAsyncCallProcPtr pluginthreadasynccall; + NPN_ConstructProcPtr construct; + NPN_GetValueForURLPtr getvalueforurl; + NPN_SetValueForURLPtr setvalueforurl; + NPN_GetAuthenticationInfoPtr getauthenticationinfo; + NPN_ScheduleTimerPtr scheduletimer; + NPN_UnscheduleTimerPtr unscheduletimer; + NPN_PopUpContextMenuPtr popupcontextmenu; + NPN_ConvertPointPtr convertpoint; + NPN_HandleEventPtr handleevent; + NPN_UnfocusInstancePtr unfocusinstance; + NPN_URLRedirectResponsePtr urlredirectresponse; + NPN_InitAsyncSurfacePtr initasyncsurface; + NPN_FinalizeAsyncSurfacePtr finalizeasyncsurface; + NPN_SetCurrentAsyncSurfacePtr setcurrentasyncsurface; +} NPNetscapeFuncs; + +#ifdef XP_MACOSX +/* + * Mac OS X version(s) of NP_GetMIMEDescription(const char *) + * These can be called to retreive MIME information from the plugin dynamically + * + * Note: For compatibility with Quicktime, BPSupportedMIMEtypes is another way + * to get mime info from the plugin only on OSX and may not be supported + * in furture version -- use NP_GetMIMEDescription instead + */ +enum +{ + kBPSupportedMIMETypesStructVers_1 = 1 +}; +typedef struct _BPSupportedMIMETypes +{ + SInt32 structVersion; /* struct version */ + Handle typeStrings; /* STR# formated handle, allocated by plug-in */ + Handle infoStrings; /* STR# formated handle, allocated by plug-in */ +} BPSupportedMIMETypes; +OSErr BP_GetSupportedMIMETypes(BPSupportedMIMETypes *mimeInfo, UInt32 flags); +#define NP_GETMIMEDESCRIPTION_NAME "NP_GetMIMEDescription" +typedef const char* (*NP_GetMIMEDescriptionProcPtr)(void); +typedef OSErr (*BP_GetSupportedMIMETypesProcPtr)(BPSupportedMIMETypes*, UInt32); +#endif + +#if defined(_WIN32) +#define OSCALL WINAPI +#else +#define OSCALL +#endif + +#if defined(XP_UNIX) +/* GCC 3.3 and later support the visibility attribute. */ +#if defined(__GNUC__) && ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3)) +#define NP_VISIBILITY_DEFAULT __attribute__((visibility("default"))) +#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +#define NP_VISIBILITY_DEFAULT __global +#else +#define NP_VISIBILITY_DEFAULT +#endif +#define NP_EXPORT(__type) NP_VISIBILITY_DEFAULT __type +#endif + +#if defined(_WIN32) +#ifdef __cplusplus +extern "C" { +#endif +/* plugin meta member functions */ +typedef NPError (OSCALL *NP_GetEntryPointsFunc)(NPPluginFuncs*); +NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs); +typedef NPError (OSCALL *NP_InitializeFunc)(NPNetscapeFuncs*); +NPError OSCALL NP_Initialize(NPNetscapeFuncs* bFuncs); +typedef NPError (OSCALL *NP_ShutdownFunc)(void); +NPError OSCALL NP_Shutdown(void); +typedef const char* (*NP_GetMIMEDescriptionFunc)(void); +const char* NP_GetMIMEDescription(void); +#ifdef __cplusplus +} +#endif +#endif + +#ifdef XP_UNIX +#ifdef __cplusplus +extern "C" { +#endif +typedef char* (*NP_GetPluginVersionFunc)(void); +NP_EXPORT(char*) NP_GetPluginVersion(void); +typedef const char* (*NP_GetMIMEDescriptionFunc)(void); +NP_EXPORT(const char*) NP_GetMIMEDescription(void); +#ifdef XP_MACOSX +typedef NPError (*NP_InitializeFunc)(NPNetscapeFuncs*); +NP_EXPORT(NPError) NP_Initialize(NPNetscapeFuncs* bFuncs); +typedef NPError (*NP_GetEntryPointsFunc)(NPPluginFuncs*); +NP_EXPORT(NPError) NP_GetEntryPoints(NPPluginFuncs* pFuncs); +#else +#ifdef MOZ_WIDGET_ANDROID +typedef NPError (*NP_InitializeFunc)(NPNetscapeFuncs*, NPPluginFuncs*, JNIEnv* pEnv); +NP_EXPORT(NPError) NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, JNIEnv* pEnv); +#else +typedef NPError (*NP_InitializeFunc)(NPNetscapeFuncs*, NPPluginFuncs*); +NP_EXPORT(NPError) NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs); +#endif +#endif +typedef NPError (*NP_ShutdownFunc)(void); +NP_EXPORT(NPError) NP_Shutdown(void); +typedef NPError (*NP_GetValueFunc)(void *, NPPVariable, void *); +NP_EXPORT(NPError) NP_GetValue(void *future, NPPVariable aVariable, void *aValue); +#ifdef __cplusplus +} +#endif +#endif + +#endif /* npfunctions_h_ */ diff --git a/dom/plugins/base/npruntime.h b/dom/plugins/base/npruntime.h new file mode 100644 index 000000000..4af7d3c34 --- /dev/null +++ b/dom/plugins/base/npruntime.h @@ -0,0 +1,393 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * Copyright (c) 2004, Apple Computer, Inc. and The Mozilla Foundation. + * 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. + * 3. Neither the names of Apple Computer, Inc. ("Apple") or The Mozilla + * Foundation ("Mozilla") nor the names of their contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE, MOZILLA AND THEIR 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, MOZILLA OR + * THEIR 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. + * + */ +#ifndef _NP_RUNTIME_H_ +#define _NP_RUNTIME_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "nptypes.h" + +/* + This API is used to facilitate binding code written in C to script + objects. The API in this header does not assume the presence of a + user agent. That is, it can be used to bind C code to scripting + environments outside of the context of a user agent. + + However, the normal use of the this API is in the context of a + scripting environment running in a browser or other user agent. + In particular it is used to support the extended Netscape + script-ability API for plugins (NP-SAP). NP-SAP is an extension + of the Netscape plugin API. As such we have adopted the use of + the "NP" prefix for this API. + + The following NP{N|P}Variables were added to the Netscape plugin + API (in npapi.h): + + NPNVWindowNPObject + NPNVPluginElementNPObject + NPPVpluginScriptableNPObject + + These variables are exposed through NPN_GetValue() and + NPP_GetValue() (respectively) and are used to establish the + initial binding between the user agent and native code. The DOM + objects in the user agent can be examined and manipulated using + the NPN_ functions that operate on NPObjects described in this + header. + + To the extent possible the assumptions about the scripting + language used by the scripting environment have been minimized. +*/ + +#define NP_BEGIN_MACRO do { +#define NP_END_MACRO } while (0) + +/* + Objects (non-primitive data) passed between 'C' and script is + always wrapped in an NPObject. The 'interface' of an NPObject is + described by an NPClass. +*/ +typedef struct NPObject NPObject; +typedef struct NPClass NPClass; + +typedef char NPUTF8; +typedef struct _NPString { + const NPUTF8 *UTF8Characters; + uint32_t UTF8Length; +} NPString; + +typedef enum { + NPVariantType_Void, + NPVariantType_Null, + NPVariantType_Bool, + NPVariantType_Int32, + NPVariantType_Double, + NPVariantType_String, + NPVariantType_Object +} NPVariantType; + +typedef struct _NPVariant { + NPVariantType type; + union { + bool boolValue; + int32_t intValue; + double doubleValue; + NPString stringValue; + NPObject *objectValue; + } value; +} NPVariant; + +/* + NPN_ReleaseVariantValue is called on all 'out' parameters + references. Specifically it is to be called on variants that own + their value, as is the case with all non-const NPVariant* + arguments after a successful call to any methods (except this one) + in this API. + + After calling NPN_ReleaseVariantValue, the type of the variant + will be NPVariantType_Void. +*/ +void NPN_ReleaseVariantValue(NPVariant *variant); + +#define NPVARIANT_IS_VOID(_v) ((_v).type == NPVariantType_Void) +#define NPVARIANT_IS_NULL(_v) ((_v).type == NPVariantType_Null) +#define NPVARIANT_IS_BOOLEAN(_v) ((_v).type == NPVariantType_Bool) +#define NPVARIANT_IS_INT32(_v) ((_v).type == NPVariantType_Int32) +#define NPVARIANT_IS_DOUBLE(_v) ((_v).type == NPVariantType_Double) +#define NPVARIANT_IS_STRING(_v) ((_v).type == NPVariantType_String) +#define NPVARIANT_IS_OBJECT(_v) ((_v).type == NPVariantType_Object) + +#define NPVARIANT_TO_BOOLEAN(_v) ((_v).value.boolValue) +#define NPVARIANT_TO_INT32(_v) ((_v).value.intValue) +#define NPVARIANT_TO_DOUBLE(_v) ((_v).value.doubleValue) +#define NPVARIANT_TO_STRING(_v) ((_v).value.stringValue) +#define NPVARIANT_TO_OBJECT(_v) ((_v).value.objectValue) + +#define VOID_TO_NPVARIANT(_v) \ +NP_BEGIN_MACRO \ + (_v).type = NPVariantType_Void; \ + (_v).value.objectValue = NULL; \ +NP_END_MACRO + +#define NULL_TO_NPVARIANT(_v) \ +NP_BEGIN_MACRO \ + (_v).type = NPVariantType_Null; \ + (_v).value.objectValue = NULL; \ +NP_END_MACRO + +#define BOOLEAN_TO_NPVARIANT(_val, _v) \ +NP_BEGIN_MACRO \ + (_v).type = NPVariantType_Bool; \ + (_v).value.boolValue = !!(_val); \ +NP_END_MACRO + +#define INT32_TO_NPVARIANT(_val, _v) \ +NP_BEGIN_MACRO \ + (_v).type = NPVariantType_Int32; \ + (_v).value.intValue = _val; \ +NP_END_MACRO + +#define DOUBLE_TO_NPVARIANT(_val, _v) \ +NP_BEGIN_MACRO \ + (_v).type = NPVariantType_Double; \ + (_v).value.doubleValue = _val; \ +NP_END_MACRO + +#define STRINGZ_TO_NPVARIANT(_val, _v) \ +NP_BEGIN_MACRO \ + (_v).type = NPVariantType_String; \ + NPString str = { _val, (uint32_t)(strlen(_val)) }; \ + (_v).value.stringValue = str; \ +NP_END_MACRO + +#define STRINGN_TO_NPVARIANT(_val, _len, _v) \ +NP_BEGIN_MACRO \ + (_v).type = NPVariantType_String; \ + NPString str = { _val, (uint32_t)(_len) }; \ + (_v).value.stringValue = str; \ +NP_END_MACRO + +#define OBJECT_TO_NPVARIANT(_val, _v) \ +NP_BEGIN_MACRO \ + (_v).type = NPVariantType_Object; \ + (_v).value.objectValue = _val; \ +NP_END_MACRO + + +/* + Type mappings (JavaScript types have been used for illustration + purposes): + + JavaScript to C (NPVariant with type:) + undefined NPVariantType_Void + null NPVariantType_Null + Boolean NPVariantType_Bool + Number NPVariantType_Double or NPVariantType_Int32 + String NPVariantType_String + Object NPVariantType_Object + + C (NPVariant with type:) to JavaScript + NPVariantType_Void undefined + NPVariantType_Null null + NPVariantType_Bool Boolean + NPVariantType_Int32 Number + NPVariantType_Double Number + NPVariantType_String String + NPVariantType_Object Object +*/ + +typedef void *NPIdentifier; + +/* + NPObjects have methods and properties. Methods and properties are + identified with NPIdentifiers. These identifiers may be reflected + in script. NPIdentifiers can be either strings or integers, IOW, + methods and properties can be identified by either strings or + integers (i.e. foo["bar"] vs foo[1]). NPIdentifiers can be + compared using ==. In case of any errors, the requested + NPIdentifier(s) will be NULL. NPIdentifier lifetime is controlled + by the browser. Plugins do not need to worry about memory management + with regards to NPIdentifiers. +*/ +NPIdentifier NPN_GetStringIdentifier(const NPUTF8 *name); +void NPN_GetStringIdentifiers(const NPUTF8 **names, int32_t nameCount, + NPIdentifier *identifiers); +NPIdentifier NPN_GetIntIdentifier(int32_t intid); +bool NPN_IdentifierIsString(NPIdentifier identifier); + +/* + The NPUTF8 returned from NPN_UTF8FromIdentifier SHOULD be freed. +*/ +NPUTF8 *NPN_UTF8FromIdentifier(NPIdentifier identifier); + +/* + Get the integer represented by identifier. If identifier is not an + integer identifier, the behaviour is undefined. +*/ +int32_t NPN_IntFromIdentifier(NPIdentifier identifier); + +/* + NPObject behavior is implemented using the following set of + callback functions. + + The NPVariant *result argument of these functions (where + applicable) should be released using NPN_ReleaseVariantValue(). +*/ +typedef NPObject *(*NPAllocateFunctionPtr)(NPP npp, NPClass *aClass); +typedef void (*NPDeallocateFunctionPtr)(NPObject *npobj); +typedef void (*NPInvalidateFunctionPtr)(NPObject *npobj); +typedef bool (*NPHasMethodFunctionPtr)(NPObject *npobj, NPIdentifier name); +typedef bool (*NPInvokeFunctionPtr)(NPObject *npobj, NPIdentifier name, + const NPVariant *args, uint32_t argCount, + NPVariant *result); +typedef bool (*NPInvokeDefaultFunctionPtr)(NPObject *npobj, + const NPVariant *args, + uint32_t argCount, + NPVariant *result); +typedef bool (*NPHasPropertyFunctionPtr)(NPObject *npobj, NPIdentifier name); +typedef bool (*NPGetPropertyFunctionPtr)(NPObject *npobj, NPIdentifier name, + NPVariant *result); +typedef bool (*NPSetPropertyFunctionPtr)(NPObject *npobj, NPIdentifier name, + const NPVariant *value); +typedef bool (*NPRemovePropertyFunctionPtr)(NPObject *npobj, + NPIdentifier name); +typedef bool (*NPEnumerationFunctionPtr)(NPObject *npobj, NPIdentifier **value, + uint32_t *count); +typedef bool (*NPConstructFunctionPtr)(NPObject *npobj, + const NPVariant *args, + uint32_t argCount, + NPVariant *result); + +/* + NPObjects returned by create, retain, invoke, and getProperty pass + a reference count to the caller. That is, the callee adds a + reference count which passes to the caller. It is the caller's + responsibility to release the returned object. + + NPInvokeFunctionPtr function may return 0 to indicate a void + result. + + NPInvalidateFunctionPtr is called by the scripting environment + when the native code is shutdown. Any attempt to message a + NPObject instance after the invalidate callback has been + called will result in undefined behavior, even if the native code + is still retaining those NPObject instances. (The runtime + will typically return immediately, with 0 or NULL, from an + attempt to dispatch to a NPObject, but this behavior should not + be depended upon.) + + The NPEnumerationFunctionPtr function may pass an array of + NPIdentifiers back to the caller. The callee allocs the memory of + the array using NPN_MemAlloc(), and it's the caller's responsibility + to release it using NPN_MemFree(). +*/ +struct NPClass +{ + uint32_t structVersion; + NPAllocateFunctionPtr allocate; + NPDeallocateFunctionPtr deallocate; + NPInvalidateFunctionPtr invalidate; + NPHasMethodFunctionPtr hasMethod; + NPInvokeFunctionPtr invoke; + NPInvokeDefaultFunctionPtr invokeDefault; + NPHasPropertyFunctionPtr hasProperty; + NPGetPropertyFunctionPtr getProperty; + NPSetPropertyFunctionPtr setProperty; + NPRemovePropertyFunctionPtr removeProperty; + NPEnumerationFunctionPtr enumerate; + NPConstructFunctionPtr construct; +}; + +#define NP_CLASS_STRUCT_VERSION 3 + +#define NP_CLASS_STRUCT_VERSION_ENUM 2 +#define NP_CLASS_STRUCT_VERSION_CTOR 3 + +#define NP_CLASS_STRUCT_VERSION_HAS_ENUM(npclass) \ + ((npclass)->structVersion >= NP_CLASS_STRUCT_VERSION_ENUM) + +#define NP_CLASS_STRUCT_VERSION_HAS_CTOR(npclass) \ + ((npclass)->structVersion >= NP_CLASS_STRUCT_VERSION_CTOR) + +struct NPObject { + NPClass *_class; + uint32_t referenceCount; + /* + * Additional space may be allocated here by types of NPObjects + */ +}; + +/* + If the class has an allocate function, NPN_CreateObject invokes + that function, otherwise a NPObject is allocated and + returned. This method will initialize the referenceCount member of + the NPObject to 1. +*/ +NPObject *NPN_CreateObject(NPP npp, NPClass *aClass); + +/* + Increment the NPObject's reference count. +*/ +NPObject *NPN_RetainObject(NPObject *npobj); + +/* + Decremented the NPObject's reference count. If the reference + count goes to zero, the class's destroy function is invoke if + specified, otherwise the object is freed directly. +*/ +void NPN_ReleaseObject(NPObject *npobj); + +/* + Functions to access script objects represented by NPObject. + + Calls to script objects are synchronous. If a function returns a + value, it will be supplied via the result NPVariant + argument. Successful calls will return true, false will be + returned in case of an error. + + Calls made from plugin code to script must be made from the thread + on which the plugin was initialized. +*/ + +bool NPN_Invoke(NPP npp, NPObject *npobj, NPIdentifier methodName, + const NPVariant *args, uint32_t argCount, NPVariant *result); +bool NPN_InvokeDefault(NPP npp, NPObject *npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result); +bool NPN_Evaluate(NPP npp, NPObject *npobj, NPString *script, + NPVariant *result); +bool NPN_GetProperty(NPP npp, NPObject *npobj, NPIdentifier propertyName, + NPVariant *result); +bool NPN_SetProperty(NPP npp, NPObject *npobj, NPIdentifier propertyName, + const NPVariant *value); +bool NPN_RemoveProperty(NPP npp, NPObject *npobj, NPIdentifier propertyName); +bool NPN_HasProperty(NPP npp, NPObject *npobj, NPIdentifier propertyName); +bool NPN_HasMethod(NPP npp, NPObject *npobj, NPIdentifier methodName); +bool NPN_Enumerate(NPP npp, NPObject *npobj, NPIdentifier **identifier, + uint32_t *count); +bool NPN_Construct(NPP npp, NPObject *npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result); + +/* + NPN_SetException may be called to trigger a script exception upon + return from entry points into NPObjects. Typical usage: + + NPN_SetException (npobj, message); +*/ +void NPN_SetException(NPObject *npobj, const NPUTF8 *message); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dom/plugins/base/nptypes.h b/dom/plugins/base/nptypes.h new file mode 100644 index 000000000..12a5fb78e --- /dev/null +++ b/dom/plugins/base/nptypes.h @@ -0,0 +1,88 @@ +/* -*- 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/. */ + +#ifndef nptypes_h_ +#define nptypes_h_ + +/* + * Header file for ensuring that C99 types ([u]int32_t, [u]int64_t and bool) and + * true/false macros are available. + */ + +#if defined(WIN32) + /* + * Win32 and OS/2 don't know C99, so define [u]int_16/32/64 here. The bool + * is predefined tho, both in C and C++. + */ + typedef short int16_t; + typedef unsigned short uint16_t; + typedef int int32_t; + typedef unsigned int uint32_t; + typedef long long int64_t; + typedef unsigned long long uint64_t; +#elif defined(_AIX) || defined(__sun) || defined(__osf__) || defined(IRIX) || defined(HPUX) + /* + * AIX and SunOS ship a inttypes.h header that defines [u]int32_t, + * but not bool for C. + */ + #include <inttypes.h> + + #ifndef __cplusplus + typedef int bool; + #define true 1 + #define false 0 + #endif +#elif defined(bsdi) || defined(FREEBSD) || defined(OPENBSD) + /* + * BSD/OS, FreeBSD, and OpenBSD ship sys/types.h that define int32_t and + * u_int32_t. + */ + #include <sys/types.h> + + /* + * BSD/OS ships no header that defines uint32_t, nor bool (for C) + */ + #if defined(bsdi) + typedef u_int32_t uint32_t; + typedef u_int64_t uint64_t; + + #if !defined(__cplusplus) + typedef int bool; + #define true 1 + #define false 0 + #endif + #else + /* + * FreeBSD and OpenBSD define uint32_t and bool. + */ + #include <inttypes.h> + #include <stdbool.h> + #endif +#elif defined(BEOS) + #include <inttypes.h> +#else + /* + * For those that ship a standard C99 stdint.h header file, include + * it. Can't do the same for stdbool.h tho, since some systems ship + * with a stdbool.h file that doesn't compile! + */ + #include <stdint.h> + + #ifndef __cplusplus + #if !defined(__GNUC__) || (__GNUC__ > 2 || __GNUC_MINOR__ > 95) + #include <stdbool.h> + #else + /* + * GCC 2.91 can't deal with a typedef for bool, but a #define + * works. + */ + #define bool int + #define true 1 + #define false 0 + #endif + #endif +#endif + +#endif /* nptypes_h_ */ diff --git a/dom/plugins/base/nsIHTTPHeaderListener.idl b/dom/plugins/base/nsIHTTPHeaderListener.idl new file mode 100644 index 000000000..7be881641 --- /dev/null +++ b/dom/plugins/base/nsIHTTPHeaderListener.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * The nsIHTTPHeaderListener interface allows plugin authors to + * access HTTP Response headers after issuing an + * nsIPluginHost::{GetURL,PostURL}() call. <P> + */ + +[scriptable, uuid(ea51e0b8-871c-4b85-92da-6f400394c5ec)] +interface nsIHTTPHeaderListener : nsISupports +{ + /** + * Called for each HTTP Response header. + * NOTE: You must copy the values of the params. + */ + void newResponseHeader(in string headerName, in string headerValue); + + /** + * Called once for the HTTP Response status line. + * Value does NOT include a terminating newline. + * NOTE: You must copy this value. + */ + void statusLine(in string line); +}; diff --git a/dom/plugins/base/nsIPluginDocument.idl b/dom/plugins/base/nsIPluginDocument.idl new file mode 100644 index 000000000..f91aac621 --- /dev/null +++ b/dom/plugins/base/nsIPluginDocument.idl @@ -0,0 +1,16 @@ +/* -*- 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 "nsISupports.idl" +#include "nsIStreamListener.idl" + +[uuid(a93a0f0f-24f0-4206-a21b-56a43dcbdd88)] +interface nsIPluginDocument : nsISupports +{ + /** + * Causes the plugin to print in full-page mode + */ + void print(); +}; diff --git a/dom/plugins/base/nsIPluginHost.idl b/dom/plugins/base/nsIPluginHost.idl new file mode 100644 index 000000000..d1124aa25 --- /dev/null +++ b/dom/plugins/base/nsIPluginHost.idl @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nspluginroot.idl" +#include "nsISupports.idl" +#include "nsIPluginTag.idl" + +%{C++ +#define MOZ_PLUGIN_HOST_CONTRACTID \ + "@mozilla.org/plugin/host;1" +%} + +[scriptable, function, uuid(9c311778-7c2c-4ad8-b439-b8a2786a20dd)] +interface nsIClearSiteDataCallback : nsISupports +{ + /** + * callback with the result from a call to clearSiteData + */ + void callback(in nsresult rv); +}; + +[scriptable, uuid(f938f5ba-7093-42cd-a559-af8039d99204)] +interface nsIPluginHost : nsISupports +{ + /** + * Causes the plugins directory to be searched again for new plugin + * libraries. + */ + void reloadPlugins(); + + void getPluginTags([optional] out unsigned long aPluginCount, + [retval, array, size_is(aPluginCount)] out nsIPluginTag aResults); + + /* + * Flags for use with clearSiteData. + * + * FLAG_CLEAR_ALL: clear all data associated with a site. + * FLAG_CLEAR_CACHE: clear cached data that can be retrieved again without + * loss of functionality. To be used out of concern for + * space and not necessarily privacy. + */ + const uint32_t FLAG_CLEAR_ALL = 0; + const uint32_t FLAG_CLEAR_CACHE = 1; + + /* + * For use with Get*ForType functions + */ + const uint32_t EXCLUDE_NONE = 0; + const uint32_t EXCLUDE_DISABLED = 1 << 0; + const uint32_t EXCLUDE_FAKE = 1 << 1; + + /* + * Clear site data for a given plugin. + * + * @param plugin: the plugin to clear data for, such as one returned by + * nsIPluginHost.getPluginTags. + * @param domain: the domain to clear data for. If this argument is null, + * clear data for all domains. Otherwise, it must be a domain + * only (not a complete URI or IRI). The base domain for the + * given site will be determined; any data for the base domain + * or its subdomains will be cleared. + * @param flags: a flag value defined above. + * @param maxAge: the maximum age in seconds of data to clear, inclusive. If + * maxAge is 0, no data is cleared; if it is -1, all data is + * cleared. + * + * @throws NS_ERROR_INVALID_ARG if the domain argument is malformed. + * @throws NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED if maxAge is a value other + * than -1 and the plugin does not support clearing by timerange in + * general or for that particular site and/or flag combination. + */ + void clearSiteData(in nsIPluginTag plugin, in AUTF8String domain, + in uint64_t flags, in int64_t maxAge, + in nsIClearSiteDataCallback callback); + + /* + * Determine if a plugin has stored data for a given site. + * + * @param plugin: the plugin to query, such as one returned by + * nsIPluginHost.getPluginTags. + * @param domain: the domain to test. If this argument is null, test if data + * is stored for any site. The base domain for the given domain + * will be determined; if any data for the base domain or its + * subdomains is found, return true. + */ + boolean siteHasData(in nsIPluginTag plugin, in AUTF8String domain); + + /** + * Get the "permission string" for the plugin. This is a string that can be + * passed to the permission manager to see whether the plugin is allowed to + * run, for example. This will typically be based on the plugin's "nice name" + * and its blocklist state. + * + * @mimeType The MIME type we're interested in. + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + */ + ACString getPermissionStringForType(in AUTF8String mimeType, + [optional] in uint32_t excludeFlags); + + /** + * Get the "permission string" for the plugin. This is a string that can be + * passed to the permission manager to see whether the plugin is allowed to + * run, for example. This will typically be based on the plugin's "nice name" + * and its blocklist state. + * + * @tag The tage we're interested in + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + */ + ACString getPermissionStringForTag(in nsIPluginTag tag, + [optional] in uint32_t excludeFlags); + + /** + * Get the nsIPluginTag for this MIME type. This method works with both + * enabled and disabled/blocklisted plugins, but an enabled plugin will + * always be returned if available. + * + * A fake plugin tag, if one exists and is available, will be returned in + * preference to NPAPI plugin tags unless excluded by the excludeFlags. + * + * @mimeType The MIME type we're interested in. + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + * + * @throws NS_ERROR_NOT_AVAILABLE if no plugin is available for this MIME + * type. + */ + nsIPluginTag getPluginTagForType(in AUTF8String mimeType, + [optional] in uint32_t excludeFlags); + + /** + * Get the nsIPluginTag enabled state for this MIME type. See + * nsIPluginTag.enabledState. + * + * @mimeType The MIME type we're interested in. + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + */ + unsigned long getStateForType(in AUTF8String mimeType, + [optional] in uint32_t excludeFlags); + + /** + * Get the blocklist state for a MIME type. See nsIPluginTag.blocklistState. + * + * @mimeType The MIME type we're interested in. + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + */ + uint32_t getBlocklistStateForType(in AUTF8String aMimeType, + [optional] in uint32_t excludeFlags); + + /** + * Create a fake plugin tag, register it, and return it. The argument is a + * FakePluginTagInit dictionary. See documentation in + * FakePluginTagInit.webidl for what it should look like. Will throw + * NS_ERROR_UNEXPECTED if there is already a fake plugin registered with the + * given handler URI. + */ + [implicit_jscontext] + nsIFakePluginTag registerFakePlugin(in jsval initDictionary); + + /** + * Get a reference to an existing fake plugin tag for the given MIME type, if + * any. Can return null. + */ + nsIFakePluginTag getFakePlugin(in AUTF8String mimeType); + + /** + * Unregister a fake plugin. The argument can be the .handlerURI.spec of an + * existing nsIFakePluginTag, or just a known handler URI string that was + * passed in the FakePluginTagInit when registering. + */ + void unregisterFakePlugin(in AUTF8String handlerURI); +}; diff --git a/dom/plugins/base/nsIPluginInputStream.idl b/dom/plugins/base/nsIPluginInputStream.idl new file mode 100644 index 000000000..886838441 --- /dev/null +++ b/dom/plugins/base/nsIPluginInputStream.idl @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIInputStream.idl" +#include "nspluginroot.idl" + +/** + * The nsIPluginInputStream interface ... + */ +[uuid(af160530-542a-11d2-8164-006008119d7a)] +interface nsIPluginInputStream : nsIInputStream { + /** + * Corresponds to NPStream's lastmodified field.) + */ + void getLastModified(out unsigned long aResult); + + void requestRead(out NPByteRange aRangeList); +}; diff --git a/dom/plugins/base/nsIPluginInstanceOwner.idl b/dom/plugins/base/nsIPluginInstanceOwner.idl new file mode 100644 index 000000000..87e51599c --- /dev/null +++ b/dom/plugins/base/nsIPluginInstanceOwner.idl @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" +#include "nspluginroot.idl" +#include "nsIInputStream.idl" + +interface nsIDocument; + +%{C++ +#include "npapi.h" +#include "mozilla/EventForwards.h" +class nsNPAPIPluginInstance; + +enum nsPluginTagType { + nsPluginTagType_Unknown, + nsPluginTagType_Embed, + nsPluginTagType_Object, + nsPluginTagType_Applet +}; +%} + +[ptr] native nsNPAPIPluginInstancePtr(nsNPAPIPluginInstance); + +// Do not make this interface scriptable, because the virtual functions in C++ +// blocks will make script call the wrong functions. +[uuid(7d65452e-c167-4cba-a0e3-ddc61bdde8c3)] +interface nsIPluginInstanceOwner : nsISupports +{ + /** + * Let the owner know what its instance is + */ + void setInstance(in nsNPAPIPluginInstancePtr aInstance); + + /** + * Get the instance associated with this owner. + */ + nsNPAPIPluginInstancePtr getInstance(); + + /** + * Get a handle to the window structure of the owner. + * This pointer cannot be made persistent by the caller. + */ + void getWindow(in NPWindowStarRef aWindow); + + /** + * Get the display mode for the plugin instance. + */ + readonly attribute int32_t mode; + + /** + * Create a place for the plugin to live in the owner's + * environment. this may or may not create a window + * depending on the windowless state of the plugin instance. + */ + void createWidget(); + +%{C++ + /** + * Called when there is a valid target so that the proper + * frame can be updated with new content. will not be called + * with nullptr aTarget. + */ + NS_IMETHOD + GetURL(const char *aURL, const char *aTarget, + nsIInputStream *aPostStream, + void *aHeadersData, uint32_t aHeadersDataLen, + bool aDoCheckLoadURIChecks) = 0; +%} + + /** + * Get the associated document. + */ + readonly attribute nsIDocument document; + + /** + * Invalidate the rectangle + */ + void invalidateRect(in NPRectPtr aRect); + + /** + * Invalidate the region + */ + void invalidateRegion(in NPRegion aRegion); + + /** + * Have the plugin recomposited. + */ + void redrawPlugin(); + + /** + * Get NetscapeWindow, corresponds to NPNVnetscapeWindow + */ + void getNetscapeWindow(in voidPtr aValue); + + /** + * Convert between plugin, window, and screen coordinate spaces. + */ +%{C++ + virtual NPBool ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace, + double *destX, double *destY, NPCoordinateSpace destSpace) = 0; + virtual NPError InitAsyncSurface(NPSize *size, NPImageFormat format, + void *initData, NPAsyncSurface *surface) = 0; + virtual NPError FinalizeAsyncSurface(NPAsyncSurface *surface) = 0; + virtual void SetCurrentAsyncSurface(NPAsyncSurface *surface, NPRect *changed) = 0; +%} + + void setEventModel(in int32_t eventModel); + + /** + * Call NPP_SetWindow on the plugin. + */ + void callSetWindow(); + + /** + * Get the contents scale factor for the screen the plugin is + * drawn on. + */ + double getContentsScaleFactor(); +}; diff --git a/dom/plugins/base/nsIPluginTag.idl b/dom/plugins/base/nsIPluginTag.idl new file mode 100644 index 000000000..1c2d47734 --- /dev/null +++ b/dom/plugins/base/nsIPluginTag.idl @@ -0,0 +1,76 @@ +/* -*- Mode: IDL; 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 "nsISupports.idl" + +interface nsIURI; + +[scriptable, uuid(5daa99d5-265a-4397-b429-c943803e2619)] +interface nsIPluginTag : nsISupports +{ + // enabledState is stored as one of the following as an integer in prefs, + // so if new states are added, they must not renumber the existing states. + const unsigned long STATE_DISABLED = 0; + const unsigned long STATE_CLICKTOPLAY = 1; + const unsigned long STATE_ENABLED = 2; + + readonly attribute AUTF8String description; + readonly attribute AUTF8String filename; + readonly attribute AUTF8String fullpath; + readonly attribute AUTF8String version; + readonly attribute AUTF8String name; + + // The 'nice' name of this plugin, e.g. 'flash' 'java' + readonly attribute AUTF8String niceName; + + /** + * true only if this plugin is "hardblocked" and cannot be enabled. + */ + // FIXME-jsplugins QI to fakePluginTag possible + // FIXME-jsplugins implement missing + tests (whatever that means) + readonly attribute boolean blocklisted; + + /** + * true if the state is non-default and locked, false otherwise. + */ + readonly attribute boolean isEnabledStateLocked; + + // If this plugin is capable of being used (not disabled, blocklisted, etc) + readonly attribute boolean active; + + // Get a specific nsIBlocklistService::STATE_* + readonly attribute unsigned long blocklistState; + + readonly attribute boolean disabled; + readonly attribute boolean clicktoplay; + readonly attribute boolean loaded; + // See the STATE_* values above. + attribute unsigned long enabledState; + + readonly attribute PRTime lastModifiedTime; + + void getMimeTypes([optional] out unsigned long aCount, + [retval, array, size_is(aCount)] out wstring aResults); + void getMimeDescriptions([optional] out unsigned long aCount, + [retval, array, size_is(aCount)] + out wstring aResults); + void getExtensions([optional] out unsigned long aCount, + [retval, array, size_is(aCount)] + out wstring aResults); +}; + +/** + * An interface representing a "fake" plugin: one implemented in JavaScript, not + * as a NPAPI plug-in. See nsIPluginHost.registerFakePlugin and the + * documentation for the FakePluginTagInit dictionary. + */ +[scriptable, uuid(6d22c968-226d-4156-b230-da6ad6bbf6e8)] +interface nsIFakePluginTag : nsIPluginTag +{ + // The URI that should be loaded into the tag (as a frame) to handle the + // plugin. Note that the original data/src value for the plugin is not loaded + // and will need to be requested by the handler via XHR or similar if desired. + readonly attribute nsIURI handlerURI; +}; diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp new file mode 100644 index 000000000..05e0ec4ba --- /dev/null +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -0,0 +1,2328 @@ +/* -*- 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 "base/basictypes.h" + +#include "jsfriendapi.h" +#include "jswrapper.h" + +#include "nsAutoPtr.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsJSNPRuntime.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsIGlobalObject.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsDOMJSUtils.h" +#include "nsJSUtils.h" +#include "nsIDocument.h" +#include "nsIXPConnect.h" +#include "xpcpublic.h" +#include "nsIDOMElement.h" +#include "prmem.h" +#include "nsIContent.h" +#include "nsPluginInstanceOwner.h" +#include "nsWrapperCacheInlines.h" +#include "js/GCHashTable.h" +#include "js/TracingAPI.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/plugins/PluginAsyncSurrogate.h" + +using mozilla::plugins::AsyncNPObject; +using mozilla::plugins::PluginAsyncSurrogate; + +#define NPRUNTIME_JSCLASS_NAME "NPObject JS wrapper class" + +using namespace mozilla::plugins::parent; +using namespace mozilla; + +#include "mozilla/plugins/PluginScriptableObjectParent.h" +using mozilla::plugins::PluginScriptableObjectParent; +using mozilla::plugins::ParentNPObject; + +struct JSObjWrapperHasher +{ + typedef nsJSObjWrapperKey Key; + typedef Key Lookup; + + static uint32_t hash(const Lookup &l) { + return js::MovableCellHasher<JS::Heap<JSObject*>>::hash(l.mJSObj) ^ + HashGeneric(l.mNpp); + } + + static bool match(const Key& k, const Lookup &l) { + return js::MovableCellHasher<JS::Heap<JSObject*>>::match(k.mJSObj, l.mJSObj) && + k.mNpp == l.mNpp; + } +}; + +namespace JS { +template <> +struct GCPolicy<nsJSObjWrapper*> { + static void trace(JSTracer* trc, nsJSObjWrapper** wrapper, const char* name) { + MOZ_ASSERT(wrapper); + MOZ_ASSERT(*wrapper); + (*wrapper)->trace(trc); + } +}; +} // namespace JS + +class NPObjWrapperHashEntry : public PLDHashEntryHdr +{ +public: + NPObject *mNPObj; // Must be the first member for the PLDHash stubs to work + JS::TenuredHeap<JSObject*> mJSObj; + NPP mNpp; +}; + +// Hash of JSObject wrappers that wraps JSObjects as NPObjects. There +// will be one wrapper per JSObject per plugin instance, i.e. if two +// plugins access the JSObject x, two wrappers for x will be +// created. This is needed to be able to properly drop the wrappers +// when a plugin is torn down in case there's a leak in the plugin (we +// don't want to leak the world just because a plugin leaks an +// NPObject). +typedef JS::GCHashMap<nsJSObjWrapperKey, + nsJSObjWrapper*, + JSObjWrapperHasher, + js::SystemAllocPolicy> JSObjWrapperTable; +static JSObjWrapperTable sJSObjWrappers; + +// Whether it's safe to iterate sJSObjWrappers. Set to true when sJSObjWrappers +// has been initialized and is not currently being enumerated. +static bool sJSObjWrappersAccessible = false; + +// Hash of NPObject wrappers that wrap NPObjects as JSObjects. +static PLDHashTable* sNPObjWrappers; + +// Global wrapper count. This includes JSObject wrappers *and* +// NPObject wrappers. When this count goes to zero, there are no more +// wrappers and we can kill off hash tables etc. +static int32_t sWrapperCount; + +static bool sCallbackIsRegistered = false; + +static nsTArray<NPObject*>* sDelayedReleases; + +namespace { + +inline void +CastNPObject(NPObject *aObj, PluginScriptableObjectParent*& aActor, + PluginAsyncSurrogate*& aSurrogate) +{ + aActor = nullptr; + aSurrogate = nullptr; + if (aObj->_class == PluginScriptableObjectParent::GetClass()) { + aActor = static_cast<ParentNPObject*>(aObj)->parent; + } else if (aObj->_class == PluginAsyncSurrogate::GetClass()) { + aSurrogate = static_cast<AsyncNPObject*>(aObj)->mSurrogate; + } +} + +inline bool +NPObjectIsOutOfProcessProxy(NPObject *obj) +{ + return obj->_class == PluginScriptableObjectParent::GetClass() || + obj->_class == PluginAsyncSurrogate::GetClass(); +} + +} // namespace + +// Helper class that suppresses any JS exceptions that were thrown while +// the plugin executed JS, if the nsJSObjWrapper has a destroy pending. +// Note that this class is the product (vestige?) of a long evolution in how +// error reporting worked, and hence the mIsDestroyPending check, and hence this +// class in general, may or may not actually be necessary. + +class MOZ_STACK_CLASS AutoJSExceptionSuppressor +{ +public: + AutoJSExceptionSuppressor(dom::AutoEntryScript& aes, nsJSObjWrapper* aWrapper) + : mAes(aes) + , mIsDestroyPending(aWrapper->mDestroyPending) + { + } + + ~AutoJSExceptionSuppressor() + { + if (mIsDestroyPending) { + mAes.ClearException(); + } + } + +protected: + dom::AutoEntryScript& mAes; + bool mIsDestroyPending; +}; + + +NPClass nsJSObjWrapper::sJSObjWrapperNPClass = + { + NP_CLASS_STRUCT_VERSION, + nsJSObjWrapper::NP_Allocate, + nsJSObjWrapper::NP_Deallocate, + nsJSObjWrapper::NP_Invalidate, + nsJSObjWrapper::NP_HasMethod, + nsJSObjWrapper::NP_Invoke, + nsJSObjWrapper::NP_InvokeDefault, + nsJSObjWrapper::NP_HasProperty, + nsJSObjWrapper::NP_GetProperty, + nsJSObjWrapper::NP_SetProperty, + nsJSObjWrapper::NP_RemoveProperty, + nsJSObjWrapper::NP_Enumerate, + nsJSObjWrapper::NP_Construct + }; + +static bool +NPObjWrapper_AddProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::Handle<JS::Value> v); + +static bool +NPObjWrapper_DelProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::ObjectOpResult &result); + +static bool +NPObjWrapper_SetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp, JS::ObjectOpResult &result); + +static bool +NPObjWrapper_GetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp); + +static bool +NPObjWrapper_Enumerate(JSContext *cx, JS::Handle<JSObject*> obj, JS::AutoIdVector &properties, + bool enumerableOnly); + +static bool +NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + bool *resolvedp); + +static void +NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj); + +static void +NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old); + +static bool +NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp); + +static bool +NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp); + +static bool +NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp); + +static bool +CreateNPObjectMember(NPP npp, JSContext *cx, + JS::Handle<JSObject*> obj, NPObject* npobj, + JS::Handle<jsid> id, NPVariant* getPropertyResult, + JS::MutableHandle<JS::Value> vp); + +const static js::ClassOps sNPObjectJSWrapperClassOps = { + NPObjWrapper_AddProperty, + NPObjWrapper_DelProperty, + NPObjWrapper_GetProperty, + NPObjWrapper_SetProperty, + nullptr, + NPObjWrapper_Resolve, + nullptr, /* mayResolve */ + NPObjWrapper_Finalize, + NPObjWrapper_Call, + nullptr, /* hasInstance */ + NPObjWrapper_Construct, + nullptr, /* trace */ +}; + +const static js::ClassExtension sNPObjectJSWrapperClassExtension = { + nullptr, /* weakmapKeyDelegateOp */ + NPObjWrapper_ObjectMoved +}; + +const static js::ObjectOps sNPObjectJSWrapperObjectOps = { + nullptr, // lookupProperty + nullptr, // defineProperty + nullptr, // hasProperty + nullptr, // getProperty + nullptr, // setProperty + nullptr, // getOwnPropertyDescriptor + nullptr, // deleteProperty + nullptr, nullptr, // watch/unwatch + nullptr, // getElements + NPObjWrapper_Enumerate, + nullptr, +}; + +const static js::Class sNPObjectJSWrapperClass = { + NPRUNTIME_JSCLASS_NAME, + JSCLASS_HAS_PRIVATE | + JSCLASS_FOREGROUND_FINALIZE, + &sNPObjectJSWrapperClassOps, + JS_NULL_CLASS_SPEC, + &sNPObjectJSWrapperClassExtension, + &sNPObjectJSWrapperObjectOps +}; + +typedef struct NPObjectMemberPrivate { + JS::Heap<JSObject *> npobjWrapper; + JS::Heap<JS::Value> fieldValue; + JS::Heap<jsid> methodName; + NPP npp; +} NPObjectMemberPrivate; + +static bool +NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id, + JS::MutableHandleValue vp); + +static void +NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj); + +static bool +NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp); + +static void +NPObjectMember_Trace(JSTracer *trc, JSObject *obj); + +static bool +NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp); + +static const JSClassOps sNPObjectMemberClassOps = { + nullptr, nullptr, NPObjectMember_GetProperty, nullptr, + nullptr, nullptr, nullptr, + NPObjectMember_Finalize, NPObjectMember_Call, + nullptr, nullptr, NPObjectMember_Trace +}; + +static const JSClass sNPObjectMemberClass = { + "NPObject Ambiguous Member class", + JSCLASS_HAS_PRIVATE | + JSCLASS_FOREGROUND_FINALIZE, + &sNPObjectMemberClassOps +}; + +static void +OnWrapperDestroyed(); + +static void +TraceJSObjWrappers(JSTracer *trc, void *data) +{ + if (sJSObjWrappers.initialized()) { + sJSObjWrappers.trace(trc); + } +} + +static void +DelayedReleaseGCCallback(JSGCStatus status) +{ + if (JSGC_END == status) { + // Take ownership of sDelayedReleases and null it out now. The + // _releaseobject call below can reenter GC and double-free these objects. + nsAutoPtr<nsTArray<NPObject*> > delayedReleases(sDelayedReleases); + sDelayedReleases = nullptr; + + if (delayedReleases) { + for (uint32_t i = 0; i < delayedReleases->Length(); ++i) { + NPObject* obj = (*delayedReleases)[i]; + if (obj) + _releaseobject(obj); + OnWrapperDestroyed(); + } + } + } +} + +static bool +RegisterGCCallbacks() +{ + if (sCallbackIsRegistered) { + return true; + } + + // Register a callback to trace wrapped JSObjects. + JSContext* cx = dom::danger::GetJSContext(); + if (!JS_AddExtraGCRootsTracer(cx, TraceJSObjWrappers, nullptr)) { + return false; + } + + // Register our GC callback to perform delayed destruction of finalized + // NPObjects. + xpc::AddGCCallback(DelayedReleaseGCCallback); + + sCallbackIsRegistered = true; + + return true; +} + +static void +UnregisterGCCallbacks() +{ + MOZ_ASSERT(sCallbackIsRegistered); + + // Remove tracing callback. + JSContext* cx = dom::danger::GetJSContext(); + JS_RemoveExtraGCRootsTracer(cx, TraceJSObjWrappers, nullptr); + + // Remove delayed destruction callback. + if (sCallbackIsRegistered) { + xpc::RemoveGCCallback(DelayedReleaseGCCallback); + sCallbackIsRegistered = false; + } +} + +static bool +CreateJSObjWrapperTable() +{ + MOZ_ASSERT(!sJSObjWrappersAccessible); + MOZ_ASSERT(!sJSObjWrappers.initialized()); + + if (!RegisterGCCallbacks()) { + return false; + } + + if (!sJSObjWrappers.init(16)) { + NS_ERROR("Error initializing PLDHashTable sJSObjWrappers!"); + return false; + } + + sJSObjWrappersAccessible = true; + return true; +} + +static void +DestroyJSObjWrapperTable() +{ + MOZ_ASSERT(sJSObjWrappersAccessible); + MOZ_ASSERT(sJSObjWrappers.initialized()); + MOZ_ASSERT(sJSObjWrappers.count() == 0); + + // No more wrappers, and our hash was initialized. Finish the + // hash to prevent leaking it. + sJSObjWrappers.finish(); + sJSObjWrappersAccessible = false; +} + +static bool +CreateNPObjWrapperTable() +{ + MOZ_ASSERT(!sNPObjWrappers); + + if (!RegisterGCCallbacks()) { + return false; + } + + sNPObjWrappers = + new PLDHashTable(PLDHashTable::StubOps(), sizeof(NPObjWrapperHashEntry)); + return true; +} + +static void +DestroyNPObjWrapperTable() +{ + MOZ_ASSERT(sNPObjWrappers->EntryCount() == 0); + + delete sNPObjWrappers; + sNPObjWrappers = nullptr; +} + +static void +OnWrapperCreated() +{ + ++sWrapperCount; +} + +static void +OnWrapperDestroyed() +{ + NS_ASSERTION(sWrapperCount, "Whaaa, unbalanced created/destroyed calls!"); + + if (--sWrapperCount == 0) { + if (sJSObjWrappersAccessible) { + DestroyJSObjWrapperTable(); + } + + if (sNPObjWrappers) { + // No more wrappers, and our hash was initialized. Finish the + // hash to prevent leaking it. + DestroyNPObjWrapperTable(); + } + + UnregisterGCCallbacks(); + } +} + +namespace mozilla { +namespace plugins { +namespace parent { + +static nsIGlobalObject* +GetGlobalObject(NPP npp) +{ + NS_ENSURE_TRUE(npp, nullptr); + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)npp->ndata; + NS_ENSURE_TRUE(inst, nullptr); + + RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner(); + NS_ENSURE_TRUE(owner, nullptr); + + nsCOMPtr<nsIDocument> doc; + owner->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_TRUE(doc, nullptr); + + return doc->GetScopeObject(); +} + +} // namespace parent +} // namespace plugins +} // namespace mozilla + +static NPP +LookupNPP(NPObject *npobj); + + +static JS::Value +NPVariantToJSVal(NPP npp, JSContext *cx, const NPVariant *variant) +{ + switch (variant->type) { + case NPVariantType_Void : + return JS::UndefinedValue(); + case NPVariantType_Null : + return JS::NullValue(); + case NPVariantType_Bool : + return JS::BooleanValue(NPVARIANT_TO_BOOLEAN(*variant)); + case NPVariantType_Int32 : + { + // Don't use INT_TO_JSVAL directly to prevent bugs when dealing + // with ints larger than what fits in a integer JS::Value. + return ::JS_NumberValue(NPVARIANT_TO_INT32(*variant)); + } + case NPVariantType_Double : + { + return ::JS_NumberValue(NPVARIANT_TO_DOUBLE(*variant)); + } + case NPVariantType_String : + { + const NPString *s = &NPVARIANT_TO_STRING(*variant); + NS_ConvertUTF8toUTF16 utf16String(s->UTF8Characters, s->UTF8Length); + + JSString *str = + ::JS_NewUCStringCopyN(cx, utf16String.get(), utf16String.Length()); + + if (str) { + return JS::StringValue(str); + } + + break; + } + case NPVariantType_Object: + { + if (npp) { + JSObject *obj = + nsNPObjWrapper::GetNewOrUsed(npp, cx, NPVARIANT_TO_OBJECT(*variant)); + + if (obj) { + return JS::ObjectValue(*obj); + } + } + + NS_ERROR("Error wrapping NPObject!"); + + break; + } + default: + NS_ERROR("Unknown NPVariant type!"); + } + + NS_ERROR("Unable to convert NPVariant to jsval!"); + + return JS::UndefinedValue(); +} + +bool +JSValToNPVariant(NPP npp, JSContext *cx, const JS::Value& val, NPVariant *variant) +{ + NS_ASSERTION(npp, "Must have an NPP to wrap a jsval!"); + + if (val.isPrimitive()) { + if (val.isUndefined()) { + VOID_TO_NPVARIANT(*variant); + } else if (val.isNull()) { + NULL_TO_NPVARIANT(*variant); + } else if (val.isBoolean()) { + BOOLEAN_TO_NPVARIANT(val.toBoolean(), *variant); + } else if (val.isInt32()) { + INT32_TO_NPVARIANT(val.toInt32(), *variant); + } else if (val.isDouble()) { + double d = val.toDouble(); + int i; + if (JS_DoubleIsInt32(d, &i)) { + INT32_TO_NPVARIANT(i, *variant); + } else { + DOUBLE_TO_NPVARIANT(d, *variant); + } + } else if (val.isString()) { + JSString *jsstr = val.toString(); + + nsAutoJSString str; + if (!str.init(cx, jsstr)) { + return false; + } + + uint32_t len; + char *p = ToNewUTF8String(str, &len); + + if (!p) { + return false; + } + + STRINGN_TO_NPVARIANT(p, len, *variant); + } else { + NS_ERROR("Unknown primitive type!"); + + return false; + } + + return true; + } + + // The reflected plugin object may be in another compartment if the plugin + // element has since been adopted into a new document. We don't bother + // transplanting the plugin objects, and just do a unwrap with security + // checks if we encounter one of them as an argument. If the unwrap fails, + // we run with the original wrapped object, since sometimes there are + // legitimate cases where a security wrapper ends up here (for example, + // Location objects, which are _always_ behind security wrappers). + JS::Rooted<JSObject*> obj(cx, val.toObjectOrNull()); + obj = js::CheckedUnwrap(obj); + if (!obj) { + obj = val.toObjectOrNull(); + } + + NPObject* npobj = nsJSObjWrapper::GetNewOrUsed(npp, obj); + if (!npobj) { + return false; + } + + // Pass over ownership of npobj to *variant + OBJECT_TO_NPVARIANT(npobj, *variant); + + return true; +} + +static void +ThrowJSExceptionASCII(JSContext *cx, const char *message) +{ + const char *ex = PeekException(); + + if (ex) { + nsAutoString ucex; + + if (message) { + AppendASCIItoUTF16(message, ucex); + + AppendASCIItoUTF16(" [plugin exception: ", ucex); + } + + AppendUTF8toUTF16(ex, ucex); + + if (message) { + AppendASCIItoUTF16("].", ucex); + } + + JSString *str = ::JS_NewUCStringCopyN(cx, ucex.get(), ucex.Length()); + + if (str) { + JS::Rooted<JS::Value> exn(cx, JS::StringValue(str)); + ::JS_SetPendingException(cx, exn); + } + + PopException(); + } else { + ::JS_ReportErrorASCII(cx, "%s", message); + } +} + +static bool +ReportExceptionIfPending(JSContext *cx) +{ + const char *ex = PeekException(); + + if (!ex) { + return true; + } + + ThrowJSExceptionASCII(cx, nullptr); + + return false; +} + +nsJSObjWrapper::nsJSObjWrapper(NPP npp) + : mJSObj(nullptr), mNpp(npp), mDestroyPending(false) +{ + MOZ_COUNT_CTOR(nsJSObjWrapper); + OnWrapperCreated(); +} + +nsJSObjWrapper::~nsJSObjWrapper() +{ + MOZ_COUNT_DTOR(nsJSObjWrapper); + + // Invalidate first, since it relies on sJSObjWrappers. + NP_Invalidate(this); + + OnWrapperDestroyed(); +} + +// static +NPObject * +nsJSObjWrapper::NP_Allocate(NPP npp, NPClass *aClass) +{ + NS_ASSERTION(aClass == &sJSObjWrapperNPClass, + "Huh, wrong class passed to NP_Allocate()!!!"); + + return new nsJSObjWrapper(npp); +} + +// static +void +nsJSObjWrapper::NP_Deallocate(NPObject *npobj) +{ + // nsJSObjWrapper::~nsJSObjWrapper() will call NP_Invalidate(). + delete (nsJSObjWrapper *)npobj; +} + +// static +void +nsJSObjWrapper::NP_Invalidate(NPObject *npobj) +{ + nsJSObjWrapper *jsnpobj = (nsJSObjWrapper *)npobj; + + if (jsnpobj && jsnpobj->mJSObj) { + + if (sJSObjWrappersAccessible) { + // Remove the wrapper from the hash + nsJSObjWrapperKey key(jsnpobj->mJSObj, jsnpobj->mNpp); + JSObjWrapperTable::Ptr ptr = sJSObjWrappers.lookup(key); + MOZ_ASSERT(ptr.found()); + sJSObjWrappers.remove(ptr); + } + + // Forget our reference to the JSObject. + jsnpobj->mJSObj = nullptr; + } +} + +static bool +GetProperty(JSContext *cx, JSObject *objArg, NPIdentifier npid, JS::MutableHandle<JS::Value> rval) +{ + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<JSObject *> obj(cx, objArg); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + return ::JS_GetPropertyById(cx, obj, id, rval); +} + +// static +bool +nsJSObjWrapper::NP_HasMethod(NPObject *npobj, NPIdentifier id) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI HasMethod"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_HasMethod!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + JSAutoCompartment ac(cx, npjsobj->mJSObj); + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + + JS::Rooted<JS::Value> v(cx); + bool ok = GetProperty(cx, npjsobj->mJSObj, id, &v); + + return ok && !v.isPrimitive() && + ::JS_ObjectIsFunction(cx, v.toObjectOrNull()); +} + +static bool +doInvoke(NPObject *npobj, NPIdentifier method, const NPVariant *args, + uint32_t argCount, bool ctorCall, NPVariant *result) +{ + NPP npp = NPPStack::Peek(); + + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + // We're about to run script via JS_CallFunctionValue, so we need an + // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec. + dom::AutoEntryScript aes(globalObject, "NPAPI doInvoke"); + JSContext *cx = aes.cx(); + + if (!npobj || !result) { + ThrowJSExceptionASCII(cx, "Null npobj, or result in doInvoke!"); + + return false; + } + + // Initialize *result + VOID_TO_NPVARIANT(*result); + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + JS::Rooted<JS::Value> fv(cx); + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + + if (method != NPIdentifier_VOID) { + if (!GetProperty(cx, jsobj, method, &fv) || + ::JS_TypeOfValue(cx, fv) != JSTYPE_FUNCTION) { + return false; + } + } else { + fv.setObject(*jsobj); + } + + // Convert args + JS::AutoValueVector jsargs(cx); + if (!jsargs.reserve(argCount)) { + ::JS_ReportOutOfMemory(cx); + return false; + } + for (uint32_t i = 0; i < argCount; ++i) { + jsargs.infallibleAppend(NPVariantToJSVal(npp, cx, args + i)); + } + + JS::Rooted<JS::Value> v(cx); + bool ok = false; + + if (ctorCall) { + JSObject *newObj = + ::JS_New(cx, jsobj, jsargs); + + if (newObj) { + v.setObject(*newObj); + ok = true; + } + } else { + ok = ::JS_CallFunctionValue(cx, jsobj, fv, jsargs, &v); + } + + if (ok) + ok = JSValToNPVariant(npp, cx, v, result); + + return ok; +} + +// static +bool +nsJSObjWrapper::NP_Invoke(NPObject *npobj, NPIdentifier method, + const NPVariant *args, uint32_t argCount, + NPVariant *result) +{ + if (method == NPIdentifier_VOID) { + return false; + } + + return doInvoke(npobj, method, args, argCount, false, result); +} + +// static +bool +nsJSObjWrapper::NP_InvokeDefault(NPObject *npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result) +{ + return doInvoke(npobj, NPIdentifier_VOID, args, argCount, false, + result); +} + +// static +bool +nsJSObjWrapper::NP_HasProperty(NPObject *npobj, NPIdentifier npid) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI HasProperty"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_HasProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + bool found, ok = false; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_HasPropertyById(cx, jsobj, id, &found); + return ok && found; +} + +// static +bool +nsJSObjWrapper::NP_GetProperty(NPObject *npobj, NPIdentifier id, + NPVariant *result) +{ + NPP npp = NPPStack::Peek(); + + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + // We're about to run script via JS_CallFunctionValue, so we need an + // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec. + dom::AutoEntryScript aes(globalObject, "NPAPI get"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_GetProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JSAutoCompartment ac(cx, npjsobj->mJSObj); + + JS::Rooted<JS::Value> v(cx); + return (GetProperty(cx, npjsobj->mJSObj, id, &v) && + JSValToNPVariant(npp, cx, v, result)); +} + +// static +bool +nsJSObjWrapper::NP_SetProperty(NPObject *npobj, NPIdentifier npid, + const NPVariant *value) +{ + NPP npp = NPPStack::Peek(); + + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + // We're about to run script via JS_CallFunctionValue, so we need an + // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec. + dom::AutoEntryScript aes(globalObject, "NPAPI set"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_SetProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + bool ok = false; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsObj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsObj); + + JS::Rooted<JS::Value> v(cx, NPVariantToJSVal(npp, cx, value)); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_SetPropertyById(cx, jsObj, id, v); + + return ok; +} + +// static +bool +nsJSObjWrapper::NP_RemoveProperty(NPObject *npobj, NPIdentifier npid) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI RemoveProperty"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_RemoveProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::ObjectOpResult result; + JS::Rooted<JSObject*> obj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, obj); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + if (!::JS_DeletePropertyById(cx, obj, id, result)) + return false; + + if (result) { + // FIXME: See bug 425823, we shouldn't need to do this, and once + // that bug is fixed we can remove this code. + bool hasProp; + if (!::JS_HasPropertyById(cx, obj, id, &hasProp)) + return false; + if (!hasProp) + return true; + + // The property might have been deleted, but it got + // re-resolved, so no, it's not really deleted. + result.failCantDelete(); + } + + return result.reportError(cx, obj, id); +} + +//static +bool +nsJSObjWrapper::NP_Enumerate(NPObject *npobj, NPIdentifier **idarray, + uint32_t *count) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI Enumerate"); + JSContext *cx = aes.cx(); + + *idarray = 0; + *count = 0; + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_Enumerate!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + + JS::Rooted<JS::IdVector> ida(cx, JS::IdVector(cx)); + if (!JS_Enumerate(cx, jsobj, &ida)) { + return false; + } + + *count = ida.length(); + *idarray = (NPIdentifier *)PR_Malloc(*count * sizeof(NPIdentifier)); + if (!*idarray) { + ThrowJSExceptionASCII(cx, "Memory allocation failed for NPIdentifier!"); + return false; + } + + for (uint32_t i = 0; i < *count; i++) { + JS::Rooted<JS::Value> v(cx); + if (!JS_IdToValue(cx, ida[i], &v)) { + PR_Free(*idarray); + return false; + } + + NPIdentifier id; + if (v.isString()) { + JS::Rooted<JSString*> str(cx, v.toString()); + str = JS_AtomizeAndPinJSString(cx, str); + if (!str) { + PR_Free(*idarray); + return false; + } + id = StringToNPIdentifier(cx, str); + } else { + NS_ASSERTION(v.isInt32(), + "The element in ida must be either string or int!\n"); + id = IntToNPIdentifier(v.toInt32()); + } + + (*idarray)[i] = id; + } + + return true; +} + +//static +bool +nsJSObjWrapper::NP_Construct(NPObject *npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result) +{ + return doInvoke(npobj, NPIdentifier_VOID, args, argCount, true, result); +} + +// Look up or create an NPObject that wraps the JSObject obj. + +// static +NPObject * +nsJSObjWrapper::GetNewOrUsed(NPP npp, JS::Handle<JSObject*> obj) +{ + if (!npp) { + NS_ERROR("Null NPP passed to nsJSObjWrapper::GetNewOrUsed()!"); + + return nullptr; + } + + // If we're running out-of-process and initializing asynchronously, and if + // the plugin has been asked to destroy itself during initialization, + // don't return any new NPObjects. + nsNPAPIPluginInstance* inst = static_cast<nsNPAPIPluginInstance*>(npp->ndata); + if (inst->GetPlugin()->GetLibrary()->IsOOP()) { + PluginAsyncSurrogate* surrogate = PluginAsyncSurrogate::Cast(npp); + if (surrogate && surrogate->IsDestroyPending()) { + return nullptr; + } + } + + // No need to enter the right compartment here as we only get the + // class and private from the JSObject, neither of which cares about + // compartments. + + if (nsNPObjWrapper::IsWrapper(obj)) { + // obj is one of our own, its private data is the NPObject we're + // looking for. + + NPObject *npobj = (NPObject *)::JS_GetPrivate(obj); + + // If the private is null, that means that the object has already been torn + // down, possible because the owning plugin was destroyed (there can be + // multiple plugins, so the fact that it was destroyed does not prevent one + // of its dead JS objects from being passed to another plugin). There's not + // much use in wrapping such a dead object, so we just return null, causing + // us to throw. + if (!npobj) + return nullptr; + + if (LookupNPP(npobj) == npp) + return _retainobject(npobj); + } + + if (!sJSObjWrappers.initialized()) { + // No hash yet (or any more), initialize it. + if (!CreateJSObjWrapperTable()) + return nullptr; + } + MOZ_ASSERT(sJSObjWrappersAccessible); + + JSObjWrapperTable::Ptr p = sJSObjWrappers.lookupForAdd(nsJSObjWrapperKey(obj, npp)); + if (p) { + MOZ_ASSERT(p->value()); + // Found a live nsJSObjWrapper, return it. + + return _retainobject(p->value()); + } + + // No existing nsJSObjWrapper, create one. + + nsJSObjWrapper *wrapper = + (nsJSObjWrapper *)_createobject(npp, &sJSObjWrapperNPClass); + + if (!wrapper) { + // Out of memory, entry not yet added to table. + return nullptr; + } + + wrapper->mJSObj = obj; + + // Insert the new wrapper into the hashtable, rooting the JSObject. Its + // lifetime is now tied to that of the NPObject. + if (!sJSObjWrappers.putNew(nsJSObjWrapperKey(obj, npp), wrapper)) { + // Out of memory, free the wrapper we created. + _releaseobject(wrapper); + return nullptr; + } + + return wrapper; +} + +// Climb the prototype chain, unwrapping as necessary until we find an NP object +// wrapper. +// +// Because this function unwraps, its return value must be wrapped for the cx +// compartment for callers that plan to hold onto the result or do anything +// substantial with it. +static JSObject * +GetNPObjectWrapper(JSContext *cx, JS::Handle<JSObject*> aObj, bool wrapResult = true) +{ + JS::Rooted<JSObject*> obj(cx, aObj); + while (obj && (obj = js::CheckedUnwrap(obj))) { + if (nsNPObjWrapper::IsWrapper(obj)) { + if (wrapResult && !JS_WrapObject(cx, &obj)) { + return nullptr; + } + return obj; + } + + JSAutoCompartment ac(cx, obj); + if (!::JS_GetPrototype(cx, obj, &obj)) { + return nullptr; + } + } + return nullptr; +} + +static NPObject * +GetNPObject(JSContext *cx, JS::Handle<JSObject*> aObj) +{ + JS::Rooted<JSObject*> obj(cx, aObj); + obj = GetNPObjectWrapper(cx, obj, /* wrapResult = */ false); + if (!obj) { + return nullptr; + } + + return (NPObject *)::JS_GetPrivate(obj); +} + + +// Does not actually add a property because this is always followed by a +// SetProperty call. +static bool +NPObjWrapper_AddProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::Handle<JS::Value> v) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->hasMethod) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + if (NPObjectIsOutOfProcessProxy(npobj)) { + return true; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (hasProperty) + return true; + + // We must permit methods here since JS_DefineUCFunction() will add + // the function as a property + bool hasMethod = npobj->_class->hasMethod(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (!hasMethod) { + ThrowJSExceptionASCII(cx, "Trying to add unsupported property on NPObject!"); + + return false; + } + + return true; +} + +static bool +NPObjWrapper_DelProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::ObjectOpResult &result) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->removeProperty) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + if (!NPObjectIsOutOfProcessProxy(npobj)) { + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (!hasProperty) + return result.succeed(); + } + + // This removeProperty hook may throw an exception and return false; or just + // return false without an exception pending, which behaves like `delete + // obj.prop` returning false: in strict mode it becomes a TypeError. Legacy + // code---nothing else that uses the JSAPI works this way anymore. + bool succeeded = npobj->_class->removeProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + return succeeded ? result.succeed() : result.failCantDelete(); +} + +static bool +NPObjWrapper_SetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp, JS::ObjectOpResult &result) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->setProperty) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + // Find out what plugin (NPP) is the owner of the object we're + // manipulating, and make it own any JSObject wrappers created here. + NPP npp = LookupNPP(npobj); + + if (!npp) { + ThrowJSExceptionASCII(cx, "No NPP found for NPObject!"); + + return false; + } + + PluginDestructionGuard pdg(npp); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + if (!NPObjectIsOutOfProcessProxy(npobj)) { + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (!hasProperty) { + ThrowJSExceptionASCII(cx, "Trying to set unsupported property on NPObject!"); + + return false; + } + } + + NPVariant npv; + if (!JSValToNPVariant(npp, cx, vp, &npv)) { + ThrowJSExceptionASCII(cx, "Error converting jsval to NPVariant!"); + + return false; + } + + bool ok = npobj->_class->setProperty(npobj, identifier, &npv); + _releasevariantvalue(&npv); // Release the variant + if (!ReportExceptionIfPending(cx)) + return false; + + if (!ok) { + ThrowJSExceptionASCII(cx, "Error setting property on NPObject!"); + + return false; + } + + return result.succeed(); +} + +static bool +NPObjWrapper_GetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->hasMethod || !npobj->_class->getProperty) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + if (JSID_IS_SYMBOL(id)) { + JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id)); + if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) { + JS::RootedObject obj(cx, JS_GetFunctionObject( + JS_NewFunction( + cx, NPObjWrapper_toPrimitive, 1, 0, + "Symbol.toPrimitive"))); + if (!obj) + return false; + vp.setObject(*obj); + return true; + } + + if (JS::GetSymbolCode(sym) == JS::SymbolCode::toStringTag) { + JS::RootedString tag(cx, JS_NewStringCopyZ(cx, NPRUNTIME_JSCLASS_NAME)); + if (!tag) { + return false; + } + + vp.setString(tag); + return true; + } + + vp.setUndefined(); + return true; + } + + // Find out what plugin (NPP) is the owner of the object we're + // manipulating, and make it own any JSObject wrappers created here. + NPP npp = LookupNPP(npobj); + if (!npp) { + ThrowJSExceptionASCII(cx, "No NPP found for NPObject!"); + + return false; + } + + PluginDestructionGuard pdg(npp); + + bool hasProperty, hasMethod; + + NPVariant npv; + VOID_TO_NPVARIANT(npv); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + if (NPObjectIsOutOfProcessProxy(npobj)) { + PluginScriptableObjectParent* actor = nullptr; + PluginAsyncSurrogate* surrogate = nullptr; + CastNPObject(npobj, actor, surrogate); + + // actor and surrogate may be null if the plugin crashed. + if (!actor && !surrogate) + return false; + + bool success = false; + if (surrogate) { + success = surrogate->GetPropertyHelper(npobj, identifier, &hasProperty, + &hasMethod, &npv); + } else if (actor) { + success = actor->GetPropertyHelper(identifier, &hasProperty, &hasMethod, + &npv); + } + + if (!ReportExceptionIfPending(cx)) { + if (success) + _releasevariantvalue(&npv); + return false; + } + + if (success) { + // We return NPObject Member class here to support ambiguous members. + if (hasProperty && hasMethod) + return CreateNPObjectMember(npp, cx, obj, npobj, id, &npv, vp); + + if (hasProperty) { + vp.set(NPVariantToJSVal(npp, cx, &npv)); + _releasevariantvalue(&npv); + + if (!ReportExceptionIfPending(cx)) + return false; + } + } + return true; + } + + hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + hasMethod = npobj->_class->hasMethod(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + // We return NPObject Member class here to support ambiguous members. + if (hasProperty && hasMethod) + return CreateNPObjectMember(npp, cx, obj, npobj, id, nullptr, vp); + + if (hasProperty) { + if (npobj->_class->getProperty(npobj, identifier, &npv)) + vp.set(NPVariantToJSVal(npp, cx, &npv)); + + _releasevariantvalue(&npv); + + if (!ReportExceptionIfPending(cx)) + return false; + } + + return true; +} + +static bool +CallNPMethodInternal(JSContext *cx, JS::Handle<JSObject*> obj, unsigned argc, + JS::Value *argv, JS::Value *rval, bool ctorCall) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + // Find out what plugin (NPP) is the owner of the object we're + // manipulating, and make it own any JSObject wrappers created here. + NPP npp = LookupNPP(npobj); + + if (!npp) { + ThrowJSExceptionASCII(cx, "Error finding NPP for NPObject!"); + + return false; + } + + PluginDestructionGuard pdg(npp); + + NPVariant npargs_buf[8]; + NPVariant *npargs = npargs_buf; + + if (argc > (sizeof(npargs_buf) / sizeof(NPVariant))) { + // Our stack buffer isn't large enough to hold all arguments, + // malloc a buffer. + npargs = (NPVariant *)PR_Malloc(argc * sizeof(NPVariant)); + + if (!npargs) { + ThrowJSExceptionASCII(cx, "Out of memory!"); + + return false; + } + } + + // Convert arguments + uint32_t i; + for (i = 0; i < argc; ++i) { + if (!JSValToNPVariant(npp, cx, argv[i], npargs + i)) { + ThrowJSExceptionASCII(cx, "Error converting jsvals to NPVariants!"); + + if (npargs != npargs_buf) { + PR_Free(npargs); + } + + return false; + } + } + + NPVariant v; + VOID_TO_NPVARIANT(v); + + JSObject *funobj = argv[-2].toObjectOrNull(); + bool ok; + const char *msg = "Error calling method on NPObject!"; + + if (ctorCall) { + // construct a new NPObject based on the NPClass in npobj. Fail if + // no construct method is available. + + if (NP_CLASS_STRUCT_VERSION_HAS_CTOR(npobj->_class) && + npobj->_class->construct) { + ok = npobj->_class->construct(npobj, npargs, argc, &v); + } else { + ok = false; + + msg = "Attempt to construct object from class with no constructor."; + } + } else if (funobj != obj) { + // A obj.function() style call is made, get the method name from + // the function object. + + if (npobj->_class->invoke) { + JSFunction *fun = ::JS_GetObjectFunction(funobj); + JS::Rooted<JSString*> funId(cx, ::JS_GetFunctionId(fun)); + JSString *name = ::JS_AtomizeAndPinJSString(cx, funId); + NPIdentifier id = StringToNPIdentifier(cx, name); + + ok = npobj->_class->invoke(npobj, id, npargs, argc, &v); + } else { + ok = false; + + msg = "Attempt to call a method on object with no invoke method."; + } + } else { + if (npobj->_class->invokeDefault) { + // obj is a callable object that is being called, no method name + // available then. Invoke the default method. + + ok = npobj->_class->invokeDefault(npobj, npargs, argc, &v); + } else { + ok = false; + + msg = "Attempt to call a default method on object with no " + "invokeDefault method."; + } + } + + // Release arguments. + for (i = 0; i < argc; ++i) { + _releasevariantvalue(npargs + i); + } + + if (npargs != npargs_buf) { + PR_Free(npargs); + } + + if (!ok) { + // ReportExceptionIfPending returns a return value, which is true + // if no exception was thrown. In that case, throw our own. + if (ReportExceptionIfPending(cx)) + ThrowJSExceptionASCII(cx, msg); + + return false; + } + + *rval = NPVariantToJSVal(npp, cx, &v); + + // *rval now owns the value, release our reference. + _releasevariantvalue(&v); + + return ReportExceptionIfPending(cx); +} + +static bool +CallNPMethod(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + + return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false); +} + +static bool +NPObjWrapper_Enumerate(JSContext *cx, JS::Handle<JSObject*> obj, + JS::AutoIdVector &properties, bool enumerableOnly) +{ + NPObject *npobj = GetNPObject(cx, obj); + if (!npobj || !npobj->_class) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + return false; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(npobj->_class) || + !npobj->_class->enumerate) { + return true; + } + + NPIdentifier *identifiers; + uint32_t length; + if (!npobj->_class->enumerate(npobj, &identifiers, &length)) { + if (ReportExceptionIfPending(cx)) { + // ReportExceptionIfPending returns a return value, which is true + // if no exception was thrown. In that case, throw our own. + ThrowJSExceptionASCII(cx, "Error enumerating properties on scriptable " + "plugin object"); + } + return false; + } + + if (!properties.reserve(length)) + return false; + + JS::Rooted<jsid> id(cx); + for (uint32_t i = 0; i < length; i++) { + id = NPIdentifierToJSId(identifiers[i]); + properties.infallibleAppend(id); + } + + PR_Free(identifiers); + return true; +} + +static bool +NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + bool *resolvedp) +{ + if (JSID_IS_SYMBOL(id)) + return true; + + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::JS); + + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->hasMethod) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (hasProperty) { + NS_ASSERTION(JSID_IS_STRING(id) || JSID_IS_INT(id), + "id must be either string or int!\n"); + if (!::JS_DefinePropertyById(cx, obj, id, JS::UndefinedHandleValue, + JSPROP_ENUMERATE | JSPROP_SHARED)) { + return false; + } + + *resolvedp = true; + + return true; + } + + bool hasMethod = npobj->_class->hasMethod(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (hasMethod) { + NS_ASSERTION(JSID_IS_STRING(id) || JSID_IS_INT(id), + "id must be either string or int!\n"); + + JSFunction *fnc = ::JS_DefineFunctionById(cx, obj, id, CallNPMethod, 0, + JSPROP_ENUMERATE); + + *resolvedp = true; + + return fnc != nullptr; + } + + // no property or method + return true; +} + +static void +NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj) +{ + NPObject *npobj = (NPObject *)::JS_GetPrivate(obj); + if (npobj) { + if (sNPObjWrappers) { + sNPObjWrappers->Remove(npobj); + } + } + + if (!sDelayedReleases) + sDelayedReleases = new nsTArray<NPObject*>; + sDelayedReleases->AppendElement(npobj); +} + +static void +NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old) +{ + // The wrapper JSObject has been moved, so we need to update the entry in the + // sNPObjWrappers hash table, if present. + + if (!sNPObjWrappers) { + return; + } + + NPObject *npobj = (NPObject *)::JS_GetPrivate(obj); + if (!npobj) { + return; + } + + // Calling PLDHashTable::Search() will not result in GC. + JS::AutoSuppressGCAnalysis nogc; + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + MOZ_ASSERT(entry && entry->mJSObj); + MOZ_ASSERT(entry->mJSObj.unbarrieredGetPtr() == old); + entry->mJSObj = obj; +} + +static bool +NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> obj(cx, &args.callee()); + return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false); +} + +static bool +NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> obj(cx, &args.callee()); + return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, true); +} + +static bool +NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp) +{ + // Plugins do not simply use the default OrdinaryToPrimitive behavior, + // because that behavior involves calling toString or valueOf on objects + // which weren't designed to accommodate this. Usually this wouldn't be a + // problem, because the absence of either property, or the presence of either + // property with a value that isn't callable, will cause that property to + // simply be ignored. But there is a problem in one specific case: Java, + // specifically java.lang.Integer. The Integer class has static valueOf + // methods, none of which are nullary, so the JS-reflected method will behave + // poorly when called with no arguments. We work around this problem by + // giving plugins a [Symbol.toPrimitive]() method which uses only toString + // and not valueOf. + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedValue thisv(cx, args.thisv()); + if (thisv.isPrimitive()) + return true; + + JS::RootedObject obj(cx, &thisv.toObject()); + JS::RootedValue v(cx); + if (!JS_GetProperty(cx, obj, "toString", &v)) + return false; + if (v.isObject() && JS::IsCallable(&v.toObject())) { + if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), args.rval())) + return false; + if (args.rval().isPrimitive()) + return true; + } + + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, + JSMSG_CANT_CONVERT_TO, + JS_GetClass(obj)->name, "primitive type"); + return false; +} + +bool +nsNPObjWrapper::IsWrapper(JSObject *obj) +{ + return js::GetObjectClass(obj) == &sNPObjectJSWrapperClass; +} + +// An NPObject is going away, make sure we null out the JS object's +// private data in case this is an NPObject that came from a plugin +// and it's destroyed prematurely. + +// static +void +nsNPObjWrapper::OnDestroy(NPObject *npobj) +{ + if (!npobj) { + return; + } + + if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { + // npobj is one of our own, no private data to clean up here. + + return; + } + + if (!sNPObjWrappers) { + // No hash yet (or any more), no used wrappers available. + + return; + } + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + + if (entry && entry->mJSObj) { + // Found a live NPObject wrapper, null out its JSObjects' private + // data. + + ::JS_SetPrivate(entry->mJSObj, nullptr); + + // Remove the npobj from the hash now that it went away. + sNPObjWrappers->RawRemove(entry); + + // The finalize hook will call OnWrapperDestroyed(). + } +} + +// Look up or create a JSObject that wraps the NPObject npobj. + +// static +JSObject * +nsNPObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, NPObject *npobj) +{ + if (!npobj) { + NS_ERROR("Null NPObject passed to nsNPObjWrapper::GetNewOrUsed()!"); + + return nullptr; + } + + if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { + // npobj is one of our own, return its existing JSObject. + + JS::Rooted<JSObject*> obj(cx, ((nsJSObjWrapper *)npobj)->mJSObj); + if (!JS_WrapObject(cx, &obj)) { + return nullptr; + } + return obj; + } + + if (!npp) { + NS_ERROR("No npp passed to nsNPObjWrapper::GetNewOrUsed()!"); + + return nullptr; + } + + if (!sNPObjWrappers) { + // No hash yet (or any more), initialize it. + if (!CreateNPObjWrapperTable()) { + return nullptr; + } + } + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Add(npobj, fallible)); + + if (!entry) { + // Out of memory + JS_ReportOutOfMemory(cx); + + return nullptr; + } + + if (entry->mJSObj) { + // Found a live NPObject wrapper. It may not be in the same compartment + // as cx, so we need to wrap it before returning it. + JS::Rooted<JSObject*> obj(cx, entry->mJSObj); + if (!JS_WrapObject(cx, &obj)) { + return nullptr; + } + return obj; + } + + entry->mNPObj = npobj; + entry->mNpp = npp; + + uint32_t generation = sNPObjWrappers->Generation(); + + // No existing JSObject, create one. + + JS::Rooted<JSObject*> obj(cx, ::JS_NewObject(cx, js::Jsvalify(&sNPObjectJSWrapperClass))); + + if (generation != sNPObjWrappers->Generation()) { + // Reload entry if the JS_NewObject call caused a GC and reallocated + // the table (see bug 445229). This is guaranteed to succeed. + + entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + NS_ASSERTION(entry, "Hashtable didn't find what we just added?"); + } + + if (!obj) { + // OOM? Remove the stale entry from the hash. + + sNPObjWrappers->RawRemove(entry); + + return nullptr; + } + + OnWrapperCreated(); + + entry->mJSObj = obj; + + ::JS_SetPrivate(obj, npobj); + + // The new JSObject now holds on to npobj + _retainobject(npobj); + + return obj; +} + +// static +void +nsJSNPRuntime::OnPluginDestroy(NPP npp) +{ + if (sJSObjWrappersAccessible) { + + // Prevent modification of sJSObjWrappers table if we go reentrant. + sJSObjWrappersAccessible = false; + + for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) { + nsJSObjWrapper *npobj = e.front().value(); + MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass); + if (npobj->mNpp == npp) { + if (npobj->_class && npobj->_class->invalidate) { + npobj->_class->invalidate(npobj); + } + + _releaseobject(npobj); + + e.removeFront(); + } + } + + sJSObjWrappersAccessible = true; + } + + if (sNPObjWrappers) { + for (auto i = sNPObjWrappers->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast<NPObjWrapperHashEntry*>(i.Get()); + + if (entry->mNpp == npp) { + // HACK: temporarily hide the table we're enumerating so that + // invalidate() and deallocate() don't touch it. + PLDHashTable *tmp = sNPObjWrappers; + sNPObjWrappers = nullptr; + + NPObject *npobj = entry->mNPObj; + + if (npobj->_class && npobj->_class->invalidate) { + npobj->_class->invalidate(npobj); + } + +#ifdef NS_BUILD_REFCNT_LOGGING + { + int32_t refCnt = npobj->referenceCount; + while (refCnt) { + --refCnt; + NS_LOG_RELEASE(npobj, refCnt, "BrowserNPObject"); + } + } +#endif + + // Force deallocation of plugin objects since the plugin they came + // from is being torn down. + if (npobj->_class && npobj->_class->deallocate) { + npobj->_class->deallocate(npobj); + } else { + PR_Free(npobj); + } + + ::JS_SetPrivate(entry->mJSObj, nullptr); + + sNPObjWrappers = tmp; + + if (sDelayedReleases && sDelayedReleases->RemoveElement(npobj)) { + OnWrapperDestroyed(); + } + + i.Remove(); + } + } + } +} + +// static +void +nsJSNPRuntime::OnPluginDestroyPending(NPP npp) +{ + if (sJSObjWrappersAccessible) { + // Prevent modification of sJSObjWrappers table if we go reentrant. + sJSObjWrappersAccessible = false; + for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) { + nsJSObjWrapper *npobj = e.front().value(); + MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass); + if (npobj->mNpp == npp) { + npobj->mDestroyPending = true; + } + } + sJSObjWrappersAccessible = true; + } +} + +// Find the NPP for a NPObject. +static NPP +LookupNPP(NPObject *npobj) +{ + if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { + nsJSObjWrapper* o = static_cast<nsJSObjWrapper*>(npobj); + return o->mNpp; + } + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Add(npobj, fallible)); + + if (!entry) { + return nullptr; + } + + NS_ASSERTION(entry->mNpp, "Live NPObject entry w/o an NPP!"); + + return entry->mNpp; +} + +static bool +CreateNPObjectMember(NPP npp, JSContext *cx, + JS::Handle<JSObject*> aObj, NPObject* npobj, + JS::Handle<jsid> id, NPVariant* getPropertyResult, + JS::MutableHandle<JS::Value> vp) +{ + if (!npobj || !npobj->_class || !npobj->_class->getProperty || + !npobj->_class->invoke) { + ThrowJSExceptionASCII(cx, "Bad NPObject"); + + return false; + } + + NPObjectMemberPrivate *memberPrivate = + (NPObjectMemberPrivate *)PR_Malloc(sizeof(NPObjectMemberPrivate)); + if (!memberPrivate) + return false; + + // Make sure to clear all members in case something fails here + // during initialization. + memset(memberPrivate, 0, sizeof(NPObjectMemberPrivate)); + + JS::Rooted<JSObject*> obj(cx, aObj); + + JSObject *memobj = ::JS_NewObject(cx, &sNPObjectMemberClass); + if (!memobj) { + PR_Free(memberPrivate); + return false; + } + + vp.setObject(*memobj); + + ::JS_SetPrivate(memobj, (void *)memberPrivate); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + JS::Rooted<JS::Value> fieldValue(cx); + NPVariant npv; + + if (getPropertyResult) { + // Plugin has already handed us the value we want here. + npv = *getPropertyResult; + } + else { + VOID_TO_NPVARIANT(npv); + + NPBool hasProperty = npobj->_class->getProperty(npobj, identifier, + &npv); + if (!ReportExceptionIfPending(cx) || !hasProperty) { + return false; + } + } + + fieldValue = NPVariantToJSVal(npp, cx, &npv); + + // npobjWrapper is the JSObject through which we make sure we don't + // outlive the underlying NPObject, so make sure it points to the + // real JSObject wrapper for the NPObject. + obj = GetNPObjectWrapper(cx, obj); + + memberPrivate->npobjWrapper = obj; + + memberPrivate->fieldValue = fieldValue; + memberPrivate->methodName = id; + memberPrivate->npp = npp; + + return true; +} + +static bool +NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id, + JS::MutableHandleValue vp) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + + if (JSID_IS_SYMBOL(id)) { + JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id)); + if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) { + JS::RootedObject obj(cx, JS_GetFunctionObject( + JS_NewFunction( + cx, NPObjectMember_toPrimitive, 1, 0, + "Symbol.toPrimitive"))); + if (!obj) + return false; + vp.setObject(*obj); + return true; + } + } + + return true; +} + +static void +NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj) +{ + NPObjectMemberPrivate *memberPrivate; + + memberPrivate = (NPObjectMemberPrivate *)::JS_GetPrivate(obj); + if (!memberPrivate) + return; + + PR_Free(memberPrivate); +} + +static bool +NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> memobj(cx, &args.callee()); + NS_ENSURE_TRUE(memobj, false); + + NPObjectMemberPrivate *memberPrivate = + (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, memobj, + &sNPObjectMemberClass, + &args); + if (!memberPrivate || !memberPrivate->npobjWrapper) + return false; + + JS::Rooted<JSObject*> objWrapper(cx, memberPrivate->npobjWrapper); + NPObject *npobj = GetNPObject(cx, objWrapper); + if (!npobj) { + ThrowJSExceptionASCII(cx, "Call on invalid member object"); + + return false; + } + + NPVariant npargs_buf[8]; + NPVariant *npargs = npargs_buf; + + if (args.length() > (sizeof(npargs_buf) / sizeof(NPVariant))) { + // Our stack buffer isn't large enough to hold all arguments, + // malloc a buffer. + npargs = (NPVariant *)PR_Malloc(args.length() * sizeof(NPVariant)); + + if (!npargs) { + ThrowJSExceptionASCII(cx, "Out of memory!"); + + return false; + } + } + + // Convert arguments + for (uint32_t i = 0; i < args.length(); ++i) { + if (!JSValToNPVariant(memberPrivate->npp, cx, args[i], npargs + i)) { + ThrowJSExceptionASCII(cx, "Error converting jsvals to NPVariants!"); + + if (npargs != npargs_buf) { + PR_Free(npargs); + } + + return false; + } + } + + + NPVariant npv; + bool ok = npobj->_class->invoke(npobj, + JSIdToNPIdentifier(memberPrivate->methodName), + npargs, args.length(), &npv); + + // Release arguments. + for (uint32_t i = 0; i < args.length(); ++i) { + _releasevariantvalue(npargs + i); + } + + if (npargs != npargs_buf) { + PR_Free(npargs); + } + + if (!ok) { + // ReportExceptionIfPending returns a return value, which is true + // if no exception was thrown. In that case, throw our own. + if (ReportExceptionIfPending(cx)) + ThrowJSExceptionASCII(cx, "Error calling method on NPObject!"); + + return false; + } + + args.rval().set(NPVariantToJSVal(memberPrivate->npp, cx, &npv)); + + // *vp now owns the value, release our reference. + _releasevariantvalue(&npv); + + return ReportExceptionIfPending(cx); +} + +static void +NPObjectMember_Trace(JSTracer *trc, JSObject *obj) +{ + NPObjectMemberPrivate *memberPrivate = + (NPObjectMemberPrivate *)::JS_GetPrivate(obj); + if (!memberPrivate) + return; + + // Our NPIdentifier is not always interned, so we must trace it. + JS::TraceEdge(trc, &memberPrivate->methodName, "NPObjectMemberPrivate.methodName"); + + JS::TraceEdge(trc, &memberPrivate->fieldValue, "NPObject Member => fieldValue"); + + // There's no strong reference from our private data to the + // NPObject, so make sure to mark the NPObject wrapper to keep the + // NPObject alive as long as this NPObjectMember is alive. + JS::TraceEdge(trc, &memberPrivate->npobjWrapper, + "NPObject Member => npobjWrapper"); +} + +static bool +NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedValue thisv(cx, args.thisv()); + if (thisv.isPrimitive()) { + args.rval().set(thisv); + return true; + } + + JS::RootedObject obj(cx, &thisv.toObject()); + NPObjectMemberPrivate *memberPrivate = + (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, obj, + &sNPObjectMemberClass, + &args); + if (!memberPrivate) + return false; + + JSType hint; + if (!JS::GetFirstArgumentAsTypeHint(cx, args, &hint)) + return false; + + args.rval().set(memberPrivate->fieldValue); + if (args.rval().isObject()) { + JS::Rooted<JSObject*> objVal(cx, &args.rval().toObject()); + return JS::ToPrimitive(cx, objVal, hint, args.rval()); + } + return true; +} + +// static +bool +nsJSObjWrapper::HasOwnProperty(NPObject *npobj, NPIdentifier npid) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI HasOwnProperty"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_HasOwnProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + bool found, ok = false; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_AlreadyHasOwnPropertyById(cx, jsobj, id, &found); + return ok && found; +} diff --git a/dom/plugins/base/nsJSNPRuntime.h b/dom/plugins/base/nsJSNPRuntime.h new file mode 100644 index 000000000..871988e06 --- /dev/null +++ b/dom/plugins/base/nsJSNPRuntime.h @@ -0,0 +1,108 @@ +/* -*- 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/. */ + +#ifndef nsJSNPRuntime_h_ +#define nsJSNPRuntime_h_ + +#include "nscore.h" +#include "npapi.h" +#include "npruntime.h" +#include "PLDHashTable.h" +#include "js/RootingAPI.h" + +class nsJSNPRuntime +{ +public: + static void OnPluginDestroy(NPP npp); + static void OnPluginDestroyPending(NPP npp); +}; + +class nsJSObjWrapperKey +{ +public: + nsJSObjWrapperKey(JSObject *obj, NPP npp) + : mJSObj(obj), mNpp(npp) + { + } + + bool operator==(const nsJSObjWrapperKey& other) const { + return mJSObj == other.mJSObj && mNpp == other.mNpp; + } + bool operator!=(const nsJSObjWrapperKey& other) const { + return !(*this == other); + } + + void trace(JSTracer* trc) { + JS::TraceEdge(trc, &mJSObj, "nsJSObjWrapperKey"); + } + + nsJSObjWrapperKey(const nsJSObjWrapperKey& other) + : mJSObj(other.mJSObj), + mNpp(other.mNpp) + {} + void operator=(const nsJSObjWrapperKey& other) { + mJSObj = other.mJSObj; + mNpp = other.mNpp; + } + + JS::Heap<JSObject*> mJSObj; + NPP mNpp; +}; + +class nsJSObjWrapper : public NPObject +{ +public: + JS::Heap<JSObject *> mJSObj; + const NPP mNpp; + bool mDestroyPending; + + static NPObject* GetNewOrUsed(NPP npp, JS::Handle<JSObject*> obj); + static bool HasOwnProperty(NPObject* npobj, NPIdentifier npid); + + void trace(JSTracer* trc) { + JS::TraceEdge(trc, &mJSObj, "nsJSObjWrapper"); + } + +protected: + explicit nsJSObjWrapper(NPP npp); + ~nsJSObjWrapper(); + + static NPObject * NP_Allocate(NPP npp, NPClass *aClass); + static void NP_Deallocate(NPObject *obj); + static void NP_Invalidate(NPObject *obj); + static bool NP_HasMethod(NPObject *, NPIdentifier identifier); + static bool NP_Invoke(NPObject *obj, NPIdentifier method, + const NPVariant *args, uint32_t argCount, + NPVariant *result); + static bool NP_InvokeDefault(NPObject *obj, const NPVariant *args, + uint32_t argCount, NPVariant *result); + static bool NP_HasProperty(NPObject * obj, NPIdentifier property); + static bool NP_GetProperty(NPObject *obj, NPIdentifier property, + NPVariant *result); + static bool NP_SetProperty(NPObject *obj, NPIdentifier property, + const NPVariant *value); + static bool NP_RemoveProperty(NPObject *obj, NPIdentifier property); + static bool NP_Enumerate(NPObject *npobj, NPIdentifier **identifier, + uint32_t *count); + static bool NP_Construct(NPObject *obj, const NPVariant *args, + uint32_t argCount, NPVariant *result); + +public: + static NPClass sJSObjWrapperNPClass; +}; + +class nsNPObjWrapper +{ +public: + static bool IsWrapper(JSObject *obj); + static void OnDestroy(NPObject *npobj); + static JSObject *GetNewOrUsed(NPP npp, JSContext *cx, NPObject *npobj); +}; + +bool +JSValToNPVariant(NPP npp, JSContext *cx, const JS::Value& val, NPVariant *variant); + + +#endif // nsJSNPRuntime_h_ diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp new file mode 100644 index 000000000..1bea269cd --- /dev/null +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -0,0 +1,2819 @@ +/* -*- 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 "base/basictypes.h" + +/* This must occur *after* layers/PLayerTransaction.h to avoid typedefs conflicts. */ +#include "mozilla/ArrayUtils.h" + +#include "pratom.h" +#include "prmem.h" +#include "prenv.h" +#include "prclist.h" + +#include "jsfriendapi.h" + +#include "nsPluginHost.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsPluginStreamListenerPeer.h" +#include "nsIServiceManager.h" +#include "nsThreadUtils.h" +#include "mozilla/Preferences.h" +#include "nsPluginInstanceOwner.h" + +#include "nsPluginsDir.h" +#include "nsPluginLogging.h" + +#include "nsIDOMElement.h" +#include "nsPIDOMWindow.h" +#include "nsGlobalWindow.h" +#include "nsIDocument.h" +#include "nsIContent.h" +#include "nsIIDNService.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsDOMJSUtils.h" +#include "nsIPrincipal.h" +#include "nsWildCard.h" +#include "nsContentUtils.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsIXULRuntime.h" +#include "nsIXPConnect.h" + +#include "nsIObserverService.h" +#include <prinrval.h> + +#ifdef MOZ_WIDGET_COCOA +#include <Carbon/Carbon.h> +#include <ApplicationServices/ApplicationServices.h> +#include <OpenGL/OpenGL.h> +#include "nsCocoaFeatures.h" +#include "PluginUtilsOSX.h" +#endif + +// needed for nppdf plugin +#if (MOZ_WIDGET_GTK) +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#if (MOZ_WIDGET_GTK == 2) +#include "gtk2xtbin.h" +#endif +#endif + +#include "nsJSUtils.h" +#include "nsJSNPRuntime.h" +#include "nsIHttpAuthManager.h" +#include "nsICookieService.h" +#include "nsILoadContext.h" +#include "nsIDocShell.h" + +#include "nsNetUtil.h" +#include "nsNetCID.h" + +#include "mozilla/Mutex.h" +#include "mozilla/PluginLibrary.h" +using mozilla::PluginLibrary; + +#include "mozilla/PluginPRLibrary.h" +using mozilla::PluginPRLibrary; + +#include "mozilla/plugins/PluginModuleParent.h" +using mozilla::plugins::PluginModuleChromeParent; +using mozilla::plugins::PluginModuleContentParent; + +#ifdef MOZ_X11 +#include "mozilla/X11Util.h" +#endif + +#ifdef XP_WIN +#include <windows.h> +#include "mozilla/WindowsVersion.h" +#ifdef ACCESSIBILITY +#include "mozilla/a11y/Compatibility.h" +#endif +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include <android/log.h> +#include "android_npapi.h" +#include "ANPBase.h" +#include "GeneratedJNIWrappers.h" +#undef LOG +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#endif + +#include "nsIAudioChannelAgent.h" +#include "AudioChannelService.h" + +using namespace mozilla; +using namespace mozilla::plugins::parent; + +// We should make this const... +static NPNetscapeFuncs sBrowserFuncs = { + sizeof(sBrowserFuncs), + (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR, + _geturl, + _posturl, + _requestread, + _newstream, + _write, + _destroystream, + _status, + _useragent, + _memalloc, + _memfree, + _memflush, + _reloadplugins, + _getJavaEnv, + _getJavaPeer, + _geturlnotify, + _posturlnotify, + _getvalue, + _setvalue, + _invalidaterect, + _invalidateregion, + _forceredraw, + _getstringidentifier, + _getstringidentifiers, + _getintidentifier, + _identifierisstring, + _utf8fromidentifier, + _intfromidentifier, + _createobject, + _retainobject, + _releaseobject, + _invoke, + _invokeDefault, + _evaluate, + _getproperty, + _setproperty, + _removeproperty, + _hasproperty, + _hasmethod, + _releasevariantvalue, + _setexception, + _pushpopupsenabledstate, + _poppopupsenabledstate, + _enumerate, + _pluginthreadasynccall, + _construct, + _getvalueforurl, + _setvalueforurl, + _getauthenticationinfo, + _scheduletimer, + _unscheduletimer, + _popupcontextmenu, + _convertpoint, + nullptr, // handleevent, unimplemented + nullptr, // unfocusinstance, unimplemented + _urlredirectresponse, + _initasyncsurface, + _finalizeasyncsurface, + _setcurrentasyncsurface +}; + +static Mutex *sPluginThreadAsyncCallLock = nullptr; +static PRCList sPendingAsyncCalls = PR_INIT_STATIC_CLIST(&sPendingAsyncCalls); + +// POST/GET stream type +enum eNPPStreamTypeInternal { + eNPPStreamTypeInternal_Get, + eNPPStreamTypeInternal_Post +}; + +void NS_NotifyBeginPluginCall(NSPluginCallReentry aReentryState) +{ + nsNPAPIPluginInstance::BeginPluginCall(aReentryState); +} + +void NS_NotifyPluginCall(NSPluginCallReentry aReentryState) +{ + nsNPAPIPluginInstance::EndPluginCall(aReentryState); +} + +static void CheckClassInitialized() +{ + static bool initialized = false; + + if (initialized) + return; + + if (!sPluginThreadAsyncCallLock) + sPluginThreadAsyncCallLock = new Mutex("nsNPAPIPlugin.sPluginThreadAsyncCallLock"); + + initialized = true; + + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL,("NPN callbacks initialized\n")); +} + +nsNPAPIPlugin::nsNPAPIPlugin() +{ + memset((void*)&mPluginFuncs, 0, sizeof(mPluginFuncs)); + mPluginFuncs.size = sizeof(mPluginFuncs); + mPluginFuncs.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + + mLibrary = nullptr; +} + +nsNPAPIPlugin::~nsNPAPIPlugin() +{ + delete mLibrary; + mLibrary = nullptr; +} + +void +nsNPAPIPlugin::PluginCrashed(const nsAString& pluginDumpID, + const nsAString& browserDumpID) +{ + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + host->PluginCrashed(this, pluginDumpID, browserDumpID); +} + +bool +nsNPAPIPlugin::RunPluginOOP(const nsPluginTag *aPluginTag) +{ +#ifdef MOZ_WIDGET_ANDROID + return false; +#else + return true; +#endif +} + +inline PluginLibrary* +GetNewPluginLibrary(nsPluginTag *aPluginTag) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + + if (!aPluginTag) { + return nullptr; + } + + if (XRE_IsContentProcess()) { + return PluginModuleContentParent::LoadModule(aPluginTag->mId, aPluginTag); + } + + if (nsNPAPIPlugin::RunPluginOOP(aPluginTag)) { + return PluginModuleChromeParent::LoadModule(aPluginTag->mFullPath.get(), aPluginTag->mId, aPluginTag); + } + return new PluginPRLibrary(aPluginTag->mFullPath.get(), aPluginTag->mLibrary); +} + +// Creates an nsNPAPIPlugin object. One nsNPAPIPlugin object exists per plugin (not instance). +nsresult +nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + *aResult = nullptr; + + if (!aPluginTag) { + return NS_ERROR_FAILURE; + } + + CheckClassInitialized(); + + RefPtr<nsNPAPIPlugin> plugin = new nsNPAPIPlugin(); + + PluginLibrary* pluginLib = GetNewPluginLibrary(aPluginTag); + if (!pluginLib) { + return NS_ERROR_FAILURE; + } + +#if defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) + if (!pluginLib->HasRequiredFunctions()) { + NS_WARNING("Not all necessary functions exposed by plugin, it will not load."); + delete pluginLib; + return NS_ERROR_FAILURE; + } +#endif + + plugin->mLibrary = pluginLib; + pluginLib->SetPlugin(plugin); + +// Exchange NPAPI entry points. +#if defined(XP_WIN) + // NP_GetEntryPoints must be called before NP_Initialize on Windows. + NPError pluginCallError; + nsresult rv = pluginLib->NP_GetEntryPoints(&plugin->mPluginFuncs, &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } + + // NP_Initialize must be called after NP_GetEntryPoints on Windows. + rv = pluginLib->NP_Initialize(&sBrowserFuncs, &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_MACOSX) + // NP_Initialize must be called before NP_GetEntryPoints on Mac OS X. + // We need to match WebKit's behavior. + NPError pluginCallError; + nsresult rv = pluginLib->NP_Initialize(&sBrowserFuncs, &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } + + rv = pluginLib->NP_GetEntryPoints(&plugin->mPluginFuncs, &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } +#elif defined(MOZ_WIDGET_GONK) +#else + NPError pluginCallError; + nsresult rv = pluginLib->NP_Initialize(&sBrowserFuncs, &plugin->mPluginFuncs, &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } +#endif + + plugin.forget(aResult); + return NS_OK; +} + +PluginLibrary* +nsNPAPIPlugin::GetLibrary() +{ + return mLibrary; +} + +NPPluginFuncs* +nsNPAPIPlugin::PluginFuncs() +{ + return &mPluginFuncs; +} + +nsresult +nsNPAPIPlugin::Shutdown() +{ + NPP_PLUGIN_LOG(PLUGIN_LOG_BASIC, + ("NPP Shutdown to be called: this=%p\n", this)); + + NPError shutdownError; + mLibrary->NP_Shutdown(&shutdownError); + + return NS_OK; +} + +nsresult +nsNPAPIPlugin::RetainStream(NPStream *pstream, nsISupports **aRetainedPeer) +{ + if (!aRetainedPeer) + return NS_ERROR_NULL_POINTER; + + *aRetainedPeer = nullptr; + + if (!pstream || !pstream->ndata) + return NS_ERROR_NULL_POINTER; + + nsNPAPIStreamWrapper* streamWrapper = static_cast<nsNPAPIStreamWrapper*>(pstream->ndata); + nsNPAPIPluginStreamListener* listener = streamWrapper->GetStreamListener(); + if (!listener) { + return NS_ERROR_NULL_POINTER; + } + + nsIStreamListener* streamListener = listener->GetStreamListenerPeer(); + if (!streamListener) { + return NS_ERROR_NULL_POINTER; + } + + *aRetainedPeer = streamListener; + NS_ADDREF(*aRetainedPeer); + return NS_OK; +} + +// Create a new NPP GET or POST (given in the type argument) url +// stream that may have a notify callback +NPError +MakeNewNPAPIStreamInternal(NPP npp, const char *relativeURL, const char *target, + eNPPStreamTypeInternal type, + bool bDoNotify = false, + void *notifyData = nullptr, uint32_t len = 0, + const char *buf = nullptr, NPBool file = false) +{ + if (!npp) + return NPERR_INVALID_INSTANCE_ERROR; + + PluginDestructionGuard guard(npp); + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *) npp->ndata; + if (!inst || !inst->IsRunning()) + return NPERR_INVALID_INSTANCE_ERROR; + + nsCOMPtr<nsIPluginHost> pluginHostCOM = do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); + nsPluginHost *pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get()); + if (!pluginHost) { + return NPERR_GENERIC_ERROR; + } + + RefPtr<nsNPAPIPluginStreamListener> listener; + // Set aCallNotify here to false. If pluginHost->GetURL or PostURL fail, + // the listener's destructor will do the notification while we are about to + // return a failure code. + // Call SetCallNotify(true) below after we are sure we cannot return a failure + // code. + if (!target) { + inst->NewStreamListener(relativeURL, notifyData, + getter_AddRefs(listener)); + if (listener) { + listener->SetCallNotify(false); + } + } + + switch (type) { + case eNPPStreamTypeInternal_Get: + { + if (NS_FAILED(pluginHost->GetURL(inst, relativeURL, target, listener, + nullptr, nullptr, false))) + return NPERR_GENERIC_ERROR; + break; + } + case eNPPStreamTypeInternal_Post: + { + if (NS_FAILED(pluginHost->PostURL(inst, relativeURL, len, buf, file, + target, listener, nullptr, nullptr, + false, 0, nullptr))) + return NPERR_GENERIC_ERROR; + break; + } + default: + NS_ERROR("how'd I get here"); + } + + if (listener) { + // SetCallNotify(bDoNotify) here, see comment above. + listener->SetCallNotify(bDoNotify); + } + + return NPERR_NO_ERROR; +} + +#if defined(MOZ_MEMORY_WINDOWS) +extern "C" size_t malloc_usable_size(const void *ptr); +#endif + +namespace { + +static char *gNPPException; + +class nsPluginThreadRunnable : public Runnable, + public PRCList +{ +public: + nsPluginThreadRunnable(NPP instance, PluginThreadCallback func, + void *userData); + virtual ~nsPluginThreadRunnable(); + + NS_IMETHOD Run(); + + bool IsForInstance(NPP instance) + { + return (mInstance == instance); + } + + void Invalidate() + { + mFunc = nullptr; + } + + bool IsValid() + { + return (mFunc != nullptr); + } + +private: + NPP mInstance; + PluginThreadCallback mFunc; + void *mUserData; +}; + +static nsIDocument * +GetDocumentFromNPP(NPP npp) +{ + NS_ENSURE_TRUE(npp, nullptr); + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)npp->ndata; + NS_ENSURE_TRUE(inst, nullptr); + + PluginDestructionGuard guard(inst); + + RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner(); + NS_ENSURE_TRUE(owner, nullptr); + + nsCOMPtr<nsIDocument> doc; + owner->GetDocument(getter_AddRefs(doc)); + + return doc; +} + +static already_AddRefed<nsIChannel> +GetChannelFromNPP(NPP npp) +{ + nsCOMPtr<nsIDocument> doc = GetDocumentFromNPP(npp); + if (!doc) + return nullptr; + nsCOMPtr<nsPIDOMWindowOuter> domwindow = doc->GetWindow(); + nsCOMPtr<nsIChannel> channel; + if (domwindow) { + nsCOMPtr<nsIDocShell> docShell = domwindow->GetDocShell(); + if (docShell) { + docShell->GetCurrentDocumentChannel(getter_AddRefs(channel)); + } + } + return channel.forget(); +} + +static NPIdentifier +doGetIdentifier(JSContext *cx, const NPUTF8* name) +{ + NS_ConvertUTF8toUTF16 utf16name(name); + + JSString *str = ::JS_AtomizeAndPinUCStringN(cx, utf16name.get(), utf16name.Length()); + + if (!str) + return nullptr; + + return StringToNPIdentifier(cx, str); +} + +#if defined(MOZ_MEMORY_WINDOWS) +BOOL +InHeap(HANDLE hHeap, LPVOID lpMem) +{ + BOOL success = FALSE; + PROCESS_HEAP_ENTRY he; + he.lpData = nullptr; + while (HeapWalk(hHeap, &he) != 0) { + if (he.lpData == lpMem) { + success = TRUE; + break; + } + } + HeapUnlock(hHeap); + return success; +} +#endif + +} /* anonymous namespace */ + +NPPExceptionAutoHolder::NPPExceptionAutoHolder() + : mOldException(gNPPException) +{ + gNPPException = nullptr; +} + +NPPExceptionAutoHolder::~NPPExceptionAutoHolder() +{ + NS_ASSERTION(!gNPPException, "NPP exception not properly cleared!"); + + gNPPException = mOldException; +} + +nsPluginThreadRunnable::nsPluginThreadRunnable(NPP instance, + PluginThreadCallback func, + void *userData) + : mInstance(instance), mFunc(func), mUserData(userData) +{ + if (!sPluginThreadAsyncCallLock) { + // Failed to create lock, not much we can do here then... + mFunc = nullptr; + + return; + } + + PR_INIT_CLIST(this); + + { + MutexAutoLock lock(*sPluginThreadAsyncCallLock); + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata; + if (!inst || !inst->IsRunning()) { + // The plugin was stopped, ignore this async call. + mFunc = nullptr; + + return; + } + + PR_APPEND_LINK(this, &sPendingAsyncCalls); + } +} + +nsPluginThreadRunnable::~nsPluginThreadRunnable() +{ + if (!sPluginThreadAsyncCallLock) { + return; + } + + { + MutexAutoLock lock(*sPluginThreadAsyncCallLock); + + PR_REMOVE_LINK(this); + } +} + +NS_IMETHODIMP +nsPluginThreadRunnable::Run() +{ + if (mFunc) { + PluginDestructionGuard guard(mInstance); + + NS_TRY_SAFE_CALL_VOID(mFunc(mUserData), nullptr, + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + } + + return NS_OK; +} + +void +OnPluginDestroy(NPP instance) +{ + if (!sPluginThreadAsyncCallLock) { + return; + } + + { + MutexAutoLock lock(*sPluginThreadAsyncCallLock); + + if (PR_CLIST_IS_EMPTY(&sPendingAsyncCalls)) { + return; + } + + nsPluginThreadRunnable *r = + (nsPluginThreadRunnable *)PR_LIST_HEAD(&sPendingAsyncCalls); + + do { + if (r->IsForInstance(instance)) { + r->Invalidate(); + } + + r = (nsPluginThreadRunnable *)PR_NEXT_LINK(r); + } while (r != &sPendingAsyncCalls); + } +} + +void +OnShutdown() +{ + NS_ASSERTION(PR_CLIST_IS_EMPTY(&sPendingAsyncCalls), + "Pending async plugin call list not cleaned up!"); + + if (sPluginThreadAsyncCallLock) { + delete sPluginThreadAsyncCallLock; + + sPluginThreadAsyncCallLock = nullptr; + } +} + +AsyncCallbackAutoLock::AsyncCallbackAutoLock() +{ + if (sPluginThreadAsyncCallLock) { + sPluginThreadAsyncCallLock->Lock(); + } +} + +AsyncCallbackAutoLock::~AsyncCallbackAutoLock() +{ + if (sPluginThreadAsyncCallLock) { + sPluginThreadAsyncCallLock->Unlock(); + } +} + +NPP NPPStack::sCurrentNPP = nullptr; + +const char * +PeekException() +{ + return gNPPException; +} + +void +PopException() +{ + NS_ASSERTION(gNPPException, "Uh, no NPP exception to pop!"); + + if (gNPPException) { + free(gNPPException); + + gNPPException = nullptr; + } +} + +// +// Static callbacks that get routed back through the new C++ API +// + +namespace mozilla { +namespace plugins { +namespace parent { + +NPError +_geturl(NPP npp, const char* relativeURL, const char* target) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_geturl called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_GetURL: npp=%p, target=%s, url=%s\n", (void *)npp, target, + relativeURL)); + + PluginDestructionGuard guard(npp); + + // Block Adobe Acrobat from loading URLs that are not http:, https:, + // or ftp: URLs if the given target is null. + if (!target && relativeURL && + (strncmp(relativeURL, "http:", 5) != 0) && + (strncmp(relativeURL, "https:", 6) != 0) && + (strncmp(relativeURL, "ftp:", 4) != 0)) { + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *) npp->ndata; + + const char *name = nullptr; + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + host->GetPluginName(inst, &name); + + if (name && strstr(name, "Adobe") && strstr(name, "Acrobat")) { + return NPERR_NO_ERROR; + } + } + + return MakeNewNPAPIStreamInternal(npp, relativeURL, target, + eNPPStreamTypeInternal_Get); +} + +NPError +_geturlnotify(NPP npp, const char* relativeURL, const char* target, + void* notifyData) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_geturlnotify called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_GetURLNotify: npp=%p, target=%s, notify=%p, url=%s\n", (void*)npp, + target, notifyData, relativeURL)); + + PluginDestructionGuard guard(npp); + + return MakeNewNPAPIStreamInternal(npp, relativeURL, target, + eNPPStreamTypeInternal_Get, true, + notifyData); +} + +NPError +_posturlnotify(NPP npp, const char *relativeURL, const char *target, + uint32_t len, const char *buf, NPBool file, void *notifyData) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_posturlnotify called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + if (!buf) + return NPERR_INVALID_PARAM; + + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_PostURLNotify: npp=%p, target=%s, len=%d, file=%d, " + "notify=%p, url=%s, buf=%s\n", + (void*)npp, target, len, file, notifyData, relativeURL, + buf)); + + PluginDestructionGuard guard(npp); + + return MakeNewNPAPIStreamInternal(npp, relativeURL, target, + eNPPStreamTypeInternal_Post, true, + notifyData, len, buf, file); +} + +NPError +_posturl(NPP npp, const char *relativeURL, const char *target, + uint32_t len, const char *buf, NPBool file) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_posturl called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_PostURL: npp=%p, target=%s, file=%d, len=%d, url=%s, " + "buf=%s\n", + (void*)npp, target, file, len, relativeURL, buf)); + + PluginDestructionGuard guard(npp); + + return MakeNewNPAPIStreamInternal(npp, relativeURL, target, + eNPPStreamTypeInternal_Post, false, nullptr, + len, buf, file); +} + +NPError +_newstream(NPP npp, NPMIMEType type, const char* target, NPStream* *result) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_newstream called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_NewStream: npp=%p, type=%s, target=%s\n", (void*)npp, + (const char *)type, target)); + + NPError err = NPERR_INVALID_INSTANCE_ERROR; + if (npp && npp->ndata) { + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance*)npp->ndata; + + PluginDestructionGuard guard(inst); + + nsCOMPtr<nsIOutputStream> stream; + if (NS_SUCCEEDED(inst->NewStreamFromPlugin((const char*) type, target, + getter_AddRefs(stream)))) { + nsNPAPIStreamWrapper* wrapper = new nsNPAPIStreamWrapper(stream, nullptr); + if (wrapper) { + (*result) = &wrapper->mNPStream; + err = NPERR_NO_ERROR; + } else { + err = NPERR_OUT_OF_MEMORY_ERROR; + } + } else { + err = NPERR_GENERIC_ERROR; + } + } + return err; +} + +int32_t +_write(NPP npp, NPStream *pstream, int32_t len, void *buffer) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_write called from the wrong thread\n")); + return 0; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_Write: npp=%p, url=%s, len=%d, buffer=%s\n", (void*)npp, + pstream->url, len, (char*)buffer)); + + // negative return indicates failure to the plugin + if (!npp) + return -1; + + PluginDestructionGuard guard(npp); + + nsNPAPIStreamWrapper* wrapper = static_cast<nsNPAPIStreamWrapper*>(pstream->ndata); + if (!wrapper) { + return -1; + } + + nsIOutputStream* stream = wrapper->GetOutputStream(); + if (!stream) { + return -1; + } + + uint32_t count = 0; + nsresult rv = stream->Write((char *)buffer, len, &count); + + if (NS_FAILED(rv)) { + return -1; + } + + return (int32_t)count; +} + +NPError +_destroystream(NPP npp, NPStream *pstream, NPError reason) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_destroystream called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_DestroyStream: npp=%p, url=%s, reason=%d\n", (void*)npp, + pstream->url, (int)reason)); + + if (!npp) + return NPERR_INVALID_INSTANCE_ERROR; + + PluginDestructionGuard guard(npp); + + nsNPAPIStreamWrapper *streamWrapper = static_cast<nsNPAPIStreamWrapper*>(pstream->ndata); + if (!streamWrapper) { + return NPERR_INVALID_PARAM; + } + + nsNPAPIPluginStreamListener *listener = streamWrapper->GetStreamListener(); + if (listener) { + // This type of stream is going from the browser to the plugin. It's either the + // initial src/data stream or another stream resulting from NPN_GetURL* or + // NPN_PostURL*. + // + // Calling OnStopBinding on the listener may cause it to be deleted due to the + // releasing of its last references. + listener->OnStopBinding(nullptr, NS_BINDING_ABORTED); + } else { + // This type of stream (NPStream) was created via NPN_NewStream. The plugin holds + // the reference until it is to be deleted here. Deleting the wrapper will + // release the wrapped nsIOutputStream. + // + // The NPStream the plugin references should always be a sub-object of its own + // 'ndata', which is our nsNPAPIStramWrapper. See bug 548441. + NS_ASSERTION((char*)streamWrapper <= (char*)pstream && + ((char*)pstream) + sizeof(*pstream) + <= ((char*)streamWrapper) + sizeof(*streamWrapper), + "pstream is not a subobject of wrapper"); + delete streamWrapper; + } + + // 'listener' and/or 'streamWrapper' may be invalid (deleted) at this point. Don't + // touch them again! + + return NPERR_NO_ERROR; +} + +void +_status(NPP npp, const char *message) +{ + // NPN_Status is no longer supported. +} + +void +_memfree (void *ptr) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_memfree called from the wrong thread\n")); + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, ("NPN_MemFree: ptr=%p\n", ptr)); + + if (ptr) + free(ptr); +} + +uint32_t +_memflush(uint32_t size) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_memflush called from the wrong thread\n")); + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, ("NPN_MemFlush: size=%d\n", size)); + + nsMemory::HeapMinimize(true); + return 0; +} + +void +_reloadplugins(NPBool reloadPages) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_reloadplugins called from the wrong thread\n")); + return; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_ReloadPlugins: reloadPages=%d\n", reloadPages)); + + nsCOMPtr<nsIPluginHost> pluginHost(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); + if (!pluginHost) + return; + + pluginHost->ReloadPlugins(); +} + +void +_invalidaterect(NPP npp, NPRect *invalidRect) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_invalidaterect called from the wrong thread\n")); + return; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_InvalidateRect: npp=%p, top=%d, left=%d, bottom=%d, " + "right=%d\n", (void *)npp, invalidRect->top, + invalidRect->left, invalidRect->bottom, invalidRect->right)); + + if (!npp || !npp->ndata) { + NS_WARNING("_invalidaterect: npp or npp->ndata == 0"); + return; + } + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance*)npp->ndata; + + PluginDestructionGuard guard(inst); + + inst->InvalidateRect((NPRect *)invalidRect); +} + +void +_invalidateregion(NPP npp, NPRegion invalidRegion) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_invalidateregion called from the wrong thread\n")); + return; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_InvalidateRegion: npp=%p, region=%p\n", (void*)npp, + (void*)invalidRegion)); + + if (!npp || !npp->ndata) { + NS_WARNING("_invalidateregion: npp or npp->ndata == 0"); + return; + } + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance*)npp->ndata; + + PluginDestructionGuard guard(inst); + + inst->InvalidateRegion((NPRegion)invalidRegion); +} + +void +_forceredraw(NPP npp) +{ +} + +NPObject* +_getwindowobject(NPP npp) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_getwindowobject called from the wrong thread\n")); + return nullptr; + } + + // The window want to return here is the outer window, *not* the inner (since + // we don't know what the plugin will do with it). + nsIDocument* doc = GetDocumentFromNPP(npp); + NS_ENSURE_TRUE(doc, nullptr); + nsCOMPtr<nsPIDOMWindowOuter> outer = doc->GetWindow(); + NS_ENSURE_TRUE(outer, nullptr); + + JS::Rooted<JSObject*> global(dom::RootingCx(), + nsGlobalWindow::Cast(outer)->GetGlobalJSObject()); + return nsJSObjWrapper::GetNewOrUsed(npp, global); +} + +NPObject* +_getpluginelement(NPP npp) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_getpluginelement called from the wrong thread\n")); + return nullptr; + } + + nsNPAPIPluginInstance* inst = static_cast<nsNPAPIPluginInstance*>(npp->ndata); + if (!inst) + return nullptr; + + nsCOMPtr<nsIDOMElement> element; + inst->GetDOMElement(getter_AddRefs(element)); + + if (!element) + return nullptr; + + nsIDocument *doc = GetDocumentFromNPP(npp); + if (NS_WARN_IF(!doc)) { + return nullptr; + } + + dom::AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(doc->GetInnerWindow()))) { + return nullptr; + } + JSContext* cx = jsapi.cx(); + + nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID())); + NS_ENSURE_TRUE(xpc, nullptr); + + JS::RootedObject obj(cx); + xpc->WrapNative(cx, ::JS::CurrentGlobalOrNull(cx), element, + NS_GET_IID(nsIDOMElement), obj.address()); + NS_ENSURE_TRUE(obj, nullptr); + + return nsJSObjWrapper::GetNewOrUsed(npp, obj); +} + +NPIdentifier +_getstringidentifier(const NPUTF8* name) +{ + if (!name) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("NPN_getstringidentifier: passed null name")); + return nullptr; + } + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_getstringidentifier called from the wrong thread\n")); + } + + AutoSafeJSContext cx; + return doGetIdentifier(cx, name); +} + +void +_getstringidentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier *identifiers) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_getstringidentifiers called from the wrong thread\n")); + } + + AutoSafeJSContext cx; + + for (int32_t i = 0; i < nameCount; ++i) { + if (names[i]) { + identifiers[i] = doGetIdentifier(cx, names[i]); + } else { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("NPN_getstringidentifiers: passed null name")); + identifiers[i] = nullptr; + } + } +} + +NPIdentifier +_getintidentifier(int32_t intid) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_getstringidentifier called from the wrong thread\n")); + } + return IntToNPIdentifier(intid); +} + +NPUTF8* +_utf8fromidentifier(NPIdentifier id) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_utf8fromidentifier called from the wrong thread\n")); + } + if (!id) + return nullptr; + + if (!NPIdentifierIsString(id)) { + return nullptr; + } + + JSString *str = NPIdentifierToString(id); + nsAutoString autoStr; + AssignJSFlatString(autoStr, JS_ASSERT_STRING_IS_FLAT(str)); + + return ToNewUTF8String(autoStr); +} + +int32_t +_intfromidentifier(NPIdentifier id) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_intfromidentifier called from the wrong thread\n")); + } + + if (!NPIdentifierIsInt(id)) { + return INT32_MIN; + } + + return NPIdentifierToInt(id); +} + +bool +_identifierisstring(NPIdentifier id) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_identifierisstring called from the wrong thread\n")); + } + + return NPIdentifierIsString(id); +} + +NPObject* +_createobject(NPP npp, NPClass* aClass) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_createobject called from the wrong thread\n")); + return nullptr; + } + if (!npp) { + NS_ERROR("Null npp passed to _createobject()!"); + + return nullptr; + } + + PluginDestructionGuard guard(npp); + + if (!aClass) { + NS_ERROR("Null class passed to _createobject()!"); + + return nullptr; + } + + NPPAutoPusher nppPusher(npp); + + NPObject *npobj; + + if (aClass->allocate) { + npobj = aClass->allocate(npp, aClass); + } else { + npobj = (NPObject *)PR_Malloc(sizeof(NPObject)); + } + + if (npobj) { + npobj->_class = aClass; + npobj->referenceCount = 1; + NS_LOG_ADDREF(npobj, 1, "BrowserNPObject", sizeof(NPObject)); + } + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("Created NPObject %p, NPClass %p\n", npobj, aClass)); + + return npobj; +} + +NPObject* +_retainobject(NPObject* npobj) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_retainobject called from the wrong thread\n")); + } + if (npobj) { +#ifdef NS_BUILD_REFCNT_LOGGING + int32_t refCnt = +#endif + PR_ATOMIC_INCREMENT((int32_t*)&npobj->referenceCount); + NS_LOG_ADDREF(npobj, refCnt, "BrowserNPObject", sizeof(NPObject)); + } + + return npobj; +} + +void +_releaseobject(NPObject* npobj) +{ + // If nothing is passed, just return, even if we're on the wrong thread. + if (!npobj) { + return; + } + + // THIS IS A KNOWN LEAK. SEE BUG 1221448. + // If releaseobject is called off the main thread and we have a valid pointer, + // we at least know it was created on the main thread (see _createobject + // implementation). However, forwarding the deletion back to the main thread + // without careful checking could cause bad memory management races. So, for + // now, we leak by warning and then just returning early. But it should fix + // java 7 crashes. + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_releaseobject called from the wrong thread\n")); + return; + } + + int32_t refCnt = PR_ATOMIC_DECREMENT((int32_t*)&npobj->referenceCount); + NS_LOG_RELEASE(npobj, refCnt, "BrowserNPObject"); + + if (refCnt == 0) { + nsNPObjWrapper::OnDestroy(npobj); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("Deleting NPObject %p, refcount hit 0\n", npobj)); + + if (npobj->_class && npobj->_class->deallocate) { + npobj->_class->deallocate(npobj); + } else { + PR_Free(npobj); + } + } +} + +bool +_invoke(NPP npp, NPObject* npobj, NPIdentifier method, const NPVariant *args, + uint32_t argCount, NPVariant *result) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_invoke called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->invoke) + return false; + + PluginDestructionGuard guard(npp); + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_Invoke(npp %p, npobj %p, method %p, args %d\n", npp, + npobj, method, argCount)); + + return npobj->_class->invoke(npobj, method, args, argCount, result); +} + +bool +_invokeDefault(NPP npp, NPObject* npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_invokedefault called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->invokeDefault) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_InvokeDefault(npp %p, npobj %p, args %d\n", npp, + npobj, argCount)); + + return npobj->_class->invokeDefault(npobj, args, argCount, result); +} + +bool +_evaluate(NPP npp, NPObject* npobj, NPString *script, NPVariant *result) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_evaluate called from the wrong thread\n")); + return false; + } + if (!npp) + return false; + + NPPAutoPusher nppPusher(npp); + + nsIDocument *doc = GetDocumentFromNPP(npp); + NS_ENSURE_TRUE(doc, false); + + nsGlobalWindow* win = nsGlobalWindow::Cast(doc->GetInnerWindow()); + if (NS_WARN_IF(!win || !win->FastGetGlobalJSObject())) { + return false; + } + + nsAutoMicroTask mt; + dom::AutoEntryScript aes(win, "NPAPI NPN_evaluate"); + JSContext* cx = aes.cx(); + + JS::Rooted<JSObject*> obj(cx, nsNPObjWrapper::GetNewOrUsed(npp, cx, npobj)); + + if (!obj) { + return false; + } + + obj = js::ToWindowIfWindowProxy(obj); + MOZ_ASSERT(obj, "ToWindowIfWindowProxy should never return null"); + + if (result) { + // Initialize the out param to void + VOID_TO_NPVARIANT(*result); + } + + if (!script || !script->UTF8Length || !script->UTF8Characters) { + // Nothing to evaluate. + + return true; + } + + NS_ConvertUTF8toUTF16 utf16script(script->UTF8Characters, + script->UTF8Length); + + nsIPrincipal *principal = doc->NodePrincipal(); + + nsAutoCString specStr; + const char *spec; + + nsCOMPtr<nsIURI> uri; + principal->GetURI(getter_AddRefs(uri)); + + if (uri) { + uri->GetSpec(specStr); + spec = specStr.get(); + } else { + // No URI in a principal means it's the system principal. If the + // document URI is a chrome:// URI, pass that in as the URI of the + // script, else pass in null for the filename as there's no way to + // know where this document really came from. Passing in null here + // also means that the script gets treated by XPConnect as if it + // needs additional protection, which is what we want for unknown + // chrome code anyways. + + uri = doc->GetDocumentURI(); + bool isChrome = false; + + if (uri && NS_SUCCEEDED(uri->SchemeIs("chrome", &isChrome)) && isChrome) { + uri->GetSpec(specStr); + spec = specStr.get(); + } else { + spec = nullptr; + } + } + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_Evaluate(npp %p, npobj %p, script <<<%s>>>) called\n", + npp, npobj, script->UTF8Characters)); + + JS::CompileOptions options(cx); + options.setFileAndLine(spec, 0) + .setVersion(JSVERSION_DEFAULT); + JS::Rooted<JS::Value> rval(cx); + nsJSUtils::EvaluateOptions evalOptions(cx); + if (obj != js::GetGlobalForObjectCrossCompartment(obj) && + !evalOptions.scopeChain.append(obj)) { + return false; + } + obj = js::GetGlobalForObjectCrossCompartment(obj); + nsresult rv = nsJSUtils::EvaluateString(cx, utf16script, obj, options, + evalOptions, &rval); + + return NS_SUCCEEDED(rv) && + (!result || JSValToNPVariant(npp, cx, rval, result)); +} + +bool +_getproperty(NPP npp, NPObject* npobj, NPIdentifier property, + NPVariant *result) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_getproperty called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->getProperty) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_GetProperty(npp %p, npobj %p, property %p) called\n", + npp, npobj, property)); + + if (!npobj->_class->getProperty(npobj, property, result)) + return false; + + // If a Java plugin tries to get the document.URL or document.documentURI + // property from us, don't pass back a value that Java won't be able to + // understand -- one that will make the URL(String) constructor throw a + // MalformedURL exception. Passing such a value causes Java Plugin2 to + // crash (to throw a RuntimeException in Plugin2Manager.getDocumentBase()). + // Also don't pass back a value that Java is likely to mishandle. + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*) npp->ndata; + if (!inst) + return false; + nsNPAPIPlugin* plugin = inst->GetPlugin(); + if (!plugin) + return false; + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + nsPluginTag* pluginTag = host->TagForPlugin(plugin); + if (!pluginTag->mIsJavaPlugin) + return true; + + if (!NPVARIANT_IS_STRING(*result)) + return true; + + NPUTF8* propertyName = _utf8fromidentifier(property); + if (!propertyName) + return true; + bool notURL = + (PL_strcasecmp(propertyName, "URL") && + PL_strcasecmp(propertyName, "documentURI")); + _memfree(propertyName); + if (notURL) + return true; + + NPObject* window_obj = _getwindowobject(npp); + if (!window_obj) + return true; + + NPVariant doc_v; + NPObject* document_obj = nullptr; + NPIdentifier doc_id = _getstringidentifier("document"); + bool ok = npobj->_class->getProperty(window_obj, doc_id, &doc_v); + _releaseobject(window_obj); + if (ok) { + if (NPVARIANT_IS_OBJECT(doc_v)) { + document_obj = NPVARIANT_TO_OBJECT(doc_v); + } else { + _releasevariantvalue(&doc_v); + return true; + } + } else { + return true; + } + _releaseobject(document_obj); + if (document_obj != npobj) + return true; + + NPString urlnp = NPVARIANT_TO_STRING(*result); + nsXPIDLCString url; + url.Assign(urlnp.UTF8Characters, urlnp.UTF8Length); + + bool javaCompatible = false; + if (NS_FAILED(NS_CheckIsJavaCompatibleURLString(url, &javaCompatible))) + javaCompatible = false; + if (javaCompatible) + return true; + + // If Java won't be able to interpret the original value of document.URL or + // document.documentURI, or is likely to mishandle it, pass back something + // that Java will understand but won't be able to use to access the network, + // and for which same-origin checks will always fail. + + if (inst->mFakeURL.IsVoid()) { + // Abort (do an error return) if NS_MakeRandomInvalidURLString() fails. + if (NS_FAILED(NS_MakeRandomInvalidURLString(inst->mFakeURL))) { + _releasevariantvalue(result); + return false; + } + } + + _releasevariantvalue(result); + char* fakeurl = (char *) _memalloc(inst->mFakeURL.Length() + 1); + strcpy(fakeurl, inst->mFakeURL); + STRINGZ_TO_NPVARIANT(fakeurl, *result); + + return true; +} + +bool +_setproperty(NPP npp, NPObject* npobj, NPIdentifier property, + const NPVariant *value) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_setproperty called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->setProperty) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_SetProperty(npp %p, npobj %p, property %p) called\n", + npp, npobj, property)); + + return npobj->_class->setProperty(npobj, property, value); +} + +bool +_removeproperty(NPP npp, NPObject* npobj, NPIdentifier property) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_removeproperty called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->removeProperty) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_RemoveProperty(npp %p, npobj %p, property %p) called\n", + npp, npobj, property)); + + return npobj->_class->removeProperty(npobj, property); +} + +bool +_hasproperty(NPP npp, NPObject* npobj, NPIdentifier propertyName) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_hasproperty called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->hasProperty) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_HasProperty(npp %p, npobj %p, property %p) called\n", + npp, npobj, propertyName)); + + return npobj->_class->hasProperty(npobj, propertyName); +} + +bool +_hasmethod(NPP npp, NPObject* npobj, NPIdentifier methodName) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_hasmethod called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->hasMethod) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_HasMethod(npp %p, npobj %p, property %p) called\n", + npp, npobj, methodName)); + + return npobj->_class->hasMethod(npobj, methodName); +} + +bool +_enumerate(NPP npp, NPObject *npobj, NPIdentifier **identifier, + uint32_t *count) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_enumerate called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class) + return false; + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_Enumerate(npp %p, npobj %p) called\n", npp, npobj)); + + if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(npobj->_class) || + !npobj->_class->enumerate) { + *identifier = 0; + *count = 0; + return true; + } + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + return npobj->_class->enumerate(npobj, identifier, count); +} + +bool +_construct(NPP npp, NPObject* npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_construct called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || + !NP_CLASS_STRUCT_VERSION_HAS_CTOR(npobj->_class) || + !npobj->_class->construct) { + return false; + } + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + return npobj->_class->construct(npobj, args, argCount, result); +} + +void +_releasevariantvalue(NPVariant* variant) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_releasevariantvalue called from the wrong thread\n")); + } + switch (variant->type) { + case NPVariantType_Void : + case NPVariantType_Null : + case NPVariantType_Bool : + case NPVariantType_Int32 : + case NPVariantType_Double : + break; + case NPVariantType_String : + { + const NPString *s = &NPVARIANT_TO_STRING(*variant); + + if (s->UTF8Characters) { +#if defined(MOZ_MEMORY_WINDOWS) + if (malloc_usable_size((void *)s->UTF8Characters) != 0) { + PR_Free((void *)s->UTF8Characters); + } else { + void *p = (void *)s->UTF8Characters; + DWORD nheaps = 0; + AutoTArray<HANDLE, 50> heaps; + nheaps = GetProcessHeaps(0, heaps.Elements()); + heaps.AppendElements(nheaps); + GetProcessHeaps(nheaps, heaps.Elements()); + for (DWORD i = 0; i < nheaps; i++) { + if (InHeap(heaps[i], p)) { + HeapFree(heaps[i], 0, p); + break; + } + } + } +#else + free((void *)s->UTF8Characters); +#endif + } + break; + } + case NPVariantType_Object: + { + NPObject *npobj = NPVARIANT_TO_OBJECT(*variant); + + if (npobj) + _releaseobject(npobj); + + break; + } + default: + NS_ERROR("Unknown NPVariant type!"); + } + + VOID_TO_NPVARIANT(*variant); +} + +void +_setexception(NPObject* npobj, const NPUTF8 *message) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_setexception called from the wrong thread\n")); + return; + } + + if (!message) return; + + if (gNPPException) { + // If a plugin throws multiple exceptions, we'll only report the + // last one for now. + free(gNPPException); + } + + gNPPException = strdup(message); +} + +NPError +_getvalue(NPP npp, NPNVariable variable, void *result) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_getvalue called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_GetValue: npp=%p, var=%d\n", + (void*)npp, (int)variable)); + + nsresult res; + + PluginDestructionGuard guard(npp); + + // Cast NPNVariable enum to int to avoid warnings about including switch + // cases for android_npapi.h's non-standard ANPInterface values. + switch (static_cast<int>(variable)) { + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + case NPNVxDisplay : { +#if defined(MOZ_X11) + if (npp) { + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *) npp->ndata; + bool windowless = false; + inst->IsWindowless(&windowless); + // The documentation on the types for many variables in NP(N|P)_GetValue + // is vague. Often boolean values are NPBool (1 byte), but + // https://developer.mozilla.org/en/XEmbed_Extension_for_Mozilla_Plugins + // treats NPPVpluginNeedsXEmbed as PRBool (int), and + // on x86/32-bit, flash stores to this using |movl 0x1,&needsXEmbed|. + // thus we can't use NPBool for needsXEmbed, or the three bytes above + // it on the stack would get clobbered. so protect with the larger bool. + int needsXEmbed = 0; + if (!windowless) { + res = inst->GetValueFromPlugin(NPPVpluginNeedsXEmbed, &needsXEmbed); + // If the call returned an error code make sure we still use our default value. + if (NS_FAILED(res)) { + needsXEmbed = 0; + } + } + if (windowless || needsXEmbed) { + (*(Display **)result) = mozilla::DefaultXDisplay(); + return NPERR_NO_ERROR; + } + } +#if (MOZ_WIDGET_GTK == 2) + // adobe nppdf calls XtGetApplicationNameAndClass(display, + // &instance, &class) we have to init Xt toolkit before get + // XtDisplay just call gtk_xtbin_new(w,0) once + static GtkWidget *gtkXtBinHolder = 0; + if (!gtkXtBinHolder) { + gtkXtBinHolder = gtk_xtbin_new(gdk_get_default_root_window(),0); + // it crashes on destroy, let it leak + // gtk_widget_destroy(gtkXtBinHolder); + } + (*(Display **)result) = GTK_XTBIN(gtkXtBinHolder)->xtdisplay; + return NPERR_NO_ERROR; +#endif +#endif + return NPERR_GENERIC_ERROR; + } + + case NPNVxtAppContext: + return NPERR_GENERIC_ERROR; +#endif + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + case NPNVnetscapeWindow: { + if (!npp || !npp->ndata) + return NPERR_INVALID_INSTANCE_ERROR; + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *) npp->ndata; + + RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner(); + NS_ENSURE_TRUE(owner, NPERR_NO_ERROR); + + if (NS_SUCCEEDED(owner->GetNetscapeWindow(result))) { + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; + } +#endif + + case NPNVjavascriptEnabledBool: { + *(NPBool*)result = false; + bool js = false; + res = Preferences::GetBool("javascript.enabled", &js); + if (NS_SUCCEEDED(res)) { + *(NPBool*)result = js; + } + return NPERR_NO_ERROR; + } + + case NPNVasdEnabledBool: + *(NPBool*)result = false; + return NPERR_NO_ERROR; + + case NPNVisOfflineBool: { + bool offline = false; + nsCOMPtr<nsIIOService> ioservice = + do_GetService(NS_IOSERVICE_CONTRACTID, &res); + if (NS_SUCCEEDED(res)) + res = ioservice->GetOffline(&offline); + if (NS_FAILED(res)) + return NPERR_GENERIC_ERROR; + + *(NPBool*)result = offline; + return NPERR_NO_ERROR; + } + + case NPNVToolkit: { +#ifdef MOZ_WIDGET_GTK + *((NPNToolkitType*)result) = NPNVGtk2; +#endif + + if (*(NPNToolkitType*)result) + return NPERR_NO_ERROR; + + return NPERR_GENERIC_ERROR; + } + + case NPNVSupportsXEmbedBool: { +#ifdef MOZ_WIDGET_GTK + *(NPBool*)result = true; +#else + *(NPBool*)result = false; +#endif + return NPERR_NO_ERROR; + } + + case NPNVWindowNPObject: { + *(NPObject **)result = _getwindowobject(npp); + + return *(NPObject **)result ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR; + } + + case NPNVPluginElementNPObject: { + *(NPObject **)result = _getpluginelement(npp); + + return *(NPObject **)result ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR; + } + + case NPNVSupportsWindowless: { +#if defined(XP_WIN) || defined(XP_MACOSX) || \ + (defined(MOZ_X11) && defined(MOZ_WIDGET_GTK)) + *(NPBool*)result = true; +#else + *(NPBool*)result = false; +#endif + return NPERR_NO_ERROR; + } + + case NPNVprivateModeBool: { + bool privacy; + nsNPAPIPluginInstance *inst = static_cast<nsNPAPIPluginInstance*>(npp->ndata); + if (!inst) + return NPERR_GENERIC_ERROR; + + nsresult rv = inst->IsPrivateBrowsing(&privacy); + if (NS_FAILED(rv)) + return NPERR_GENERIC_ERROR; + *(NPBool*)result = (NPBool)privacy; + return NPERR_NO_ERROR; + } + + case NPNVdocumentOrigin: { + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)npp->ndata; + if (!inst) { + return NPERR_GENERIC_ERROR; + } + + nsCOMPtr<nsIDOMElement> element; + inst->GetDOMElement(getter_AddRefs(element)); + if (!element) { + return NPERR_GENERIC_ERROR; + } + + nsCOMPtr<nsIContent> content(do_QueryInterface(element)); + if (!content) { + return NPERR_GENERIC_ERROR; + } + + nsIPrincipal* principal = content->NodePrincipal(); + + nsAutoString utf16Origin; + res = nsContentUtils::GetUTFOrigin(principal, utf16Origin); + if (NS_FAILED(res)) { + return NPERR_GENERIC_ERROR; + } + + nsCOMPtr<nsIIDNService> idnService = do_GetService(NS_IDNSERVICE_CONTRACTID); + if (!idnService) { + return NPERR_GENERIC_ERROR; + } + + // This is a bit messy: we convert to UTF-8 here, but then + // nsIDNService::Normalize will convert back to UTF-16 for processing, + // and back to UTF-8 again to return the result. + // Alternative: perhaps we should add a NormalizeUTF16 version of the API, + // and just convert to UTF-8 for the final return (resulting in one + // encoding form conversion instead of three). + NS_ConvertUTF16toUTF8 utf8Origin(utf16Origin); + nsAutoCString normalizedUTF8Origin; + res = idnService->Normalize(utf8Origin, normalizedUTF8Origin); + if (NS_FAILED(res)) { + return NPERR_GENERIC_ERROR; + } + + *(char**)result = ToNewCString(normalizedUTF8Origin); + return *(char**)result ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR; + } + +#ifdef XP_MACOSX + case NPNVpluginDrawingModel: { + if (npp) { + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance*)npp->ndata; + if (inst) { + NPDrawingModel drawingModel; + inst->GetDrawingModel((int32_t*)&drawingModel); + *(NPDrawingModel*)result = drawingModel; + return NPERR_NO_ERROR; + } + } + return NPERR_GENERIC_ERROR; + } + +#ifndef NP_NO_QUICKDRAW + case NPNVsupportsQuickDrawBool: { + *(NPBool*)result = false; + + return NPERR_NO_ERROR; + } +#endif + + case NPNVsupportsCoreGraphicsBool: { + *(NPBool*)result = true; + + return NPERR_NO_ERROR; + } + + case NPNVsupportsCoreAnimationBool: { + *(NPBool*)result = true; + + return NPERR_NO_ERROR; + } + + case NPNVsupportsInvalidatingCoreAnimationBool: { + *(NPBool*)result = true; + + return NPERR_NO_ERROR; + } + + case NPNVsupportsCompositingCoreAnimationPluginsBool: { + *(NPBool*)result = PR_TRUE; + + return NPERR_NO_ERROR; + } + +#ifndef NP_NO_CARBON + case NPNVsupportsCarbonBool: { + *(NPBool*)result = false; + + return NPERR_NO_ERROR; + } +#endif + case NPNVsupportsCocoaBool: { + *(NPBool*)result = true; + + return NPERR_NO_ERROR; + } + + case NPNVsupportsUpdatedCocoaTextInputBool: { + *(NPBool*)result = true; + return NPERR_NO_ERROR; + } +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + case NPNVcontentsScaleFactor: { + nsNPAPIPluginInstance *inst = + (nsNPAPIPluginInstance *) (npp ? npp->ndata : nullptr); + double scaleFactor = inst ? inst->GetContentsScaleFactor() : 1.0; + *(double*)result = scaleFactor; + return NPERR_NO_ERROR; + } +#endif + + case NPNVCSSZoomFactor: { + nsNPAPIPluginInstance *inst = + (nsNPAPIPluginInstance *) (npp ? npp->ndata : nullptr); + double scaleFactor = inst ? inst->GetCSSZoomFactor() : 1.0; + *(double*)result = scaleFactor; + return NPERR_NO_ERROR; + } + +#ifdef MOZ_WIDGET_ANDROID + case kLogInterfaceV0_ANPGetValue: { + LOG("get log interface"); + ANPLogInterfaceV0 *i = (ANPLogInterfaceV0 *) result; + InitLogInterface(i); + return NPERR_NO_ERROR; + } + + case kBitmapInterfaceV0_ANPGetValue: { + LOG("get bitmap interface"); + return NPERR_GENERIC_ERROR; + } + + case kMatrixInterfaceV0_ANPGetValue: { + LOG("get matrix interface"); + return NPERR_GENERIC_ERROR; + } + + case kPathInterfaceV0_ANPGetValue: { + LOG("get path interface"); + return NPERR_GENERIC_ERROR; + } + + case kTypefaceInterfaceV0_ANPGetValue: { + LOG("get typeface interface"); + ANPTypefaceInterfaceV0 *i = (ANPTypefaceInterfaceV0 *) result; + InitTypeFaceInterface(i); + return NPERR_NO_ERROR; + } + + case kPaintInterfaceV0_ANPGetValue: { + LOG("get paint interface"); + ANPPaintInterfaceV0 *i = (ANPPaintInterfaceV0 *) result; + InitPaintInterface(i); + return NPERR_NO_ERROR; + } + + case kCanvasInterfaceV0_ANPGetValue: { + LOG("get canvas interface"); + ANPCanvasInterfaceV0 *i = (ANPCanvasInterfaceV0 *) result; + InitCanvasInterface(i); + return NPERR_NO_ERROR; + } + + case kWindowInterfaceV0_ANPGetValue: { + LOG("get window interface"); + ANPWindowInterfaceV0 *i = (ANPWindowInterfaceV0 *) result; + InitWindowInterface(i); + return NPERR_NO_ERROR; + } + + case kAudioTrackInterfaceV0_ANPGetValue: { + LOG("get audio interface"); + ANPAudioTrackInterfaceV0 *i = (ANPAudioTrackInterfaceV0 *) result; + InitAudioTrackInterfaceV0(i); + return NPERR_NO_ERROR; + } + + case kEventInterfaceV0_ANPGetValue: { + LOG("get event interface"); + ANPEventInterfaceV0 *i = (ANPEventInterfaceV0 *) result; + InitEventInterface(i); + return NPERR_NO_ERROR; + } + + case kSystemInterfaceV0_ANPGetValue: { + LOG("get system interface"); + return NPERR_GENERIC_ERROR; + } + + case kSurfaceInterfaceV0_ANPGetValue: { + LOG("get surface interface"); + ANPSurfaceInterfaceV0 *i = (ANPSurfaceInterfaceV0 *) result; + InitSurfaceInterface(i); + return NPERR_NO_ERROR; + } + + case kSupportedDrawingModel_ANPGetValue: { + LOG("get supported drawing model"); + return NPERR_GENERIC_ERROR; + } + + case kJavaContext_ANPGetValue: { + LOG("get java context"); + auto ret = java::GeckoAppShell::GetContext(); + if (!ret) + return NPERR_GENERIC_ERROR; + + *static_cast<jobject*>(result) = ret.Forget(); + return NPERR_NO_ERROR; + } + + case kAudioTrackInterfaceV1_ANPGetValue: { + LOG("get audio interface v1"); + ANPAudioTrackInterfaceV1 *i = (ANPAudioTrackInterfaceV1 *) result; + InitAudioTrackInterfaceV1(i); + return NPERR_NO_ERROR; + } + + case kNativeWindowInterfaceV0_ANPGetValue: { + LOG("get native window interface v0"); + ANPNativeWindowInterfaceV0* i = (ANPNativeWindowInterfaceV0 *) result; + InitNativeWindowInterface(i); + return NPERR_NO_ERROR; + } + + case kOpenGLInterfaceV0_ANPGetValue: { + LOG("get openGL interface"); + return NPERR_GENERIC_ERROR; + } + + case kWindowInterfaceV1_ANPGetValue: { + LOG("get Window interface V1"); + return NPERR_GENERIC_ERROR; + } + + case kWindowInterfaceV2_ANPGetValue: { + LOG("get Window interface V2"); + ANPWindowInterfaceV2 *i = (ANPWindowInterfaceV2 *) result; + InitWindowInterfaceV2(i); + return NPERR_NO_ERROR; + } + + case kVideoInterfaceV0_ANPGetValue: { + LOG("get video interface V0"); + return NPERR_GENERIC_ERROR; + } + + case kVideoInterfaceV1_ANPGetValue: { + LOG("get video interface V1"); + ANPVideoInterfaceV1 *i = (ANPVideoInterfaceV1*) result; + InitVideoInterfaceV1(i); + return NPERR_NO_ERROR; + } + + case kSystemInterfaceV1_ANPGetValue: { + LOG("get system interface v1"); + ANPSystemInterfaceV1* i = reinterpret_cast<ANPSystemInterfaceV1*>(result); + InitSystemInterfaceV1(i); + return NPERR_NO_ERROR; + } + + case kSystemInterfaceV2_ANPGetValue: { + LOG("get system interface v2"); + ANPSystemInterfaceV2* i = reinterpret_cast<ANPSystemInterfaceV2*>(result); + InitSystemInterfaceV2(i); + return NPERR_NO_ERROR; + } +#endif + + // we no longer hand out any XPCOM objects + case NPNVDOMElement: + case NPNVDOMWindow: + case NPNVserviceManager: + // old XPCOM objects, no longer supported, but null out the out + // param to avoid crashing plugins that still try to use this. + *(nsISupports**)result = nullptr; + MOZ_FALLTHROUGH; + + default: + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_getvalue unhandled get value: %d\n", variable)); + return NPERR_GENERIC_ERROR; + } +} + +NPError +_setvalue(NPP npp, NPPVariable variable, void *result) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_setvalue called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_SetValue: npp=%p, var=%d\n", + (void*)npp, (int)variable)); + + if (!npp) + return NPERR_INVALID_INSTANCE_ERROR; + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *) npp->ndata; + + NS_ASSERTION(inst, "null instance"); + + if (!inst) + return NPERR_INVALID_INSTANCE_ERROR; + + PluginDestructionGuard guard(inst); + + // Cast NPNVariable enum to int to avoid warnings about including switch + // cases for android_npapi.h's non-standard ANPInterface values. + switch (static_cast<int>(variable)) { + + // we should keep backward compatibility with NPAPI where the + // actual pointer value is checked rather than its content + // when passing booleans + case NPPVpluginWindowBool: { +#ifdef XP_MACOSX + // This setting doesn't apply to OS X (only to Windows and Unix/Linux). + // See https://developer.mozilla.org/En/NPN_SetValue#section_5. Return + // NPERR_NO_ERROR here to conform to other browsers' behavior on OS X + // (e.g. Safari and Opera). + return NPERR_NO_ERROR; +#else + NPBool bWindowless = (result == nullptr); + return inst->SetWindowless(bWindowless); +#endif + } + case NPPVpluginTransparentBool: { + NPBool bTransparent = (result != nullptr); + return inst->SetTransparent(bTransparent); + } + + case NPPVjavascriptPushCallerBool: { + return NPERR_NO_ERROR; + } + + case NPPVpluginKeepLibraryInMemory: { + NPBool bCached = (result != nullptr); + inst->SetCached(bCached); + return NPERR_NO_ERROR; + } + + case NPPVpluginUsesDOMForCursorBool: { + bool useDOMForCursor = (result != nullptr); + return inst->SetUsesDOMForCursor(useDOMForCursor); + } + + case NPPVpluginIsPlayingAudio: { + bool isMuted = !result; + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*) npp->ndata; + MOZ_ASSERT(inst); + + if (isMuted && !inst->HasAudioChannelAgent()) { + return NPERR_NO_ERROR; + } + + nsCOMPtr<nsIAudioChannelAgent> agent; + nsresult rv = inst->GetOrCreateAudioChannelAgent(getter_AddRefs(agent)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NPERR_NO_ERROR; + } + + MOZ_ASSERT(agent); + + if (isMuted) { + rv = agent->NotifyStoppedPlaying(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NPERR_NO_ERROR; + } + } else { + + dom::AudioPlaybackConfig config; + rv = agent->NotifyStartedPlaying(&config, + dom::AudioChannelService::AudibleState::eAudible); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NPERR_NO_ERROR; + } + + rv = inst->WindowVolumeChanged(config.mVolume, config.mMuted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NPERR_NO_ERROR; + } + + // Since we only support for muting 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 NPERR_NO_ERROR; + } + + rv = inst->WindowSuspendChanged(config.mSuspend); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NPERR_NO_ERROR; + } + } + + return NPERR_NO_ERROR; + } + +#ifndef MOZ_WIDGET_ANDROID + // On android, their 'drawing model' uses the same constant! + case NPPVpluginDrawingModel: { + if (inst) { + inst->SetDrawingModel((NPDrawingModel)NS_PTR_TO_INT32(result)); + return NPERR_NO_ERROR; + } + else { + return NPERR_GENERIC_ERROR; + } + } +#endif + +#ifdef XP_MACOSX + case NPPVpluginEventModel: { + if (inst) { + inst->SetEventModel((NPEventModel)NS_PTR_TO_INT32(result)); + return NPERR_NO_ERROR; + } + else { + return NPERR_GENERIC_ERROR; + } + } +#endif +#ifdef MOZ_WIDGET_ANDROID + case kRequestDrawingModel_ANPSetValue: + if (inst) + inst->SetANPDrawingModel(NS_PTR_TO_INT32(result)); + return NPERR_NO_ERROR; + case kAcceptEvents_ANPSetValue: + return NPERR_NO_ERROR; +#endif + default: + return NPERR_GENERIC_ERROR; + } +} + +NPError +_requestread(NPStream *pstream, NPByteRange *rangeList) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_requestread called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_RequestRead: stream=%p\n", + (void*)pstream)); + +#ifdef PLUGIN_LOGGING + for(NPByteRange * range = rangeList; range != nullptr; range = range->next) + MOZ_LOG(nsPluginLogging::gNPNLog,PLUGIN_LOG_NOISY, + ("%i-%i", range->offset, range->offset + range->length - 1)); + + MOZ_LOG(nsPluginLogging::gNPNLog,PLUGIN_LOG_NOISY, ("\n\n")); + PR_LogFlush(); +#endif + + if (!pstream || !rangeList || !pstream->ndata) + return NPERR_INVALID_PARAM; + + nsNPAPIStreamWrapper* streamWrapper = static_cast<nsNPAPIStreamWrapper*>(pstream->ndata); + nsNPAPIPluginStreamListener* streamlistener = streamWrapper->GetStreamListener(); + if (!streamlistener) { + return NPERR_GENERIC_ERROR; + } + + int32_t streamtype = NP_NORMAL; + + streamlistener->GetStreamType(&streamtype); + + if (streamtype != NP_SEEK) + return NPERR_STREAM_NOT_SEEKABLE; + + if (!streamlistener->mStreamListenerPeer) + return NPERR_GENERIC_ERROR; + + nsresult rv = streamlistener->mStreamListenerPeer->RequestRead((NPByteRange *)rangeList); + if (NS_FAILED(rv)) + return NPERR_GENERIC_ERROR; + + return NPERR_NO_ERROR; +} + +// Deprecated, only stubbed out +void* /* OJI type: JRIEnv* */ +_getJavaEnv() +{ + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_GetJavaEnv\n")); + return nullptr; +} + +const char * +_useragent(NPP npp) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_useragent called from the wrong thread\n")); + return nullptr; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_UserAgent: npp=%p\n", (void*)npp)); + + nsCOMPtr<nsIPluginHost> pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); + nsPluginHost *pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get()); + if (!pluginHost) { + return nullptr; + } + + const char *retstr; + nsresult rv = pluginHost->UserAgent(&retstr); + if (NS_FAILED(rv)) + return nullptr; + + return retstr; +} + +void * +_memalloc (uint32_t size) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL,("NPN_memalloc called from the wrong thread\n")); + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, ("NPN_MemAlloc: size=%d\n", size)); + return moz_xmalloc(size); +} + +// Deprecated, only stubbed out +void* /* OJI type: jref */ +_getJavaPeer(NPP npp) +{ + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_GetJavaPeer: npp=%p\n", (void*)npp)); + return nullptr; +} + +void +_pushpopupsenabledstate(NPP npp, NPBool enabled) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_pushpopupsenabledstate called from the wrong thread\n")); + return; + } + nsNPAPIPluginInstance *inst = npp ? (nsNPAPIPluginInstance *)npp->ndata : nullptr; + if (!inst) + return; + + inst->PushPopupsEnabledState(enabled); +} + +void +_poppopupsenabledstate(NPP npp) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_poppopupsenabledstate called from the wrong thread\n")); + return; + } + nsNPAPIPluginInstance *inst = npp ? (nsNPAPIPluginInstance *)npp->ndata : nullptr; + if (!inst) + return; + + inst->PopPopupsEnabledState(); +} + +void +_pluginthreadasynccall(NPP instance, PluginThreadCallback func, void *userData) +{ + if (NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY,("NPN_pluginthreadasynccall called from the main thread\n")); + } else { + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY,("NPN_pluginthreadasynccall called from a non main thread\n")); + } + RefPtr<nsPluginThreadRunnable> evt = + new nsPluginThreadRunnable(instance, func, userData); + + if (evt && evt->IsValid()) { + NS_DispatchToMainThread(evt); + } +} + +NPError +_getvalueforurl(NPP instance, NPNURLVariable variable, const char *url, + char **value, uint32_t *len) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_getvalueforurl called from the wrong thread\n")); + return NPERR_GENERIC_ERROR; + } + + if (!instance) { + return NPERR_INVALID_PARAM; + } + + if (!url || !*url || !len) { + return NPERR_INVALID_URL; + } + + *len = 0; + + switch (variable) { + case NPNURLVProxy: + { + nsCOMPtr<nsIPluginHost> pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); + nsPluginHost *pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get()); + if (pluginHost && NS_SUCCEEDED(pluginHost->FindProxyForURL(url, value))) { + *len = *value ? strlen(*value) : 0; + return NPERR_NO_ERROR; + } + break; + } + case NPNURLVCookie: + { + nsCOMPtr<nsICookieService> cookieService = + do_GetService(NS_COOKIESERVICE_CONTRACTID); + + if (!cookieService) + return NPERR_GENERIC_ERROR; + + // Make an nsURI from the url argument + nsCOMPtr<nsIURI> uri; + if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), nsDependentCString(url)))) { + return NPERR_GENERIC_ERROR; + } + + nsCOMPtr<nsIChannel> channel = GetChannelFromNPP(instance); + + if (NS_FAILED(cookieService->GetCookieString(uri, channel, value)) || + !*value) { + return NPERR_GENERIC_ERROR; + } + + *len = strlen(*value); + return NPERR_NO_ERROR; + } + + default: + // Fall through and return an error... + ; + } + + return NPERR_GENERIC_ERROR; +} + +NPError +_setvalueforurl(NPP instance, NPNURLVariable variable, const char *url, + const char *value, uint32_t len) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_setvalueforurl called from the wrong thread\n")); + return NPERR_GENERIC_ERROR; + } + + if (!instance) { + return NPERR_INVALID_PARAM; + } + + if (!url || !*url) { + return NPERR_INVALID_URL; + } + + switch (variable) { + case NPNURLVCookie: + { + if (!value || 0 == len) + return NPERR_INVALID_PARAM; + + nsresult rv = NS_ERROR_FAILURE; + nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return NPERR_GENERIC_ERROR; + + nsCOMPtr<nsICookieService> cookieService = do_GetService(NS_COOKIESERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return NPERR_GENERIC_ERROR; + + nsCOMPtr<nsIURI> uriIn; + rv = ioService->NewURI(nsDependentCString(url), nullptr, nullptr, getter_AddRefs(uriIn)); + if (NS_FAILED(rv)) + return NPERR_GENERIC_ERROR; + + nsCOMPtr<nsIChannel> channel = GetChannelFromNPP(instance); + + char *cookie = (char*)value; + char c = cookie[len]; + cookie[len] = '\0'; + rv = cookieService->SetCookieString(uriIn, nullptr, cookie, channel); + cookie[len] = c; + if (NS_SUCCEEDED(rv)) + return NPERR_NO_ERROR; + } + + break; + case NPNURLVProxy: + // We don't support setting proxy values, fall through... + default: + // Fall through and return an error... + ; + } + + return NPERR_GENERIC_ERROR; +} + +NPError +_getauthenticationinfo(NPP instance, const char *protocol, const char *host, + int32_t port, const char *scheme, const char *realm, + char **username, uint32_t *ulen, char **password, + uint32_t *plen) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_getauthenticationinfo called from the wrong thread\n")); + return NPERR_GENERIC_ERROR; + } + + if (!instance || !protocol || !host || !scheme || !realm || !username || + !ulen || !password || !plen) + return NPERR_INVALID_PARAM; + + *username = nullptr; + *password = nullptr; + *ulen = 0; + *plen = 0; + + nsDependentCString proto(protocol); + + if (!proto.LowerCaseEqualsLiteral("http") && + !proto.LowerCaseEqualsLiteral("https")) + return NPERR_GENERIC_ERROR; + + nsCOMPtr<nsIHttpAuthManager> authManager = + do_GetService("@mozilla.org/network/http-auth-manager;1"); + if (!authManager) + return NPERR_GENERIC_ERROR; + + nsNPAPIPluginInstance *inst = static_cast<nsNPAPIPluginInstance*>(instance->ndata); + if (!inst) + return NPERR_GENERIC_ERROR; + + bool authPrivate = false; + if (NS_FAILED(inst->IsPrivateBrowsing(&authPrivate))) + return NPERR_GENERIC_ERROR; + + nsIDocument *doc = GetDocumentFromNPP(instance); + NS_ENSURE_TRUE(doc, NPERR_GENERIC_ERROR); + nsIPrincipal *principal = doc->NodePrincipal(); + + nsAutoString unused, uname16, pwd16; + if (NS_FAILED(authManager->GetAuthIdentity(proto, nsDependentCString(host), + port, nsDependentCString(scheme), + nsDependentCString(realm), + EmptyCString(), unused, uname16, + pwd16, authPrivate, principal))) { + return NPERR_GENERIC_ERROR; + } + + NS_ConvertUTF16toUTF8 uname8(uname16); + NS_ConvertUTF16toUTF8 pwd8(pwd16); + + *username = ToNewCString(uname8); + *ulen = *username ? uname8.Length() : 0; + + *password = ToNewCString(pwd8); + *plen = *password ? pwd8.Length() : 0; + + return NPERR_NO_ERROR; +} + +uint32_t +_scheduletimer(NPP instance, uint32_t interval, NPBool repeat, PluginTimerFunc timerFunc) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_scheduletimer called from the wrong thread\n")); + return 0; + } + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata; + if (!inst) + return 0; + + return inst->ScheduleTimer(interval, repeat, timerFunc); +} + +void +_unscheduletimer(NPP instance, uint32_t timerID) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_unscheduletimer called from the wrong thread\n")); + return; + } + +#ifdef MOZ_WIDGET_ANDROID + // Sometimes Flash calls this with a dead NPP instance. Ensure the one we have + // here is valid and maps to a nsNPAPIPluginInstance. + nsNPAPIPluginInstance *inst = nsNPAPIPluginInstance::GetFromNPP(instance); +#else + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata; +#endif + if (!inst) + return; + + inst->UnscheduleTimer(timerID); +} + +NPError +_popupcontextmenu(NPP instance, NPMenu* menu) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_popupcontextmenu called from the wrong thread\n")); + return 0; + } + +#ifdef MOZ_WIDGET_COCOA + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata; + + double pluginX, pluginY; + double screenX, screenY; + + const NPCocoaEvent* currentEvent = static_cast<NPCocoaEvent*>(inst->GetCurrentEvent()); + if (!currentEvent) { + return NPERR_GENERIC_ERROR; + } + + // Ensure that the events has an x/y value. + if (currentEvent->type != NPCocoaEventMouseDown && + currentEvent->type != NPCocoaEventMouseUp && + currentEvent->type != NPCocoaEventMouseMoved && + currentEvent->type != NPCocoaEventMouseEntered && + currentEvent->type != NPCocoaEventMouseExited && + currentEvent->type != NPCocoaEventMouseDragged) { + return NPERR_GENERIC_ERROR; + } + + pluginX = currentEvent->data.mouse.pluginX; + pluginY = currentEvent->data.mouse.pluginY; + + if ((pluginX < 0.0) || (pluginY < 0.0)) + return NPERR_GENERIC_ERROR; + + NPBool success = _convertpoint(instance, + pluginX, pluginY, NPCoordinateSpacePlugin, + &screenX, &screenY, NPCoordinateSpaceScreen); + + if (success) { + return mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(menu, + screenX, screenY, + nullptr, + nullptr); + } else { + NS_WARNING("Convertpoint failed, could not created contextmenu."); + return NPERR_GENERIC_ERROR; + } +#else + NS_WARNING("Not supported on this platform!"); + return NPERR_GENERIC_ERROR; +#endif +} + +NPBool +_convertpoint(NPP instance, double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double *destX, double *destY, NPCoordinateSpace destSpace) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_convertpoint called from the wrong thread\n")); + return 0; + } + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata; + if (!inst) + return false; + + return inst->ConvertPoint(sourceX, sourceY, sourceSpace, destX, destY, destSpace); +} + +void +_urlredirectresponse(NPP instance, void* notifyData, NPBool allow) +{ + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_convertpoint called from the wrong thread\n")); + return; + } + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata; + if (!inst) { + return; + } + + inst->URLRedirectResponse(notifyData, allow); +} + +NPError +_initasyncsurface(NPP instance, NPSize *size, NPImageFormat format, void *initData, NPAsyncSurface *surface) +{ + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata; + if (!inst) { + return NPERR_GENERIC_ERROR; + } + + return inst->InitAsyncSurface(size, format, initData, surface); +} + +NPError +_finalizeasyncsurface(NPP instance, NPAsyncSurface *surface) +{ + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata; + if (!inst) { + return NPERR_GENERIC_ERROR; + } + + return inst->FinalizeAsyncSurface(surface); +} + +void +_setcurrentasyncsurface(NPP instance, NPAsyncSurface *surface, NPRect *changed) +{ + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata; + if (!inst) { + return; + } + + inst->SetCurrentAsyncSurface(surface, changed); +} + +} /* namespace parent */ +} /* namespace plugins */ +} /* namespace mozilla */ diff --git a/dom/plugins/base/nsNPAPIPlugin.h b/dom/plugins/base/nsNPAPIPlugin.h new file mode 100644 index 000000000..96e630b62 --- /dev/null +++ b/dom/plugins/base/nsNPAPIPlugin.h @@ -0,0 +1,414 @@ +/* -*- 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/. */ + +#ifndef nsNPAPIPlugin_h_ +#define nsNPAPIPlugin_h_ + +#include "prlink.h" +#include "npfunctions.h" +#include "nsPluginHost.h" + +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/PluginLibrary.h" +#include "mozilla/RefCounted.h" + +#if defined(XP_WIN) +#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (__stdcall * _name) +#else +#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (* _name) +#endif + +typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_GETENTRYPOINTS) (NPPluginFuncs* pCallbacks); +typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGININIT) (const NPNetscapeFuncs* pCallbacks); +typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINUNIXINIT) (const NPNetscapeFuncs* pCallbacks, NPPluginFuncs* fCallbacks); +typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINSHUTDOWN) (); + +// nsNPAPIPlugin is held alive both by active nsPluginTag instances and +// by active nsNPAPIPluginInstance. +class nsNPAPIPlugin final +{ +private: + typedef mozilla::PluginLibrary PluginLibrary; + +public: + nsNPAPIPlugin(); + + NS_INLINE_DECL_REFCOUNTING(nsNPAPIPlugin) + + // Constructs and initializes an nsNPAPIPlugin object. A nullptr file path + // will prevent this from calling NP_Initialize. + static nsresult CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult); + + PluginLibrary* GetLibrary(); + // PluginFuncs() can't fail but results are only valid if GetLibrary() succeeds + NPPluginFuncs* PluginFuncs(); + +#if defined(XP_MACOSX) && !defined(__LP64__) + void SetPluginRefNum(short aRefNum); +#endif + + // The IPC mechanism notifies the nsNPAPIPlugin if the plugin + // crashes and is no longer usable. pluginDumpID/browserDumpID are + // the IDs of respective minidumps that were written, or empty if no + // minidump was written. + void PluginCrashed(const nsAString& pluginDumpID, + const nsAString& browserDumpID); + + static bool RunPluginOOP(const nsPluginTag *aPluginTag); + + nsresult Shutdown(); + + static nsresult RetainStream(NPStream *pstream, nsISupports **aRetainedPeer); + +private: + ~nsNPAPIPlugin(); + + NPPluginFuncs mPluginFuncs; + PluginLibrary* mLibrary; +}; + +namespace mozilla { +namespace plugins { +namespace parent { + +static_assert(sizeof(NPIdentifier) == sizeof(jsid), + "NPIdentifier must be binary compatible with jsid."); + +inline jsid +NPIdentifierToJSId(NPIdentifier id) +{ + jsid tmp; + JSID_BITS(tmp) = (size_t)id; + return tmp; +} + +inline NPIdentifier +JSIdToNPIdentifier(jsid id) +{ + return (NPIdentifier)JSID_BITS(id); +} + +inline bool +NPIdentifierIsString(NPIdentifier id) +{ + return JSID_IS_STRING(NPIdentifierToJSId(id)); +} + +inline JSString * +NPIdentifierToString(NPIdentifier id) +{ + return JSID_TO_STRING(NPIdentifierToJSId(id)); +} + +inline NPIdentifier +StringToNPIdentifier(JSContext *cx, JSString *str) +{ + return JSIdToNPIdentifier(INTERNED_STRING_TO_JSID(cx, str)); +} + +inline bool +NPIdentifierIsInt(NPIdentifier id) +{ + return JSID_IS_INT(NPIdentifierToJSId(id)); +} + +inline int +NPIdentifierToInt(NPIdentifier id) +{ + return JSID_TO_INT(NPIdentifierToJSId(id)); +} + +inline NPIdentifier +IntToNPIdentifier(int i) +{ + return JSIdToNPIdentifier(INT_TO_JSID(i)); +} + +JSContext* GetJSContext(NPP npp); + +inline bool +NPStringIdentifierIsPermanent(NPIdentifier id) +{ + AutoSafeJSContext cx; + return JS_StringHasBeenPinned(cx, NPIdentifierToString(id)); +} + +#define NPIdentifier_VOID (JSIdToNPIdentifier(JSID_VOID)) + +NPObject* +_getwindowobject(NPP npp); + +NPObject* +_getpluginelement(NPP npp); + +NPIdentifier +_getstringidentifier(const NPUTF8* name); + +void +_getstringidentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier *identifiers); + +bool +_identifierisstring(NPIdentifier identifiers); + +NPIdentifier +_getintidentifier(int32_t intid); + +NPUTF8* +_utf8fromidentifier(NPIdentifier identifier); + +int32_t +_intfromidentifier(NPIdentifier identifier); + +NPObject* +_createobject(NPP npp, NPClass* aClass); + +NPObject* +_retainobject(NPObject* npobj); + +void +_releaseobject(NPObject* npobj); + +bool +_invoke(NPP npp, NPObject* npobj, NPIdentifier method, const NPVariant *args, + uint32_t argCount, NPVariant *result); + +bool +_invokeDefault(NPP npp, NPObject* npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result); + +bool +_evaluate(NPP npp, NPObject* npobj, NPString *script, NPVariant *result); + +bool +_getproperty(NPP npp, NPObject* npobj, NPIdentifier property, + NPVariant *result); + +bool +_setproperty(NPP npp, NPObject* npobj, NPIdentifier property, + const NPVariant *value); + +bool +_removeproperty(NPP npp, NPObject* npobj, NPIdentifier property); + +bool +_hasproperty(NPP npp, NPObject* npobj, NPIdentifier propertyName); + +bool +_hasmethod(NPP npp, NPObject* npobj, NPIdentifier methodName); + +bool +_enumerate(NPP npp, NPObject *npobj, NPIdentifier **identifier, + uint32_t *count); + +bool +_construct(NPP npp, NPObject* npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result); + +void +_releasevariantvalue(NPVariant *variant); + +void +_setexception(NPObject* npobj, const NPUTF8 *message); + +void +_pushpopupsenabledstate(NPP npp, NPBool enabled); + +void +_poppopupsenabledstate(NPP npp); + +typedef void(*PluginThreadCallback)(void *); + +void +_pluginthreadasynccall(NPP instance, PluginThreadCallback func, + void *userData); + +NPError +_getvalueforurl(NPP instance, NPNURLVariable variable, const char *url, + char **value, uint32_t *len); + +NPError +_setvalueforurl(NPP instance, NPNURLVariable variable, const char *url, + const char *value, uint32_t len); + +NPError +_getauthenticationinfo(NPP instance, const char *protocol, const char *host, + int32_t port, const char *scheme, const char *realm, + char **username, uint32_t *ulen, char **password, + uint32_t *plen); + +typedef void(*PluginTimerFunc)(NPP npp, uint32_t timerID); + +uint32_t +_scheduletimer(NPP instance, uint32_t interval, NPBool repeat, PluginTimerFunc timerFunc); + +void +_unscheduletimer(NPP instance, uint32_t timerID); + +NPError +_popupcontextmenu(NPP instance, NPMenu* menu); + +NPBool +_convertpoint(NPP instance, double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double *destX, double *destY, NPCoordinateSpace destSpace); + +NPError +_requestread(NPStream *pstream, NPByteRange *rangeList); + +NPError +_geturlnotify(NPP npp, const char* relativeURL, const char* target, + void* notifyData); + +NPError +_getvalue(NPP npp, NPNVariable variable, void *r_value); + +NPError +_setvalue(NPP npp, NPPVariable variable, void *r_value); + +NPError +_geturl(NPP npp, const char* relativeURL, const char* target); + +NPError +_posturlnotify(NPP npp, const char* relativeURL, const char *target, + uint32_t len, const char *buf, NPBool file, void* notifyData); + +NPError +_posturl(NPP npp, const char* relativeURL, const char *target, uint32_t len, + const char *buf, NPBool file); + +NPError +_newstream(NPP npp, NPMIMEType type, const char* window, NPStream** pstream); + +int32_t +_write(NPP npp, NPStream *pstream, int32_t len, void *buffer); + +NPError +_destroystream(NPP npp, NPStream *pstream, NPError reason); + +void +_status(NPP npp, const char *message); + +void +_memfree (void *ptr); + +uint32_t +_memflush(uint32_t size); + +void +_reloadplugins(NPBool reloadPages); + +void +_invalidaterect(NPP npp, NPRect *invalidRect); + +void +_invalidateregion(NPP npp, NPRegion invalidRegion); + +void +_forceredraw(NPP npp); + +const char* +_useragent(NPP npp); + +void* +_memalloc (uint32_t size); + +// Deprecated entry points for the old Java plugin. +void* /* OJI type: JRIEnv* */ +_getJavaEnv(); + +void* /* OJI type: jref */ +_getJavaPeer(NPP npp); + +void +_urlredirectresponse(NPP instance, void* notifyData, NPBool allow); + +NPError +_initasyncsurface(NPP instance, NPSize *size, NPImageFormat format, void *initData, NPAsyncSurface *surface); + +NPError +_finalizeasyncsurface(NPP instance, NPAsyncSurface *surface); + +void +_setcurrentasyncsurface(NPP instance, NPAsyncSurface *surface, NPRect *changed); + +} /* namespace parent */ +} /* namespace plugins */ +} /* namespace mozilla */ + +const char * +PeekException(); + +void +PopException(); + +void +OnPluginDestroy(NPP instance); + +void +OnShutdown(); + +/** + * within a lexical scope, locks and unlocks the mutex used to + * serialize modifications to plugin async callback state. + */ +struct MOZ_STACK_CLASS AsyncCallbackAutoLock +{ + AsyncCallbackAutoLock(); + ~AsyncCallbackAutoLock(); +}; + +class NPPStack +{ +public: + static NPP Peek() + { + return sCurrentNPP; + } + +protected: + static NPP sCurrentNPP; +}; + +// XXXjst: The NPPAutoPusher stack is a bit redundant now that +// PluginDestructionGuard exists, and could thus be replaced by code +// that uses the PluginDestructionGuard list of plugins on the +// stack. But they're not identical, and to minimize code changes +// we're keeping both for the moment, and making NPPAutoPusher inherit +// the PluginDestructionGuard class to avoid having to keep two +// separate objects on the stack since we always want a +// PluginDestructionGuard where we use an NPPAutoPusher. + +class MOZ_STACK_CLASS NPPAutoPusher : public NPPStack, + protected PluginDestructionGuard +{ +public: + explicit NPPAutoPusher(NPP aNpp) + : PluginDestructionGuard(aNpp), + mOldNPP(sCurrentNPP) + { + NS_ASSERTION(aNpp, "Uh, null aNpp passed to NPPAutoPusher!"); + + sCurrentNPP = aNpp; + } + + ~NPPAutoPusher() + { + sCurrentNPP = mOldNPP; + } + +private: + NPP mOldNPP; +}; + +class NPPExceptionAutoHolder +{ +public: + NPPExceptionAutoHolder(); + ~NPPExceptionAutoHolder(); + +protected: + char *mOldException; +}; + +#endif // nsNPAPIPlugin_h_ diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp new file mode 100644 index 000000000..4b211ed7e --- /dev/null +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -0,0 +1,1861 @@ +/* -*- 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" + +#ifdef MOZ_WIDGET_ANDROID +// For ScreenOrientation.h and Hal.h +#include "base/basictypes.h" +#endif + +#include "mozilla/Logging.h" +#include "prmem.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; + +#ifdef MOZ_WIDGET_ANDROID +#include "ANPBase.h" +#include <android/log.h> +#include "android_npapi.h" +#include "mozilla/Mutex.h" +#include "mozilla/CondVar.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "mozilla/Hal.h" +#include "GLContextProvider.h" +#include "GLContext.h" +#include "TexturePoolOGL.h" +#include "SurfaceTypes.h" +#include "EGLUtils.h" + +using namespace mozilla; +using namespace mozilla::gl; + +typedef nsNPAPIPluginInstance::VideoInfo VideoInfo; + +class PluginEventRunnable : public Runnable +{ +public: + PluginEventRunnable(nsNPAPIPluginInstance* instance, ANPEvent* event) + : mInstance(instance), mEvent(*event), mCanceled(false) {} + + virtual nsresult Run() { + if (mCanceled) + return NS_OK; + + mInstance->HandleEvent(&mEvent, nullptr); + mInstance->PopPostedEvent(this); + return NS_OK; + } + + void Cancel() { mCanceled = true; } +private: + nsNPAPIPluginInstance* mInstance; + ANPEvent mEvent; + bool mCanceled; +}; + +static RefPtr<GLContext> sPluginContext = nullptr; + +static bool EnsureGLContext() +{ + if (!sPluginContext) { + const auto flags = CreateContextFlags::REQUIRE_COMPAT_PROFILE; + nsCString discardedFailureId; + sPluginContext = GLContextProvider::CreateHeadless(flags, &discardedFailureId); + } + + return sPluginContext != nullptr; +} + +static std::map<NPP, nsNPAPIPluginInstance*> sPluginNPPMap; + +#endif + +using namespace mozilla; +using namespace mozilla::plugins::parent; +using namespace mozilla::layers; + +static NS_DEFINE_IID(kIOutputStreamIID, NS_IOUTPUTSTREAM_IID); + +NS_IMPL_ISUPPORTS(nsNPAPIPluginInstance, nsIAudioChannelAgentCallback) + +nsNPAPIPluginInstance::nsNPAPIPluginInstance() + : mDrawingModel(kDefaultDrawingModel) +#ifdef MOZ_WIDGET_ANDROID + , mANPDrawingModel(0) + , mFullScreenOrientation(dom::eScreenOrientation_LandscapePrimary) + , mWakeLocked(false) + , mFullScreen(false) + , mOriginPos(gl::OriginPos::TopLeft) +#endif + , 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 +#ifdef MOZ_WIDGET_ANDROID + , mOnScreen(true) +#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)); + +#ifdef MOZ_WIDGET_ANDROID + sPluginNPPMap[&mNPP] = this; +#endif +} + +nsNPAPIPluginInstance::~nsNPAPIPluginInstance() +{ + PLUGIN_LOG(PLUGIN_LOG_BASIC, ("nsNPAPIPluginInstance dtor: this=%p\n",this)); + +#ifdef MOZ_WIDGET_ANDROID + sPluginNPPMap.erase(&mNPP); +#endif + + if (mMIMEType) { + PR_Free((void *)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; + +#if MOZ_WIDGET_ANDROID + if (mContentSurface) + mContentSurface->SetFrameAvailableCallback(nullptr); + + mContentSurface = nullptr; + + std::map<void*, VideoInfo*>::iterator it; + for (it = mVideos.begin(); it != mVideos.end(); it++) { + it->second->mSurfaceTexture->SetFrameAvailableCallback(nullptr); + delete it->second; + } + mVideos.clear(); + SetWakeLock(false); +#endif +} + +TimeStamp +nsNPAPIPluginInstance::StopTime() +{ + return mStopTime; +} + +nsresult nsNPAPIPluginInstance::Initialize(nsNPAPIPlugin *aPlugin, nsPluginInstanceOwner* aOwner, const nsACString& aMIMEType) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::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; + } + + // Make sure we lock while we're writing to mRunning after we've + // started as other threads might be checking that inside a lock. + { + AsyncCallbackAutoLock lock; + mRunning = DESTROYING; + mStopTime = TimeStamp::Now(); + } + + OnPluginDestroy(&mNPP); + + // 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; + +#if MOZ_WIDGET_ANDROID + for (uint32_t i = 0; i < mPostedEvents.Length(); i++) { + mPostedEvents[i]->Cancel(); + } + + mPostedEvents.Clear(); +#endif + + 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); +} + +nsresult +nsNPAPIPluginInstance::GetMode(int32_t *result) +{ + if (mOwner) + return mOwner->GetMode(result); + else + return NS_ERROR_FAILURE; +} + +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); + } + + // Android expects and empty string instead of null. + mCachedParamNames[attributes.Length()] = ToNewUTF8String(NS_LITERAL_STRING("PARAM")); + #ifdef MOZ_WIDGET_ANDROID + mCachedParamValues[attributes.Length()] = ToNewUTF8String(NS_LITERAL_STRING("")); + #else + mCachedParamValues[attributes.Length()] = nullptr; + #endif + + 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++; + } + + int32_t mode; + const char* mimetype; + NPError error = NPERR_GENERIC_ERROR; + + GetMode(&mode); + GetMIMEType(&mimetype); + + CheckJavaC2PJSObjectQuirk(quirkParamLength, mCachedParamNames, mCachedParamValues); + + 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, (uint16_t)mode, + quirkParamLength, mCachedParamNames, + mCachedParamValues, nullptr, &error); + mInPluginInitCall = oldVal; + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP New called: this=%p, npp=%p, mime=%s, mode=%d, argc=%d, return=%d\n", + this, &mNPP, mimetype, mode, 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, but Java needs wants this call. + if (window && window->type == NPWindowTypeWindow && + (window->width <= 0 || window->height <= 0) && + (nsPluginHost::GetSpecialType(nsDependentCString(mMIMEType)) != + nsPluginHost::eSpecialType_Java)) { + 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::NewStreamFromPlugin(const char* type, const char* target, + nsIOutputStream* *result) +{ + nsPluginStreamToFile* stream = new nsPluginStreamToFile(target, mOwner); + return stream->QueryInterface(kIOutputStreamIID, (void**)result); +} + +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; + + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::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 + MAIN_THREAD_JNI_REF_GUARD; + 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=%d, 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; + + if (mMIMEType) { + // bug 558434 - Prior to 3.6.4, we assumed windowless was transparent. + // Silverlight apparently relied on this quirk, so we default to + // transparent unless they specify otherwise after setting the windowless + // property. (Last tested version: sl 4.0). + // Changes to this code should be matched with changes in + // PluginInstanceChild::InitQuirksMode. + if (nsPluginHost::GetSpecialType(nsDependentCString(mMIMEType)) == + nsPluginHost::eSpecialType_Silverlight) { + mTransparent = true; + } + } + + 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 + +#if defined(MOZ_WIDGET_ANDROID) + +static void SendLifecycleEvent(nsNPAPIPluginInstance* aInstance, uint32_t aAction) +{ + ANPEvent event; + event.inSize = sizeof(ANPEvent); + event.eventType = kLifecycle_ANPEventType; + event.data.lifecycle.action = aAction; + aInstance->HandleEvent(&event, nullptr); +} + +void nsNPAPIPluginInstance::NotifyForeground(bool aForeground) +{ + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::SetForeground this=%p\n foreground=%d",this, aForeground)); + if (RUNNING != mRunning) + return; + + SendLifecycleEvent(this, aForeground ? kResume_ANPLifecycleAction : kPause_ANPLifecycleAction); +} + +void nsNPAPIPluginInstance::NotifyOnScreen(bool aOnScreen) +{ + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::SetOnScreen this=%p\n onScreen=%d",this, aOnScreen)); + if (RUNNING != mRunning || mOnScreen == aOnScreen) + return; + + mOnScreen = aOnScreen; + SendLifecycleEvent(this, aOnScreen ? kOnScreen_ANPLifecycleAction : kOffScreen_ANPLifecycleAction); +} + +void nsNPAPIPluginInstance::MemoryPressure() +{ + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::MemoryPressure this=%p\n",this)); + if (RUNNING != mRunning) + return; + + SendLifecycleEvent(this, kFreeMemory_ANPLifecycleAction); +} + +void nsNPAPIPluginInstance::NotifyFullScreen(bool aFullScreen) +{ + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::NotifyFullScreen this=%p\n",this)); + + if (RUNNING != mRunning || mFullScreen == aFullScreen) + return; + + mFullScreen = aFullScreen; + SendLifecycleEvent(this, mFullScreen ? kEnterFullScreen_ANPLifecycleAction : kExitFullScreen_ANPLifecycleAction); + + if (mFullScreen && mFullScreenOrientation != dom::eScreenOrientation_None) { + java::GeckoAppShell::LockScreenOrientation(mFullScreenOrientation); + } +} + +void nsNPAPIPluginInstance::NotifySize(nsIntSize size) +{ + if (kOpenGL_ANPDrawingModel != GetANPDrawingModel() || + size == mCurrentSize) + return; + + mCurrentSize = size; + + ANPEvent event; + event.inSize = sizeof(ANPEvent); + event.eventType = kDraw_ANPEventType; + event.data.draw.model = kOpenGL_ANPDrawingModel; + event.data.draw.data.surfaceSize.width = size.width; + event.data.draw.data.surfaceSize.height = size.height; + + HandleEvent(&event, nullptr); +} + +void nsNPAPIPluginInstance::SetANPDrawingModel(uint32_t aModel) +{ + mANPDrawingModel = aModel; +} + +void* nsNPAPIPluginInstance::GetJavaSurface() +{ + void* surface = nullptr; + nsresult rv = GetValueFromPlugin(kJavaSurface_ANPGetValue, &surface); + if (NS_FAILED(rv)) + return nullptr; + + return surface; +} + +void nsNPAPIPluginInstance::PostEvent(void* event) +{ + PluginEventRunnable *r = new PluginEventRunnable(this, (ANPEvent*)event); + mPostedEvents.AppendElement(RefPtr<PluginEventRunnable>(r)); + + NS_DispatchToMainThread(r); +} + +void nsNPAPIPluginInstance::SetFullScreenOrientation(uint32_t orientation) +{ + if (mFullScreenOrientation == orientation) + return; + + uint32_t oldOrientation = mFullScreenOrientation; + mFullScreenOrientation = orientation; + + if (mFullScreen) { + // We're already fullscreen so immediately apply the orientation change + + if (mFullScreenOrientation != dom::eScreenOrientation_None) { + java::GeckoAppShell::LockScreenOrientation(mFullScreenOrientation); + } else if (oldOrientation != dom::eScreenOrientation_None) { + // We applied an orientation when we entered fullscreen, but + // we don't want it anymore + java::GeckoAppShell::UnlockScreenOrientation(); + } + } +} + +void nsNPAPIPluginInstance::PopPostedEvent(PluginEventRunnable* r) +{ + mPostedEvents.RemoveElement(r); +} + +void nsNPAPIPluginInstance::SetWakeLock(bool aLocked) +{ + if (aLocked == mWakeLocked) + return; + + mWakeLocked = aLocked; + hal::ModifyWakeLock(NS_LITERAL_STRING("screen"), + mWakeLocked ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_REMOVE_ONE, + hal::WAKE_LOCK_NO_CHANGE); +} + +GLContext* nsNPAPIPluginInstance::GLContext() +{ + if (!EnsureGLContext()) + return nullptr; + + return sPluginContext; +} + +already_AddRefed<AndroidSurfaceTexture> nsNPAPIPluginInstance::CreateSurfaceTexture() +{ + if (!EnsureGLContext()) + return nullptr; + + GLuint texture = TexturePoolOGL::AcquireTexture(); + if (!texture) + return nullptr; + + RefPtr<AndroidSurfaceTexture> surface = AndroidSurfaceTexture::Create(TexturePoolOGL::GetGLContext(), + texture); + if (!surface) { + return nullptr; + } + + nsCOMPtr<nsIRunnable> frameCallback = NewRunnableMethod(this, &nsNPAPIPluginInstance::OnSurfaceTextureFrameAvailable); + surface->SetFrameAvailableCallback(frameCallback); + return surface.forget(); +} + +void nsNPAPIPluginInstance::OnSurfaceTextureFrameAvailable() +{ + if (mRunning == RUNNING && mOwner) + mOwner->Recomposite(); +} + +void* nsNPAPIPluginInstance::AcquireContentWindow() +{ + if (!mContentSurface) { + mContentSurface = CreateSurfaceTexture(); + + if (!mContentSurface) + return nullptr; + } + + return mContentSurface->NativeWindow(); +} + +AndroidSurfaceTexture* +nsNPAPIPluginInstance::AsSurfaceTexture() +{ + if (!mContentSurface) + return nullptr; + + return mContentSurface; +} + +void* nsNPAPIPluginInstance::AcquireVideoWindow() +{ + RefPtr<AndroidSurfaceTexture> surface = CreateSurfaceTexture(); + if (!surface) { + return nullptr; + } + + VideoInfo* info = new VideoInfo(surface); + + void* window = info->mSurfaceTexture->NativeWindow(); + mVideos.insert(std::pair<void*, VideoInfo*>(window, info)); + + return window; +} + +void nsNPAPIPluginInstance::ReleaseVideoWindow(void* window) +{ + std::map<void*, VideoInfo*>::iterator it = mVideos.find(window); + if (it == mVideos.end()) + return; + + delete it->second; + mVideos.erase(window); +} + +void nsNPAPIPluginInstance::SetVideoDimensions(void* window, gfxRect aDimensions) +{ + std::map<void*, VideoInfo*>::iterator it; + + it = mVideos.find(window); + if (it == mVideos.end()) + return; + + it->second->mDimensions = aDimensions; +} + +void nsNPAPIPluginInstance::GetVideos(nsTArray<VideoInfo*>& aVideos) +{ + std::map<void*, VideoInfo*>::iterator it; + for (it = mVideos.begin(); it != mVideos.end(); it++) + aVideos.AppendElement(it->second); +} + +nsNPAPIPluginInstance* nsNPAPIPluginInstance::GetFromNPP(NPP npp) +{ + std::map<NPP, nsNPAPIPluginInstance*>::iterator it; + + it = sPluginNPPMap.find(npp); + if (it == sPluginNPPMap.end()) + return nullptr; + + return it->second; +} + +#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) +{ + if (mHaveJavaC2PJSObjectQuirk) { + return NS_ERROR_FAILURE; + } + + 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(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + // All OS X plugins are windowless. + // On android, pre-honeycomb, all plugins are treated as 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)); + + MAIN_THREAD_JNI_REF_GUARD; + // 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; + nsCOMPtr<nsITimer> xpcomTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + delete newTimer; + return 0; + } + const short timerType = (repeat ? (short)nsITimer::TYPE_REPEATING_SLACK : (short)nsITimer::TYPE_ONE_SHOT); + xpcomTimer->InitWithFuncCallback(PluginTimerCallback, newTimer, interval, timerType); + newTimer->timer = xpcomTimer; + + // 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); + } +} + +class CarbonEventModelFailureEvent : public Runnable { +public: + nsCOMPtr<nsIContent> mContent; + + explicit CarbonEventModelFailureEvent(nsIContent* aContent) + : mContent(aContent) + {} + + ~CarbonEventModelFailureEvent() {} + + NS_IMETHOD Run(); +}; + +NS_IMETHODIMP +CarbonEventModelFailureEvent::Run() +{ + nsString type = NS_LITERAL_STRING("npapi-carbon-event-model-failure"); + nsContentUtils::DispatchTrustedEvent(mContent->GetComposedDoc(), mContent, + type, true, true); + return NS_OK; +} + +void +nsNPAPIPluginInstance::CarbonNPAPIFailure() +{ + nsCOMPtr<nsIDOMElement> element; + GetDOMElement(getter_AddRefs(element)); + if (!element) { + return; + } + + nsCOMPtr<nsIContent> content(do_QueryInterface(element)); + if (!content) { + return; + } + + nsCOMPtr<nsIRunnable> e = new CarbonEventModelFailureEvent(content); + nsresult rv = NS_DispatchToCurrentThread(e); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch CarbonEventModelFailureEvent."); + } +} + +static bool +GetJavaVersionFromMimetype(nsPluginTag* pluginTag, nsCString& version) +{ + for (uint32_t i = 0; i < pluginTag->MimeTypes().Length(); ++i) { + nsCString type = pluginTag->MimeTypes()[i]; + nsAutoCString jpi("application/x-java-applet;jpi-version="); + + int32_t idx = type.Find(jpi, false, 0, -1); + if (idx != 0) { + continue; + } + + type.Cut(0, jpi.Length()); + if (type.IsEmpty()) { + continue; + } + + type.ReplaceChar('_', '.'); + version = type; + return true; + } + + return false; +} + +void +nsNPAPIPluginInstance::CheckJavaC2PJSObjectQuirk(uint16_t paramCount, + const char* const* paramNames, + const char* const* paramValues) +{ + if (!mMIMEType || !mPlugin) { + return; + } + + nsPluginTagType tagtype; + nsresult rv = GetTagType(&tagtype); + if (NS_FAILED(rv) || + (tagtype != nsPluginTagType_Applet)) { + return; + } + + RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst(); + if (!pluginHost) { + return; + } + + nsPluginTag* pluginTag = pluginHost->TagForPlugin(mPlugin); + if (!pluginTag || + !pluginTag->mIsJavaPlugin) { + return; + } + + // check the params for "code" being present and non-empty + bool haveCodeParam = false; + bool isCodeParamEmpty = true; + + for (uint16_t i = paramCount; i > 0; --i) { + if (PL_strcasecmp(paramNames[i - 1], "code") == 0) { + haveCodeParam = true; + if (strlen(paramValues[i - 1]) > 0) { + isCodeParamEmpty = false; + } + break; + } + } + + // Due to the Java version being specified inconsistently across platforms + // check the version via the mimetype for choosing specific Java versions + nsCString javaVersion; + if (!GetJavaVersionFromMimetype(pluginTag, javaVersion)) { + return; + } + + mozilla::Version version(javaVersion.get()); + + if (version >= "1.7.0.4") { + return; + } + + if (!haveCodeParam && version >= "1.6.0.34" && version < "1.7") { + return; + } + + if (haveCodeParam && !isCodeParamEmpty) { + return; + } + + mHaveJavaC2PJSObjectQuirk = true; +} + +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::GetOrCreateAudioChannelAgent(nsIAudioChannelAgent** aAgent) +{ + if (!mAudioChannelAgent) { + 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(), + (int32_t)AudioChannelService::GetDefaultAudioChannel(), + this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsCOMPtr<nsIAudioChannelAgent> agent = mAudioChannelAgent; + agent.forget(aAgent); + return NS_OK; +} + +NS_IMETHODIMP +nsNPAPIPluginInstance::WindowVolumeChanged(float aVolume, bool aMuted) +{ + // We just support mute/unmute + nsresult rv = SetMuted(aMuted); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetMuted failed"); + if (mMuted != aMuted) { + mMuted = aMuted; + AudioChannelService::AudibleState audible = aMuted ? + AudioChannelService::AudibleState::eNotAudible : + AudioChannelService::AudibleState::eAudible; + mAudioChannelAgent->NotifyStartedAudible(audible, + AudioChannelService::AudibleChangedReasons::eVolumeChanged); + } + return rv; +} + +NS_IMETHODIMP +nsNPAPIPluginInstance::WindowSuspendChanged(nsSuspendedTypes 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; +} diff --git a/dom/plugins/base/nsNPAPIPluginInstance.h b/dom/plugins/base/nsNPAPIPluginInstance.h new file mode 100644 index 000000000..5f0375637 --- /dev/null +++ b/dom/plugins/base/nsNPAPIPluginInstance.h @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsNPAPIPluginInstance_h_ +#define nsNPAPIPluginInstance_h_ + +#include "nsSize.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsPIDOMWindow.h" +#include "nsITimer.h" +#include "nsIPluginInstanceOwner.h" +#include "nsIURI.h" +#include "nsIChannel.h" +#include "nsHashKeys.h" +#include <prinrval.h> +#include "js/TypeDecls.h" +#include "nsIAudioChannelAgent.h" +#ifdef MOZ_WIDGET_ANDROID +#include "nsIRunnable.h" +#include "GLContextTypes.h" +#include "AndroidSurfaceTexture.h" +#include "AndroidBridge.h" +#include <map> +class PluginEventRunnable; +#endif + +#include "mozilla/EventForwards.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/PluginLibrary.h" +#include "mozilla/RefPtr.h" +#include "mozilla/WeakPtr.h" + +class nsPluginStreamListenerPeer; // browser-initiated stream class +class nsNPAPIPluginStreamListener; // plugin-initiated stream class +class nsIPluginInstanceOwner; +class nsIOutputStream; +class nsPluginInstanceOwner; + +#if defined(OS_WIN) +const NPDrawingModel kDefaultDrawingModel = NPDrawingModelSyncWin; +#elif defined(MOZ_X11) +const NPDrawingModel kDefaultDrawingModel = NPDrawingModelSyncX; +#elif defined(XP_MACOSX) +#ifndef NP_NO_QUICKDRAW +const NPDrawingModel kDefaultDrawingModel = NPDrawingModelQuickDraw; // Not supported +#else +const NPDrawingModel kDefaultDrawingModel = NPDrawingModelCoreGraphics; +#endif +#else +const NPDrawingModel kDefaultDrawingModel = static_cast<NPDrawingModel>(0); +#endif + +/** + * Used to indicate whether it's OK to reenter Gecko and repaint, flush frames, + * run scripts, etc, during this plugin call. + * When NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO is set, we try to avoid dangerous + * Gecko activities when the plugin spins a nested event loop, on a best-effort + * basis. + */ +enum NSPluginCallReentry { + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO +}; + +class nsNPAPITimer +{ +public: + NPP npp; + uint32_t id; + nsCOMPtr<nsITimer> timer; + void (*callback)(NPP npp, uint32_t timerID); + bool inCallback; + bool needUnschedule; +}; + +class nsNPAPIPluginInstance final : public nsIAudioChannelAgentCallback + , public mozilla::SupportsWeakPtr<nsNPAPIPluginInstance> +{ +private: + typedef mozilla::PluginLibrary PluginLibrary; + +public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsNPAPIPluginInstance) + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK + + nsresult Initialize(nsNPAPIPlugin *aPlugin, nsPluginInstanceOwner* aOwner, const nsACString& aMIMEType); + nsresult Start(); + nsresult Stop(); + nsresult SetWindow(NPWindow* window); + nsresult NewStreamFromPlugin(const char* type, const char* target, nsIOutputStream* *result); + nsresult Print(NPPrint* platformPrint); + nsresult HandleEvent(void* event, int16_t* result, + NSPluginCallReentry aSafeToReenterGecko = NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + nsresult GetValueFromPlugin(NPPVariable variable, void* value); + nsresult GetDrawingModel(int32_t* aModel); + nsresult IsRemoteDrawingCoreAnimation(bool* aDrawing); + nsresult ContentsScaleFactorChanged(double aContentsScaleFactor); + nsresult CSSZoomFactorChanged(float aCSSZoomFactor); + nsresult GetJSObject(JSContext *cx, JSObject** outObject); + bool ShouldCache(); + nsresult IsWindowless(bool* isWindowless); + nsresult AsyncSetWindow(NPWindow* window); + nsresult GetImageContainer(mozilla::layers::ImageContainer **aContainer); + nsresult GetImageSize(nsIntSize* aSize); + nsresult NotifyPainted(void); + nsresult GetIsOOP(bool* aIsOOP); + nsresult SetBackgroundUnknown(); + nsresult BeginUpdateBackground(nsIntRect* aRect, DrawTarget** aContext); + nsresult EndUpdateBackground(nsIntRect* aRect); + nsresult IsTransparent(bool* isTransparent); + nsresult GetFormValue(nsAString& aValue); + nsresult PushPopupsEnabledState(bool aEnabled); + nsresult PopPopupsEnabledState(); + nsresult GetPluginAPIVersion(uint16_t* version); + nsresult InvalidateRect(NPRect *invalidRect); + nsresult InvalidateRegion(NPRegion invalidRegion); + nsresult GetMIMEType(const char* *result); +#if defined(XP_WIN) + nsresult GetScrollCaptureContainer(mozilla::layers::ImageContainer **aContainer); +#endif + nsresult HandledWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, + bool aIsConsumed); + nsPluginInstanceOwner* GetOwner(); + void SetOwner(nsPluginInstanceOwner *aOwner); + void DidComposite(); + + bool HasAudioChannelAgent() const + { + return !!mAudioChannelAgent; + } + + nsresult GetOrCreateAudioChannelAgent(nsIAudioChannelAgent** aAgent); + + nsresult SetMuted(bool aIsMuted); + + nsNPAPIPlugin* GetPlugin(); + + nsresult GetNPP(NPP * aNPP); + + NPError SetWindowless(bool aWindowless); + + NPError SetTransparent(bool aTransparent); + + NPError SetWantsAllNetworkStreams(bool aWantsAllNetworkStreams); + + NPError SetUsesDOMForCursor(bool aUsesDOMForCursor); + bool UsesDOMForCursor(); + + void SetDrawingModel(NPDrawingModel aModel); + void RedrawPlugin(); +#ifdef XP_MACOSX + void SetEventModel(NPEventModel aModel); + + void* GetCurrentEvent() { + return mCurrentPluginEvent; + } +#endif + +#ifdef MOZ_WIDGET_ANDROID + void NotifyForeground(bool aForeground); + void NotifyOnScreen(bool aOnScreen); + void MemoryPressure(); + void NotifyFullScreen(bool aFullScreen); + void NotifySize(nsIntSize size); + + nsIntSize CurrentSize() { return mCurrentSize; } + + bool IsOnScreen() { + return mOnScreen; + } + + uint32_t GetANPDrawingModel() { return mANPDrawingModel; } + void SetANPDrawingModel(uint32_t aModel); + + void* GetJavaSurface(); + + void PostEvent(void* event); + + // These are really mozilla::dom::ScreenOrientation, but it's + // difficult to include that here + uint32_t FullScreenOrientation() { return mFullScreenOrientation; } + void SetFullScreenOrientation(uint32_t orientation); + + void SetWakeLock(bool aLock); + + mozilla::gl::GLContext* GLContext(); + + // For ANPOpenGL + class TextureInfo { + public: + TextureInfo() : + mTexture(0), mWidth(0), mHeight(0), mInternalFormat(0) + { + } + + TextureInfo(GLuint aTexture, int32_t aWidth, int32_t aHeight, GLuint aInternalFormat) : + mTexture(aTexture), mWidth(aWidth), mHeight(aHeight), mInternalFormat(aInternalFormat) + { + } + + GLuint mTexture; + int32_t mWidth; + int32_t mHeight; + GLuint mInternalFormat; + }; + + // For ANPNativeWindow + void* AcquireContentWindow(); + + mozilla::gl::AndroidSurfaceTexture* AsSurfaceTexture(); + + // For ANPVideo + class VideoInfo { + public: + VideoInfo(mozilla::gl::AndroidSurfaceTexture* aSurfaceTexture) : + mSurfaceTexture(aSurfaceTexture) + { + } + + ~VideoInfo() + { + mSurfaceTexture = nullptr; + } + + RefPtr<mozilla::gl::AndroidSurfaceTexture> mSurfaceTexture; + gfxRect mDimensions; + }; + + void* AcquireVideoWindow(); + void ReleaseVideoWindow(void* aWindow); + void SetVideoDimensions(void* aWindow, gfxRect aDimensions); + + void GetVideos(nsTArray<VideoInfo*>& aVideos); + + void SetOriginPos(mozilla::gl::OriginPos aOriginPos) { + mOriginPos = aOriginPos; + } + mozilla::gl::OriginPos OriginPos() const { return mOriginPos; } + + static nsNPAPIPluginInstance* GetFromNPP(NPP npp); +#endif + + nsresult NewStreamListener(const char* aURL, void* notifyData, + nsNPAPIPluginStreamListener** listener); + + nsNPAPIPluginInstance(); + + // To be called when an instance becomes orphaned, when + // it's plugin is no longer guaranteed to be around. + void Destroy(); + + // Indicates whether the plugin is running normally. + bool IsRunning() { + return RUNNING == mRunning; + } + bool HasStartedDestroying() { + return mRunning >= DESTROYING; + } + + // Indicates whether the plugin is running normally or being shut down + bool CanFireNotifications() { + return mRunning == RUNNING || mRunning == DESTROYING; + } + + // return is only valid when the plugin is not running + mozilla::TimeStamp StopTime(); + + // cache this NPAPI plugin + void SetCached(bool aCache); + + already_AddRefed<nsPIDOMWindowOuter> GetDOMWindow(); + + nsresult PrivateModeStateChanged(bool aEnabled); + + nsresult IsPrivateBrowsing(bool *aEnabled); + + nsresult GetDOMElement(nsIDOMElement* *result); + + nsNPAPITimer* TimerWithID(uint32_t id, uint32_t* index); + uint32_t ScheduleTimer(uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID)); + void UnscheduleTimer(uint32_t timerID); + NPBool ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double *destX, double *destY, NPCoordinateSpace destSpace); + + + nsTArray<nsNPAPIPluginStreamListener*> *StreamListeners(); + + nsTArray<nsPluginStreamListenerPeer*> *FileCachedStreamListeners(); + + nsresult AsyncSetWindow(NPWindow& window); + + void URLRedirectResponse(void* notifyData, NPBool allow); + + NPError InitAsyncSurface(NPSize *size, NPImageFormat format, + void *initData, NPAsyncSurface *surface); + NPError FinalizeAsyncSurface(NPAsyncSurface *surface); + void SetCurrentAsyncSurface(NPAsyncSurface *surface, NPRect *changed); + + // Called when the instance fails to instantiate beceause the Carbon + // event model is not supported. + void CarbonNPAPIFailure(); + + // Returns the contents scale factor of the screen the plugin is drawn on. + double GetContentsScaleFactor(); + + // Returns the css zoom factor of the document the plugin is drawn on. + float GetCSSZoomFactor(); + + nsresult GetRunID(uint32_t *aRunID); + + static bool InPluginCallUnsafeForReentry() { return gInUnsafePluginCalls > 0; } + static void BeginPluginCall(NSPluginCallReentry aReentryState) + { + if (aReentryState == NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO) { + ++gInUnsafePluginCalls; + } + } + static void EndPluginCall(NSPluginCallReentry aReentryState) + { + if (aReentryState == NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO) { + NS_ASSERTION(gInUnsafePluginCalls > 0, "Must be in plugin call"); + --gInUnsafePluginCalls; + } + } + +protected: + + virtual ~nsNPAPIPluginInstance(); + + nsresult GetTagType(nsPluginTagType *result); + nsresult GetMode(int32_t *result); + + // check if this is a Java applet and affected by bug 750480 + void CheckJavaC2PJSObjectQuirk(uint16_t paramCount, + const char* const* names, + const char* const* values); + + // The structure used to communicate between the plugin instance and + // the browser. + NPP_t mNPP; + + NPDrawingModel mDrawingModel; + +#ifdef MOZ_WIDGET_ANDROID + uint32_t mANPDrawingModel; + + friend class PluginEventRunnable; + + nsTArray<RefPtr<PluginEventRunnable>> mPostedEvents; + void PopPostedEvent(PluginEventRunnable* r); + void OnSurfaceTextureFrameAvailable(); + + uint32_t mFullScreenOrientation; + bool mWakeLocked; + bool mFullScreen; + mozilla::gl::OriginPos mOriginPos; + + RefPtr<mozilla::gl::AndroidSurfaceTexture> mContentSurface; +#endif + + enum { + NOT_STARTED, + RUNNING, + DESTROYING, + DESTROYED + } mRunning; + + // these are used to store the windowless properties + // which the browser will later query + bool mWindowless; + bool mTransparent; + bool mCached; + bool mUsesDOMForCursor; + +public: + // True while creating the plugin, or calling NPP_SetWindow() on it. + bool mInPluginInitCall; + + nsXPIDLCString mFakeURL; + +private: + RefPtr<nsNPAPIPlugin> mPlugin; + + nsTArray<nsNPAPIPluginStreamListener*> mStreamListeners; + + nsTArray<nsPluginStreamListenerPeer*> mFileCachedStreamListeners; + + nsTArray<PopupControlState> mPopupStates; + + char* mMIMEType; + + // Weak pointer to the owner. The owner nulls this out (by calling + // InvalidateOwner()) when it's no longer our owner. + nsPluginInstanceOwner *mOwner; + + nsTArray<nsNPAPITimer*> mTimers; + +#ifdef XP_MACOSX + // non-null during a HandleEvent call + void* mCurrentPluginEvent; +#endif + + // Timestamp for the last time this plugin was stopped. + // This is only valid when the plugin is actually stopped! + mozilla::TimeStamp mStopTime; + +#ifdef MOZ_WIDGET_ANDROID + already_AddRefed<mozilla::gl::AndroidSurfaceTexture> CreateSurfaceTexture(); + + std::map<void*, VideoInfo*> mVideos; + bool mOnScreen; + + nsIntSize mCurrentSize; +#endif + + // is this instance Java and affected by bug 750480? + bool mHaveJavaC2PJSObjectQuirk; + + static uint32_t gInUnsafePluginCalls; + + // The arrays can only be released when the plugin instance is destroyed, + // because the plugin, in in-process mode, might keep a reference to them. + uint32_t mCachedParamLength; + char **mCachedParamNames; + char **mCachedParamValues; + + nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent; + bool mMuted; +}; + +// On Android, we need to guard against plugin code leaking entries in the local +// JNI ref table. See https://bugzilla.mozilla.org/show_bug.cgi?id=780831#c21 +#ifdef MOZ_WIDGET_ANDROID + #define MAIN_THREAD_JNI_REF_GUARD mozilla::AutoLocalJNIFrame jniFrame +#else + #define MAIN_THREAD_JNI_REF_GUARD +#endif + +void NS_NotifyBeginPluginCall(NSPluginCallReentry aReentryState); +void NS_NotifyPluginCall(NSPluginCallReentry aReentryState); + +#define NS_TRY_SAFE_CALL_RETURN(ret, fun, pluginInst, pluginCallReentry) \ +PR_BEGIN_MACRO \ + MAIN_THREAD_JNI_REF_GUARD; \ + NS_NotifyBeginPluginCall(pluginCallReentry); \ + ret = fun; \ + NS_NotifyPluginCall(pluginCallReentry); \ +PR_END_MACRO + +#define NS_TRY_SAFE_CALL_VOID(fun, pluginInst, pluginCallReentry) \ +PR_BEGIN_MACRO \ + MAIN_THREAD_JNI_REF_GUARD; \ + NS_NotifyBeginPluginCall(pluginCallReentry); \ + fun; \ + NS_NotifyPluginCall(pluginCallReentry); \ +PR_END_MACRO + +#endif // nsNPAPIPluginInstance_h_ diff --git a/dom/plugins/base/nsNPAPIPluginStreamListener.cpp b/dom/plugins/base/nsNPAPIPluginStreamListener.cpp new file mode 100644 index 000000000..0f500a1ae --- /dev/null +++ b/dom/plugins/base/nsNPAPIPluginStreamListener.cpp @@ -0,0 +1,959 @@ +/* -*- 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 "nsNPAPIPluginStreamListener.h" +#include "plstr.h" +#include "prmem.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIFile.h" +#include "nsNetUtil.h" +#include "nsPluginHost.h" +#include "nsNPAPIPlugin.h" +#include "nsPluginLogging.h" +#include "nsPluginStreamListenerPeer.h" + +#include <stdint.h> +#include <algorithm> + +nsNPAPIStreamWrapper::nsNPAPIStreamWrapper(nsIOutputStream *outputStream, + nsNPAPIPluginStreamListener *streamListener) +{ + mOutputStream = outputStream; + mStreamListener = streamListener; + + memset(&mNPStream, 0, sizeof(mNPStream)); + mNPStream.ndata = static_cast<void*>(this); +} + +nsNPAPIStreamWrapper::~nsNPAPIStreamWrapper() +{ + if (mOutputStream) { + mOutputStream->Close(); + } +} + +NS_IMPL_ISUPPORTS(nsPluginStreamToFile, nsIOutputStream) + +nsPluginStreamToFile::nsPluginStreamToFile(const char* target, + nsIPluginInstanceOwner* owner) +: mTarget(PL_strdup(target)), +mOwner(owner) +{ + nsresult rv; + nsCOMPtr<nsIFile> pluginTmp; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(pluginTmp)); + if (NS_FAILED(rv)) return; + + mTempFile = do_QueryInterface(pluginTmp, &rv); + if (NS_FAILED(rv)) return; + + // need to create a file with a unique name - use target as the basis + rv = mTempFile->AppendNative(nsDependentCString(target)); + if (NS_FAILED(rv)) return; + + // Yes, make it unique. + rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700); + if (NS_FAILED(rv)) return; + + // create the file + rv = NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), mTempFile, -1, 00600); + if (NS_FAILED(rv)) + return; + + // construct the URL we'll use later in calls to GetURL() + NS_GetURLSpecFromFile(mTempFile, mFileURL); + +#ifdef DEBUG + printf("File URL = %s\n", mFileURL.get()); +#endif +} + +nsPluginStreamToFile::~nsPluginStreamToFile() +{ + // should we be deleting mTempFile here? + if (nullptr != mTarget) + PL_strfree(mTarget); +} + +NS_IMETHODIMP +nsPluginStreamToFile::Flush() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsPluginStreamToFile::Write(const char* aBuf, uint32_t aCount, + uint32_t *aWriteCount) +{ + mOutputStream->Write(aBuf, aCount, aWriteCount); + mOutputStream->Flush(); + mOwner->GetURL(mFileURL.get(), mTarget, nullptr, nullptr, 0, false); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginStreamToFile::WriteFrom(nsIInputStream *inStr, uint32_t count, + uint32_t *_retval) +{ + NS_NOTREACHED("WriteFrom"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPluginStreamToFile::WriteSegments(nsReadSegmentFun reader, void * closure, + uint32_t count, uint32_t *_retval) +{ + NS_NOTREACHED("WriteSegments"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPluginStreamToFile::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginStreamToFile::Close(void) +{ + mOutputStream->Close(); + mOwner->GetURL(mFileURL.get(), mTarget, nullptr, nullptr, 0, false); + return NS_OK; +} + +// nsNPAPIPluginStreamListener Methods + +NS_IMPL_ISUPPORTS(nsNPAPIPluginStreamListener, + nsITimerCallback, nsIHTTPHeaderListener) + +nsNPAPIPluginStreamListener::nsNPAPIPluginStreamListener(nsNPAPIPluginInstance* inst, + void* notifyData, + const char* aURL) + : mStreamBuffer(nullptr) + , mNotifyURL(aURL ? PL_strdup(aURL) : nullptr) + , mInst(inst) + , mStreamBufferSize(0) + , mStreamBufferByteCount(0) + , mStreamType(NP_NORMAL) + , mStreamState(eStreamStopped) + , mStreamCleanedUp(false) + , mCallNotify(notifyData ? true : false) + , mIsSuspended(false) + , mIsPluginInitJSStream(mInst->mInPluginInitCall && + aURL && strncmp(aURL, "javascript:", + sizeof("javascript:") - 1) == 0) + , mRedirectDenied(false) + , mResponseHeaderBuf(nullptr) + , mStreamStopMode(eNormalStop) + , mPendingStopBindingStatus(NS_OK) +{ + mNPStreamWrapper = new nsNPAPIStreamWrapper(nullptr, this); + mNPStreamWrapper->mNPStream.notifyData = notifyData; +} + +nsNPAPIPluginStreamListener::~nsNPAPIPluginStreamListener() +{ + // remove this from the plugin instance's stream list + nsTArray<nsNPAPIPluginStreamListener*> *streamListeners = mInst->StreamListeners(); + streamListeners->RemoveElement(this); + + // For those cases when NewStream is never called, we still may need + // to fire a notification callback. Return network error as fallback + // reason because for other cases, notify should have already been + // called for other reasons elsewhere. + CallURLNotify(NPRES_NETWORK_ERR); + + // lets get rid of the buffer + if (mStreamBuffer) { + PR_Free(mStreamBuffer); + mStreamBuffer=nullptr; + } + + if (mNotifyURL) + PL_strfree(mNotifyURL); + + if (mResponseHeaderBuf) + PL_strfree(mResponseHeaderBuf); + + if (mNPStreamWrapper) { + delete mNPStreamWrapper; + } +} + +nsresult +nsNPAPIPluginStreamListener::CleanUpStream(NPReason reason) +{ + nsresult rv = NS_ERROR_FAILURE; + + // Various bits of code in the rest of this method may result in the + // deletion of this object. Use a KungFuDeathGrip to keep ourselves + // alive during cleanup. + RefPtr<nsNPAPIPluginStreamListener> kungFuDeathGrip(this); + + if (mStreamCleanedUp) + return NS_OK; + + mStreamCleanedUp = true; + + StopDataPump(); + + // Release any outstanding redirect callback. + if (mHTTPRedirectCallback) { + mHTTPRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_FAILURE); + mHTTPRedirectCallback = nullptr; + } + + // Seekable streams have an extra addref when they are created which must + // be matched here. + if (NP_SEEK == mStreamType && mStreamState == eStreamTypeSet) + NS_RELEASE_THIS(); + + if (mStreamListenerPeer) { + mStreamListenerPeer->CancelRequests(NS_BINDING_ABORTED); + mStreamListenerPeer = nullptr; + } + + if (!mInst || !mInst->CanFireNotifications()) + return rv; + + PluginDestructionGuard guard(mInst); + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) + return rv; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + NPP npp; + mInst->GetNPP(&npp); + + if (mStreamState >= eNewStreamCalled && pluginFunctions->destroystream) { + NPPAutoPusher nppPusher(npp); + + NPError error; + NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->destroystream)(npp, &mNPStreamWrapper->mNPStream, reason), mInst, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP DestroyStream called: this=%p, npp=%p, reason=%d, return=%d, url=%s\n", + this, npp, reason, error, mNPStreamWrapper->mNPStream.url)); + + if (error == NPERR_NO_ERROR) + rv = NS_OK; + } + + mStreamState = eStreamStopped; + + // fire notification back to plugin, just like before + CallURLNotify(reason); + + return rv; +} + +void +nsNPAPIPluginStreamListener::CallURLNotify(NPReason reason) +{ + if (!mCallNotify || !mInst || !mInst->CanFireNotifications()) + return; + + PluginDestructionGuard guard(mInst); + + mCallNotify = false; // only do this ONCE and prevent recursion + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) + return; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + if (pluginFunctions->urlnotify) { + NPP npp; + mInst->GetNPP(&npp); + + NS_TRY_SAFE_CALL_VOID((*pluginFunctions->urlnotify)(npp, mNotifyURL, reason, mNPStreamWrapper->mNPStream.notifyData), mInst, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP URLNotify called: this=%p, npp=%p, notify=%p, reason=%d, url=%s\n", + this, npp, mNPStreamWrapper->mNPStream.notifyData, reason, mNotifyURL)); + } +} + +nsresult +nsNPAPIPluginStreamListener::OnStartBinding(nsPluginStreamListenerPeer* streamPeer) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + if (!mInst || !mInst->CanFireNotifications() || mStreamCleanedUp) + return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(mInst); + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) + return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + if (!pluginFunctions->newstream) + return NS_ERROR_FAILURE; + + NPP npp; + mInst->GetNPP(&npp); + + bool seekable; + char* contentType; + uint16_t streamType = NP_NORMAL; + NPError error; + + streamPeer->GetURL(&mNPStreamWrapper->mNPStream.url); + streamPeer->GetLength((uint32_t*)&(mNPStreamWrapper->mNPStream.end)); + streamPeer->GetLastModified((uint32_t*)&(mNPStreamWrapper->mNPStream.lastmodified)); + streamPeer->IsSeekable(&seekable); + streamPeer->GetContentType(&contentType); + + if (!mResponseHeaders.IsEmpty()) { + mResponseHeaderBuf = PL_strdup(mResponseHeaders.get()); + mNPStreamWrapper->mNPStream.headers = mResponseHeaderBuf; + } + + mStreamListenerPeer = streamPeer; + + NPPAutoPusher nppPusher(npp); + + NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->newstream)(npp, (char*)contentType, &mNPStreamWrapper->mNPStream, seekable, &streamType), mInst, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP NewStream called: this=%p, npp=%p, mime=%s, seek=%d, type=%d, return=%d, url=%s\n", + this, npp, (char *)contentType, seekable, streamType, error, mNPStreamWrapper->mNPStream.url)); + + if (error != NPERR_NO_ERROR) + return NS_ERROR_FAILURE; + + mStreamState = eNewStreamCalled; + + if (!SetStreamType(streamType, false)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool +nsNPAPIPluginStreamListener::SetStreamType(uint16_t aType, bool aNeedsResume) +{ + switch(aType) + { + case NP_NORMAL: + mStreamType = NP_NORMAL; + break; + case NP_ASFILEONLY: + mStreamType = NP_ASFILEONLY; + break; + case NP_ASFILE: + mStreamType = NP_ASFILE; + break; + case NP_SEEK: + mStreamType = NP_SEEK; + // Seekable streams should continue to exist even after OnStopRequest + // is fired, so we AddRef ourself an extra time and Release when the + // plugin calls NPN_DestroyStream (CleanUpStream). If the plugin never + // calls NPN_DestroyStream the stream will be destroyed before the plugin + // instance is destroyed. + NS_ADDREF_THIS(); + break; + case nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN: + MOZ_ASSERT(!aNeedsResume); + mStreamType = nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN; + SuspendRequest(); + mStreamStopMode = eDoDeferredStop; + // In this case we do not want to execute anything else in this function. + return true; + default: + return false; + } + mStreamState = eStreamTypeSet; + if (aNeedsResume) { + if (mStreamListenerPeer) { + mStreamListenerPeer->OnStreamTypeSet(mStreamType); + } + ResumeRequest(); + } + return true; +} + +void +nsNPAPIPluginStreamListener::SuspendRequest() +{ + NS_ASSERTION(!mIsSuspended, + "Suspending a request that's already suspended!"); + + nsresult rv = StartDataPump(); + if (NS_FAILED(rv)) + return; + + mIsSuspended = true; + + if (mStreamListenerPeer) { + mStreamListenerPeer->SuspendRequests(); + } +} + +void +nsNPAPIPluginStreamListener::ResumeRequest() +{ + if (mStreamListenerPeer) { + mStreamListenerPeer->ResumeRequests(); + } + mIsSuspended = false; +} + +nsresult +nsNPAPIPluginStreamListener::StartDataPump() +{ + nsresult rv; + mDataPumpTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Start pumping data to the plugin every 100ms until it obeys and + // eats the data. + return mDataPumpTimer->InitWithCallback(this, 100, + nsITimer::TYPE_REPEATING_SLACK); +} + +void +nsNPAPIPluginStreamListener::StopDataPump() +{ + if (mDataPumpTimer) { + mDataPumpTimer->Cancel(); + mDataPumpTimer = nullptr; + } +} + +// Return true if a javascript: load that was started while the plugin +// was being initialized is still in progress. +bool +nsNPAPIPluginStreamListener::PluginInitJSLoadInProgress() +{ + if (!mInst) + return false; + + nsTArray<nsNPAPIPluginStreamListener*> *streamListeners = mInst->StreamListeners(); + for (unsigned int i = 0; i < streamListeners->Length(); i++) { + if (streamListeners->ElementAt(i)->mIsPluginInitJSStream) { + return true; + } + } + + return false; +} + +// This method is called when there's more data available off the +// network, but it's also called from our data pump when we're feeding +// the plugin data that we already got off the network, but the plugin +// was unable to consume it at the point it arrived. In the case when +// the plugin pump calls this method, the input argument will be null, +// and the length will be the number of bytes available in our +// internal buffer. +nsresult +nsNPAPIPluginStreamListener::OnDataAvailable(nsPluginStreamListenerPeer* streamPeer, + nsIInputStream* input, + uint32_t length) +{ + if (!length || !mInst || !mInst->CanFireNotifications()) + return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(mInst); + + // Just in case the caller switches plugin info on us. + mStreamListenerPeer = streamPeer; + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) + return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + // check out if plugin implements NPP_Write call + if (!pluginFunctions->write) + return NS_ERROR_FAILURE; // it'll cancel necko transaction + + if (!mStreamBuffer) { + // To optimize the mem usage & performance we have to allocate + // mStreamBuffer here in first ODA when length of data available + // in input stream is known. mStreamBuffer will be freed in DTOR. + // we also have to remember the size of that buff to make safe + // consecutive Read() calls form input stream into our buff. + + uint32_t contentLength; + streamPeer->GetLength(&contentLength); + + mStreamBufferSize = std::max(length, contentLength); + + // Limit the size of the initial buffer to MAX_PLUGIN_NECKO_BUFFER + // (16k). This buffer will grow if needed, as in the case where + // we're getting data faster than the plugin can process it. + mStreamBufferSize = std::min(mStreamBufferSize, + uint32_t(MAX_PLUGIN_NECKO_BUFFER)); + + mStreamBuffer = (char*) PR_Malloc(mStreamBufferSize); + if (!mStreamBuffer) + return NS_ERROR_OUT_OF_MEMORY; + } + + // prepare NPP_ calls params + NPP npp; + mInst->GetNPP(&npp); + + int32_t streamPosition; + streamPeer->GetStreamOffset(&streamPosition); + int32_t streamOffset = streamPosition; + + if (input) { + streamOffset += length; + + // Set new stream offset for the next ODA call regardless of how + // following NPP_Write call will behave we pretend to consume all + // data from the input stream. It's possible that current steam + // position will be overwritten from NPP_RangeRequest call made + // from NPP_Write, so we cannot call SetStreamOffset after + // NPP_Write. + // + // Note: there is a special case when data flow should be + // temporarily stopped if NPP_WriteReady returns 0 (bug #89270) + streamPeer->SetStreamOffset(streamOffset); + + // set new end in case the content is compressed + // initial end is less than end of decompressed stream + // and some plugins (e.g. acrobat) can fail. + if ((int32_t)mNPStreamWrapper->mNPStream.end < streamOffset) + mNPStreamWrapper->mNPStream.end = streamOffset; + } + + nsresult rv = NS_OK; + while (NS_SUCCEEDED(rv) && length > 0) { + if (input && length) { + if (mStreamBufferSize < mStreamBufferByteCount + length) { + // We're in the ::OnDataAvailable() call that we might get + // after suspending a request, or we suspended the request + // from within this ::OnDataAvailable() call while there's + // still data in the input, or we have resumed a previously + // suspended request and our buffer is already full, and we + // don't have enough space to store what we got off the network. + // Reallocate our internal buffer. + mStreamBufferSize = mStreamBufferByteCount + length; + char *buf = (char*)PR_Realloc(mStreamBuffer, mStreamBufferSize); + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; + + mStreamBuffer = buf; + } + + uint32_t bytesToRead = + std::min(length, mStreamBufferSize - mStreamBufferByteCount); + MOZ_ASSERT(bytesToRead > 0); + + uint32_t amountRead = 0; + rv = input->Read(mStreamBuffer + mStreamBufferByteCount, bytesToRead, + &amountRead); + NS_ENSURE_SUCCESS(rv, rv); + + if (amountRead == 0) { + NS_NOTREACHED("input->Read() returns no data, it's almost impossible " + "to get here"); + + break; + } + + mStreamBufferByteCount += amountRead; + length -= amountRead; + } else { + // No input, nothing to read. Set length to 0 so that we don't + // keep iterating through this outer loop any more. + + length = 0; + } + + // Temporary pointer to the beginning of the data we're writing as + // we loop and feed the plugin data. + char *ptrStreamBuffer = mStreamBuffer; + + // it is possible plugin's NPP_Write() returns 0 byte consumed. We + // use zeroBytesWriteCount to count situation like this and break + // the loop + int32_t zeroBytesWriteCount = 0; + + // mStreamBufferByteCount tells us how many bytes there are in the + // buffer. WriteReady returns to us how many bytes the plugin is + // ready to handle. + while (mStreamBufferByteCount > 0) { + int32_t numtowrite; + if (pluginFunctions->writeready) { + NPPAutoPusher nppPusher(npp); + + NS_TRY_SAFE_CALL_RETURN(numtowrite, (*pluginFunctions->writeready)(npp, &mNPStreamWrapper->mNPStream), mInst, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPP WriteReady called: this=%p, npp=%p, " + "return(towrite)=%d, url=%s\n", + this, npp, numtowrite, mNPStreamWrapper->mNPStream.url)); + + if (mStreamState == eStreamStopped) { + // The plugin called NPN_DestroyStream() from within + // NPP_WriteReady(), kill the stream. + + return NS_BINDING_ABORTED; + } + + // if WriteReady returned 0, the plugin is not ready to handle + // the data, suspend the stream (if it isn't already + // suspended). + // + // Also suspend the stream if the stream we're loading is not + // a javascript: URL load that was initiated during plugin + // initialization and there currently is such a stream + // loading. This is done to work around a Windows Media Player + // plugin bug where it can't deal with being fed data for + // other streams while it's waiting for data from the + // javascript: URL loads it requests during + // initialization. See bug 386493 for more details. + + if (numtowrite <= 0 || + (!mIsPluginInitJSStream && PluginInitJSLoadInProgress())) { + if (!mIsSuspended) { + SuspendRequest(); + } + + // Break out of the inner loop, but keep going through the + // outer loop in case there's more data to read from the + // input stream. + + break; + } + + numtowrite = std::min(numtowrite, mStreamBufferByteCount); + } else { + // if WriteReady is not supported by the plugin, just write + // the whole buffer + numtowrite = mStreamBufferByteCount; + } + + NPPAutoPusher nppPusher(npp); + + int32_t writeCount = 0; // bytes consumed by plugin instance + NS_TRY_SAFE_CALL_RETURN(writeCount, (*pluginFunctions->write)(npp, &mNPStreamWrapper->mNPStream, streamPosition, numtowrite, ptrStreamBuffer), mInst, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPP Write called: this=%p, npp=%p, pos=%d, len=%d, " + "buf=%s, return(written)=%d, url=%s\n", + this, npp, streamPosition, numtowrite, + ptrStreamBuffer, writeCount, mNPStreamWrapper->mNPStream.url)); + + if (mStreamState == eStreamStopped) { + // The plugin called NPN_DestroyStream() from within + // NPP_Write(), kill the stream. + return NS_BINDING_ABORTED; + } + + if (writeCount > 0) { + NS_ASSERTION(writeCount <= mStreamBufferByteCount, + "Plugin read past the end of the available data!"); + + writeCount = std::min(writeCount, mStreamBufferByteCount); + mStreamBufferByteCount -= writeCount; + + streamPosition += writeCount; + + zeroBytesWriteCount = 0; + + if (mStreamBufferByteCount > 0) { + // This alignment code is most likely bogus, but we'll leave + // it in for now in case it matters for some plugins on some + // architectures. Who knows... + if (writeCount % sizeof(intptr_t)) { + // memmove will take care about alignment + memmove(mStreamBuffer, ptrStreamBuffer + writeCount, + mStreamBufferByteCount); + ptrStreamBuffer = mStreamBuffer; + } else { + // if aligned we can use ptrStreamBuffer += to eliminate + // memmove() + ptrStreamBuffer += writeCount; + } + } + } else if (writeCount == 0) { + // if NPP_Write() returns writeCount == 0 lets say 3 times in + // a row, suspend the request and continue feeding the plugin + // the data we got so far. Once that data is consumed, we'll + // resume the request. + if (mIsSuspended || ++zeroBytesWriteCount == 3) { + if (!mIsSuspended) { + SuspendRequest(); + } + + // Break out of the for loop, but keep going through the + // while loop in case there's more data to read from the + // input stream. + + break; + } + } else { + // Something's really wrong, kill the stream. + rv = NS_ERROR_FAILURE; + + break; + } + } // end of inner while loop + + if (mStreamBufferByteCount && mStreamBuffer != ptrStreamBuffer) { + memmove(mStreamBuffer, ptrStreamBuffer, mStreamBufferByteCount); + } + } + + if (streamPosition != streamOffset) { + // The plugin didn't consume all available data, or consumed some + // of our cached data while we're pumping cached data. Adjust the + // plugin info's stream offset to match reality, except if the + // plugin info's stream offset was set by a re-entering + // NPN_RequestRead() call. + + int32_t postWriteStreamPosition; + streamPeer->GetStreamOffset(&postWriteStreamPosition); + + if (postWriteStreamPosition == streamOffset) { + streamPeer->SetStreamOffset(streamPosition); + } + } + + return rv; +} + +nsresult +nsNPAPIPluginStreamListener::OnFileAvailable(nsPluginStreamListenerPeer* streamPeer, + const char* fileName) +{ + if (!mInst || !mInst->CanFireNotifications()) + return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(mInst); + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) + return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + if (!pluginFunctions->asfile) + return NS_ERROR_FAILURE; + + NPP npp; + mInst->GetNPP(&npp); + + NS_TRY_SAFE_CALL_VOID((*pluginFunctions->asfile)(npp, &mNPStreamWrapper->mNPStream, fileName), mInst, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP StreamAsFile called: this=%p, npp=%p, url=%s, file=%s\n", + this, npp, mNPStreamWrapper->mNPStream.url, fileName)); + + return NS_OK; +} + +nsresult +nsNPAPIPluginStreamListener::OnStopBinding(nsPluginStreamListenerPeer* streamPeer, + nsresult status) +{ + if (NS_FAILED(status)) { + // The stream was destroyed, or died for some reason. Make sure we + // cancel the underlying request. + if (mStreamListenerPeer) { + mStreamListenerPeer->CancelRequests(status); + } + } + + if (!mInst || !mInst->CanFireNotifications()) { + StopDataPump(); + return NS_ERROR_FAILURE; + } + + // We need to detect that the stop is due to async stream init completion. + if (mStreamStopMode == eDoDeferredStop) { + // We shouldn't be delivering this until async init is done + mStreamStopMode = eStopPending; + mPendingStopBindingStatus = status; + if (!mDataPumpTimer) { + StartDataPump(); + } + return NS_OK; + } + + StopDataPump(); + + NPReason reason = NS_FAILED(status) ? NPRES_NETWORK_ERR : NPRES_DONE; + if (mRedirectDenied || status == NS_BINDING_ABORTED) { + reason = NPRES_USER_BREAK; + } + + // The following code can result in the deletion of 'this'. Don't + // assume we are alive after this! + // + // Delay cleanup if the stream is of type NP_SEEK and status isn't + // NS_BINDING_ABORTED (meaning the plugin hasn't called NPN_DestroyStream). + // This is because even though we're done delivering data the plugin may + // want to seek. Eventually either the plugin will call NPN_DestroyStream + // or we'll perform cleanup when the instance goes away. See bug 91140. + if (mStreamType != NP_SEEK || + (NP_SEEK == mStreamType && NS_BINDING_ABORTED == status)) { + return CleanUpStream(reason); + } + + return NS_OK; +} + +nsresult +nsNPAPIPluginStreamListener::GetStreamType(int32_t *result) +{ + *result = mStreamType; + return NS_OK; +} + +bool +nsNPAPIPluginStreamListener::MaybeRunStopBinding() +{ + if (mIsSuspended || mStreamStopMode != eStopPending) { + return false; + } + OnStopBinding(mStreamListenerPeer, mPendingStopBindingStatus); + mStreamStopMode = eNormalStop; + return true; +} + +NS_IMETHODIMP +nsNPAPIPluginStreamListener::Notify(nsITimer *aTimer) +{ + NS_ASSERTION(aTimer == mDataPumpTimer, "Uh, wrong timer?"); + + int32_t oldStreamBufferByteCount = mStreamBufferByteCount; + + nsresult rv = OnDataAvailable(mStreamListenerPeer, nullptr, mStreamBufferByteCount); + + if (NS_FAILED(rv)) { + // We ran into an error, no need to keep firing this timer then. + StopDataPump(); + MaybeRunStopBinding(); + return NS_OK; + } + + if (mStreamBufferByteCount != oldStreamBufferByteCount && + ((mStreamState == eStreamTypeSet && mStreamBufferByteCount < 1024) || + mStreamBufferByteCount == 0)) { + // The plugin read some data and we've got less than 1024 bytes in + // our buffer (or its empty and the stream is already + // done). Resume the request so that we get more data off the + // network. + ResumeRequest(); + // Necko will pump data now that we've resumed the request. + StopDataPump(); + } + + MaybeRunStopBinding(); + return NS_OK; +} + +NS_IMETHODIMP +nsNPAPIPluginStreamListener::StatusLine(const char* line) +{ + mResponseHeaders.Append(line); + mResponseHeaders.Append('\n'); + return NS_OK; +} + +NS_IMETHODIMP +nsNPAPIPluginStreamListener::NewResponseHeader(const char* headerName, + const char* headerValue) +{ + mResponseHeaders.Append(headerName); + mResponseHeaders.AppendLiteral(": "); + mResponseHeaders.Append(headerValue); + mResponseHeaders.Append('\n'); + return NS_OK; +} + +bool +nsNPAPIPluginStreamListener::HandleRedirectNotification(nsIChannel *oldChannel, nsIChannel *newChannel, + nsIAsyncVerifyRedirectCallback* callback) +{ + nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(oldChannel); + nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel); + if (!oldHttpChannel || !newHttpChannel) { + return false; + } + + if (!mInst || !mInst->CanFireNotifications()) { + return false; + } + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) { + return false; + } + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + if (!pluginFunctions->urlredirectnotify) { + return false; + } + + // A non-null closure is required for redirect handling support. + if (mNPStreamWrapper->mNPStream.notifyData) { + uint32_t status; + if (NS_SUCCEEDED(oldHttpChannel->GetResponseStatus(&status))) { + nsCOMPtr<nsIURI> uri; + if (NS_SUCCEEDED(newHttpChannel->GetURI(getter_AddRefs(uri))) && uri) { + nsAutoCString spec; + if (NS_SUCCEEDED(uri->GetAsciiSpec(spec))) { + // At this point the plugin will be responsible for making the callback + // so save the callback object. + mHTTPRedirectCallback = callback; + + NPP npp; + mInst->GetNPP(&npp); +#if defined(XP_WIN) + NS_TRY_SAFE_CALL_VOID((*pluginFunctions->urlredirectnotify)(npp, spec.get(), static_cast<int32_t>(status), mNPStreamWrapper->mNPStream.notifyData), mInst, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); +#else + MAIN_THREAD_JNI_REF_GUARD; + (*pluginFunctions->urlredirectnotify)(npp, spec.get(), static_cast<int32_t>(status), mNPStreamWrapper->mNPStream.notifyData); +#endif + return true; + } + } + } + } + + callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE); + return true; +} + +void +nsNPAPIPluginStreamListener::URLRedirectResponse(NPBool allow) +{ + if (mHTTPRedirectCallback) { + mHTTPRedirectCallback->OnRedirectVerifyCallback(allow ? NS_OK : NS_ERROR_FAILURE); + mRedirectDenied = allow ? false : true; + mHTTPRedirectCallback = nullptr; + } +} + +void* +nsNPAPIPluginStreamListener::GetNotifyData() +{ + if (mNPStreamWrapper) { + return mNPStreamWrapper->mNPStream.notifyData; + } + return nullptr; +} diff --git a/dom/plugins/base/nsNPAPIPluginStreamListener.h b/dom/plugins/base/nsNPAPIPluginStreamListener.h new file mode 100644 index 000000000..dd567f99b --- /dev/null +++ b/dom/plugins/base/nsNPAPIPluginStreamListener.h @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsNPAPIPluginStreamListener_h_ +#define nsNPAPIPluginStreamListener_h_ + +#include "nscore.h" +#include "nsIHTTPHeaderListener.h" +#include "nsIRequest.h" +#include "nsITimer.h" +#include "nsCOMArray.h" +#include "nsIOutputStream.h" +#include "nsIPluginInstanceOwner.h" +#include "nsString.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "mozilla/PluginLibrary.h" + +#define MAX_PLUGIN_NECKO_BUFFER 16384 + +class nsPluginStreamListenerPeer; +class nsNPAPIPluginStreamListener; +class nsNPAPIPluginInstance; +class nsIChannel; + +class nsNPAPIStreamWrapper +{ +public: + nsNPAPIStreamWrapper(nsIOutputStream *outputStream, + nsNPAPIPluginStreamListener *streamListener); + ~nsNPAPIStreamWrapper(); + + nsIOutputStream* GetOutputStream() { return mOutputStream.get(); } + nsNPAPIPluginStreamListener* GetStreamListener() { return mStreamListener; } + + NPStream mNPStream; +protected: + nsCOMPtr<nsIOutputStream> mOutputStream; // only valid if not browser initiated + nsNPAPIPluginStreamListener* mStreamListener; // only valid if browser initiated +}; + +// Used to handle NPN_NewStream() - writes the stream as received by the plugin +// to a file and at completion (NPN_DestroyStream), tells the browser to load it into +// a plugin-specified target +class nsPluginStreamToFile : public nsIOutputStream +{ +public: + nsPluginStreamToFile(const char* target, nsIPluginInstanceOwner* owner); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM +protected: + virtual ~nsPluginStreamToFile(); + char* mTarget; + nsCString mFileURL; + nsCOMPtr<nsIFile> mTempFile; + nsCOMPtr<nsIOutputStream> mOutputStream; + nsIPluginInstanceOwner* mOwner; +}; + +class nsNPAPIPluginStreamListener : public nsITimerCallback, + public nsIHTTPHeaderListener +{ +private: + typedef mozilla::PluginLibrary PluginLibrary; + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIHTTPHEADERLISTENER + + nsNPAPIPluginStreamListener(nsNPAPIPluginInstance* inst, void* notifyData, + const char* aURL); + + nsresult OnStartBinding(nsPluginStreamListenerPeer* streamPeer); + nsresult OnDataAvailable(nsPluginStreamListenerPeer* streamPeer, + nsIInputStream* input, + uint32_t length); + nsresult OnFileAvailable(nsPluginStreamListenerPeer* streamPeer, + const char* fileName); + nsresult OnStopBinding(nsPluginStreamListenerPeer* streamPeer, + nsresult status); + nsresult GetStreamType(int32_t *result); + bool SetStreamType(uint16_t aType, bool aNeedsResume = true); + + bool IsStarted(); + nsresult CleanUpStream(NPReason reason); + void CallURLNotify(NPReason reason); + void SetCallNotify(bool aCallNotify) { mCallNotify = aCallNotify; } + void SuspendRequest(); + void ResumeRequest(); + nsresult StartDataPump(); + void StopDataPump(); + bool PluginInitJSLoadInProgress(); + + void* GetNotifyData(); + nsPluginStreamListenerPeer* GetStreamListenerPeer() { return mStreamListenerPeer; } + void SetStreamListenerPeer(nsPluginStreamListenerPeer* aPeer) { mStreamListenerPeer = aPeer; } + + // Returns true if the redirect will be handled by NPAPI, false otherwise. + bool HandleRedirectNotification(nsIChannel *oldChannel, nsIChannel *newChannel, + nsIAsyncVerifyRedirectCallback* callback); + void URLRedirectResponse(NPBool allow); + +protected: + + enum StreamState + { + eStreamStopped = 0, // The stream is stopped + eNewStreamCalled, // NPP_NewStream was called but has not completed yet + eStreamTypeSet // The stream is fully initialized + }; + + enum StreamStopMode + { + eNormalStop = 0, + eDoDeferredStop, + eStopPending + }; + + virtual ~nsNPAPIPluginStreamListener(); + bool MaybeRunStopBinding(); + + char* mStreamBuffer; + char* mNotifyURL; + RefPtr<nsNPAPIPluginInstance> mInst; + nsNPAPIStreamWrapper *mNPStreamWrapper; + uint32_t mStreamBufferSize; + int32_t mStreamBufferByteCount; + int32_t mStreamType; + StreamState mStreamState; + bool mStreamCleanedUp; + bool mCallNotify; + bool mIsSuspended; + bool mIsPluginInitJSStream; + bool mRedirectDenied; + nsCString mResponseHeaders; + char* mResponseHeaderBuf; + nsCOMPtr<nsITimer> mDataPumpTimer; + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mHTTPRedirectCallback; + StreamStopMode mStreamStopMode; + nsresult mPendingStopBindingStatus; + +public: + RefPtr<nsPluginStreamListenerPeer> mStreamListenerPeer; +}; + +#endif // nsNPAPIPluginStreamListener_h_ diff --git a/dom/plugins/base/nsPluginDirServiceProvider.cpp b/dom/plugins/base/nsPluginDirServiceProvider.cpp new file mode 100644 index 000000000..92348c182 --- /dev/null +++ b/dom/plugins/base/nsPluginDirServiceProvider.cpp @@ -0,0 +1,448 @@ +/* -*- 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 "nsPluginDirServiceProvider.h" + +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsDependentString.h" +#include "nsArrayEnumerator.h" +#include "mozilla/Preferences.h" + +#include <windows.h> +#include "nsIWindowsRegKey.h" + +using namespace mozilla; + +typedef struct structVer +{ + WORD wMajor; + WORD wMinor; + WORD wRelease; + WORD wBuild; +} verBlock; + +static void +ClearVersion(verBlock *ver) +{ + ver->wMajor = 0; + ver->wMinor = 0; + ver->wRelease = 0; + ver->wBuild = 0; +} + +static BOOL +FileExists(LPCWSTR szFile) +{ + return GetFileAttributesW(szFile) != 0xFFFFFFFF; +} + +// Get file version information from a file +static BOOL +GetFileVersion(LPCWSTR szFile, verBlock *vbVersion) +{ + UINT uLen; + UINT dwLen; + BOOL bRv; + DWORD dwHandle; + LPVOID lpData; + LPVOID lpBuffer; + VS_FIXEDFILEINFO *lpBuffer2; + + ClearVersion(vbVersion); + if (FileExists(szFile)) { + bRv = TRUE; + LPCWSTR lpFilepath = szFile; + dwLen = GetFileVersionInfoSizeW(lpFilepath, &dwHandle); + lpData = (LPVOID)malloc(dwLen); + uLen = 0; + + if (lpData && GetFileVersionInfoW(lpFilepath, dwHandle, dwLen, lpData) != 0) { + if (VerQueryValueW(lpData, L"\\", &lpBuffer, &uLen) != 0) { + lpBuffer2 = (VS_FIXEDFILEINFO *)lpBuffer; + + vbVersion->wMajor = HIWORD(lpBuffer2->dwFileVersionMS); + vbVersion->wMinor = LOWORD(lpBuffer2->dwFileVersionMS); + vbVersion->wRelease = HIWORD(lpBuffer2->dwFileVersionLS); + vbVersion->wBuild = LOWORD(lpBuffer2->dwFileVersionLS); + } + } + + free(lpData); + } else { + /* File does not exist */ + bRv = FALSE; + } + + return bRv; +} + +// Will deep copy ver2 into ver1 +static void +CopyVersion(verBlock *ver1, verBlock *ver2) +{ + ver1->wMajor = ver2->wMajor; + ver1->wMinor = ver2->wMinor; + ver1->wRelease = ver2->wRelease; + ver1->wBuild = ver2->wBuild; +} + +// Convert a string version to a version struct +static void +TranslateVersionStr(const WCHAR* szVersion, verBlock *vbVersion) +{ + WCHAR* szNum1 = nullptr; + WCHAR* szNum2 = nullptr; + WCHAR* szNum3 = nullptr; + WCHAR* szNum4 = nullptr; + WCHAR* szJavaBuild = nullptr; + + WCHAR *strVer = nullptr; + if (szVersion) { + strVer = wcsdup(szVersion); + } + + if (!strVer) { + // Out of memory + ClearVersion(vbVersion); + return; + } + + // Java may be using an underscore instead of a dot for the build ID + szJavaBuild = wcschr(strVer, '_'); + if (szJavaBuild) { + szJavaBuild[0] = '.'; + } + +#if defined(__MINGW32__) + // MSVC 2013 and earlier provided only a non-standard two-argument variant of + // wcstok that is generally not thread-safe. For our purposes here, it works + // fine, though. + auto wcstok = [](wchar_t* strToken, const wchar_t* strDelimit, + wchar_t** /*ctx*/) { + return ::std::wcstok(strToken, strDelimit); + }; +#endif + + wchar_t* ctx = nullptr; + szNum1 = wcstok(strVer, L".", &ctx); + szNum2 = wcstok(nullptr, L".", &ctx); + szNum3 = wcstok(nullptr, L".", &ctx); + szNum4 = wcstok(nullptr, L".", &ctx); + + vbVersion->wMajor = szNum1 ? (WORD) _wtoi(szNum1) : 0; + vbVersion->wMinor = szNum2 ? (WORD) _wtoi(szNum2) : 0; + vbVersion->wRelease = szNum3 ? (WORD) _wtoi(szNum3) : 0; + vbVersion->wBuild = szNum4 ? (WORD) _wtoi(szNum4) : 0; + + free(strVer); +} + +// Compare two version struct, return zero if the same +static int +CompareVersion(verBlock vbVersionOld, verBlock vbVersionNew) +{ + if (vbVersionOld.wMajor > vbVersionNew.wMajor) { + return 4; + } else if (vbVersionOld.wMajor < vbVersionNew.wMajor) { + return -4; + } + + if (vbVersionOld.wMinor > vbVersionNew.wMinor) { + return 3; + } else if (vbVersionOld.wMinor < vbVersionNew.wMinor) { + return -3; + } + + if (vbVersionOld.wRelease > vbVersionNew.wRelease) { + return 2; + } else if (vbVersionOld.wRelease < vbVersionNew.wRelease) { + return -2; + } + + if (vbVersionOld.wBuild > vbVersionNew.wBuild) { + return 1; + } else if (vbVersionOld.wBuild < vbVersionNew.wBuild) { + return -1; + } + + /* the versions are all the same */ + return 0; +} + +//***************************************************************************** +// nsPluginDirServiceProvider::Constructor/Destructor +//***************************************************************************** + +nsPluginDirServiceProvider::nsPluginDirServiceProvider() +{ +} + +nsPluginDirServiceProvider::~nsPluginDirServiceProvider() +{ +} + +//***************************************************************************** +// nsPluginDirServiceProvider::nsISupports +//***************************************************************************** + +NS_IMPL_ISUPPORTS(nsPluginDirServiceProvider, + nsIDirectoryServiceProvider) + +//***************************************************************************** +// nsPluginDirServiceProvider::nsIDirectoryServiceProvider +//***************************************************************************** + +NS_IMETHODIMP +nsPluginDirServiceProvider::GetFile(const char *charProp, bool *persistant, + nsIFile **_retval) +{ + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_ERROR_FAILURE; + + NS_ENSURE_ARG(charProp); + + *_retval = nullptr; + *persistant = false; + + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + NS_ENSURE_TRUE(regKey, NS_ERROR_FAILURE); + + if (nsCRT::strcmp(charProp, NS_WIN_QUICKTIME_SCAN_KEY) == 0) { + nsAdoptingCString strVer = Preferences::GetCString(charProp); + if (!strVer) { + return NS_ERROR_FAILURE; + } + verBlock minVer; + TranslateVersionStr(NS_ConvertASCIItoUTF16(strVer).get(), &minVer); + + // Look for the Quicktime system installation plugins directory + verBlock qtVer; + ClearVersion(&qtVer); + + // First we need to check the version of Quicktime via checking + // the EXE's version table + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + NS_LITERAL_STRING("software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\QuickTimePlayer.exe"), + nsIWindowsRegKey::ACCESS_READ); + if (NS_SUCCEEDED(rv)) { + nsAutoString path; + rv = regKey->ReadStringValue(NS_LITERAL_STRING(""), path); + if (NS_SUCCEEDED(rv)) { + GetFileVersion(path.get(), &qtVer); + } + regKey->Close(); + } + if (CompareVersion(qtVer, minVer) < 0) + return rv; + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + NS_LITERAL_STRING("software\\Apple Computer, Inc.\\QuickTime"), + nsIWindowsRegKey::ACCESS_READ); + if (NS_SUCCEEDED(rv)) { + nsAutoString path; + rv = regKey->ReadStringValue(NS_LITERAL_STRING("InstallDir"), path); + if (NS_SUCCEEDED(rv)) { + path += NS_LITERAL_STRING("\\Plugins"); + rv = NS_NewLocalFile(path, true, + getter_AddRefs(localFile)); + } + } + } else if (nsCRT::strcmp(charProp, NS_WIN_WMP_SCAN_KEY) == 0) { + nsAdoptingCString strVer = Preferences::GetCString(charProp); + if (!strVer) { + return NS_ERROR_FAILURE; + } + verBlock minVer; + TranslateVersionStr(NS_ConvertASCIItoUTF16(strVer).get(), &minVer); + + // Look for Windows Media Player system installation plugins directory + verBlock wmpVer; + ClearVersion(&wmpVer); + + // First we need to check the version of WMP + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + NS_LITERAL_STRING("software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\wmplayer.exe"), + nsIWindowsRegKey::ACCESS_READ); + if (NS_SUCCEEDED(rv)) { + nsAutoString path; + rv = regKey->ReadStringValue(NS_LITERAL_STRING(""), path); + if (NS_SUCCEEDED(rv)) { + GetFileVersion(path.get(), &wmpVer); + } + regKey->Close(); + } + if (CompareVersion(wmpVer, minVer) < 0) + return rv; + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + NS_LITERAL_STRING("software\\Microsoft\\MediaPlayer"), + nsIWindowsRegKey::ACCESS_READ); + if (NS_SUCCEEDED(rv)) { + nsAutoString path; + rv = regKey->ReadStringValue(NS_LITERAL_STRING("Installation Directory"), + path); + if (NS_SUCCEEDED(rv)) { + rv = NS_NewLocalFile(path, true, + getter_AddRefs(localFile)); + } + } + } else if (nsCRT::strcmp(charProp, NS_WIN_ACROBAT_SCAN_KEY) == 0) { + nsAdoptingCString strVer = Preferences::GetCString(charProp); + if (!strVer) { + return NS_ERROR_FAILURE; + } + + verBlock minVer; + TranslateVersionStr(NS_ConvertASCIItoUTF16(strVer).get(), &minVer); + + // Look for Adobe Acrobat system installation plugins directory + verBlock maxVer; + ClearVersion(&maxVer); + + nsAutoString newestPath; + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + NS_LITERAL_STRING("software\\Adobe\\Acrobat Reader"), + nsIWindowsRegKey::ACCESS_READ); + if (NS_FAILED(rv)) { + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + NS_LITERAL_STRING("software\\Adobe\\Adobe Acrobat"), + nsIWindowsRegKey::ACCESS_READ); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + + // We must enumerate through the keys because what if there is + // more than one version? + uint32_t childCount = 0; + regKey->GetChildCount(&childCount); + + for (uint32_t index = 0; index < childCount; ++index) { + nsAutoString childName; + rv = regKey->GetChildName(index, childName); + if (NS_SUCCEEDED(rv)) { + verBlock curVer; + TranslateVersionStr(childName.get(), &curVer); + + childName += NS_LITERAL_STRING("\\InstallPath"); + + nsCOMPtr<nsIWindowsRegKey> childKey; + rv = regKey->OpenChild(childName, nsIWindowsRegKey::ACCESS_QUERY_VALUE, + getter_AddRefs(childKey)); + if (NS_SUCCEEDED(rv)) { + // We have a sub key + nsAutoString path; + rv = childKey->ReadStringValue(NS_LITERAL_STRING(""), path); + if (NS_SUCCEEDED(rv)) { + if (CompareVersion(curVer, maxVer) >= 0 && + CompareVersion(curVer, minVer) >= 0) { + newestPath = path; + CopyVersion(&maxVer, &curVer); + } + } + } + } + } + + if (!newestPath.IsEmpty()) { + newestPath += NS_LITERAL_STRING("\\browser"); + rv = NS_NewLocalFile(newestPath, true, + getter_AddRefs(localFile)); + } + } + + if (NS_FAILED(rv)) { + return rv; + } + + localFile.forget(_retval); + return NS_OK; +} + +nsresult +nsPluginDirServiceProvider::GetPLIDDirectories(nsISimpleEnumerator **aEnumerator) +{ + NS_ENSURE_ARG_POINTER(aEnumerator); + *aEnumerator = nullptr; + + nsCOMArray<nsIFile> dirs; + + GetPLIDDirectoriesWithRootKey(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, dirs); + GetPLIDDirectoriesWithRootKey(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, dirs); + + return NS_NewArrayEnumerator(aEnumerator, dirs); +} + +nsresult +nsPluginDirServiceProvider::GetPLIDDirectoriesWithRootKey(uint32_t aKey, nsCOMArray<nsIFile> &aDirs) +{ + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + NS_ENSURE_TRUE(regKey, NS_ERROR_FAILURE); + + nsresult rv = regKey->Open(aKey, + NS_LITERAL_STRING("Software\\MozillaPlugins"), + nsIWindowsRegKey::ACCESS_READ); + if (NS_FAILED(rv)) { + return rv; + } + + uint32_t childCount = 0; + regKey->GetChildCount(&childCount); + + for (uint32_t index = 0; index < childCount; ++index) { + nsAutoString childName; + rv = regKey->GetChildName(index, childName); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIWindowsRegKey> childKey; + rv = regKey->OpenChild(childName, nsIWindowsRegKey::ACCESS_QUERY_VALUE, + getter_AddRefs(childKey)); + if (NS_SUCCEEDED(rv) && childKey) { + nsAutoString path; + rv = childKey->ReadStringValue(NS_LITERAL_STRING("Path"), path); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> localFile; + if (NS_SUCCEEDED(NS_NewLocalFile(path, true, + getter_AddRefs(localFile))) && + localFile) { + // Some vendors use a path directly to the DLL so chop off + // the filename + bool isDir = false; + if (NS_SUCCEEDED(localFile->IsDirectory(&isDir)) && !isDir) { + nsCOMPtr<nsIFile> temp; + localFile->GetParent(getter_AddRefs(temp)); + if (temp) + localFile = temp; + } + + // Now we check to make sure it's actually on disk and + // To see if we already have this directory in the array + bool isFileThere = false; + bool isDupEntry = false; + if (NS_SUCCEEDED(localFile->Exists(&isFileThere)) && isFileThere) { + int32_t c = aDirs.Count(); + for (int32_t i = 0; i < c; i++) { + nsIFile *dup = static_cast<nsIFile*>(aDirs[i]); + if (dup && + NS_SUCCEEDED(dup->Equals(localFile, &isDupEntry)) && + isDupEntry) { + break; + } + } + + if (!isDupEntry) { + aDirs.AppendObject(localFile); + } + } + } + } + } + } + } + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginDirServiceProvider.h b/dom/plugins/base/nsPluginDirServiceProvider.h new file mode 100644 index 000000000..1fc735d02 --- /dev/null +++ b/dom/plugins/base/nsPluginDirServiceProvider.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsPluginDirServiceProvider_h_ +#define nsPluginDirServiceProvider_h_ + +#include "nsIDirectoryService.h" + +#if defined (XP_WIN) +#include "nsCOMArray.h" +#endif + +class nsISimpleEnumerator; + +// Note: Our directory service provider scan keys are prefs which are check +// for minimum versions compatibility +#define NS_WIN_ACROBAT_SCAN_KEY "plugin.scan.Acrobat" +#define NS_WIN_QUICKTIME_SCAN_KEY "plugin.scan.Quicktime" +#define NS_WIN_WMP_SCAN_KEY "plugin.scan.WindowsMediaPlayer" + +//***************************************************************************** +// class nsPluginDirServiceProvider +//***************************************************************************** + +class nsPluginDirServiceProvider : public nsIDirectoryServiceProvider +{ +public: + nsPluginDirServiceProvider(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + +#ifdef XP_WIN + static nsresult GetPLIDDirectories(nsISimpleEnumerator **aEnumerator); +private: + static nsresult GetPLIDDirectoriesWithRootKey(uint32_t aKey, + nsCOMArray<nsIFile> &aDirs); +#endif + +protected: + virtual ~nsPluginDirServiceProvider(); +}; + +#endif // nsPluginDirServiceProvider_h_ diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp new file mode 100644 index 000000000..6ee23f38b --- /dev/null +++ b/dom/plugins/base/nsPluginHost.cpp @@ -0,0 +1,4239 @@ +/* -*- 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/. */ + +/* nsPluginHost.cpp - top-level plugin management code */ + +#include "nscore.h" +#include "nsPluginHost.h" + +#include <cstdlib> +#include <stdio.h> +#include "prio.h" +#include "prmem.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsNPAPIPluginInstance.h" +#include "nsPluginInstanceOwner.h" +#include "nsObjectLoadingContent.h" +#include "nsIHTTPHeaderListener.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsIObserverService.h" +#include "nsIHttpProtocolHandler.h" +#include "nsIHttpChannel.h" +#include "nsIUploadChannel.h" +#include "nsIByteRangeRequest.h" +#include "nsIStreamListener.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIURL.h" +#include "nsTArray.h" +#include "nsReadableUtils.h" +#include "nsProtocolProxyService.h" +#include "nsIStreamConverterService.h" +#include "nsIFile.h" +#if defined(XP_MACOSX) +#include "nsILocalFileMac.h" +#endif +#include "nsISeekableStream.h" +#include "nsNetUtil.h" +#include "nsIFileStreams.h" +#include "nsISimpleEnumerator.h" +#include "nsIStringStream.h" +#include "nsIProgressEventSink.h" +#include "nsIDocument.h" +#include "nsPluginLogging.h" +#include "nsIScriptChannel.h" +#include "nsIBlocklistService.h" +#include "nsVersionComparator.h" +#include "nsIObjectLoadingContent.h" +#include "nsIWritablePropertyBag2.h" +#include "nsICategoryManager.h" +#include "nsPluginStreamListenerPeer.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/FakePluginTagInitBinding.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/plugins/PluginAsyncSurrogate.h" +#include "mozilla/plugins/PluginBridge.h" +#include "mozilla/plugins/PluginTypes.h" +#include "mozilla/Preferences.h" + +#include "nsEnumeratorUtils.h" +#include "nsXPCOM.h" +#include "nsXPCOMCID.h" +#include "nsISupportsPrimitives.h" + +#include "nsXULAppAPI.h" +#include "nsIXULRuntime.h" + +// for the dialog +#include "nsIWindowWatcher.h" +#include "nsIDOMElement.h" +#include "nsIDOMWindow.h" + +#include "nsNetCID.h" +#include "mozilla/Sprintf.h" +#include "nsThreadUtils.h" +#include "nsIInputStreamTee.h" +#include "nsQueryObject.h" + +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsPluginDirServiceProvider.h" + +#include "nsUnicharUtils.h" +#include "nsPluginManifestLineReader.h" + +#include "nsIWeakReferenceUtils.h" +#include "nsIPresShell.h" +#include "nsPluginNativeWindow.h" +#include "nsIContentPolicy.h" +#include "nsContentPolicyUtils.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Telemetry.h" +#include "nsIImageLoadingContent.h" +#include "mozilla/Preferences.h" +#include "nsVersionComparator.h" +#include "nsNullPrincipal.h" + +#if defined(XP_WIN) +#include "nsIWindowMediator.h" +#include "nsIBaseWindow.h" +#include "windows.h" +#include "winbase.h" +#endif + +#ifdef ANDROID +#include <android/log.h> +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#endif + +#if MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +#include "npapi.h" + +using namespace mozilla; +using mozilla::TimeStamp; +using mozilla::plugins::PluginTag; +using mozilla::plugins::PluginAsyncSurrogate; +using mozilla::dom::FakePluginTagInit; + +// Null out a strong ref to a linked list iteratively to avoid +// exhausting the stack (bug 486349). +#define NS_ITERATIVE_UNREF_LIST(type_, list_, mNext_) \ + { \ + while (list_) { \ + type_ temp = list_->mNext_; \ + list_->mNext_ = nullptr; \ + list_ = temp; \ + } \ + } + +// this is the name of the directory which will be created +// to cache temporary files. +#define kPluginTmpDirName NS_LITERAL_CSTRING("plugtmp") + +static const char *kPrefWhitelist = "plugin.allowed_types"; +static const char *kPrefLoadInParentPrefix = "plugin.load_in_parent_process."; +static const char *kPrefDisableFullPage = "plugin.disable_full_page_plugin_for_types"; +static const char *kPrefJavaMIME = "plugin.java.mime"; + +// How long we wait before unloading an idle plugin process. +// Defaults to 30 seconds. +static const char *kPrefUnloadPluginTimeoutSecs = "dom.ipc.plugins.unloadTimeoutSecs"; +static const uint32_t kDefaultPluginUnloadingTimeout = 30; + +static const char *kPluginRegistryVersion = "0.18"; + +static const char kDirectoryServiceContractID[] = "@mozilla.org/file/directory_service;1"; + +#define kPluginRegistryFilename NS_LITERAL_CSTRING("pluginreg.dat") + +#ifdef PLUGIN_LOGGING +LazyLogModule nsPluginLogging::gNPNLog(NPN_LOG_NAME); +LazyLogModule nsPluginLogging::gNPPLog(NPP_LOG_NAME); +LazyLogModule nsPluginLogging::gPluginLog(PLUGIN_LOG_NAME); +#endif + +// #defines for plugin cache and prefs +#define NS_PREF_MAX_NUM_CACHED_INSTANCES "browser.plugins.max_num_cached_plugins" +// Raise this from '10' to '50' to work around a bug in Apple's current Java +// plugins on OS X Lion and SnowLeopard. See bug 705931. +#define DEFAULT_NUMBER_OF_STOPPED_INSTANCES 50 + +nsIFile *nsPluginHost::sPluginTempDir; +nsPluginHost *nsPluginHost::sInst; + +/* to cope with short read */ +/* we should probably put this into a global library now that this is the second + time we need this. */ +static +int32_t +busy_beaver_PR_Read(PRFileDesc *fd, void * start, int32_t len) +{ + int n; + int32_t remaining = len; + + while (remaining > 0) + { + n = PR_Read(fd, start, remaining); + if (n < 0) + { + /* may want to repeat if errno == EINTR */ + if( (len - remaining) == 0 ) // no octet is ever read + return -1; + break; + } + else + { + remaining -= n; + char *cp = (char *) start; + cp += n; + start = cp; + } + } + return len - remaining; +} + +NS_IMPL_ISUPPORTS0(nsInvalidPluginTag) + +nsInvalidPluginTag::nsInvalidPluginTag(const char* aFullPath, int64_t aLastModifiedTime) +: mFullPath(aFullPath), + mLastModifiedTime(aLastModifiedTime), + mSeen(false) +{} + +nsInvalidPluginTag::~nsInvalidPluginTag() +{} + +// Helper to check for a MIME in a comma-delimited preference +static bool +IsTypeInList(const nsCString& aMimeType, nsCString aTypeList) +{ + nsAutoCString searchStr; + searchStr.Assign(','); + searchStr.Append(aTypeList); + searchStr.Append(','); + + nsACString::const_iterator start, end; + + searchStr.BeginReading(start); + searchStr.EndReading(end); + + nsAutoCString commaSeparated; + commaSeparated.Assign(','); + commaSeparated += aMimeType; + commaSeparated.Append(','); + + // Lower-case the search string and MIME type to properly handle a mixed-case + // type, as MIME types are case insensitive. + ToLowerCase(searchStr); + ToLowerCase(commaSeparated); + + return FindInReadable(commaSeparated, start, end); +} + +// flat file reg funcs +static +bool ReadSectionHeader(nsPluginManifestLineReader& reader, const char *token) +{ + do { + if (*reader.LinePtr() == '[') { + char* p = reader.LinePtr() + (reader.LineLength() - 1); + if (*p != ']') + break; + *p = 0; + + char* values[1]; + if (1 != reader.ParseLine(values, 1)) + break; + // ignore the leading '[' + if (PL_strcmp(values[0]+1, token)) { + break; // it's wrong token + } + return true; + } + } while (reader.NextLine()); + return false; +} + +static bool UnloadPluginsASAP() +{ + return (Preferences::GetUint(kPrefUnloadPluginTimeoutSecs, kDefaultPluginUnloadingTimeout) == 0); +} + +nsPluginHost::nsPluginHost() + // No need to initialize members to nullptr, false etc because this class + // has a zeroing operator new. +{ + // Bump the pluginchanged epoch on startup. This insures content gets a + // good plugin list the first time it requests it. Normally we'd just + // init this to 1, but due to the unique nature of our ctor we need to do + // this manually. + if (XRE_IsParentProcess()) { + IncrementChromeEpoch(); + } else { + // When NPAPI requests the proxy setting by calling |FindProxyForURL|, + // the service is requested and initialized asynchronously, but + // |FindProxyForURL| is synchronous, so we should initialize this earlier. + nsCOMPtr<nsIProtocolProxyService> proxyService = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); + } + + // check to see if pref is set at startup to let plugins take over in + // full page mode for certain image mime types that we handle internally + mOverrideInternalTypes = + Preferences::GetBool("plugin.override_internal_types", false); + + mPluginsDisabled = Preferences::GetBool("plugin.disable", false); + + Preferences::AddStrongObserver(this, "plugin.disable"); + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) { + obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + obsService->AddObserver(this, "blocklist-updated", false); +#ifdef MOZ_WIDGET_ANDROID + obsService->AddObserver(this, "application-foreground", false); + obsService->AddObserver(this, "application-background", false); +#endif + } + +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gNPNLog, PLUGIN_LOG_ALWAYS,("NPN Logging Active!\n")); + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_ALWAYS,("General Plugin Logging Active! (nsPluginHost::ctor)\n")); + MOZ_LOG(nsPluginLogging::gNPPLog, PLUGIN_LOG_ALWAYS,("NPP Logging Active!\n")); + + PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("nsPluginHost::ctor\n")); + PR_LogFlush(); +#endif +} + +nsPluginHost::~nsPluginHost() +{ + PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("nsPluginHost::dtor\n")); + + UnloadPlugins(); + sInst = nullptr; +} + +NS_IMPL_ISUPPORTS(nsPluginHost, + nsIPluginHost, + nsIObserver, + nsITimerCallback, + nsISupportsWeakReference) + +already_AddRefed<nsPluginHost> +nsPluginHost::GetInst() +{ + if (!sInst) { + sInst = new nsPluginHost(); + if (!sInst) + return nullptr; + NS_ADDREF(sInst); + } + + RefPtr<nsPluginHost> inst = sInst; + return inst.forget(); +} + +bool nsPluginHost::IsRunningPlugin(nsPluginTag * aPluginTag) +{ + if (!aPluginTag || !aPluginTag->mPlugin) { + return false; + } + + if (aPluginTag->mContentProcessRunningCount) { + return true; + } + + for (uint32_t i = 0; i < mInstances.Length(); i++) { + nsNPAPIPluginInstance *instance = mInstances[i].get(); + if (instance && + instance->GetPlugin() == aPluginTag->mPlugin && + instance->IsRunning()) { + return true; + } + } + + return false; +} + +nsresult nsPluginHost::ReloadPlugins() +{ + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsPluginHost::ReloadPlugins Begin\n")); + + nsresult rv = NS_OK; + + // this will create the initial plugin list out of cache + // if it was not created yet + if (!mPluginsLoaded) + return LoadPlugins(); + + // we are re-scanning plugins. New plugins may have been added, also some + // plugins may have been removed, so we should probably shut everything down + // but don't touch running (active and not stopped) plugins + + // check if plugins changed, no need to do anything else + // if no changes to plugins have been made + // false instructs not to touch the plugin list, just to + // look for possible changes + bool pluginschanged = true; + FindPlugins(false, &pluginschanged); + + // if no changed detected, return an appropriate error code + if (!pluginschanged) + return NS_ERROR_PLUGINS_PLUGINSNOTCHANGED; + + // shutdown plugins and kill the list if there are no running plugins + RefPtr<nsPluginTag> prev; + RefPtr<nsPluginTag> next; + + for (RefPtr<nsPluginTag> p = mPlugins; p != nullptr;) { + next = p->mNext; + + // only remove our plugin from the list if it's not running. + if (!IsRunningPlugin(p)) { + if (p == mPlugins) + mPlugins = next; + else + prev->mNext = next; + + p->mNext = nullptr; + + // attempt to unload plugins whenever they are removed from the list + p->TryUnloadPlugin(false); + + p = next; + continue; + } + + prev = p; + p = next; + } + + // set flags + mPluginsLoaded = false; + + // load them again + rv = LoadPlugins(); + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsPluginHost::ReloadPlugins End\n")); + + return rv; +} + +#define NS_RETURN_UASTRING_SIZE 128 + +nsresult nsPluginHost::UserAgent(const char **retstring) +{ + static char resultString[NS_RETURN_UASTRING_SIZE]; + nsresult res; + + nsCOMPtr<nsIHttpProtocolHandler> http = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res); + if (NS_FAILED(res)) + return res; + + nsAutoCString uaString; + res = http->GetUserAgent(uaString); + + if (NS_SUCCEEDED(res)) { + if (NS_RETURN_UASTRING_SIZE > uaString.Length()) { + PL_strcpy(resultString, uaString.get()); + } else { + // Copy as much of UA string as we can (terminate at right-most space). + PL_strncpy(resultString, uaString.get(), NS_RETURN_UASTRING_SIZE); + for (int i = NS_RETURN_UASTRING_SIZE - 1; i >= 0; i--) { + if (i == 0) { + resultString[NS_RETURN_UASTRING_SIZE - 1] = '\0'; + } + else if (resultString[i] == ' ') { + resultString[i] = '\0'; + break; + } + } + } + *retstring = resultString; + } + else { + *retstring = nullptr; + } + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::UserAgent return=%s\n", *retstring)); + + return res; +} + +nsresult nsPluginHost::GetURL(nsISupports* pluginInst, + const char* url, + const char* target, + nsNPAPIPluginStreamListener* streamListener, + const char* altHost, + const char* referrer, + bool forceJSEnabled) +{ + return GetURLWithHeaders(static_cast<nsNPAPIPluginInstance*>(pluginInst), + url, target, streamListener, altHost, referrer, + forceJSEnabled, 0, nullptr); +} + +nsresult nsPluginHost::GetURLWithHeaders(nsNPAPIPluginInstance* pluginInst, + const char* url, + const char* target, + nsNPAPIPluginStreamListener* streamListener, + const char* altHost, + const char* referrer, + bool forceJSEnabled, + uint32_t getHeadersLength, + const char* getHeaders) +{ + // we can only send a stream back to the plugin (as specified by a + // null target) if we also have a nsNPAPIPluginStreamListener to talk to + if (!target && !streamListener) { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsresult rv = NS_OK; + + if (target) { + RefPtr<nsPluginInstanceOwner> owner = pluginInst->GetOwner(); + if (owner) { + rv = owner->GetURL(url, target, nullptr, nullptr, 0, true); + } + } + + if (streamListener) { + rv = NewPluginURLStream(NS_ConvertUTF8toUTF16(url), pluginInst, + streamListener, nullptr, + getHeaders, getHeadersLength); + } + return rv; +} + +nsresult nsPluginHost::PostURL(nsISupports* pluginInst, + const char* url, + uint32_t postDataLen, + const char* postData, + bool isFile, + const char* target, + nsNPAPIPluginStreamListener* streamListener, + const char* altHost, + const char* referrer, + bool forceJSEnabled, + uint32_t postHeadersLength, + const char* postHeaders) +{ + nsresult rv; + + // we can only send a stream back to the plugin (as specified + // by a null target) if we also have a nsNPAPIPluginStreamListener + // to talk to also + if (!target && !streamListener) + return NS_ERROR_ILLEGAL_VALUE; + + nsNPAPIPluginInstance* instance = static_cast<nsNPAPIPluginInstance*>(pluginInst); + + nsCOMPtr<nsIInputStream> postStream; + if (isFile) { + nsCOMPtr<nsIFile> file; + rv = CreateTempFileToPost(postData, getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIInputStream> fileStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), + file, + PR_RDONLY, + 0600, + nsIFileInputStream::DELETE_ON_CLOSE | + nsIFileInputStream::CLOSE_ON_EOF); + if (NS_FAILED(rv)) + return rv; + + rv = NS_NewBufferedInputStream(getter_AddRefs(postStream), fileStream, 8192); + if (NS_FAILED(rv)) + return rv; + } else { + char *dataToPost; + uint32_t newDataToPostLen; + ParsePostBufferToFixHeaders(postData, postDataLen, &dataToPost, &newDataToPostLen); + if (!dataToPost) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIStringInputStream> sis = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + if (!sis) { + free(dataToPost); + return rv; + } + + // data allocated by ParsePostBufferToFixHeaders() is managed and + // freed by the string stream. + postDataLen = newDataToPostLen; + sis->AdoptData(dataToPost, postDataLen); + postStream = sis; + } + + if (target) { + RefPtr<nsPluginInstanceOwner> owner = instance->GetOwner(); + if (owner) { + rv = owner->GetURL(url, target, postStream, + (void*)postHeaders, postHeadersLength, true); + } + } + + // if we don't have a target, just create a stream. + if (streamListener) { + rv = NewPluginURLStream(NS_ConvertUTF8toUTF16(url), instance, + streamListener, + postStream, postHeaders, postHeadersLength); + } + return rv; +} + +/* This method queries the prefs for proxy information. + * It has been tested and is known to work in the following three cases + * when no proxy host or port is specified + * when only the proxy host is specified + * when only the proxy port is specified + * This method conforms to the return code specified in + * http://developer.netscape.com/docs/manuals/proxy/adminnt/autoconf.htm#1020923 + * with the exception that multiple values are not implemented. + */ + +nsresult nsPluginHost::FindProxyForURL(const char* url, char* *result) +{ + if (!url || !result) { + return NS_ERROR_INVALID_ARG; + } + nsresult res; + + nsCOMPtr<nsIProtocolProxyService> proxyService = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &res); + if (NS_FAILED(res) || !proxyService) + return res; + + RefPtr<nsProtocolProxyService> rawProxyService = do_QueryObject(proxyService); + if (!rawProxyService) { + return NS_ERROR_FAILURE; + } + + // make a temporary channel from the argument url + nsCOMPtr<nsIURI> uri; + res = NS_NewURI(getter_AddRefs(uri), nsDependentCString(url)); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create(); + // The following channel is never openend, so it does not matter what + // securityFlags we pass; let's follow the principle of least privilege. + nsCOMPtr<nsIChannel> tempChannel; + res = NS_NewChannel(getter_AddRefs(tempChannel), uri, nullPrincipal, + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(res, res); + + nsCOMPtr<nsIProxyInfo> pi; + + // Remove this deprecated call in the future (see Bug 778201): + res = rawProxyService->DeprecatedBlockingResolve(tempChannel, 0, getter_AddRefs(pi)); + if (NS_FAILED(res)) + return res; + + nsAutoCString host, type; + int32_t port = -1; + + // These won't fail, and even if they do... we'll be ok. + if (pi) { + pi->GetType(type); + pi->GetHost(host); + pi->GetPort(&port); + } + + if (!pi || host.IsEmpty() || port <= 0 || host.EqualsLiteral("direct")) { + *result = PL_strdup("DIRECT"); + } else if (type.EqualsLiteral("http")) { + *result = PR_smprintf("PROXY %s:%d", host.get(), port); + } else if (type.EqualsLiteral("socks4")) { + *result = PR_smprintf("SOCKS %s:%d", host.get(), port); + } else if (type.EqualsLiteral("socks")) { + // XXX - this is socks5, but there is no API for us to tell the + // plugin that fact. SOCKS for now, in case the proxy server + // speaks SOCKS4 as well. See bug 78176 + // For a long time this was returning an http proxy type, so + // very little is probably broken by this + *result = PR_smprintf("SOCKS %s:%d", host.get(), port); + } else { + NS_ASSERTION(false, "Unknown proxy type!"); + *result = PL_strdup("DIRECT"); + } + + if (nullptr == *result) + res = NS_ERROR_OUT_OF_MEMORY; + + return res; +} + +nsresult nsPluginHost::UnloadPlugins() +{ + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::UnloadPlugins Called\n")); + + if (!mPluginsLoaded) + return NS_OK; + + // we should call nsIPluginInstance::Stop and nsIPluginInstance::SetWindow + // for those plugins who want it + DestroyRunningInstances(nullptr); + + nsPluginTag *pluginTag; + for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { + pluginTag->TryUnloadPlugin(true); + } + + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext); + + // Lets remove any of the temporary files that we created. + if (sPluginTempDir) { + sPluginTempDir->Remove(true); + NS_RELEASE(sPluginTempDir); + } + +#ifdef XP_WIN + if (mPrivateDirServiceProvider) { + nsCOMPtr<nsIDirectoryService> dirService = + do_GetService(kDirectoryServiceContractID); + if (dirService) + dirService->UnregisterProvider(mPrivateDirServiceProvider); + mPrivateDirServiceProvider = nullptr; + } +#endif /* XP_WIN */ + + mPluginsLoaded = false; + + return NS_OK; +} + +void nsPluginHost::OnPluginInstanceDestroyed(nsPluginTag* aPluginTag) +{ + bool hasInstance = false; + for (uint32_t i = 0; i < mInstances.Length(); i++) { + if (TagForPlugin(mInstances[i]->GetPlugin()) == aPluginTag) { + hasInstance = true; + break; + } + } + + // We have some options for unloading plugins if they have no instances. + // + // Unloading plugins immediately can be bad - some plugins retain state + // between instances even when there are none. This is largely limited to + // going from one page to another, so state is retained without an instance + // for only a very short period of time. In order to allow this to work + // we don't unload plugins immediately by default. This is supported + // via a hidden user pref though. + // + // Another reason not to unload immediately is that loading is expensive, + // and it is better to leave popular plugins loaded. + // + // Our default behavior is to try to unload a plugin after a pref-controlled + // delay once its last instance is destroyed. This seems like a reasonable + // compromise that allows us to reclaim memory while allowing short state + // retention and avoid perf hits for loading popular plugins. + if (!hasInstance) { + if (UnloadPluginsASAP()) { + aPluginTag->TryUnloadPlugin(false); + } else { + if (aPluginTag->mUnloadTimer) { + aPluginTag->mUnloadTimer->Cancel(); + } else { + aPluginTag->mUnloadTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + } + uint32_t unloadTimeout = Preferences::GetUint(kPrefUnloadPluginTimeoutSecs, + kDefaultPluginUnloadingTimeout); + aPluginTag->mUnloadTimer->InitWithCallback(this, + 1000 * unloadTimeout, + nsITimer::TYPE_ONE_SHOT); + } + } +} + +nsresult +nsPluginHost::GetPluginTempDir(nsIFile **aDir) +{ + if (!sPluginTempDir) { + nsCOMPtr<nsIFile> tmpDir; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, + getter_AddRefs(tmpDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = tmpDir->AppendNative(kPluginTmpDirName); + + // make it unique, and mode == 0700, not world-readable + rv = tmpDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700); + NS_ENSURE_SUCCESS(rv, rv); + + tmpDir.swap(sPluginTempDir); + } + + return sPluginTempDir->Clone(aDir); +} + +nsresult +nsPluginHost::InstantiatePluginInstance(const nsACString& aMimeType, nsIURI* aURL, + nsObjectLoadingContent *aContent, + nsPluginInstanceOwner** aOwner) +{ + NS_ENSURE_ARG_POINTER(aOwner); + +#ifdef PLUGIN_LOGGING + nsAutoCString urlSpec; + if (aURL) + aURL->GetAsciiSpec(urlSpec); + + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginHost::InstantiatePlugin Begin mime=%s, url=%s\n", + PromiseFlatCString(aMimeType).get(), urlSpec.get())); + + PR_LogFlush(); +#endif + + if (aMimeType.IsEmpty()) { + NS_NOTREACHED("Attempting to spawn a plugin with no mime type"); + return NS_ERROR_FAILURE; + } + + RefPtr<nsPluginInstanceOwner> instanceOwner = new nsPluginInstanceOwner(); + if (!instanceOwner) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<nsIContent> ourContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(aContent)); + nsresult rv = instanceOwner->Init(ourContent); + if (NS_FAILED(rv)) { + return rv; + } + + nsPluginTagType tagType; + rv = instanceOwner->GetTagType(&tagType); + if (NS_FAILED(rv)) { + instanceOwner->Destroy(); + return rv; + } + + if (tagType != nsPluginTagType_Embed && + tagType != nsPluginTagType_Applet && + tagType != nsPluginTagType_Object) { + instanceOwner->Destroy(); + return NS_ERROR_FAILURE; + } + + rv = SetUpPluginInstance(aMimeType, aURL, instanceOwner); + if (NS_FAILED(rv)) { + instanceOwner->Destroy(); + return NS_ERROR_FAILURE; + } + const bool isAsyncInit = (rv == NS_PLUGIN_INIT_PENDING); + + RefPtr<nsNPAPIPluginInstance> instance; + rv = instanceOwner->GetInstance(getter_AddRefs(instance)); + if (NS_FAILED(rv)) { + instanceOwner->Destroy(); + return rv; + } + + // Async init plugins will initiate their own widget creation. + if (!isAsyncInit && instance) { + CreateWidget(instanceOwner); + } + + // At this point we consider instantiation to be successful. Do not return an error. + instanceOwner.forget(aOwner); + +#ifdef PLUGIN_LOGGING + nsAutoCString urlSpec2; + if (aURL != nullptr) aURL->GetAsciiSpec(urlSpec2); + + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginHost::InstantiatePlugin Finished mime=%s, rv=%d, url=%s\n", + PromiseFlatCString(aMimeType).get(), rv, urlSpec2.get())); + + PR_LogFlush(); +#endif + + return NS_OK; +} + +nsPluginTag* +nsPluginHost::FindTagForLibrary(PRLibrary* aLibrary) +{ + nsPluginTag* pluginTag; + for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { + if (pluginTag->mLibrary == aLibrary) { + return pluginTag; + } + } + return nullptr; +} + +nsPluginTag* +nsPluginHost::TagForPlugin(nsNPAPIPlugin* aPlugin) +{ + nsPluginTag* pluginTag; + for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { + if (pluginTag->mPlugin == aPlugin) { + return pluginTag; + } + } + // a plugin should never exist without a corresponding tag + NS_ERROR("TagForPlugin has failed"); + return nullptr; +} + +nsresult nsPluginHost::SetUpPluginInstance(const nsACString &aMimeType, + nsIURI *aURL, + nsPluginInstanceOwner *aOwner) +{ + NS_ENSURE_ARG_POINTER(aOwner); + + nsresult rv = TrySetUpPluginInstance(aMimeType, aURL, aOwner); + if (NS_SUCCEEDED(rv)) { + return rv; + } + + // If we failed to load a plugin instance we'll try again after + // reloading our plugin list. Only do that once per document to + // avoid redundant high resource usage on pages with multiple + // unkown instance types. We'll do that by caching the document. + nsCOMPtr<nsIDocument> document; + aOwner->GetDocument(getter_AddRefs(document)); + + nsCOMPtr<nsIDocument> currentdocument = do_QueryReferent(mCurrentDocument); + if (document == currentdocument) { + return rv; + } + + mCurrentDocument = do_GetWeakReference(document); + + // Don't try to set up an instance again if nothing changed. + if (ReloadPlugins() == NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) { + return rv; + } + + return TrySetUpPluginInstance(aMimeType, aURL, aOwner); +} + +nsresult +nsPluginHost::TrySetUpPluginInstance(const nsACString &aMimeType, + nsIURI *aURL, + nsPluginInstanceOwner *aOwner) +{ +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginHost::TrySetupPluginInstance Begin mime=%s, owner=%p, url=%s\n", + PromiseFlatCString(aMimeType).get(), aOwner, + aURL ? aURL->GetSpecOrDefault().get() : "")); + + PR_LogFlush(); +#endif + +#ifdef XP_WIN + bool changed; + if ((mRegKeyHKLM && NS_SUCCEEDED(mRegKeyHKLM->HasChanged(&changed)) && changed) || + (mRegKeyHKCU && NS_SUCCEEDED(mRegKeyHKCU->HasChanged(&changed)) && changed)) { + ReloadPlugins(); + } +#endif + + RefPtr<nsNPAPIPlugin> plugin; + GetPlugin(aMimeType, getter_AddRefs(plugin)); + if (!plugin) { + return NS_ERROR_FAILURE; + } + + nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true); + + NS_ASSERTION(pluginTag, "Must have plugin tag here!"); + + plugin->GetLibrary()->SetHasLocalInstance(); + +#if defined(MOZ_WIDGET_ANDROID) && defined(MOZ_CRASHREPORTER) + if (pluginTag->mIsFlashPlugin) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("FlashVersion"), pluginTag->Version()); + } +#endif + + RefPtr<nsNPAPIPluginInstance> instance = new nsNPAPIPluginInstance(); + + // This will create the owning reference. The connection must be made between the + // instance and the instance owner before initialization. Plugins can call into + // the browser during initialization. + aOwner->SetInstance(instance.get()); + + // Add the instance to the instances list before we call NPP_New so that + // it is "in play" before NPP_New happens. Take it out if NPP_New fails. + mInstances.AppendElement(instance.get()); + + // this should not addref the instance or owner + // except in some cases not Java, see bug 140931 + // our COM pointer will free the peer + nsresult rv = instance->Initialize(plugin.get(), aOwner, aMimeType); + if (NS_FAILED(rv)) { + mInstances.RemoveElement(instance.get()); + aOwner->SetInstance(nullptr); + return rv; + } + + // Cancel the plugin unload timer since we are creating + // an instance for it. + if (pluginTag->mUnloadTimer) { + pluginTag->mUnloadTimer->Cancel(); + } + +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_BASIC, + ("nsPluginHost::TrySetupPluginInstance Finished mime=%s, rv=%d, owner=%p, url=%s\n", + PromiseFlatCString(aMimeType).get(), rv, aOwner, + aURL ? aURL->GetSpecOrDefault().get() : "")); + + PR_LogFlush(); +#endif + + return rv; +} + +bool +nsPluginHost::HavePluginForType(const nsACString & aMimeType, + PluginFilter aFilter) +{ + bool checkEnabled = aFilter & eExcludeDisabled; + bool allowFake = !(aFilter & eExcludeFake); + return FindPluginForType(aMimeType, allowFake, checkEnabled); +} + +nsIInternalPluginTag* +nsPluginHost::FindPluginForType(const nsACString& aMimeType, + bool aIncludeFake, bool aCheckEnabled) +{ + if (aIncludeFake) { + nsFakePluginTag* fakeTag = FindFakePluginForType(aMimeType, aCheckEnabled); + if (fakeTag) { + return fakeTag; + } + } + + return FindNativePluginForType(aMimeType, aCheckEnabled); +} + +NS_IMETHODIMP +nsPluginHost::GetPluginTagForType(const nsACString& aMimeType, + uint32_t aExcludeFlags, + nsIPluginTag** aResult) +{ + bool includeFake = !(aExcludeFlags & eExcludeFake); + bool includeDisabled = !(aExcludeFlags & eExcludeDisabled); + + // First look for an enabled plugin. + RefPtr<nsIInternalPluginTag> tag = FindPluginForType(aMimeType, includeFake, + true); + if (!tag && includeDisabled) { + tag = FindPluginForType(aMimeType, includeFake, false); + } + + if (tag) { + tag.forget(aResult); + return NS_OK; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsPluginHost::GetStateForType(const nsACString &aMimeType, + uint32_t aExcludeFlags, + uint32_t* aResult) +{ + nsCOMPtr<nsIPluginTag> tag; + nsresult rv = GetPluginTagForType(aMimeType, aExcludeFlags, + getter_AddRefs(tag)); + NS_ENSURE_SUCCESS(rv, rv); + + return tag->GetEnabledState(aResult); +} + +NS_IMETHODIMP +nsPluginHost::GetBlocklistStateForType(const nsACString &aMimeType, + uint32_t aExcludeFlags, + uint32_t *aState) +{ + nsCOMPtr<nsIPluginTag> tag; + nsresult rv = GetPluginTagForType(aMimeType, + aExcludeFlags, + getter_AddRefs(tag)); + NS_ENSURE_SUCCESS(rv, rv); + return tag->GetBlocklistState(aState); +} + +NS_IMETHODIMP +nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType, + uint32_t aExcludeFlags, + nsACString &aPermissionString) +{ + nsCOMPtr<nsIPluginTag> tag; + nsresult rv = GetPluginTagForType(aMimeType, aExcludeFlags, + getter_AddRefs(tag)); + NS_ENSURE_SUCCESS(rv, rv); + return GetPermissionStringForTag(tag, aExcludeFlags, aPermissionString); +} + +NS_IMETHODIMP +nsPluginHost::GetPermissionStringForTag(nsIPluginTag* aTag, + uint32_t aExcludeFlags, + nsACString &aPermissionString) +{ + NS_ENSURE_TRUE(aTag, NS_ERROR_FAILURE); + + aPermissionString.Truncate(); + uint32_t blocklistState; + nsresult rv = aTag->GetBlocklistState(&blocklistState); + NS_ENSURE_SUCCESS(rv, rv); + + if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE || + blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) { + aPermissionString.AssignLiteral("plugin-vulnerable:"); + } + else { + aPermissionString.AssignLiteral("plugin:"); + } + + nsCString niceName; + rv = aTag->GetNiceName(niceName); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(!niceName.IsEmpty(), NS_ERROR_FAILURE); + + aPermissionString.Append(niceName); + + return NS_OK; +} + +bool +nsPluginHost::HavePluginForExtension(const nsACString & aExtension, + /* out */ nsACString & aMimeType, + PluginFilter aFilter) +{ + bool checkEnabled = aFilter & eExcludeDisabled; + bool allowFake = !(aFilter & eExcludeFake); + return FindNativePluginForExtension(aExtension, aMimeType, checkEnabled) || + (allowFake && + FindFakePluginForExtension(aExtension, aMimeType, checkEnabled)); +} + +void +nsPluginHost::GetPlugins(nsTArray<nsCOMPtr<nsIInternalPluginTag>>& aPluginArray, + bool aIncludeDisabled) +{ + aPluginArray.Clear(); + + LoadPlugins(); + + // Append fake plugins, then normal plugins. + + uint32_t numFake = mFakePlugins.Length(); + for (uint32_t i = 0; i < numFake; i++) { + aPluginArray.AppendElement(mFakePlugins[i]); + } + + // Regular plugins + nsPluginTag* plugin = mPlugins; + while (plugin != nullptr) { + if (plugin->IsEnabled() || aIncludeDisabled) { + aPluginArray.AppendElement(plugin); + } + plugin = plugin->mNext; + } +} + +// FIXME-jsplugins Check users for order of fake v non-fake +NS_IMETHODIMP +nsPluginHost::GetPluginTags(uint32_t* aPluginCount, nsIPluginTag*** aResults) +{ + LoadPlugins(); + + uint32_t count = 0; + uint32_t fakeCount = mFakePlugins.Length(); + RefPtr<nsPluginTag> plugin = mPlugins; + while (plugin != nullptr) { + count++; + plugin = plugin->mNext; + } + + *aResults = static_cast<nsIPluginTag**> + (moz_xmalloc((fakeCount + count) * sizeof(**aResults))); + if (!*aResults) + return NS_ERROR_OUT_OF_MEMORY; + + *aPluginCount = count + fakeCount; + + plugin = mPlugins; + for (uint32_t i = 0; i < count; i++) { + (*aResults)[i] = plugin; + NS_ADDREF((*aResults)[i]); + plugin = plugin->mNext; + } + + for (uint32_t i = 0; i < fakeCount; i++) { + (*aResults)[i + count] = static_cast<nsIInternalPluginTag*>(mFakePlugins[i]); + NS_ADDREF((*aResults)[i + count]); + } + + return NS_OK; +} + +nsPluginTag* +nsPluginHost::FindPreferredPlugin(const InfallibleTArray<nsPluginTag*>& matches) +{ + // We prefer the plugin with the highest version number. + /// XXX(johns): This seems to assume the only time multiple plugins will have + /// the same MIME type is if they're multiple versions of the same + /// plugin -- but since plugin filenames and pretty names can both + /// update, it's probably less arbitrary than just going at it + /// alphabetically. + + if (matches.IsEmpty()) { + return nullptr; + } + + nsPluginTag *preferredPlugin = matches[0]; + for (unsigned int i = 1; i < matches.Length(); i++) { + if (mozilla::Version(matches[i]->Version().get()) > preferredPlugin->Version().get()) { + preferredPlugin = matches[i]; + } + } + + return preferredPlugin; +} + +nsFakePluginTag* +nsPluginHost::FindFakePluginForExtension(const nsACString & aExtension, + /* out */ nsACString & aMimeType, + bool aCheckEnabled) +{ + if (aExtension.IsEmpty()) { + return nullptr; + } + + int32_t numFakePlugins = mFakePlugins.Length(); + for (int32_t i = 0; i < numFakePlugins; i++) { + nsFakePluginTag *plugin = mFakePlugins[i]; + bool active; + if ((!aCheckEnabled || + (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) && + plugin->HasExtension(aExtension, aMimeType)) { + return plugin; + } + } + + return nullptr; +} + +nsFakePluginTag* +nsPluginHost::FindFakePluginForType(const nsACString & aMimeType, + bool aCheckEnabled) +{ + int32_t numFakePlugins = mFakePlugins.Length(); + for (int32_t i = 0; i < numFakePlugins; i++) { + nsFakePluginTag *plugin = mFakePlugins[i]; + bool active; + if ((!aCheckEnabled || + (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) && + plugin->HasMimeType(aMimeType)) { + return plugin; + } + } + + return nullptr; +} + +nsPluginTag* +nsPluginHost::FindNativePluginForType(const nsACString & aMimeType, + bool aCheckEnabled) +{ + if (aMimeType.IsEmpty()) { + return nullptr; + } + + LoadPlugins(); + + InfallibleTArray<nsPluginTag*> matchingPlugins; + + nsPluginTag *plugin = mPlugins; + while (plugin) { + if ((!aCheckEnabled || plugin->IsActive()) && + plugin->HasMimeType(aMimeType)) { + matchingPlugins.AppendElement(plugin); + } + plugin = plugin->mNext; + } + + return FindPreferredPlugin(matchingPlugins); +} + +nsPluginTag* +nsPluginHost::FindNativePluginForExtension(const nsACString & aExtension, + /* out */ nsACString & aMimeType, + bool aCheckEnabled) +{ + if (aExtension.IsEmpty()) { + return nullptr; + } + + LoadPlugins(); + + InfallibleTArray<nsPluginTag*> matchingPlugins; + nsCString matchingMime; // Don't mutate aMimeType unless returning a match + nsPluginTag *plugin = mPlugins; + + while (plugin) { + if (!aCheckEnabled || plugin->IsActive()) { + if (plugin->HasExtension(aExtension, matchingMime)) { + matchingPlugins.AppendElement(plugin); + } + } + plugin = plugin->mNext; + } + + nsPluginTag *preferredPlugin = FindPreferredPlugin(matchingPlugins); + if (!preferredPlugin) { + return nullptr; + } + + // Re-fetch the matching type because of how FindPreferredPlugin works... + preferredPlugin->HasExtension(aExtension, aMimeType); + return preferredPlugin; +} + +static nsresult CreateNPAPIPlugin(nsPluginTag *aPluginTag, + nsNPAPIPlugin **aOutNPAPIPlugin) +{ + // If this is an in-process plugin we'll need to load it here if we haven't already. + if (!nsNPAPIPlugin::RunPluginOOP(aPluginTag)) { + if (aPluginTag->mFullPath.IsEmpty()) + return NS_ERROR_FAILURE; + nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); + file->InitWithPath(NS_ConvertUTF8toUTF16(aPluginTag->mFullPath)); + nsPluginFile pluginFile(file); + PRLibrary* pluginLibrary = nullptr; + + if (NS_FAILED(pluginFile.LoadPlugin(&pluginLibrary)) || !pluginLibrary) + return NS_ERROR_FAILURE; + + aPluginTag->mLibrary = pluginLibrary; + } + + nsresult rv; + rv = nsNPAPIPlugin::CreatePlugin(aPluginTag, aOutNPAPIPlugin); + + return rv; +} + +nsresult nsPluginHost::EnsurePluginLoaded(nsPluginTag* aPluginTag) +{ + RefPtr<nsNPAPIPlugin> plugin = aPluginTag->mPlugin; + if (!plugin) { + nsresult rv = CreateNPAPIPlugin(aPluginTag, getter_AddRefs(plugin)); + if (NS_FAILED(rv)) { + return rv; + } + aPluginTag->mPlugin = plugin; + } + return NS_OK; +} + +nsresult +nsPluginHost::GetPluginForContentProcess(uint32_t aPluginId, nsNPAPIPlugin** aPlugin) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + MOZ_ASSERT(XRE_IsParentProcess()); + + // If plugins haven't been scanned yet, do so now + LoadPlugins(); + + nsPluginTag* pluginTag = PluginWithId(aPluginId); + if (pluginTag) { + // When setting up a bridge, double check with chrome to see if this plugin + // is blocked hard. Note this does not protect against vulnerable plugins + // that the user has explicitly allowed. :( + if (pluginTag->IsBlocklisted()) { + return NS_ERROR_PLUGIN_BLOCKLISTED; + } + + nsresult rv = EnsurePluginLoaded(pluginTag); + if (NS_FAILED(rv)) { + return rv; + } + + // We only get here if a content process doesn't have a PluginModuleParent + // for the given plugin already. Therefore, this counter is counting the + // number of outstanding PluginModuleParents for the plugin, excluding the + // one from the chrome process. + pluginTag->mContentProcessRunningCount++; + NS_ADDREF(*aPlugin = pluginTag->mPlugin); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +class nsPluginUnloadRunnable : public Runnable +{ +public: + explicit nsPluginUnloadRunnable(uint32_t aPluginId) : mPluginId(aPluginId) {} + + NS_IMETHOD Run() override + { + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + if (!host) { + return NS_OK; + } + nsPluginTag* pluginTag = host->PluginWithId(mPluginId); + if (!pluginTag) { + return NS_OK; + } + + MOZ_ASSERT(pluginTag->mContentProcessRunningCount > 0); + pluginTag->mContentProcessRunningCount--; + + if (!pluginTag->mContentProcessRunningCount) { + if (!host->IsRunningPlugin(pluginTag)) { + pluginTag->TryUnloadPlugin(false); + } + } + return NS_OK; + } + +protected: + uint32_t mPluginId; +}; + +void +nsPluginHost::NotifyContentModuleDestroyed(uint32_t aPluginId) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + // This is called in response to a message from the plugin. Don't unload the + // plugin until the message handler is off the stack. + RefPtr<nsPluginUnloadRunnable> runnable = + new nsPluginUnloadRunnable(aPluginId); + NS_DispatchToMainThread(runnable); +} + +nsresult nsPluginHost::GetPlugin(const nsACString &aMimeType, + nsNPAPIPlugin** aPlugin) +{ + nsresult rv = NS_ERROR_FAILURE; + *aPlugin = nullptr; + + // If plugins haven't been scanned yet, do so now + LoadPlugins(); + + nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true); + if (pluginTag) { + rv = NS_OK; + PLUGIN_LOG(PLUGIN_LOG_BASIC, + ("nsPluginHost::GetPlugin Begin mime=%s, plugin=%s\n", + PromiseFlatCString(aMimeType).get(), pluginTag->FileName().get())); + +#ifdef DEBUG + if (!pluginTag->FileName().IsEmpty()) + printf("For %s found plugin %s\n", + PromiseFlatCString(aMimeType).get(), pluginTag->FileName().get()); +#endif + + rv = EnsurePluginLoaded(pluginTag); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ADDREF(*aPlugin = pluginTag->mPlugin); + return NS_OK; + } + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsPluginHost::GetPlugin End mime=%s, rv=%d, plugin=%p name=%s\n", + PromiseFlatCString(aMimeType).get(), rv, *aPlugin, + (pluginTag ? pluginTag->FileName().get() : "(not found)"))); + + return rv; +} + +// Normalize 'host' to ACE. +nsresult +nsPluginHost::NormalizeHostname(nsCString& host) +{ + if (IsASCII(host)) { + ToLowerCase(host); + return NS_OK; + } + + if (!mIDNService) { + nsresult rv; + mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mIDNService->ConvertUTF8toACE(host, host); +} + +// Enumerate a 'sites' array returned by GetSitesWithData and determine if +// any of them have a base domain in common with 'domain'; if so, append them +// to the 'result' array. If 'firstMatchOnly' is true, return after finding the +// first match. +nsresult +nsPluginHost::EnumerateSiteData(const nsACString& domain, + const InfallibleTArray<nsCString>& sites, + InfallibleTArray<nsCString>& result, + bool firstMatchOnly) +{ + NS_ASSERTION(!domain.IsVoid(), "null domain string"); + + nsresult rv; + if (!mTLDService) { + mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Get the base domain from the domain. + nsCString baseDomain; + rv = mTLDService->GetBaseDomainFromHost(domain, 0, baseDomain); + bool isIP = rv == NS_ERROR_HOST_IS_IP_ADDRESS; + if (isIP || rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + // The base domain is the site itself. However, we must be careful to + // normalize. + baseDomain = domain; + rv = NormalizeHostname(baseDomain); + NS_ENSURE_SUCCESS(rv, rv); + } else if (NS_FAILED(rv)) { + return rv; + } + + // Enumerate the array of sites with data. + for (uint32_t i = 0; i < sites.Length(); ++i) { + const nsCString& site = sites[i]; + + // Check if the site is an IP address. + bool siteIsIP = + site.Length() >= 2 && site.First() == '[' && site.Last() == ']'; + if (siteIsIP != isIP) + continue; + + nsCString siteBaseDomain; + if (siteIsIP) { + // Strip the '[]'. + siteBaseDomain = Substring(site, 1, site.Length() - 2); + } else { + // Determine the base domain of the site. + rv = mTLDService->GetBaseDomainFromHost(site, 0, siteBaseDomain); + if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + // The base domain is the site itself. However, we must be careful to + // normalize. + siteBaseDomain = site; + rv = NormalizeHostname(siteBaseDomain); + NS_ENSURE_SUCCESS(rv, rv); + } else if (NS_FAILED(rv)) { + return rv; + } + } + + // At this point, we can do an exact comparison of the two domains. + if (baseDomain != siteBaseDomain) { + continue; + } + + // Append the site to the result array. + result.AppendElement(site); + + // If we're supposed to return early, do so. + if (firstMatchOnly) { + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPluginHost::RegisterFakePlugin(JS::Handle<JS::Value> aInitDictionary, + JSContext* aCx, + nsIFakePluginTag **aResult) +{ + FakePluginTagInit initDictionary; + if (!initDictionary.Init(aCx, aInitDictionary)) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsFakePluginTag> newTag; + nsresult rv = nsFakePluginTag::Create(initDictionary, getter_AddRefs(newTag)); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto existingTag : mFakePlugins) { + if (newTag->HandlerURIMatches(existingTag->HandlerURI())) { + return NS_ERROR_UNEXPECTED; + } + } + + mFakePlugins.AppendElement(newTag); + // FIXME-jsplugins do we need to register with the category manager here? For + // shumway, for now, probably not. + newTag.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginHost::UnregisterFakePlugin(const nsACString& aHandlerURI) +{ + nsCOMPtr<nsIURI> handlerURI; + nsresult rv = NS_NewURI(getter_AddRefs(handlerURI), aHandlerURI); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < mFakePlugins.Length(); ++i) { + if (mFakePlugins[i]->HandlerURIMatches(handlerURI)) { + mFakePlugins.RemoveElementAt(i); + return NS_OK; + } + } + + return NS_OK; +} + +// FIXME-jsplugins Is this method actually needed? +NS_IMETHODIMP +nsPluginHost::GetFakePlugin(const nsACString & aMimeType, + nsIFakePluginTag** aResult) +{ + RefPtr<nsFakePluginTag> result = FindFakePluginForType(aMimeType, false); + if (result) { + result.forget(aResult); + return NS_OK; + } + + *aResult = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +#define ClearDataFromSitesClosure_CID {0x9fb21761, 0x2403, 0x41ad, {0x9e, 0xfd, 0x36, 0x7e, 0xc4, 0x4f, 0xa4, 0x5e}} + + +// Class to hold all the data we need need for IterateMatchesAndClear and ClearDataFromSites +class ClearDataFromSitesClosure : public nsIClearSiteDataCallback, public nsIGetSitesWithDataCallback { +public: + ClearDataFromSitesClosure(nsIPluginTag* plugin, const nsACString& domain, uint64_t flags, + int64_t maxAge, nsCOMPtr<nsIClearSiteDataCallback> callback, + nsPluginHost* host) : + domain(domain), callback(callback), tag(plugin), flags(flags), maxAge(maxAge), host(host) {} + NS_DECL_ISUPPORTS + + // Callback from NPP_ClearSiteData, continue to iterate the matches and clear + NS_IMETHOD Callback(nsresult rv) override { + if (NS_FAILED(rv)) { + callback->Callback(rv); + return NS_OK; + } + if (!matches.Length()) { + callback->Callback(NS_OK); + return NS_OK; + } + + const nsCString match(matches[0]); + matches.RemoveElement(match); + PluginLibrary* library = static_cast<nsPluginTag*>(tag)->mPlugin->GetLibrary(); + rv = library->NPP_ClearSiteData(match.get(), flags, maxAge, this); + if (NS_FAILED(rv)) { + callback->Callback(rv); + return NS_OK; + } + return NS_OK; + } + + // Callback from NPP_GetSitesWithData, kick the iteration off to clear the data + NS_IMETHOD SitesWithData(InfallibleTArray<nsCString>& sites) override + { + // Enumerate the sites and build a list of matches. + nsresult rv = host->EnumerateSiteData(domain, sites, matches, false); + Callback(rv); + return NS_OK; + } + + nsCString domain; + nsCOMPtr<nsIClearSiteDataCallback> callback; + InfallibleTArray<nsCString> matches; + nsIPluginTag* tag; + uint64_t flags; + int64_t maxAge; + nsPluginHost* host; + NS_DECLARE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure_CID) + private: + virtual ~ClearDataFromSitesClosure() {} +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure, ClearDataFromSitesClosure_CID) + +NS_IMPL_ADDREF(ClearDataFromSitesClosure) +NS_IMPL_RELEASE(ClearDataFromSitesClosure) + +NS_INTERFACE_MAP_BEGIN(ClearDataFromSitesClosure) + NS_INTERFACE_MAP_ENTRY(nsIClearSiteDataCallback) + NS_INTERFACE_MAP_ENTRY(nsIGetSitesWithDataCallback) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIClearSiteDataCallback) +NS_INTERFACE_MAP_END + +// FIXME-jsplugins what should this do for fake plugins? +NS_IMETHODIMP +nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain, + uint64_t flags, int64_t maxAge, nsIClearSiteDataCallback* callbackFunc) +{ + nsCOMPtr<nsIClearSiteDataCallback> callback(callbackFunc); + // maxAge must be either a nonnegative integer or -1. + NS_ENSURE_ARG(maxAge >= 0 || maxAge == -1); + + // Caller may give us a tag object that is no longer live. + if (!IsLiveTag(plugin)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsPluginTag* tag = static_cast<nsPluginTag*>(plugin); + + if (!tag->IsEnabled()) { + return NS_ERROR_NOT_AVAILABLE; + } + + // We only ensure support for clearing Flash site data for now. + // We will also attempt to clear data for any plugin that happens + // to be loaded already. + if (!tag->mIsFlashPlugin && !tag->mPlugin) { + return NS_ERROR_FAILURE; + } + + // Make sure the plugin is loaded. + nsresult rv = EnsurePluginLoaded(tag); + if (NS_FAILED(rv)) { + return rv; + } + + PluginLibrary* library = tag->mPlugin->GetLibrary(); + + // If 'domain' is the null string, clear everything. + if (domain.IsVoid()) { + return library->NPP_ClearSiteData(nullptr, flags, maxAge, callback); + } + nsCOMPtr<nsIGetSitesWithDataCallback> closure(new ClearDataFromSitesClosure(plugin, domain, flags, + maxAge, callback, this)); + rv = library->NPP_GetSitesWithData(closure); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +#define GetSitesClosure_CID {0x4c9268ac, 0x2fd1, 0x4f2a, {0x9a, 0x10, 0x7a, 0x09, 0xf1, 0xb7, 0x60, 0x3a}} + +// Closure to contain the data needed to handle the callback from NPP_GetSitesWithData +class GetSitesClosure : public nsIGetSitesWithDataCallback { +public: + NS_DECL_ISUPPORTS + GetSitesClosure(const nsACString& domain, nsPluginHost* host) + : domain(domain), host(host), keepWaiting(true) + { + } + NS_IMETHOD SitesWithData(InfallibleTArray<nsCString>& sites) override { + retVal = HandleGetSites(sites); + keepWaiting = false; + return NS_OK; + } + + nsresult HandleGetSites(InfallibleTArray<nsCString>& sites) { + // If there's no data, we're done. + if (sites.IsEmpty()) { + result = false; + return NS_OK; + } + + // If 'domain' is the null string, and there's data for at least one site, + // we're done. + if (domain.IsVoid()) { + result = true; + return NS_OK; + } + + // Enumerate the sites and determine if there's a match. + InfallibleTArray<nsCString> matches; + nsresult rv = host->EnumerateSiteData(domain, sites, matches, true); + NS_ENSURE_SUCCESS(rv, rv); + + result = !matches.IsEmpty(); + return NS_OK; + } + + nsCString domain; + RefPtr<nsPluginHost> host; + bool result; + bool keepWaiting; + nsresult retVal; + NS_DECLARE_STATIC_IID_ACCESSOR(GetSitesClosure_CID) + private: + virtual ~GetSitesClosure() { + } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(GetSitesClosure, GetSitesClosure_CID) + +NS_IMPL_ISUPPORTS(GetSitesClosure, GetSitesClosure, nsIGetSitesWithDataCallback) + +// This will spin the event loop while waiting on an async +// call to GetSitesWithData +NS_IMETHODIMP +nsPluginHost::SiteHasData(nsIPluginTag* plugin, const nsACString& domain, + bool* result) +{ + // Caller may give us a tag object that is no longer live. + if (!IsLiveTag(plugin)) { + return NS_ERROR_NOT_AVAILABLE; + } + + // FIXME-jsplugins audit casts + nsPluginTag* tag = static_cast<nsPluginTag*>(plugin); + + // We only ensure support for clearing Flash site data for now. + // We will also attempt to clear data for any plugin that happens + // to be loaded already. + if (!tag->mIsFlashPlugin && !tag->mPlugin) { + return NS_ERROR_FAILURE; + } + + // Make sure the plugin is loaded. + nsresult rv = EnsurePluginLoaded(tag); + if (NS_FAILED(rv)) { + return rv; + } + + PluginLibrary* library = tag->mPlugin->GetLibrary(); + + // Get the list of sites from the plugin + nsCOMPtr<GetSitesClosure> closure(new GetSitesClosure(domain, this)); + rv = library->NPP_GetSitesWithData(nsCOMPtr<nsIGetSitesWithDataCallback>(do_QueryInterface(closure))); + NS_ENSURE_SUCCESS(rv, rv); + // Spin the event loop while we wait for the async call to GetSitesWithData + while (closure->keepWaiting) { + NS_ProcessNextEvent(nullptr, true); + } + *result = closure->result; + return closure->retVal; +} + +nsPluginHost::SpecialType +nsPluginHost::GetSpecialType(const nsACString & aMIMEType) +{ + if (aMIMEType.LowerCaseEqualsASCII("application/x-test")) { + return eSpecialType_Test; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash") || + aMIMEType.LowerCaseEqualsASCII("application/futuresplash") || + aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash-test")) { + return eSpecialType_Flash; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/x-silverlight") || + aMIMEType.LowerCaseEqualsASCII("application/x-silverlight-2") || + aMIMEType.LowerCaseEqualsASCII("application/x-silverlight-test")) { + return eSpecialType_Silverlight; + } + + if (aMIMEType.LowerCaseEqualsASCII("audio/x-pn-realaudio-plugin")) { + NS_WARNING("You are loading RealPlayer"); + return eSpecialType_RealPlayer; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/pdf")) { + return eSpecialType_PDF; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/vnd.unity")) { + return eSpecialType_Unity; + } + + // Java registers variants of its MIME with parameters, e.g. + // application/x-java-vm;version=1.3 + const nsACString &noParam = Substring(aMIMEType, 0, aMIMEType.FindChar(';')); + + // The java mime pref may well not be one of these, + // e.g. application/x-java-test used in the test suite + nsAdoptingCString javaMIME = Preferences::GetCString(kPrefJavaMIME); + if ((!javaMIME.IsEmpty() && noParam.LowerCaseEqualsASCII(javaMIME)) || + noParam.LowerCaseEqualsASCII("application/x-java-vm") || + noParam.LowerCaseEqualsASCII("application/x-java-applet") || + noParam.LowerCaseEqualsASCII("application/x-java-bean")) { + return eSpecialType_Java; + } + + return eSpecialType_None; +} + +// Check whether or not a tag is a live, valid tag, and that it's loaded. +bool +nsPluginHost::IsLiveTag(nsIPluginTag* aPluginTag) +{ + nsCOMPtr<nsIInternalPluginTag> internalTag(do_QueryInterface(aPluginTag)); + uint32_t fakeCount = mFakePlugins.Length(); + for (uint32_t i = 0; i < fakeCount; i++) { + if (mFakePlugins[i] == internalTag) { + return true; + } + } + + nsPluginTag* tag; + for (tag = mPlugins; tag; tag = tag->mNext) { + if (tag == internalTag) { + return true; + } + } + return false; +} + +// FIXME-jsplugins what should happen with jsplugins here, if anything? +nsPluginTag* +nsPluginHost::HaveSamePlugin(const nsPluginTag* aPluginTag) +{ + for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { + if (tag->HasSameNameAndMimes(aPluginTag)) { + return tag; + } + } + return nullptr; +} + +// Don't have to worry about fake plugins here, since this is only used during +// the plugin directory scan, which doesn't pick up fake plugins. +nsPluginTag* +nsPluginHost::FirstPluginWithPath(const nsCString& path) +{ + for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { + if (tag->mFullPath.Equals(path)) { + return tag; + } + } + return nullptr; +} + +nsPluginTag* +nsPluginHost::PluginWithId(uint32_t aId) +{ + for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { + if (tag->mId == aId) { + return tag; + } + } + return nullptr; +} + +namespace { + +int64_t GetPluginLastModifiedTime(const nsCOMPtr<nsIFile>& localfile) +{ + PRTime fileModTime = 0; + +#if defined(XP_MACOSX) + // On OS X the date of a bundle's "contents" (i.e. of its Info.plist file) + // is a much better guide to when it was last modified than the date of + // its package directory. See bug 313700. + nsCOMPtr<nsILocalFileMac> localFileMac = do_QueryInterface(localfile); + if (localFileMac) { + localFileMac->GetBundleContentsLastModifiedTime(&fileModTime); + } else { + localfile->GetLastModifiedTime(&fileModTime); + } +#else + localfile->GetLastModifiedTime(&fileModTime); +#endif + + return fileModTime; +} + +bool +GetPluginIsFromExtension(const nsCOMPtr<nsIFile>& pluginFile, + const nsCOMArray<nsIFile>& extensionDirs) +{ + for (uint32_t i = 0; i < extensionDirs.Length(); ++i) { + bool contains; + if (NS_FAILED(extensionDirs[i]->Contains(pluginFile, &contains)) || !contains) { + continue; + } + + return true; + } + + return false; +} + +void +GetExtensionDirectories(nsCOMArray<nsIFile>& dirs) +{ + nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + if (!dirService) { + return; + } + + nsCOMPtr<nsISimpleEnumerator> list; + nsresult rv = dirService->Get(XRE_EXTENSIONS_DIR_LIST, + NS_GET_IID(nsISimpleEnumerator), + getter_AddRefs(list)); + if (NS_FAILED(rv)) { + return; + } + + bool more; + while (NS_SUCCEEDED(list->HasMoreElements(&more)) && more) { + nsCOMPtr<nsISupports> next; + if (NS_FAILED(list->GetNext(getter_AddRefs(next)))) { + break; + } + nsCOMPtr<nsIFile> file = do_QueryInterface(next); + if (file) { + file->Normalize(); + dirs.AppendElement(file.forget()); + } + } +} + +struct CompareFilesByTime +{ + bool + LessThan(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const + { + return GetPluginLastModifiedTime(a) < GetPluginLastModifiedTime(b); + } + + bool + Equals(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const + { + return GetPluginLastModifiedTime(a) == GetPluginLastModifiedTime(b); + } +}; + +} // namespace + +bool +nsPluginHost::ShouldAddPlugin(nsPluginTag* aPluginTag) +{ +#if defined(XP_WIN) && (defined(__x86_64__) || defined(_M_X64)) + // On 64-bit windows, the only plugins we should load are flash and + // silverlight. Use library filename and MIME type to check. + if (StringBeginsWith(aPluginTag->FileName(), NS_LITERAL_CSTRING("NPSWF"), nsCaseInsensitiveCStringComparator()) && + (aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-shockwave-flash")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-shockwave-flash-test")))) { + return true; + } + if (StringBeginsWith(aPluginTag->FileName(), NS_LITERAL_CSTRING("npctrl"), nsCaseInsensitiveCStringComparator()) && + (aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-silverlight-test")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-silverlight-2")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-silverlight")))) { + return true; + } + // Accept the test plugin MIME types, so mochitests still work. + if (aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-test")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-Second-Test")) || + aPluginTag->HasMimeType(NS_LITERAL_CSTRING("application/x-java-test"))) { + return true; + } +#ifdef PLUGIN_LOGGING + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("ShouldAddPlugin : Ignoring non-flash plugin library %s\n", aPluginTag->FileName().get())); +#endif // PLUGIN_LOGGING + return false; +#else + return true; +#endif // defined(XP_WIN) && (defined(__x86_64__) || defined(_M_X64)) +} + +void +nsPluginHost::AddPluginTag(nsPluginTag* aPluginTag) +{ + if (!ShouldAddPlugin(aPluginTag)) { + return; + } + aPluginTag->mNext = mPlugins; + mPlugins = aPluginTag; + + if (aPluginTag->IsActive()) { + nsAdoptingCString disableFullPage = + Preferences::GetCString(kPrefDisableFullPage); + for (uint32_t i = 0; i < aPluginTag->MimeTypes().Length(); i++) { + if (!IsTypeInList(aPluginTag->MimeTypes()[i], disableFullPage)) { + RegisterWithCategoryManager(aPluginTag->MimeTypes()[i], + ePluginRegister); + } + } + } +} + +static bool +PluginInfoIsFlash(const nsPluginInfo& info) +{ + if (!info.fName || strcmp(info.fName, "Shockwave Flash") != 0) { + return false; + } + for (uint32_t i = 0; i < info.fVariantCount; ++i) { + if (info.fMimeTypeArray[i] && + (!strcmp(info.fMimeTypeArray[i], "application/x-shockwave-flash") || + !strcmp(info.fMimeTypeArray[i], "application/x-shockwave-flash-test"))) { + return true; + } + } + return false; +} + +typedef NS_NPAPIPLUGIN_CALLBACK(char *, NP_GETMIMEDESCRIPTION)(void); + +nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir, + bool aCreatePluginList, + bool *aPluginsChanged) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + NS_ENSURE_ARG_POINTER(aPluginsChanged); + nsresult rv; + + *aPluginsChanged = false; + +#ifdef PLUGIN_LOGGING + nsAutoCString dirPath; + pluginsDir->GetNativePath(dirPath); + PLUGIN_LOG(PLUGIN_LOG_BASIC, + ("nsPluginHost::ScanPluginsDirectory dir=%s\n", dirPath.get())); +#endif + + bool flashOnly = Preferences::GetBool("plugin.load_flash_only", true); + + nsCOMPtr<nsISimpleEnumerator> iter; + rv = pluginsDir->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) + return rv; + + AutoTArray<nsCOMPtr<nsIFile>, 6> pluginFiles; + + bool hasMore; + while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> supports; + rv = iter->GetNext(getter_AddRefs(supports)); + if (NS_FAILED(rv)) + continue; + nsCOMPtr<nsIFile> dirEntry(do_QueryInterface(supports, &rv)); + if (NS_FAILED(rv)) + continue; + + // Sun's JRE 1.3.1 plugin must have symbolic links resolved or else it'll crash. + // See bug 197855. + dirEntry->Normalize(); + + if (nsPluginsDir::IsPluginFile(dirEntry)) { + pluginFiles.AppendElement(dirEntry); + } + } + + pluginFiles.Sort(CompareFilesByTime()); + + nsCOMArray<nsIFile> extensionDirs; + GetExtensionDirectories(extensionDirs); + + for (int32_t i = (pluginFiles.Length() - 1); i >= 0; i--) { + nsCOMPtr<nsIFile>& localfile = pluginFiles[i]; + + nsString utf16FilePath; + rv = localfile->GetPath(utf16FilePath); + if (NS_FAILED(rv)) + continue; + + const int64_t fileModTime = GetPluginLastModifiedTime(localfile); + const bool fromExtension = GetPluginIsFromExtension(localfile, extensionDirs); + + // Look for it in our cache + NS_ConvertUTF16toUTF8 filePath(utf16FilePath); + RefPtr<nsPluginTag> pluginTag; + RemoveCachedPluginsInfo(filePath.get(), getter_AddRefs(pluginTag)); + + bool seenBefore = false; + + if (pluginTag) { + seenBefore = true; + // If plugin changed, delete cachedPluginTag and don't use cache + if (fileModTime != pluginTag->mLastModifiedTime) { + // Plugins has changed. Don't use cached plugin info. + pluginTag = nullptr; + + // plugin file changed, flag this fact + *aPluginsChanged = true; + } + + // If we're not creating a list and we already know something changed then + // we're done. + if (!aCreatePluginList) { + if (*aPluginsChanged) { + return NS_OK; + } + continue; + } + } + + bool isKnownInvalidPlugin = false; + for (RefPtr<nsInvalidPluginTag> invalidPlugins = mInvalidPlugins; + invalidPlugins; invalidPlugins = invalidPlugins->mNext) { + // If already marked as invalid, ignore it + if (invalidPlugins->mFullPath.Equals(filePath.get()) && + invalidPlugins->mLastModifiedTime == fileModTime) { + if (aCreatePluginList) { + invalidPlugins->mSeen = true; + } + isKnownInvalidPlugin = true; + break; + } + } + if (isKnownInvalidPlugin) { + continue; + } + + // if it is not found in cache info list or has been changed, create a new one + if (!pluginTag) { + nsPluginFile pluginFile(localfile); + + // create a tag describing this plugin. + PRLibrary *library = nullptr; + nsPluginInfo info; + memset(&info, 0, sizeof(info)); + nsresult res; + // Opening a block for the telemetry AutoTimer + { + Telemetry::AutoTimer<Telemetry::PLUGIN_LOAD_METADATA> telemetry; + res = pluginFile.GetPluginInfo(info, &library); + } + // if we don't have mime type don't proceed, this is not a plugin + if (NS_FAILED(res) || !info.fMimeTypeArray || + (flashOnly && !PluginInfoIsFlash(info))) { + RefPtr<nsInvalidPluginTag> invalidTag = new nsInvalidPluginTag(filePath.get(), + fileModTime); + pluginFile.FreePluginInfo(info); + + if (aCreatePluginList) { + invalidTag->mSeen = true; + } + invalidTag->mNext = mInvalidPlugins; + if (mInvalidPlugins) { + mInvalidPlugins->mPrev = invalidTag; + } + mInvalidPlugins = invalidTag; + + // Mark aPluginsChanged so pluginreg is rewritten + *aPluginsChanged = true; + continue; + } + + pluginTag = new nsPluginTag(&info, fileModTime, fromExtension); + pluginFile.FreePluginInfo(info); + pluginTag->mLibrary = library; + uint32_t state; + rv = pluginTag->GetBlocklistState(&state); + NS_ENSURE_SUCCESS(rv, rv); + + // If the blocklist says it is risky and we have never seen this + // plugin before, then disable it. + if (state == nsIBlocklistService::STATE_SOFTBLOCKED && !seenBefore) { + pluginTag->SetEnabledState(nsIPluginTag::STATE_DISABLED); + } + + // Plugin unloading is tag-based. If we created a new tag and loaded + // the library in the process then we want to attempt to unload it here. + // Only do this if the pref is set for aggressive unloading. + if (UnloadPluginsASAP()) { + pluginTag->TryUnloadPlugin(false); + } + } + + // do it if we still want it + if (!seenBefore) { + // We have a valid new plugin so report that plugins have changed. + *aPluginsChanged = true; + } + + // Avoid adding different versions of the same plugin if they are running + // in-process, otherwise we risk undefined behaviour. + if (!nsNPAPIPlugin::RunPluginOOP(pluginTag)) { + if (HaveSamePlugin(pluginTag)) { + continue; + } + } + + // Don't add the same plugin again if it hasn't changed + if (nsPluginTag* duplicate = FirstPluginWithPath(pluginTag->mFullPath)) { + if (pluginTag->mLastModifiedTime == duplicate->mLastModifiedTime) { + continue; + } + } + + // If we're not creating a plugin list, simply looking for changes, + // then we're done. + if (!aCreatePluginList) { + return NS_OK; + } + + AddPluginTag(pluginTag); + } + + return NS_OK; +} + +nsresult nsPluginHost::ScanPluginsDirectoryList(nsISimpleEnumerator *dirEnum, + bool aCreatePluginList, + bool *aPluginsChanged) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + bool hasMore; + while (NS_SUCCEEDED(dirEnum->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> supports; + nsresult rv = dirEnum->GetNext(getter_AddRefs(supports)); + if (NS_FAILED(rv)) + continue; + nsCOMPtr<nsIFile> nextDir(do_QueryInterface(supports, &rv)); + if (NS_FAILED(rv)) + continue; + + // don't pass aPluginsChanged directly to prevent it from been reset + bool pluginschanged = false; + ScanPluginsDirectory(nextDir, aCreatePluginList, &pluginschanged); + + if (pluginschanged) + *aPluginsChanged = true; + + // if changes are detected and we are not creating the list, do not proceed + if (!aCreatePluginList && *aPluginsChanged) + break; + } + return NS_OK; +} + +void +nsPluginHost::IncrementChromeEpoch() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + mPluginEpoch++; +} + +uint32_t +nsPluginHost::ChromeEpoch() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + return mPluginEpoch; +} + +uint32_t +nsPluginHost::ChromeEpochForContent() +{ + MOZ_ASSERT(XRE_IsContentProcess()); + return mPluginEpoch; +} + +void +nsPluginHost::SetChromeEpochForContent(uint32_t aEpoch) +{ + MOZ_ASSERT(XRE_IsContentProcess()); + mPluginEpoch = aEpoch; +} + +#ifdef XP_WIN +static void +WatchRegKey(uint32_t aRoot, nsCOMPtr<nsIWindowsRegKey>& aKey) +{ + if (aKey) { + return; + } + + aKey = do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!aKey) { + return; + } + nsresult rv = aKey->Open(aRoot, + NS_LITERAL_STRING("Software\\MozillaPlugins"), + nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::ACCESS_NOTIFY); + if (NS_FAILED(rv)) { + aKey = nullptr; + return; + } + aKey->StartWatching(true); +} +#endif + +nsresult nsPluginHost::LoadPlugins() +{ +#ifdef ANDROID + if (XRE_IsContentProcess()) { + return NS_OK; + } +#endif + // do not do anything if it is already done + // use ReloadPlugins() to enforce loading + if (mPluginsLoaded) + return NS_OK; + + if (mPluginsDisabled) + return NS_OK; + +#ifdef XP_WIN + WatchRegKey(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, mRegKeyHKLM); + WatchRegKey(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, mRegKeyHKCU); +#endif + + bool pluginschanged; + nsresult rv = FindPlugins(true, &pluginschanged); + if (NS_FAILED(rv)) + return rv; + + // only if plugins have changed will we notify plugin-change observers + if (pluginschanged) { + if (XRE_IsParentProcess()) { + IncrementChromeEpoch(); + } + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) + obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr); + } + + return NS_OK; +} + +nsresult +nsPluginHost::FindPluginsInContent(bool aCreatePluginList, bool* aPluginsChanged) +{ + MOZ_ASSERT(XRE_IsContentProcess()); + + dom::ContentChild* cp = dom::ContentChild::GetSingleton(); + nsresult rv; + nsTArray<PluginTag> plugins; + uint32_t parentEpoch; + if (!cp->SendFindPlugins(ChromeEpochForContent(), &rv, &plugins, &parentEpoch) || + NS_FAILED(rv)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (parentEpoch != ChromeEpochForContent()) { + *aPluginsChanged = true; + if (!aCreatePluginList) { + return NS_OK; + } + + // Don't do this if aCreatePluginList is false. Otherwise, when we actually + // want to create the list, we'll come back here and do nothing. + SetChromeEpochForContent(parentEpoch); + + for (size_t i = 0; i < plugins.Length(); i++) { + PluginTag& tag = plugins[i]; + + // Don't add the same plugin again. + if (nsPluginTag* existing = PluginWithId(tag.id())) { + UpdateInMemoryPluginInfo(existing); + continue; + } + + nsPluginTag *pluginTag = new nsPluginTag(tag.id(), + tag.name().get(), + tag.description().get(), + tag.filename().get(), + "", // aFullPath + tag.version().get(), + nsTArray<nsCString>(tag.mimeTypes()), + nsTArray<nsCString>(tag.mimeDescriptions()), + nsTArray<nsCString>(tag.extensions()), + tag.isJavaPlugin(), + tag.isFlashPlugin(), + tag.supportsAsyncInit(), + tag.supportsAsyncRender(), + tag.lastModifiedTime(), + tag.isFromExtension(), + tag.sandboxLevel()); + AddPluginTag(pluginTag); + } + } + + mPluginsLoaded = true; + return NS_OK; +} + +// if aCreatePluginList is false we will just scan for plugins +// and see if any changes have been made to the plugins. +// This is needed in ReloadPlugins to prevent possible recursive reloads +nsresult nsPluginHost::FindPlugins(bool aCreatePluginList, bool * aPluginsChanged) +{ + Telemetry::AutoTimer<Telemetry::FIND_PLUGINS> telemetry; + + NS_ENSURE_ARG_POINTER(aPluginsChanged); + + *aPluginsChanged = false; + + if (XRE_IsContentProcess()) { + return FindPluginsInContent(aCreatePluginList, aPluginsChanged); + } + + nsresult rv; + + // Read cached plugins info. If the profile isn't yet available then don't + // scan for plugins + if (ReadPluginInfo() == NS_ERROR_NOT_AVAILABLE) + return NS_OK; + +#ifdef XP_WIN + // Failure here is not a show-stopper so just warn. + rv = EnsurePrivateDirServiceProvider(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to register dir service provider."); +#endif /* XP_WIN */ + + nsCOMPtr<nsIProperties> dirService(do_GetService(kDirectoryServiceContractID, &rv)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsISimpleEnumerator> dirList; + + // Scan plugins directories; + // don't pass aPluginsChanged directly, to prevent its + // possible reset in subsequent ScanPluginsDirectory calls + bool pluginschanged = false; + + // Scan the app-defined list of plugin dirs. + rv = dirService->Get(NS_APP_PLUGINS_DIR_LIST, NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(dirList)); + if (NS_SUCCEEDED(rv)) { + ScanPluginsDirectoryList(dirList, aCreatePluginList, &pluginschanged); + + if (pluginschanged) + *aPluginsChanged = true; + + // if we are just looking for possible changes, + // no need to proceed if changes are detected + if (!aCreatePluginList && *aPluginsChanged) { + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext); + return NS_OK; + } + } else { +#ifdef ANDROID + LOG("getting plugins dir failed"); +#endif + } + + mPluginsLoaded = true; // at this point 'some' plugins have been loaded, + // the rest is optional + +#ifdef XP_WIN + bool bScanPLIDs = Preferences::GetBool("plugin.scan.plid.all", false); + + // Now lets scan any PLID directories + if (bScanPLIDs && mPrivateDirServiceProvider) { + rv = mPrivateDirServiceProvider->GetPLIDDirectories(getter_AddRefs(dirList)); + if (NS_SUCCEEDED(rv)) { + ScanPluginsDirectoryList(dirList, aCreatePluginList, &pluginschanged); + + if (pluginschanged) + *aPluginsChanged = true; + + // if we are just looking for possible changes, + // no need to proceed if changes are detected + if (!aCreatePluginList && *aPluginsChanged) { + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext); + return NS_OK; + } + } + } + + + // Scan the installation paths of our popular plugins if the prefs are enabled + + // This table controls the order of scanning + const char* const prefs[] = {NS_WIN_ACROBAT_SCAN_KEY, + NS_WIN_QUICKTIME_SCAN_KEY, + NS_WIN_WMP_SCAN_KEY}; + + uint32_t size = sizeof(prefs) / sizeof(prefs[0]); + + for (uint32_t i = 0; i < size; i+=1) { + nsCOMPtr<nsIFile> dirToScan; + bool bExists; + if (NS_SUCCEEDED(dirService->Get(prefs[i], NS_GET_IID(nsIFile), getter_AddRefs(dirToScan))) && + dirToScan && + NS_SUCCEEDED(dirToScan->Exists(&bExists)) && + bExists) { + + ScanPluginsDirectory(dirToScan, aCreatePluginList, &pluginschanged); + + if (pluginschanged) + *aPluginsChanged = true; + + // if we are just looking for possible changes, + // no need to proceed if changes are detected + if (!aCreatePluginList && *aPluginsChanged) { + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext); + return NS_OK; + } + } + } +#endif + + // We should also consider plugins to have changed if any plugins have been removed. + // We'll know if any were removed if they weren't taken out of the cached plugins list + // during our scan, thus we can assume something was removed if the cached plugins list + // contains anything. + if (!*aPluginsChanged && mCachedPlugins) { + *aPluginsChanged = true; + } + + // Remove unseen invalid plugins + RefPtr<nsInvalidPluginTag> invalidPlugins = mInvalidPlugins; + while (invalidPlugins) { + if (!invalidPlugins->mSeen) { + RefPtr<nsInvalidPluginTag> invalidPlugin = invalidPlugins; + + if (invalidPlugin->mPrev) { + invalidPlugin->mPrev->mNext = invalidPlugin->mNext; + } + else { + mInvalidPlugins = invalidPlugin->mNext; + } + if (invalidPlugin->mNext) { + invalidPlugin->mNext->mPrev = invalidPlugin->mPrev; + } + + invalidPlugins = invalidPlugin->mNext; + + invalidPlugin->mPrev = nullptr; + invalidPlugin->mNext = nullptr; + } + else { + invalidPlugins->mSeen = false; + invalidPlugins = invalidPlugins->mNext; + } + } + + // if we are not creating the list, there is no need to proceed + if (!aCreatePluginList) { + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext); + return NS_OK; + } + + // if we are creating the list, it is already done; + // update the plugins info cache if changes are detected + if (*aPluginsChanged) + WritePluginInfo(); + + // No more need for cached plugins. Clear it up. + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext); + + return NS_OK; +} + +nsresult +mozilla::plugins::FindPluginsForContent(uint32_t aPluginEpoch, + nsTArray<PluginTag>* aPlugins, + uint32_t* aNewPluginEpoch) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + return host->FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch); +} + +nsresult +nsPluginHost::FindPluginsForContent(uint32_t aPluginEpoch, + nsTArray<PluginTag>* aPlugins, + uint32_t* aNewPluginEpoch) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + // Load plugins so that the epoch is correct. + nsresult rv = LoadPlugins(); + if (NS_FAILED(rv)) { + return rv; + } + + *aNewPluginEpoch = ChromeEpoch(); + if (aPluginEpoch == ChromeEpoch()) { + return NS_OK; + } + + nsTArray<nsCOMPtr<nsIInternalPluginTag>> plugins; + GetPlugins(plugins, true); + + for (size_t i = 0; i < plugins.Length(); i++) { + nsCOMPtr<nsIInternalPluginTag> basetag = plugins[i]; + + nsCOMPtr<nsIFakePluginTag> faketag = do_QueryInterface(basetag); + if (faketag) { + /// FIXME-jsplugins - We need to make content processes properly + /// aware of jsplugins (and add a nsIInternalPluginTag->AsNative() to + /// avoid this hacky static cast) + continue; + } + + /// FIXME-jsplugins - We need to cleanup the various plugintag classes + /// to be more sane and avoid this dance + nsPluginTag *tag = static_cast<nsPluginTag *>(basetag.get()); + + aPlugins->AppendElement(PluginTag(tag->mId, + tag->Name(), + tag->Description(), + tag->MimeTypes(), + tag->MimeDescriptions(), + tag->Extensions(), + tag->mIsJavaPlugin, + tag->mIsFlashPlugin, + tag->mSupportsAsyncInit, + tag->mSupportsAsyncRender, + tag->FileName(), + tag->Version(), + tag->mLastModifiedTime, + tag->IsFromExtension(), + tag->mSandboxLevel)); + } + return NS_OK; +} + +void +nsPluginHost::UpdateInMemoryPluginInfo(nsPluginTag* aPluginTag) +{ + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext); + NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext); + + if (!aPluginTag) { + return; + } + + // Update types with category manager + nsAdoptingCString disableFullPage = + Preferences::GetCString(kPrefDisableFullPage); + for (uint32_t i = 0; i < aPluginTag->MimeTypes().Length(); i++) { + nsRegisterType shouldRegister; + + if (IsTypeInList(aPluginTag->MimeTypes()[i], disableFullPage)) { + shouldRegister = ePluginUnregister; + } else { + nsPluginTag *plugin = FindNativePluginForType(aPluginTag->MimeTypes()[i], + true); + shouldRegister = plugin ? ePluginRegister : ePluginUnregister; + } + + RegisterWithCategoryManager(aPluginTag->MimeTypes()[i], shouldRegister); + } + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) + obsService->NotifyObservers(nullptr, "plugin-info-updated", nullptr); +} + +// This function is not relevant for fake plugins. +void +nsPluginHost::UpdatePluginInfo(nsPluginTag* aPluginTag) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + ReadPluginInfo(); + WritePluginInfo(); + + IncrementChromeEpoch(); + + UpdateInMemoryPluginInfo(aPluginTag); +} + +/* static */ bool +nsPluginHost::IsTypeWhitelisted(const char *aMimeType) +{ + nsAdoptingCString whitelist = Preferences::GetCString(kPrefWhitelist); + if (!whitelist.Length()) { + return true; + } + nsDependentCString wrap(aMimeType); + return IsTypeInList(wrap, whitelist); +} + +/* static */ bool +nsPluginHost::ShouldLoadTypeInParent(const nsACString& aMimeType) +{ + nsCString prefName(kPrefLoadInParentPrefix); + prefName += aMimeType; + return Preferences::GetBool(prefName.get(), false); +} + +void +nsPluginHost::RegisterWithCategoryManager(const nsCString& aMimeType, + nsRegisterType aType) +{ + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsPluginTag::RegisterWithCategoryManager type = %s, removing = %s\n", + aMimeType.get(), aType == ePluginUnregister ? "yes" : "no")); + + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return; + } + + const char *contractId = + "@mozilla.org/content/plugin/document-loader-factory;1"; + + if (aType == ePluginRegister) { + catMan->AddCategoryEntry("Gecko-Content-Viewers", + aMimeType.get(), + contractId, + false, /* persist: broken by bug 193031 */ + mOverrideInternalTypes, + nullptr); + } else { + if (aType == ePluginMaybeUnregister) { + // Bail out if this type is still used by an enabled plugin + if (HavePluginForType(aMimeType)) { + return; + } + } else { + MOZ_ASSERT(aType == ePluginUnregister, "Unknown nsRegisterType"); + } + + // Only delete the entry if a plugin registered for it + nsXPIDLCString value; + nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", + aMimeType.get(), + getter_Copies(value)); + if (NS_SUCCEEDED(rv) && strcmp(value, contractId) == 0) { + catMan->DeleteCategoryEntry("Gecko-Content-Viewers", + aMimeType.get(), + true); + } + } +} + +nsresult +nsPluginHost::WritePluginInfo() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + nsresult rv = NS_OK; + nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID,&rv)); + if (NS_FAILED(rv)) + return rv; + + directoryService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(mPluginRegFile)); + + if (!mPluginRegFile) + return NS_ERROR_FAILURE; + + PRFileDesc* fd = nullptr; + + nsCOMPtr<nsIFile> pluginReg; + + rv = mPluginRegFile->Clone(getter_AddRefs(pluginReg)); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString filename(kPluginRegistryFilename); + filename.AppendLiteral(".tmp"); + rv = pluginReg->AppendNative(filename); + if (NS_FAILED(rv)) + return rv; + + rv = pluginReg->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, &fd); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1"); + if (!runtime) { + return NS_ERROR_FAILURE; + } + + nsAutoCString arch; + rv = runtime->GetXPCOMABI(arch); + if (NS_FAILED(rv)) { + return rv; + } + + bool flashOnly = Preferences::GetBool("plugin.load_flash_only", true); + + PR_fprintf(fd, "Generated File. Do not edit.\n"); + + PR_fprintf(fd, "\n[HEADER]\nVersion%c%s%c%c%c\nArch%c%s%c%c\n", + PLUGIN_REGISTRY_FIELD_DELIMITER, + kPluginRegistryVersion, + flashOnly ? 't' : 'f', + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER, + PLUGIN_REGISTRY_FIELD_DELIMITER, + arch.get(), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER); + + // Store all plugins in the mPlugins list - all plugins currently in use. + PR_fprintf(fd, "\n[PLUGINS]\n"); + + for (nsPluginTag *tag = mPlugins; tag; tag = tag->mNext) { + // store each plugin info into the registry + // filename & fullpath are on separate line + // because they can contain field delimiter char + PR_fprintf(fd, "%s%c%c\n%s%c%c\n%s%c%c\n", + (tag->FileName().get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER, + (tag->mFullPath.get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER, + (tag->Version().get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER); + + // lastModifiedTimeStamp|canUnload|tag->mFlags|fromExtension + PR_fprintf(fd, "%lld%c%d%c%lu%c%d%c%c\n", + tag->mLastModifiedTime, + PLUGIN_REGISTRY_FIELD_DELIMITER, + false, // did store whether or not to unload in-process plugins + PLUGIN_REGISTRY_FIELD_DELIMITER, + 0, // legacy field for flags + PLUGIN_REGISTRY_FIELD_DELIMITER, + tag->IsFromExtension(), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER); + + //description, name & mtypecount are on separate line + PR_fprintf(fd, "%s%c%c\n%s%c%c\n%d\n", + (tag->Description().get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER, + (tag->Name().get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER, + tag->MimeTypes().Length()); + + // Add in each mimetype this plugin supports + for (uint32_t i = 0; i < tag->MimeTypes().Length(); i++) { + PR_fprintf(fd, "%d%c%s%c%s%c%s%c%c\n", + i,PLUGIN_REGISTRY_FIELD_DELIMITER, + (tag->MimeTypes()[i].get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + (tag->MimeDescriptions()[i].get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + (tag->Extensions()[i].get()), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER); + } + } + + PR_fprintf(fd, "\n[INVALID]\n"); + + RefPtr<nsInvalidPluginTag> invalidPlugins = mInvalidPlugins; + while (invalidPlugins) { + // fullPath + PR_fprintf(fd, "%s%c%c\n", + (!invalidPlugins->mFullPath.IsEmpty() ? invalidPlugins->mFullPath.get() : ""), + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER); + + // lastModifiedTimeStamp + PR_fprintf(fd, "%lld%c%c\n", + invalidPlugins->mLastModifiedTime, + PLUGIN_REGISTRY_FIELD_DELIMITER, + PLUGIN_REGISTRY_END_OF_LINE_MARKER); + + invalidPlugins = invalidPlugins->mNext; + } + + PRStatus prrc; + prrc = PR_Close(fd); + if (prrc != PR_SUCCESS) { + // we should obtain a refined value based on prrc; + rv = NS_ERROR_FAILURE; + MOZ_ASSERT(false, "PR_Close() failed."); + return rv; + } + nsCOMPtr<nsIFile> parent; + rv = pluginReg->GetParent(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + rv = pluginReg->MoveToNative(parent, kPluginRegistryFilename); + return rv; +} + +nsresult +nsPluginHost::ReadPluginInfo() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + const long PLUGIN_REG_MIMETYPES_ARRAY_SIZE = 12; + const long PLUGIN_REG_MAX_MIMETYPES = 1000; + + nsresult rv; + + nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID,&rv)); + if (NS_FAILED(rv)) + return rv; + + directoryService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(mPluginRegFile)); + + if (!mPluginRegFile) { + // There is no profile yet, this will tell us if there is going to be one + // in the future. + directoryService->Get(NS_APP_PROFILE_DIR_STARTUP, NS_GET_IID(nsIFile), + getter_AddRefs(mPluginRegFile)); + if (!mPluginRegFile) + return NS_ERROR_FAILURE; + else + return NS_ERROR_NOT_AVAILABLE; + } + + PRFileDesc* fd = nullptr; + + nsCOMPtr<nsIFile> pluginReg; + + rv = mPluginRegFile->Clone(getter_AddRefs(pluginReg)); + if (NS_FAILED(rv)) + return rv; + + rv = pluginReg->AppendNative(kPluginRegistryFilename); + if (NS_FAILED(rv)) + return rv; + + int64_t fileSize; + rv = pluginReg->GetFileSize(&fileSize); + if (NS_FAILED(rv)) + return rv; + + if (fileSize > INT32_MAX) { + return NS_ERROR_FAILURE; + } + int32_t flen = int32_t(fileSize); + if (flen == 0) { + NS_WARNING("Plugins Registry Empty!"); + return NS_OK; // ERROR CONDITION + } + + nsPluginManifestLineReader reader; + char* registry = reader.Init(flen); + if (!registry) + return NS_ERROR_OUT_OF_MEMORY; + + rv = pluginReg->OpenNSPRFileDesc(PR_RDONLY, 0444, &fd); + if (NS_FAILED(rv)) + return rv; + + // set rv to return an error on goto out + rv = NS_ERROR_FAILURE; + + // We know how many octes we are supposed to read. + // So let use the busy_beaver_PR_Read version. + int32_t bread = busy_beaver_PR_Read(fd, registry, flen); + + PRStatus prrc; + prrc = PR_Close(fd); + if (prrc != PR_SUCCESS) { + // Strange error: this is one of those "Should not happen" error. + // we may want to report something more refined than NS_ERROR_FAILURE. + MOZ_ASSERT(false, "PR_Close() failed."); + return rv; + } + + // short read error, so to speak. + if (flen > bread) + return rv; + + if (!ReadSectionHeader(reader, "HEADER")) + return rv;; + + if (!reader.NextLine()) + return rv; + + char* values[6]; + + // VersionLiteral, kPluginRegistryVersion + if (2 != reader.ParseLine(values, 2)) + return rv; + + // VersionLiteral + if (PL_strcmp(values[0], "Version")) + return rv; + + // If we're reading an old registry, ignore it + // If we flipped the flash-only pref, ignore it + bool flashOnly = Preferences::GetBool("plugin.load_flash_only", true); + nsAutoCString expectedVersion(kPluginRegistryVersion); + expectedVersion.Append(flashOnly ? 't' : 'f'); + + if (!expectedVersion.Equals(values[1])) { + return rv; + } + + char* archValues[6]; + if (!reader.NextLine()) { + return rv; + } + + // ArchLiteral, Architecture + if (2 != reader.ParseLine(archValues, 2)) { + return rv; + } + + // ArchLiteral + if (PL_strcmp(archValues[0], "Arch")) { + return rv; + } + + nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1"); + if (!runtime) { + return rv; + } + + nsAutoCString arch; + if (NS_FAILED(runtime->GetXPCOMABI(arch))) { + return rv; + } + + // If this is a registry from a different architecture then don't attempt to read it + if (PL_strcmp(archValues[1], arch.get())) { + return rv; + } + + if (!ReadSectionHeader(reader, "PLUGINS")) + return rv; + + while (reader.NextLine()) { + if (*reader.LinePtr() == '[') { + break; + } + + const char* filename = reader.LinePtr(); + if (!reader.NextLine()) + return rv; + + const char* fullpath = reader.LinePtr(); + if (!reader.NextLine()) + return rv; + + const char *version; + version = reader.LinePtr(); + if (!reader.NextLine()) + return rv; + + // lastModifiedTimeStamp|canUnload|tag.mFlag|fromExtension + if (4 != reader.ParseLine(values, 4)) + return rv; + + int64_t lastmod = nsCRT::atoll(values[0]); + bool fromExtension = atoi(values[3]); + if (!reader.NextLine()) + return rv; + + char *description = reader.LinePtr(); + if (!reader.NextLine()) + return rv; + +#if MOZ_WIDGET_ANDROID + // Flash on Android does not populate the version field, but it is tacked on to the description. + // For example, "Shockwave Flash 11.1 r115" + if (PL_strncmp("Shockwave Flash ", description, 16) == 0 && description[16]) { + version = &description[16]; + } +#endif + + const char *name = reader.LinePtr(); + if (!reader.NextLine()) + return rv; + + long mimetypecount = std::strtol(reader.LinePtr(), nullptr, 10); + if (mimetypecount == LONG_MAX || mimetypecount == LONG_MIN || + mimetypecount >= PLUGIN_REG_MAX_MIMETYPES || mimetypecount < 0) { + return NS_ERROR_FAILURE; + } + + char *stackalloced[PLUGIN_REG_MIMETYPES_ARRAY_SIZE * 3]; + char **mimetypes; + char **mimedescriptions; + char **extensions; + char **heapalloced = 0; + if (mimetypecount > PLUGIN_REG_MIMETYPES_ARRAY_SIZE - 1) { + heapalloced = new char *[mimetypecount * 3]; + mimetypes = heapalloced; + } else { + mimetypes = stackalloced; + } + mimedescriptions = mimetypes + mimetypecount; + extensions = mimedescriptions + mimetypecount; + + int mtr = 0; //mimetype read + for (; mtr < mimetypecount; mtr++) { + if (!reader.NextLine()) + break; + + //line number|mimetype|description|extension + if (4 != reader.ParseLine(values, 4)) + break; + int line = atoi(values[0]); + if (line != mtr) + break; + mimetypes[mtr] = values[1]; + mimedescriptions[mtr] = values[2]; + extensions[mtr] = values[3]; + } + + if (mtr != mimetypecount) { + if (heapalloced) { + delete [] heapalloced; + } + return rv; + } + + RefPtr<nsPluginTag> tag = new nsPluginTag(name, + description, + filename, + fullpath, + version, + (const char* const*)mimetypes, + (const char* const*)mimedescriptions, + (const char* const*)extensions, + mimetypecount, lastmod, fromExtension, true); + if (heapalloced) + delete [] heapalloced; + + // Import flags from registry into prefs for old registry versions + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_BASIC, + ("LoadCachedPluginsInfo : Loading Cached plugininfo for %s\n", tag->FileName().get())); + + if (!ShouldAddPlugin(tag)) { + continue; + } + + tag->mNext = mCachedPlugins; + mCachedPlugins = tag; + } + +// On Android we always want to try to load a plugin again (Flash). Bug 935676. +#ifndef MOZ_WIDGET_ANDROID + if (!ReadSectionHeader(reader, "INVALID")) { + return rv; + } + + while (reader.NextLine()) { + const char *fullpath = reader.LinePtr(); + if (!reader.NextLine()) { + return rv; + } + + const char *lastModifiedTimeStamp = reader.LinePtr(); + int64_t lastmod = nsCRT::atoll(lastModifiedTimeStamp); + + RefPtr<nsInvalidPluginTag> invalidTag = new nsInvalidPluginTag(fullpath, lastmod); + + invalidTag->mNext = mInvalidPlugins; + if (mInvalidPlugins) { + mInvalidPlugins->mPrev = invalidTag; + } + mInvalidPlugins = invalidTag; + } +#endif + + return NS_OK; +} + +void +nsPluginHost::RemoveCachedPluginsInfo(const char *filePath, nsPluginTag **result) +{ + RefPtr<nsPluginTag> prev; + RefPtr<nsPluginTag> tag = mCachedPlugins; + while (tag) + { + if (tag->mFullPath.Equals(filePath)) { + // Found it. Remove it from our list + if (prev) + prev->mNext = tag->mNext; + else + mCachedPlugins = tag->mNext; + tag->mNext = nullptr; + *result = tag; + NS_ADDREF(*result); + break; + } + prev = tag; + tag = tag->mNext; + } +} + +#ifdef XP_WIN +nsresult +nsPluginHost::EnsurePrivateDirServiceProvider() +{ + if (!mPrivateDirServiceProvider) { + nsresult rv; + mPrivateDirServiceProvider = new nsPluginDirServiceProvider(); + nsCOMPtr<nsIDirectoryService> dirService(do_GetService(kDirectoryServiceContractID, &rv)); + if (NS_FAILED(rv)) + return rv; + rv = dirService->RegisterProvider(mPrivateDirServiceProvider); + if (NS_FAILED(rv)) + return rv; + } + return NS_OK; +} +#endif /* XP_WIN */ + +nsresult nsPluginHost::NewPluginURLStream(const nsString& aURL, + nsNPAPIPluginInstance *aInstance, + nsNPAPIPluginStreamListener* aListener, + nsIInputStream *aPostStream, + const char *aHeadersData, + uint32_t aHeadersDataLen) +{ + nsCOMPtr<nsIURI> url; + nsAutoString absUrl; + nsresult rv; + + if (aURL.Length() <= 0) + return NS_OK; + + // get the base URI for the plugin to create an absolute url + // in case aURL is relative + RefPtr<nsPluginInstanceOwner> owner = aInstance->GetOwner(); + if (owner) { + nsCOMPtr<nsIURI> baseURI = owner->GetBaseURI(); + rv = NS_MakeAbsoluteURI(absUrl, aURL, baseURI); + } + + if (absUrl.IsEmpty()) + absUrl.Assign(aURL); + + rv = NS_NewURI(getter_AddRefs(url), absUrl); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsPluginStreamListenerPeer> listenerPeer = new nsPluginStreamListenerPeer(); + NS_ENSURE_TRUE(listenerPeer, NS_ERROR_OUT_OF_MEMORY); + + rv = listenerPeer->Initialize(url, aInstance, aListener); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDOMElement> element; + nsCOMPtr<nsIDocument> doc; + if (owner) { + owner->GetDOMElement(getter_AddRefs(element)); + owner->GetDocument(getter_AddRefs(doc)); + } + + nsCOMPtr<nsINode> requestingNode(do_QueryInterface(element)); + NS_ENSURE_TRUE(requestingNode, NS_ERROR_FAILURE); + + nsCOMPtr<nsIChannel> channel; + // @arg loadgroup: + // do not add this internal plugin's channel on the + // load group otherwise this channel could be canceled + // form |nsDocShell::OnLinkClickSync| bug 166613 + rv = NS_NewChannel(getter_AddRefs(channel), + url, + requestingNode, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS | + nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, + nsIContentPolicy::TYPE_OBJECT_SUBREQUEST, + nullptr, // aLoadGroup + listenerPeer, + nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI | + nsIChannel::LOAD_BYPASS_SERVICE_WORKER); + NS_ENSURE_SUCCESS(rv, rv); + + if (doc) { + // And if it's a script allow it to execute against the + // document's script context. + nsCOMPtr<nsIScriptChannel> scriptChannel(do_QueryInterface(channel)); + if (scriptChannel) { + scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); + // Plug-ins seem to depend on javascript: URIs running synchronously + scriptChannel->SetExecuteAsync(false); + } + } + + // deal with headers and post data + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + if (!aPostStream) { + // Only set the Referer header for GET requests because IIS throws + // errors about malformed requests if we include it in POSTs. See + // bug 724465. + nsCOMPtr<nsIURI> referer; + net::ReferrerPolicy referrerPolicy = net::RP_Default; + + nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(element); + if (olc) + olc->GetSrcURI(getter_AddRefs(referer)); + + + if (!referer) { + if (!doc) { + return NS_ERROR_FAILURE; + } + referer = doc->GetDocumentURI(); + referrerPolicy = doc->GetReferrerPolicy(); + } + + rv = httpChannel->SetReferrerWithPolicy(referer, referrerPolicy); + NS_ENSURE_SUCCESS(rv,rv); + } + + if (aPostStream) { + // XXX it's a bit of a hack to rewind the postdata stream + // here but it has to be done in case the post data is + // being reused multiple times. + nsCOMPtr<nsISeekableStream> + postDataSeekable(do_QueryInterface(aPostStream)); + if (postDataSeekable) + postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel"); + + uploadChannel->SetUploadStream(aPostStream, EmptyCString(), -1); + } + + if (aHeadersData) { + rv = AddHeadersToChannel(aHeadersData, aHeadersDataLen, httpChannel); + NS_ENSURE_SUCCESS(rv,rv); + } + } + rv = channel->AsyncOpen2(listenerPeer); + if (NS_SUCCEEDED(rv)) + listenerPeer->TrackRequest(channel); + return rv; +} + +nsresult +nsPluginHost::AddHeadersToChannel(const char *aHeadersData, + uint32_t aHeadersDataLen, + nsIChannel *aGenericChannel) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIHttpChannel> aChannel = do_QueryInterface(aGenericChannel); + if (!aChannel) { + return NS_ERROR_NULL_POINTER; + } + + // used during the manipulation of the String from the aHeadersData + nsAutoCString headersString; + nsAutoCString oneHeader; + nsAutoCString headerName; + nsAutoCString headerValue; + int32_t crlf = 0; + int32_t colon = 0; + + // Turn the char * buffer into an nsString. + headersString = aHeadersData; + + // Iterate over the nsString: for each "\r\n" delimited chunk, + // add the value as a header to the nsIHTTPChannel + while (true) { + crlf = headersString.Find("\r\n", true); + if (-1 == crlf) { + rv = NS_OK; + return rv; + } + headersString.Mid(oneHeader, 0, crlf); + headersString.Cut(0, crlf + 2); + oneHeader.StripWhitespace(); + colon = oneHeader.Find(":"); + if (-1 == colon) { + rv = NS_ERROR_NULL_POINTER; + return rv; + } + oneHeader.Left(headerName, colon); + colon++; + oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon); + + // FINALLY: we can set the header! + + rv = aChannel->SetRequestHeader(headerName, headerValue, true); + if (NS_FAILED(rv)) { + rv = NS_ERROR_NULL_POINTER; + return rv; + } + } +} + +nsresult +nsPluginHost::StopPluginInstance(nsNPAPIPluginInstance* aInstance) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + if (PluginDestructionGuard::DelayDestroy(aInstance)) { + return NS_OK; + } + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsPluginHost::StopPluginInstance called instance=%p\n",aInstance)); + + if (aInstance->HasStartedDestroying()) { + return NS_OK; + } + + Telemetry::AutoTimer<Telemetry::PLUGIN_SHUTDOWN_MS> timer; + aInstance->Stop(); + + // if the instance does not want to be 'cached' just remove it + bool doCache = aInstance->ShouldCache(); + if (doCache) { + // try to get the max cached instances from a pref or use default + uint32_t cachedInstanceLimit = + Preferences::GetUint(NS_PREF_MAX_NUM_CACHED_INSTANCES, + DEFAULT_NUMBER_OF_STOPPED_INSTANCES); + if (StoppedInstanceCount() >= cachedInstanceLimit) { + nsNPAPIPluginInstance *oldestInstance = FindOldestStoppedInstance(); + if (oldestInstance) { + nsPluginTag* pluginTag = TagForPlugin(oldestInstance->GetPlugin()); + oldestInstance->Destroy(); + mInstances.RemoveElement(oldestInstance); + // TODO: Remove this check once bug 752422 was investigated + if (pluginTag) { + OnPluginInstanceDestroyed(pluginTag); + } + } + } + } else { + nsPluginTag* pluginTag = TagForPlugin(aInstance->GetPlugin()); + aInstance->Destroy(); + mInstances.RemoveElement(aInstance); + // TODO: Remove this check once bug 752422 was investigated + if (pluginTag) { + OnPluginInstanceDestroyed(pluginTag); + } + } + + return NS_OK; +} + +nsresult nsPluginHost::NewPluginStreamListener(nsIURI* aURI, + nsNPAPIPluginInstance* aInstance, + nsIStreamListener **aStreamListener) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aStreamListener); + + RefPtr<nsPluginStreamListenerPeer> listener = new nsPluginStreamListenerPeer(); + nsresult rv = listener->Initialize(aURI, aInstance, nullptr); + if (NS_FAILED(rv)) { + return rv; + } + + listener.forget(aStreamListener); + + return NS_OK; +} + +void nsPluginHost::CreateWidget(nsPluginInstanceOwner* aOwner) +{ + aOwner->CreateWidget(); + + // If we've got a native window, the let the plugin know about it. + aOwner->CallSetWindow(); +} + +NS_IMETHODIMP nsPluginHost::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *someData) +{ + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) { + OnShutdown(); + UnloadPlugins(); + sInst->Release(); + } + if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) { + mPluginsDisabled = Preferences::GetBool("plugin.disable", false); + // Unload or load plugins as needed + if (mPluginsDisabled) { + UnloadPlugins(); + } else { + LoadPlugins(); + } + } + if (!strcmp("blocklist-updated", aTopic)) { + nsPluginTag* plugin = mPlugins; + while (plugin) { + plugin->InvalidateBlocklistState(); + plugin = plugin->mNext; + } + } +#ifdef MOZ_WIDGET_ANDROID + if (!strcmp("application-background", aTopic)) { + for(uint32_t i = 0; i < mInstances.Length(); i++) { + mInstances[i]->NotifyForeground(false); + } + } + if (!strcmp("application-foreground", aTopic)) { + for(uint32_t i = 0; i < mInstances.Length(); i++) { + if (mInstances[i]->IsOnScreen()) + mInstances[i]->NotifyForeground(true); + } + } + if (!strcmp("memory-pressure", aTopic)) { + for(uint32_t i = 0; i < mInstances.Length(); i++) { + mInstances[i]->MemoryPressure(); + } + } +#endif + return NS_OK; +} + +nsresult +nsPluginHost::ParsePostBufferToFixHeaders(const char *inPostData, uint32_t inPostDataLen, + char **outPostData, uint32_t *outPostDataLen) +{ + if (!inPostData || !outPostData || !outPostDataLen) + return NS_ERROR_NULL_POINTER; + + *outPostData = 0; + *outPostDataLen = 0; + + const char CR = '\r'; + const char LF = '\n'; + const char CRLFCRLF[] = {CR,LF,CR,LF,'\0'}; // C string"\r\n\r\n" + const char ContentLenHeader[] = "Content-length"; + + AutoTArray<const char*, 8> singleLF; + const char *pSCntlh = 0;// pointer to start of ContentLenHeader in inPostData + const char *pSod = 0; // pointer to start of data in inPostData + const char *pEoh = 0; // pointer to end of headers in inPostData + const char *pEod = inPostData + inPostDataLen; // pointer to end of inPostData + if (*inPostData == LF) { + // If no custom headers are required, simply add a blank + // line ('\n') to the beginning of the file or buffer. + // so *inPostData == '\n' is valid + pSod = inPostData + 1; + } else { + const char *s = inPostData; //tmp pointer to sourse inPostData + while (s < pEod) { + if (!pSCntlh && + (*s == 'C' || *s == 'c') && + (s + sizeof(ContentLenHeader) - 1 < pEod) && + (!PL_strncasecmp(s, ContentLenHeader, sizeof(ContentLenHeader) - 1))) + { + // lets assume this is ContentLenHeader for now + const char *p = pSCntlh = s; + p += sizeof(ContentLenHeader) - 1; + // search for first CR or LF == end of ContentLenHeader + for (; p < pEod; p++) { + if (*p == CR || *p == LF) { + // got delimiter, + // one more check; if previous char is a digit + // most likely pSCntlh points to the start of ContentLenHeader + if (*(p-1) >= '0' && *(p-1) <= '9') { + s = p; + } + break; //for loop + } + } + if (pSCntlh == s) { // curret ptr is the same + pSCntlh = 0; // that was not ContentLenHeader + break; // there is nothing to parse, break *WHILE LOOP* here + } + } + + if (*s == CR) { + if (pSCntlh && // only if ContentLenHeader is found we are looking for end of headers + ((s + sizeof(CRLFCRLF)-1) <= pEod) && + !memcmp(s, CRLFCRLF, sizeof(CRLFCRLF)-1)) + { + s += sizeof(CRLFCRLF)-1; + pEoh = pSod = s; // data stars here + break; + } + } else if (*s == LF) { + if (*(s-1) != CR) { + singleLF.AppendElement(s); + } + if (pSCntlh && (s+1 < pEod) && (*(s+1) == LF)) { + s++; + singleLF.AppendElement(s); + s++; + pEoh = pSod = s; // data stars here + break; + } + } + s++; + } + } + + // deal with output buffer + if (!pSod) { // lets assume whole buffer is a data + pSod = inPostData; + } + + uint32_t newBufferLen = 0; + uint32_t dataLen = pEod - pSod; + uint32_t headersLen = pEoh ? pSod - inPostData : 0; + + char *p; // tmp ptr into new output buf + if (headersLen) { // we got a headers + // this function does not make any assumption on correctness + // of ContentLenHeader value in this case. + + newBufferLen = dataLen + headersLen; + // in case there were single LFs in headers + // reserve an extra space for CR will be added before each single LF + int cntSingleLF = singleLF.Length(); + newBufferLen += cntSingleLF; + + if (!(*outPostData = p = (char*)moz_xmalloc(newBufferLen))) + return NS_ERROR_OUT_OF_MEMORY; + + // deal with single LF + const char *s = inPostData; + if (cntSingleLF) { + for (int i=0; i<cntSingleLF; i++) { + const char *plf = singleLF.ElementAt(i); // ptr to single LF in headers + int n = plf - s; // bytes to copy + if (n) { // for '\n\n' there is nothing to memcpy + memcpy(p, s, n); + p += n; + } + *p++ = CR; + s = plf; + *p++ = *s++; + } + } + // are we done with headers? + headersLen = pEoh - s; + if (headersLen) { // not yet + memcpy(p, s, headersLen); // copy the rest + p += headersLen; + } + } else if (dataLen) { // no ContentLenHeader is found but there is a data + // make new output buffer big enough + // to keep ContentLenHeader+value followed by data + uint32_t l = sizeof(ContentLenHeader) + sizeof(CRLFCRLF) + 32; + newBufferLen = dataLen + l; + if (!(*outPostData = p = (char*)moz_xmalloc(newBufferLen))) + return NS_ERROR_OUT_OF_MEMORY; + headersLen = snprintf(p, l,"%s: %u%s", ContentLenHeader, dataLen, CRLFCRLF); + if (headersLen == l) { // if snprintf has ate all extra space consider this as an error + free(p); + *outPostData = 0; + return NS_ERROR_FAILURE; + } + p += headersLen; + newBufferLen = headersLen + dataLen; + } + // at this point we've done with headers. + // there is a possibility that input buffer has only headers info in it + // which already parsed and copied into output buffer. + // copy the data + if (dataLen) { + memcpy(p, pSod, dataLen); + } + + *outPostDataLen = newBufferLen; + + return NS_OK; +} + +nsresult +nsPluginHost::CreateTempFileToPost(const char *aPostDataURL, nsIFile **aTmpFile) +{ + nsresult rv; + int64_t fileSize; + nsAutoCString filename; + + // stat file == get size & convert file:///c:/ to c: if needed + nsCOMPtr<nsIFile> inFile; + rv = NS_GetFileFromURLSpec(nsDependentCString(aPostDataURL), + getter_AddRefs(inFile)); + if (NS_FAILED(rv)) { + nsCOMPtr<nsIFile> localFile; + rv = NS_NewNativeLocalFile(nsDependentCString(aPostDataURL), false, + getter_AddRefs(localFile)); + if (NS_FAILED(rv)) return rv; + inFile = localFile; + } + rv = inFile->GetFileSize(&fileSize); + if (NS_FAILED(rv)) return rv; + rv = inFile->GetNativePath(filename); + if (NS_FAILED(rv)) return rv; + + if (fileSize != 0) { + nsCOMPtr<nsIInputStream> inStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), inFile); + if (NS_FAILED(rv)) return rv; + + // Create a temporary file to write the http Content-length: + // %ld\r\n\" header and "\r\n" == end of headers for post data to + + nsCOMPtr<nsIFile> tempFile; + rv = GetPluginTempDir(getter_AddRefs(tempFile)); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString inFileName; + inFile->GetNativeLeafName(inFileName); + // XXX hack around bug 70083 + inFileName.Insert(NS_LITERAL_CSTRING("post-"), 0); + rv = tempFile->AppendNative(inFileName); + + if (NS_FAILED(rv)) + return rv; + + // make it unique, and mode == 0600, not world-readable + rv = tempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIOutputStream> outStream; + if (NS_SUCCEEDED(rv)) { + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), + tempFile, + (PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE), + 0600); // 600 so others can't read our form data + } + NS_ASSERTION(NS_SUCCEEDED(rv), "Post data file couldn't be created!"); + if (NS_FAILED(rv)) + return rv; + + char buf[1024]; + uint32_t br, bw; + bool firstRead = true; + while (1) { + // Read() mallocs if buffer is null + rv = inStream->Read(buf, 1024, &br); + if (NS_FAILED(rv) || (int32_t)br <= 0) + break; + if (firstRead) { + //"For protocols in which the headers must be distinguished from the body, + // such as HTTP, the buffer or file should contain the headers, followed by + // a blank line, then the body. If no custom headers are required, simply + // add a blank line ('\n') to the beginning of the file or buffer. + + char *parsedBuf; + // assuming first 1K (or what we got) has all headers in, + // lets parse it through nsPluginHost::ParsePostBufferToFixHeaders() + ParsePostBufferToFixHeaders((const char *)buf, br, &parsedBuf, &bw); + rv = outStream->Write(parsedBuf, bw, &br); + free(parsedBuf); + if (NS_FAILED(rv) || (bw != br)) + break; + + firstRead = false; + continue; + } + bw = br; + rv = outStream->Write(buf, bw, &br); + if (NS_FAILED(rv) || (bw != br)) + break; + } + + inStream->Close(); + outStream->Close(); + if (NS_SUCCEEDED(rv)) + tempFile.forget(aTmpFile); + } + return rv; +} + +nsresult +nsPluginHost::NewPluginNativeWindow(nsPluginNativeWindow ** aPluginNativeWindow) +{ + return PLUG_NewPluginNativeWindow(aPluginNativeWindow); +} + +nsresult +nsPluginHost::GetPluginName(nsNPAPIPluginInstance *aPluginInstance, + const char** aPluginName) +{ + nsNPAPIPluginInstance *instance = static_cast<nsNPAPIPluginInstance*>(aPluginInstance); + if (!instance) + return NS_ERROR_FAILURE; + + nsNPAPIPlugin* plugin = instance->GetPlugin(); + if (!plugin) + return NS_ERROR_FAILURE; + + *aPluginName = TagForPlugin(plugin)->Name().get(); + + return NS_OK; +} + +nsresult +nsPluginHost::GetPluginTagForInstance(nsNPAPIPluginInstance *aPluginInstance, + nsIPluginTag **aPluginTag) +{ + NS_ENSURE_ARG_POINTER(aPluginInstance); + NS_ENSURE_ARG_POINTER(aPluginTag); + + nsNPAPIPlugin *plugin = aPluginInstance->GetPlugin(); + if (!plugin) + return NS_ERROR_FAILURE; + + *aPluginTag = TagForPlugin(plugin); + + NS_ADDREF(*aPluginTag); + return NS_OK; +} + +NS_IMETHODIMP nsPluginHost::Notify(nsITimer* timer) +{ + RefPtr<nsPluginTag> pluginTag = mPlugins; + while (pluginTag) { + if (pluginTag->mUnloadTimer == timer) { + if (!IsRunningPlugin(pluginTag)) { + pluginTag->TryUnloadPlugin(false); + } + return NS_OK; + } + pluginTag = pluginTag->mNext; + } + + return NS_ERROR_FAILURE; +} + +#ifdef XP_WIN +// Re-enable any top level browser windows that were disabled by modal dialogs +// displayed by the crashed plugin. +static void +CheckForDisabledWindows() +{ + nsCOMPtr<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!wm) + return; + + nsCOMPtr<nsISimpleEnumerator> windowList; + wm->GetXULWindowEnumerator(nullptr, getter_AddRefs(windowList)); + if (!windowList) + return; + + bool haveWindows; + do { + windowList->HasMoreElements(&haveWindows); + if (!haveWindows) + return; + + nsCOMPtr<nsISupports> supportsWindow; + windowList->GetNext(getter_AddRefs(supportsWindow)); + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(supportsWindow)); + if (baseWin) { + nsCOMPtr<nsIWidget> widget; + baseWin->GetMainWidget(getter_AddRefs(widget)); + if (widget && !widget->GetParent() && + widget->IsVisible() && + !widget->IsEnabled()) { + nsIWidget* child = widget->GetFirstChild(); + bool enable = true; + while (child) { + if (child->WindowType() == eWindowType_dialog) { + enable = false; + break; + } + child = child->GetNextSibling(); + } + if (enable) { + widget->Enable(true); + } + } + } + } while (haveWindows); +} +#endif + +void +nsPluginHost::PluginCrashed(nsNPAPIPlugin* aPlugin, + const nsAString& pluginDumpID, + const nsAString& browserDumpID) +{ + nsPluginTag* crashedPluginTag = TagForPlugin(aPlugin); + MOZ_ASSERT(crashedPluginTag); + + // Notify the app's observer that a plugin crashed so it can submit + // a crashreport. + bool submittedCrashReport = false; + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + nsCOMPtr<nsIWritablePropertyBag2> propbag = + do_CreateInstance("@mozilla.org/hash-property-bag;1"); + if (obsService && propbag) { + uint32_t runID = 0; + PluginLibrary* library = aPlugin->GetLibrary(); + + if (!NS_WARN_IF(!library)) { + library->GetRunID(&runID); + } + propbag->SetPropertyAsUint32(NS_LITERAL_STRING("runID"), runID); + + nsCString pluginName; + crashedPluginTag->GetName(pluginName); + propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginName"), + NS_ConvertUTF8toUTF16(pluginName)); + propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"), + pluginDumpID); + propbag->SetPropertyAsAString(NS_LITERAL_STRING("browserDumpID"), + browserDumpID); + propbag->SetPropertyAsBool(NS_LITERAL_STRING("submittedCrashReport"), + submittedCrashReport); + obsService->NotifyObservers(propbag, "plugin-crashed", nullptr); + // see if an observer submitted a crash report. + propbag->GetPropertyAsBool(NS_LITERAL_STRING("submittedCrashReport"), + &submittedCrashReport); + } + + // Invalidate each nsPluginInstanceTag for the crashed plugin + + for (uint32_t i = mInstances.Length(); i > 0; i--) { + nsNPAPIPluginInstance* instance = mInstances[i - 1]; + if (instance->GetPlugin() == aPlugin) { + // notify the content node (nsIObjectLoadingContent) that the + // plugin has crashed + nsCOMPtr<nsIDOMElement> domElement; + instance->GetDOMElement(getter_AddRefs(domElement)); + nsCOMPtr<nsIObjectLoadingContent> objectContent(do_QueryInterface(domElement)); + if (objectContent) { + objectContent->PluginCrashed(crashedPluginTag, pluginDumpID, browserDumpID, + submittedCrashReport); + } + + instance->Destroy(); + mInstances.RemoveElement(instance); + OnPluginInstanceDestroyed(crashedPluginTag); + } + } + + // Only after all instances have been invalidated is it safe to null + // out nsPluginTag.mPlugin. The next time we try to create an + // instance of this plugin we reload it (launch a new plugin process). + + crashedPluginTag->mPlugin = nullptr; + crashedPluginTag->mContentProcessRunningCount = 0; + +#ifdef XP_WIN + CheckForDisabledWindows(); +#endif +} + +nsNPAPIPluginInstance* +nsPluginHost::FindInstance(const char *mimetype) +{ + for (uint32_t i = 0; i < mInstances.Length(); i++) { + nsNPAPIPluginInstance* instance = mInstances[i]; + + const char* mt; + nsresult rv = instance->GetMIMEType(&mt); + if (NS_FAILED(rv)) + continue; + + if (PL_strcasecmp(mt, mimetype) == 0) + return instance; + } + + return nullptr; +} + +nsNPAPIPluginInstance* +nsPluginHost::FindOldestStoppedInstance() +{ + nsNPAPIPluginInstance *oldestInstance = nullptr; + TimeStamp oldestTime = TimeStamp::Now(); + for (uint32_t i = 0; i < mInstances.Length(); i++) { + nsNPAPIPluginInstance *instance = mInstances[i]; + if (instance->IsRunning()) + continue; + + TimeStamp time = instance->StopTime(); + if (time < oldestTime) { + oldestTime = time; + oldestInstance = instance; + } + } + + return oldestInstance; +} + +uint32_t +nsPluginHost::StoppedInstanceCount() +{ + uint32_t stoppedCount = 0; + for (uint32_t i = 0; i < mInstances.Length(); i++) { + nsNPAPIPluginInstance *instance = mInstances[i]; + if (!instance->IsRunning()) + stoppedCount++; + } + return stoppedCount; +} + +nsTArray< RefPtr<nsNPAPIPluginInstance> >* +nsPluginHost::InstanceArray() +{ + return &mInstances; +} + +void +nsPluginHost::DestroyRunningInstances(nsPluginTag* aPluginTag) +{ + for (int32_t i = mInstances.Length(); i > 0; i--) { + nsNPAPIPluginInstance *instance = mInstances[i - 1]; + if (instance->IsRunning() && (!aPluginTag || aPluginTag == TagForPlugin(instance->GetPlugin()))) { + instance->SetWindow(nullptr); + instance->Stop(); + + // Get rid of all the instances without the possibility of caching. + nsPluginTag* pluginTag = TagForPlugin(instance->GetPlugin()); + instance->SetWindow(nullptr); + + nsCOMPtr<nsIDOMElement> domElement; + instance->GetDOMElement(getter_AddRefs(domElement)); + nsCOMPtr<nsIObjectLoadingContent> objectContent = + do_QueryInterface(domElement); + + instance->Destroy(); + + mInstances.RemoveElement(instance); + OnPluginInstanceDestroyed(pluginTag); + + // Notify owning content that we destroyed its plugin out from under it + if (objectContent) { + objectContent->PluginDestroyed(); + } + } + } +} + +// Runnable that does an async destroy of a plugin. + +class nsPluginDestroyRunnable : public Runnable, + public PRCList +{ +public: + explicit nsPluginDestroyRunnable(nsNPAPIPluginInstance *aInstance) + : mInstance(aInstance) + { + PR_INIT_CLIST(this); + PR_APPEND_LINK(this, &sRunnableListHead); + } + + virtual ~nsPluginDestroyRunnable() + { + PR_REMOVE_LINK(this); + } + + NS_IMETHOD Run() override + { + RefPtr<nsNPAPIPluginInstance> instance; + + // Null out mInstance to make sure this code in another runnable + // will do the right thing even if someone was holding on to this + // runnable longer than we expect. + instance.swap(mInstance); + + if (PluginDestructionGuard::DelayDestroy(instance)) { + // It's still not safe to destroy the plugin, it's now up to the + // outermost guard on the stack to take care of the destruction. + return NS_OK; + } + + nsPluginDestroyRunnable *r = + static_cast<nsPluginDestroyRunnable*>(PR_NEXT_LINK(&sRunnableListHead)); + + while (r != &sRunnableListHead) { + if (r != this && r->mInstance == instance) { + // There's another runnable scheduled to tear down + // instance. Let it do the job. + return NS_OK; + } + r = static_cast<nsPluginDestroyRunnable*>(PR_NEXT_LINK(r)); + } + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("Doing delayed destroy of instance %p\n", instance.get())); + + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + if (host) + host->StopPluginInstance(instance); + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("Done with delayed destroy of instance %p\n", instance.get())); + + return NS_OK; + } + +protected: + RefPtr<nsNPAPIPluginInstance> mInstance; + + static PRCList sRunnableListHead; +}; + +PRCList nsPluginDestroyRunnable::sRunnableListHead = + PR_INIT_STATIC_CLIST(&nsPluginDestroyRunnable::sRunnableListHead); + +PRCList PluginDestructionGuard::sListHead = + PR_INIT_STATIC_CLIST(&PluginDestructionGuard::sListHead); + +PluginDestructionGuard::PluginDestructionGuard(nsNPAPIPluginInstance *aInstance) + : mInstance(aInstance) +{ + Init(); +} + +PluginDestructionGuard::PluginDestructionGuard(PluginAsyncSurrogate *aSurrogate) + : mInstance(static_cast<nsNPAPIPluginInstance*>(aSurrogate->GetNPP()->ndata)) +{ + InitAsync(); +} + +PluginDestructionGuard::PluginDestructionGuard(NPP npp) + : mInstance(npp ? static_cast<nsNPAPIPluginInstance*>(npp->ndata) : nullptr) +{ + Init(); +} + +PluginDestructionGuard::~PluginDestructionGuard() +{ + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); + + PR_REMOVE_LINK(this); + + if (mDelayedDestroy) { + // We've attempted to destroy the plugin instance we're holding on + // to while we were guarding it. Do the actual destroy now, off of + // a runnable. + RefPtr<nsPluginDestroyRunnable> evt = + new nsPluginDestroyRunnable(mInstance); + + NS_DispatchToMainThread(evt); + } +} + +// static +bool +PluginDestructionGuard::DelayDestroy(nsNPAPIPluginInstance *aInstance) +{ + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); + NS_ASSERTION(aInstance, "Uh, I need an instance!"); + + // Find the first guard on the stack and make it do a delayed + // destroy upon destruction. + + PluginDestructionGuard *g = + static_cast<PluginDestructionGuard*>(PR_LIST_HEAD(&sListHead)); + + while (g != &sListHead) { + if (g->mInstance == aInstance) { + g->mDelayedDestroy = true; + + return true; + } + g = static_cast<PluginDestructionGuard*>(PR_NEXT_LINK(g)); + } + + return false; +} diff --git a/dom/plugins/base/nsPluginHost.h b/dom/plugins/base/nsPluginHost.h new file mode 100644 index 000000000..27e90c991 --- /dev/null +++ b/dom/plugins/base/nsPluginHost.h @@ -0,0 +1,465 @@ +/* -*- 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/. */ + +#ifndef nsPluginHost_h_ +#define nsPluginHost_h_ + +#include "nsIPluginHost.h" +#include "nsIObserver.h" +#include "nsCOMPtr.h" +#include "prlink.h" +#include "prclist.h" +#include "nsIPluginTag.h" +#include "nsPluginsDir.h" +#include "nsPluginDirServiceProvider.h" +#include "nsWeakPtr.h" +#include "nsIPrompt.h" +#include "nsWeakReference.h" +#include "MainThreadUtils.h" +#include "nsTArray.h" +#include "nsTObserverArray.h" +#include "nsITimer.h" +#include "nsPluginTags.h" +#include "nsIEffectiveTLDService.h" +#include "nsIIDNService.h" +#include "nsCRT.h" + +#ifdef XP_WIN +#include <minwindef.h> +#include "nsIWindowsRegKey.h" +#endif + +namespace mozilla { +namespace plugins { +class PluginAsyncSurrogate; +class PluginTag; +} // namespace plugins +} // namespace mozilla + +class nsNPAPIPlugin; +class nsIFile; +class nsIChannel; +class nsPluginNativeWindow; +class nsObjectLoadingContent; +class nsPluginInstanceOwner; +class nsPluginUnloadRunnable; +class nsNPAPIPluginInstance; +class nsNPAPIPluginStreamListener; +class nsIPluginInstanceOwner; +class nsIInputStream; +class nsIStreamListener; +#ifndef npapi_h_ +struct _NPP; +typedef _NPP* NPP; +#endif + +class nsInvalidPluginTag : public nsISupports +{ + virtual ~nsInvalidPluginTag(); +public: + explicit nsInvalidPluginTag(const char* aFullPath, int64_t aLastModifiedTime = 0); + + NS_DECL_ISUPPORTS + + nsCString mFullPath; + int64_t mLastModifiedTime; + bool mSeen; + + RefPtr<nsInvalidPluginTag> mPrev; + RefPtr<nsInvalidPluginTag> mNext; +}; + +class nsPluginHost final : public nsIPluginHost, + public nsIObserver, + public nsITimerCallback, + public nsSupportsWeakReference +{ + friend class nsPluginTag; + friend class nsFakePluginTag; + virtual ~nsPluginHost(); + +public: + nsPluginHost(); + + static already_AddRefed<nsPluginHost> GetInst(); + + NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW + + NS_DECL_ISUPPORTS + NS_DECL_NSIPLUGINHOST + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + + nsresult LoadPlugins(); + nsresult UnloadPlugins(); + + nsresult SetUpPluginInstance(const nsACString &aMimeType, + nsIURI *aURL, + nsPluginInstanceOwner *aOwner); + + // Acts like a bitfield + enum PluginFilter { + eExcludeNone = nsIPluginHost::EXCLUDE_NONE, + eExcludeDisabled = nsIPluginHost::EXCLUDE_DISABLED, + eExcludeFake = nsIPluginHost::EXCLUDE_FAKE + }; + // FIXME-jsplugins comment about fake + bool HavePluginForType(const nsACString & aMimeType, + PluginFilter aFilter = eExcludeDisabled); + + // FIXME-jsplugins what if fake has different extensions + bool HavePluginForExtension(const nsACString & aExtension, + /* out */ nsACString & aMimeType, + PluginFilter aFilter = eExcludeDisabled); + + void GetPlugins(nsTArray<nsCOMPtr<nsIInternalPluginTag>>& aPluginArray, + bool aIncludeDisabled = false); + + nsresult FindPluginsForContent(uint32_t aPluginEpoch, + nsTArray<mozilla::plugins::PluginTag>* aPlugins, + uint32_t* aNewPluginEpoch); + + nsresult GetURL(nsISupports* pluginInst, + const char* url, + const char* target, + nsNPAPIPluginStreamListener* streamListener, + const char* altHost, + const char* referrer, + bool forceJSEnabled); + nsresult PostURL(nsISupports* pluginInst, + const char* url, + uint32_t postDataLen, + const char* postData, + bool isFile, + const char* target, + nsNPAPIPluginStreamListener* streamListener, + const char* altHost, + const char* referrer, + bool forceJSEnabled, + uint32_t postHeadersLength, + const char* postHeaders); + + nsresult FindProxyForURL(const char* url, char* *result); + nsresult UserAgent(const char **retstring); + nsresult ParsePostBufferToFixHeaders(const char *inPostData, + uint32_t inPostDataLen, + char **outPostData, + uint32_t *outPostDataLen); + nsresult CreateTempFileToPost(const char *aPostDataURL, nsIFile **aTmpFile); + nsresult NewPluginNativeWindow(nsPluginNativeWindow ** aPluginNativeWindow); + + void AddIdleTimeTarget(nsIPluginInstanceOwner* objectFrame, bool isVisible); + void RemoveIdleTimeTarget(nsIPluginInstanceOwner* objectFrame); + + nsresult GetPluginName(nsNPAPIPluginInstance *aPluginInstance, + const char** aPluginName); + nsresult StopPluginInstance(nsNPAPIPluginInstance* aInstance); + nsresult GetPluginTagForInstance(nsNPAPIPluginInstance *aPluginInstance, + nsIPluginTag **aPluginTag); + + nsresult + NewPluginURLStream(const nsString& aURL, + nsNPAPIPluginInstance *aInstance, + nsNPAPIPluginStreamListener *aListener, + nsIInputStream *aPostStream = nullptr, + const char *aHeadersData = nullptr, + uint32_t aHeadersDataLen = 0); + + nsresult + GetURLWithHeaders(nsNPAPIPluginInstance *pluginInst, + const char* url, + const char* target = nullptr, + nsNPAPIPluginStreamListener* streamListener = nullptr, + const char* altHost = nullptr, + const char* referrer = nullptr, + bool forceJSEnabled = false, + uint32_t getHeadersLength = 0, + const char* getHeaders = nullptr); + + nsresult + AddHeadersToChannel(const char *aHeadersData, uint32_t aHeadersDataLen, + nsIChannel *aGenericChannel); + + static nsresult GetPluginTempDir(nsIFile **aDir); + + // Helper that checks if a type is whitelisted in plugin.allowed_types. + // Always returns true if plugin.allowed_types is not set + static bool IsTypeWhitelisted(const char *aType); + + // Helper that checks if a plugin of a given MIME type can be loaded by the + // parent process. It checks the plugin.load_in_parent_process.<mime> pref. + // Always returns false if plugin.load_in_parent_process.<mime> is not set. + static bool ShouldLoadTypeInParent(const nsACString& aMimeType); + + // checks whether aType is a type we recognize for potential special handling + enum SpecialType { eSpecialType_None, + // Needed to whitelist for async init support + eSpecialType_Test, + // Informs some decisions about OOP and quirks + eSpecialType_Flash, + // Binds to the <applet> tag, has various special + // rules around opening channels, codebase, ... + eSpecialType_Java, + // Some IPC quirks + eSpecialType_Silverlight, + // Native widget quirks + eSpecialType_PDF, + // Native widget quirks + eSpecialType_RealPlayer, + // Native widget quirks + eSpecialType_Unity }; + static SpecialType GetSpecialType(const nsACString & aMIMEType); + + static nsresult PostPluginUnloadEvent(PRLibrary* aLibrary); + + void PluginCrashed(nsNPAPIPlugin* plugin, + const nsAString& pluginDumpID, + const nsAString& browserDumpID); + + nsNPAPIPluginInstance *FindInstance(const char *mimetype); + nsNPAPIPluginInstance *FindOldestStoppedInstance(); + uint32_t StoppedInstanceCount(); + + nsTArray< RefPtr<nsNPAPIPluginInstance> > *InstanceArray(); + + // Return the tag for |aLibrary| if found, nullptr if not. + nsPluginTag* FindTagForLibrary(PRLibrary* aLibrary); + + // The last argument should be false if we already have an in-flight stream + // and don't need to set up a new stream. + nsresult InstantiatePluginInstance(const nsACString& aMimeType, nsIURI* aURL, + nsObjectLoadingContent *aContent, + nsPluginInstanceOwner** aOwner); + + // Does not accept nullptr and should never fail. + nsPluginTag* TagForPlugin(nsNPAPIPlugin* aPlugin); + + nsPluginTag* PluginWithId(uint32_t aId); + + nsresult GetPlugin(const nsACString &aMimeType, nsNPAPIPlugin** aPlugin); + nsresult GetPluginForContentProcess(uint32_t aPluginId, nsNPAPIPlugin** aPlugin); + void NotifyContentModuleDestroyed(uint32_t aPluginId); + + nsresult NewPluginStreamListener(nsIURI* aURL, + nsNPAPIPluginInstance* aInstance, + nsIStreamListener **aStreamListener); + + void CreateWidget(nsPluginInstanceOwner* aOwner); + + nsresult EnumerateSiteData(const nsACString& domain, + const InfallibleTArray<nsCString>& sites, + InfallibleTArray<nsCString>& result, + bool firstMatchOnly); + +private: + friend class nsPluginUnloadRunnable; + + void DestroyRunningInstances(nsPluginTag* aPluginTag); + + // Writes updated plugins settings to disk and unloads the plugin + // if it is now disabled. Should only be called by the plugin tag in question + void UpdatePluginInfo(nsPluginTag* aPluginTag); + + nsresult TrySetUpPluginInstance(const nsACString &aMimeType, nsIURI *aURL, + nsPluginInstanceOwner *aOwner); + + // FIXME-jsplugins comment here about when things may be fake + nsPluginTag* + FindPreferredPlugin(const InfallibleTArray<nsPluginTag*>& matches); + + // Find a plugin for the given type. If aIncludeFake is true a fake plugin + // will be preferred if one exists; otherwise a fake plugin will never be + // returned. If aCheckEnabled is false, disabled plugins can be returned. + nsIInternalPluginTag* FindPluginForType(const nsACString& aMimeType, + bool aIncludeFake, bool aCheckEnabled); + + // Find specifically a fake plugin for the given type. If aCheckEnabled is + // false, disabled plugins can be returned. + nsFakePluginTag* FindFakePluginForType(const nsACString & aMimeType, + bool aCheckEnabled); + + // Find specifically a fake plugin for the given extension. If aCheckEnabled + // is false, disabled plugins can be returned. aMimeType will be filled in + // with the MIME type the plugin is registered for. + nsFakePluginTag* FindFakePluginForExtension(const nsACString & aExtension, + /* out */ nsACString & aMimeType, + bool aCheckEnabled); + + // Find specifically a native (NPAPI) plugin for the given type. If + // aCheckEnabled is false, disabled plugins can be returned. + nsPluginTag* FindNativePluginForType(const nsACString & aMimeType, + bool aCheckEnabled); + + // Find specifically a native (NPAPI) plugin for the given extension. If + // aCheckEnabled is false, disabled plugins can be returned. aMimeType will + // be filled in with the MIME type the plugin is registered for. + nsPluginTag* FindNativePluginForExtension(const nsACString & aExtension, + /* out */ nsACString & aMimeType, + bool aCheckEnabled); + + nsresult + FindStoppedPluginForURL(nsIURI* aURL, nsIPluginInstanceOwner *aOwner); + + nsresult FindPluginsInContent(bool aCreatePluginList, bool * aPluginsChanged); + + nsresult + FindPlugins(bool aCreatePluginList, bool * aPluginsChanged); + + // FIXME revisit, no ns prefix + // Registers or unregisters the given mime type with the category manager + enum nsRegisterType { ePluginRegister, + ePluginUnregister, + // Checks if this type should still be registered first + ePluginMaybeUnregister }; + void RegisterWithCategoryManager(const nsCString& aMimeType, + nsRegisterType aType); + + void AddPluginTag(nsPluginTag* aPluginTag); + + nsresult + ScanPluginsDirectory(nsIFile *pluginsDir, + bool aCreatePluginList, + bool *aPluginsChanged); + + nsresult + ScanPluginsDirectoryList(nsISimpleEnumerator *dirEnum, + bool aCreatePluginList, + bool *aPluginsChanged); + + nsresult EnsurePluginLoaded(nsPluginTag* aPluginTag); + + bool IsRunningPlugin(nsPluginTag * aPluginTag); + + // Stores all plugins info into the registry + nsresult WritePluginInfo(); + + // Loads all cached plugins info into mCachedPlugins + nsresult ReadPluginInfo(); + + // Given a file path, returns the plugins info from our cache + // and removes it from the cache. + void RemoveCachedPluginsInfo(const char *filePath, + nsPluginTag **result); + + // Checks to see if a tag object is in our list of live tags. + bool IsLiveTag(nsIPluginTag* tag); + + // Checks our list of live tags for an equivalent tag. + nsPluginTag* HaveSamePlugin(const nsPluginTag * aPluginTag); + + // Returns the first plugin at |path| + nsPluginTag* FirstPluginWithPath(const nsCString& path); + + nsresult EnsurePrivateDirServiceProvider(); + + void OnPluginInstanceDestroyed(nsPluginTag* aPluginTag); + + // To be used by the chrome process whenever the set of plugins changes. + void IncrementChromeEpoch(); + + // To be used by the chrome process; returns the current epoch. + uint32_t ChromeEpoch(); + + // To be used by the content process to get/set the last observed epoch value + // from the chrome process. + uint32_t ChromeEpochForContent(); + void SetChromeEpochForContent(uint32_t aEpoch); + + void UpdateInMemoryPluginInfo(nsPluginTag* aPluginTag); + + // On certain platforms, we only want to load certain plugins. This function + // centralizes loading rules. + bool ShouldAddPlugin(nsPluginTag* aPluginTag); + + RefPtr<nsPluginTag> mPlugins; + RefPtr<nsPluginTag> mCachedPlugins; + RefPtr<nsInvalidPluginTag> mInvalidPlugins; + + nsTArray< RefPtr<nsFakePluginTag> > mFakePlugins; + + bool mPluginsLoaded; + + // set by pref plugin.override_internal_types + bool mOverrideInternalTypes; + + // set by pref plugin.disable + bool mPluginsDisabled; + + // Any instances in this array will have valid plugin objects via GetPlugin(). + // When removing an instance it might not die - be sure to null out it's plugin. + nsTArray< RefPtr<nsNPAPIPluginInstance> > mInstances; + + nsCOMPtr<nsIFile> mPluginRegFile; +#ifdef XP_WIN + RefPtr<nsPluginDirServiceProvider> mPrivateDirServiceProvider; + + // In order to reload plugins when they change, we watch the registry via + // this object. + nsCOMPtr<nsIWindowsRegKey> mRegKeyHKLM; + nsCOMPtr<nsIWindowsRegKey> mRegKeyHKCU; +#endif + + nsCOMPtr<nsIEffectiveTLDService> mTLDService; + nsCOMPtr<nsIIDNService> mIDNService; + + // Helpers for ClearSiteData and SiteHasData. + nsresult NormalizeHostname(nsCString& host); + + nsWeakPtr mCurrentDocument; // weak reference, we use it to id document only + + // This epoch increases each time we load the list of plugins from disk. + // In the chrome process, this stores the actual epoch. + // In the content process, this stores the last epoch value observed + // when reading plugins from chrome. + uint32_t mPluginEpoch; + + static nsIFile *sPluginTempDir; + + // We need to hold a global ptr to ourselves because we register for + // two different CIDs for some reason... + static nsPluginHost* sInst; +}; + +class PluginDestructionGuard : protected PRCList +{ +public: + explicit PluginDestructionGuard(nsNPAPIPluginInstance *aInstance); + explicit PluginDestructionGuard(mozilla::plugins::PluginAsyncSurrogate *aSurrogate); + explicit PluginDestructionGuard(NPP npp); + + ~PluginDestructionGuard(); + + static bool DelayDestroy(nsNPAPIPluginInstance *aInstance); + +protected: + void Init() + { + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); + + mDelayedDestroy = false; + + PR_INIT_CLIST(this); + PR_INSERT_BEFORE(this, &sListHead); + } + + void InitAsync() + { + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); + + mDelayedDestroy = false; + + PR_INIT_CLIST(this); + // Instances with active surrogates must be inserted *after* sListHead so + // that they appear to be at the bottom of the stack + PR_INSERT_AFTER(this, &sListHead); + } + + RefPtr<nsNPAPIPluginInstance> mInstance; + bool mDelayedDestroy; + + static PRCList sListHead; +}; + +#endif // nsPluginHost_h_ diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp new file mode 100644 index 000000000..b7651be1a --- /dev/null +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -0,0 +1,3894 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* 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/. */ + +#ifdef MOZ_X11 +#include <cairo-xlib.h> +#include "gfxXlibSurface.h" +/* X headers suck */ +enum { XKeyPress = KeyPress }; +#include "mozilla/X11Util.h" +using mozilla::DefaultXDisplay; +#endif + +#include "nsPluginInstanceOwner.h" + +#include "gfxUtils.h" +#include "nsIRunnable.h" +#include "nsContentUtils.h" +#include "nsRect.h" +#include "nsSize.h" +#include "nsDisplayList.h" +#include "ImageLayers.h" +#include "GLImages.h" +#include "nsPluginFrame.h" +#include "nsIPluginDocument.h" +#include "nsIStringStream.h" +#include "nsNetUtil.h" +#include "mozilla/Preferences.h" +#include "nsILinkHandler.h" +#include "nsIDocShellTreeItem.h" +#include "nsIWebBrowserChrome.h" +#include "nsLayoutUtils.h" +#include "nsIPluginWidget.h" +#include "nsViewManager.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDOMHTMLObjectElement.h" +#include "nsIAppShell.h" +#include "nsIDOMHTMLAppletElement.h" +#include "nsIObjectLoadingContent.h" +#include "nsObjectLoadingContent.h" +#include "nsAttrName.h" +#include "nsIFocusManager.h" +#include "nsFocusManager.h" +#include "nsIDOMDragEvent.h" +#include "nsIScriptSecurityManager.h" +#include "nsIScrollableFrame.h" +#include "nsIDocShell.h" +#include "ImageContainer.h" +#include "nsIDOMHTMLCollection.h" +#include "GLContext.h" +#include "EGLUtils.h" +#include "nsIContentInlines.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/HTMLObjectElementBinding.h" +#include "mozilla/dom/TabChild.h" +#include "nsFrameSelection.h" +#include "PuppetWidget.h" +#include "nsPIWindowRoot.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/TextComposition.h" +#include "mozilla/AutoRestore.h" + +#include "nsContentCID.h" +#include "nsWidgetsCID.h" +static NS_DEFINE_CID(kWidgetCID, NS_CHILD_CID); +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + +#ifdef XP_WIN +#include <wtypes.h> +#include <winuser.h> +#include "mozilla/widget/WinMessages.h" +#endif // #ifdef XP_WIN + +#ifdef XP_MACOSX +#include "ComplexTextInputPanel.h" +#include "nsIDOMXULDocument.h" +#include "nsIDOMXULCommandDispatcher.h" +#endif + +#ifdef MOZ_WIDGET_GTK +#include <gdk/gdk.h> +#include <gtk/gtk.h> +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include "ANPBase.h" +#include "AndroidBridge.h" +#include "ClientLayerManager.h" +#include "nsWindow.h" + +static nsPluginInstanceOwner* sFullScreenInstance = nullptr; + +using namespace mozilla::dom; + +#include <android/log.h> +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args) +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::layers; + +static inline nsPoint AsNsPoint(const nsIntPoint &p) { + return nsPoint(p.x, p.y); +} + +// special class for handeling DOM context menu events because for +// some reason it starves other mouse events if implemented on the +// same class +class nsPluginDOMContextMenuListener : public nsIDOMEventListener +{ + virtual ~nsPluginDOMContextMenuListener(); + +public: + explicit nsPluginDOMContextMenuListener(nsIContent* aContent); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + void Destroy(nsIContent* aContent); + + nsEventStatus ProcessEvent(const WidgetGUIEvent& anEvent) + { + return nsEventStatus_eConsumeNoDefault; + } +}; + +class AsyncPaintWaitEvent : public Runnable +{ +public: + AsyncPaintWaitEvent(nsIContent* aContent, bool aFinished) : + mContent(aContent), mFinished(aFinished) + { + } + + NS_IMETHOD Run() override + { + nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, + mFinished ? NS_LITERAL_STRING("MozPaintWaitFinished") : NS_LITERAL_STRING("MozPaintWait"), + true, true); + return NS_OK; + } + +private: + nsCOMPtr<nsIContent> mContent; + bool mFinished; +}; + +void +nsPluginInstanceOwner::NotifyPaintWaiter(nsDisplayListBuilder* aBuilder) +{ + // This is notification for reftests about async plugin paint start + if (!mWaitingForPaint && !IsUpToDate() && aBuilder->ShouldSyncDecodeImages()) { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + nsCOMPtr<nsIRunnable> event = new AsyncPaintWaitEvent(content, false); + // Run this event as soon as it's safe to do so, since listeners need to + // receive it immediately + nsContentUtils::AddScriptRunner(event); + mWaitingForPaint = true; + } +} + +#if MOZ_WIDGET_ANDROID +static void +AttachToContainerAsSurfaceTexture(ImageContainer* container, + nsNPAPIPluginInstance* instance, + const LayoutDeviceRect& rect, + RefPtr<Image>* out_image) +{ + MOZ_ASSERT(out_image); + MOZ_ASSERT(!*out_image); + + mozilla::gl::AndroidSurfaceTexture* surfTex = instance->AsSurfaceTexture(); + if (!surfTex) { + return; + } + + RefPtr<Image> img = new SurfaceTextureImage( + surfTex, + gfx::IntSize::Truncate(rect.width, rect.height), + instance->OriginPos()); + *out_image = img; +} +#endif + +bool +nsPluginInstanceOwner::NeedsScrollImageLayer() +{ +#if defined(XP_WIN) + // If this is a windowed plugin and we're doing layout in the content + // process, force the creation of an image layer for the plugin. We'll + // paint to this when scrolling. + return XRE_IsContentProcess() && + mPluginWindow && + mPluginWindow->type == NPWindowTypeWindow; +#else + return false; +#endif +} + +already_AddRefed<ImageContainer> +nsPluginInstanceOwner::GetImageContainer() +{ + if (!mInstance) + return nullptr; + + RefPtr<ImageContainer> container; + +#if MOZ_WIDGET_ANDROID + LayoutDeviceRect r = GetPluginRect(); + + // NotifySize() causes Flash to do a bunch of stuff like ask for surfaces to render + // into, set y-flip flags, etc, so we do this at the beginning. + float resolution = mPluginFrame->PresContext()->PresShell()->GetCumulativeResolution(); + ScreenSize screenSize = (r * LayoutDeviceToScreenScale(resolution)).Size(); + mInstance->NotifySize(nsIntSize::Truncate(screenSize.width, screenSize.height)); + + container = LayerManager::CreateImageContainer(); + + if (r.width && r.height) { + // Try to get it as an EGLImage first. + RefPtr<Image> img; + AttachToContainerAsSurfaceTexture(container, mInstance, r, &img); + + if (img) { + container->SetCurrentImageInTransaction(img); + } + } +#else + if (NeedsScrollImageLayer()) { + // windowed plugin under e10s +#if defined(XP_WIN) + mInstance->GetScrollCaptureContainer(getter_AddRefs(container)); +#endif + } else { + // async windowless rendering + mInstance->GetImageContainer(getter_AddRefs(container)); + } +#endif + + return container.forget(); +} + +void +nsPluginInstanceOwner::DidComposite() +{ + if (mInstance) { + mInstance->DidComposite(); + } +} + +void +nsPluginInstanceOwner::SetBackgroundUnknown() +{ + if (mInstance) { + mInstance->SetBackgroundUnknown(); + } +} + +already_AddRefed<mozilla::gfx::DrawTarget> +nsPluginInstanceOwner::BeginUpdateBackground(const nsIntRect& aRect) +{ + nsIntRect rect = aRect; + RefPtr<DrawTarget> dt; + if (mInstance && + NS_SUCCEEDED(mInstance->BeginUpdateBackground(&rect, getter_AddRefs(dt)))) { + return dt.forget(); + } + return nullptr; +} + +void +nsPluginInstanceOwner::EndUpdateBackground(const nsIntRect& aRect) +{ + nsIntRect rect = aRect; + if (mInstance) { + mInstance->EndUpdateBackground(&rect); + } +} + +bool +nsPluginInstanceOwner::UseAsyncRendering() +{ +#ifdef XP_MACOSX + if (mUseAsyncRendering) { + return true; + } +#endif + + bool isOOP; + bool result = (mInstance && + NS_SUCCEEDED(mInstance->GetIsOOP(&isOOP)) && isOOP +#ifndef XP_MACOSX + && (!mPluginWindow || + mPluginWindow->type == NPWindowTypeDrawable) +#endif + ); + +#ifdef XP_MACOSX + if (result) { + mUseAsyncRendering = true; + } +#endif + + return result; +} + +nsIntSize +nsPluginInstanceOwner::GetCurrentImageSize() +{ + nsIntSize size(0,0); + if (mInstance) { + mInstance->GetImageSize(&size); + } + return size; +} + +nsPluginInstanceOwner::nsPluginInstanceOwner() + : mPluginWindow(nullptr) +{ + // create nsPluginNativeWindow object, it is derived from NPWindow + // struct and allows to manipulate native window procedure + nsCOMPtr<nsIPluginHost> pluginHostCOM = do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); + mPluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get()); + if (mPluginHost) + mPluginHost->NewPluginNativeWindow(&mPluginWindow); + + mPluginFrame = nullptr; + mWidgetCreationComplete = false; +#ifdef XP_MACOSX + mSentInitialTopLevelWindowEvent = false; + mLastWindowIsActive = false; + mLastContentFocused = false; + mLastScaleFactor = 1.0; + mShouldBlurOnActivate = false; +#endif + mLastCSSZoomFactor = 1.0; + mContentFocused = false; + mWidgetVisible = true; + mPluginWindowVisible = false; + mPluginDocumentActiveState = true; + mLastMouseDownButtonType = -1; + +#ifdef XP_MACOSX +#ifndef NP_NO_CARBON + // We don't support Carbon, but it is still the default model for i386 NPAPI. + mEventModel = NPEventModelCarbon; +#else + mEventModel = NPEventModelCocoa; +#endif + mUseAsyncRendering = false; +#endif + + mWaitingForPaint = false; + +#ifdef MOZ_WIDGET_ANDROID + mFullScreen = false; + mJavaView = nullptr; +#endif + +#ifdef XP_WIN + mGotCompositionData = false; + mSentStartComposition = false; + mPluginDidNotHandleIMEComposition = false; +#endif +} + +nsPluginInstanceOwner::~nsPluginInstanceOwner() +{ + if (mWaitingForPaint) { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (content) { + // We don't care when the event is dispatched as long as it's "soon", + // since whoever needs it will be waiting for it. + nsCOMPtr<nsIRunnable> event = new AsyncPaintWaitEvent(content, true); + NS_DispatchToMainThread(event); + } + } + + mPluginFrame = nullptr; + + PLUG_DeletePluginNativeWindow(mPluginWindow); + mPluginWindow = nullptr; + +#ifdef MOZ_WIDGET_ANDROID + RemovePluginView(); +#endif + + if (mInstance) { + mInstance->SetOwner(nullptr); + } +} + +NS_IMPL_ISUPPORTS(nsPluginInstanceOwner, + nsIPluginInstanceOwner, + nsIDOMEventListener, + nsIPrivacyTransitionObserver, + nsIKeyEventInPluginCallback, + nsISupportsWeakReference) + +nsresult +nsPluginInstanceOwner::SetInstance(nsNPAPIPluginInstance *aInstance) +{ + NS_ASSERTION(!mInstance || !aInstance, "mInstance should only be set or unset!"); + + // If we're going to null out mInstance after use, be sure to call + // mInstance->SetOwner(nullptr) here, since it now won't be called + // from our destructor. This fixes bug 613376. + if (mInstance && !aInstance) { + mInstance->SetOwner(nullptr); + +#ifdef MOZ_WIDGET_ANDROID + RemovePluginView(); +#endif + } + + mInstance = aInstance; + + nsCOMPtr<nsIDocument> doc; + GetDocument(getter_AddRefs(doc)); + if (doc) { + if (nsCOMPtr<nsPIDOMWindowOuter> domWindow = doc->GetWindow()) { + nsCOMPtr<nsIDocShell> docShell = domWindow->GetDocShell(); + if (docShell) + docShell->AddWeakPrivacyTransitionObserver(this); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetWindow(NPWindow *&aWindow) +{ + NS_ASSERTION(mPluginWindow, "the plugin window object being returned is null"); + aWindow = mPluginWindow; + return NS_OK; +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetMode(int32_t *aMode) +{ + nsCOMPtr<nsIDocument> doc; + nsresult rv = GetDocument(getter_AddRefs(doc)); + nsCOMPtr<nsIPluginDocument> pDoc (do_QueryInterface(doc)); + + if (pDoc) { + *aMode = NP_FULL; + } else { + *aMode = NP_EMBED; + } + + return rv; +} + +void nsPluginInstanceOwner::GetAttributes(nsTArray<MozPluginParameter>& attributes) +{ + nsCOMPtr<nsIObjectLoadingContent> content = do_QueryReferent(mContent); + nsObjectLoadingContent *loadingContent = + static_cast<nsObjectLoadingContent*>(content.get()); + + loadingContent->GetPluginAttributes(attributes); +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetDOMElement(nsIDOMElement* *result) +{ + return CallQueryReferent(mContent.get(), result); +} + +nsresult nsPluginInstanceOwner::GetInstance(nsNPAPIPluginInstance **aInstance) +{ + NS_ENSURE_ARG_POINTER(aInstance); + + *aInstance = mInstance; + NS_IF_ADDREF(*aInstance); + return NS_OK; +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetURL(const char *aURL, + const char *aTarget, + nsIInputStream *aPostStream, + void *aHeadersData, + uint32_t aHeadersDataLen, + bool aDoCheckLoadURIChecks) +{ + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (!content) { + return NS_ERROR_NULL_POINTER; + } + + if (content->IsEditable()) { + return NS_OK; + } + + nsIDocument *doc = content->GetUncomposedDoc(); + if (!doc) { + return NS_ERROR_FAILURE; + } + + nsIPresShell *presShell = doc->GetShell(); + if (!presShell) { + return NS_ERROR_FAILURE; + } + + nsPresContext *presContext = presShell->GetPresContext(); + if (!presContext) { + return NS_ERROR_FAILURE; + } + + // the container of the pres context will give us the link handler + nsCOMPtr<nsISupports> container = presContext->GetContainerWeak(); + NS_ENSURE_TRUE(container,NS_ERROR_FAILURE); + nsCOMPtr<nsILinkHandler> lh = do_QueryInterface(container); + NS_ENSURE_TRUE(lh, NS_ERROR_FAILURE); + + nsAutoString unitarget; + if ((0 == PL_strcmp(aTarget, "newwindow")) || + (0 == PL_strcmp(aTarget, "_new"))) { + unitarget.AssignASCII("_blank"); + } + else if (0 == PL_strcmp(aTarget, "_current")) { + unitarget.AssignASCII("_self"); + } + else { + unitarget.AssignASCII(aTarget); // XXX could this be nonascii? + } + + nsCOMPtr<nsIURI> baseURI = GetBaseURI(); + + // Create an absolute URL + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, baseURI); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + if (aDoCheckLoadURIChecks) { + nsCOMPtr<nsIScriptSecurityManager> secMan( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); + NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE); + + rv = secMan->CheckLoadURIWithPrincipal(content->NodePrincipal(), uri, + nsIScriptSecurityManager::STANDARD); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIInputStream> headersDataStream; + if (aPostStream && aHeadersData) { + if (!aHeadersDataLen) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIStringInputStream> sis = do_CreateInstance("@mozilla.org/io/string-input-stream;1"); + if (!sis) + return NS_ERROR_OUT_OF_MEMORY; + + rv = sis->SetData((char *)aHeadersData, aHeadersDataLen); + NS_ENSURE_SUCCESS(rv, rv); + headersDataStream = do_QueryInterface(sis); + } + + int32_t blockPopups = + Preferences::GetInt("privacy.popups.disable_from_plugins"); + nsAutoPopupStatePusher popupStatePusher((PopupControlState)blockPopups); + + rv = lh->OnLinkClick(content, uri, unitarget.get(), NullString(), + aPostStream, headersDataStream, true); + + return rv; +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetDocument(nsIDocument* *aDocument) +{ + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (!aDocument || !content) { + return NS_ERROR_NULL_POINTER; + } + + // XXX sXBL/XBL2 issue: current doc or owner doc? + // But keep in mind bug 322414 comment 33 + NS_IF_ADDREF(*aDocument = content->OwnerDoc()); + return NS_OK; +} + +NS_IMETHODIMP nsPluginInstanceOwner::InvalidateRect(NPRect *invalidRect) +{ + // If our object frame has gone away, we won't be able to determine + // up-to-date-ness, so just fire off the event. + if (mWaitingForPaint && (!mPluginFrame || IsUpToDate())) { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + // We don't care when the event is dispatched as long as it's "soon", + // since whoever needs it will be waiting for it. + nsCOMPtr<nsIRunnable> event = new AsyncPaintWaitEvent(content, true); + NS_DispatchToMainThread(event); + mWaitingForPaint = false; + } + + if (!mPluginFrame || !invalidRect || !mWidgetVisible) + return NS_ERROR_FAILURE; + +#if defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) + // Each time an asynchronously-drawing plugin sends a new surface to display, + // the image in the ImageContainer is updated and InvalidateRect is called. + // There are different side effects for (sync) Android plugins. + RefPtr<ImageContainer> container; + mInstance->GetImageContainer(getter_AddRefs(container)); +#endif + +#ifndef XP_MACOSX + // Silverlight calls invalidate for windowed plugins so this needs to work. + if (mWidget) { + mWidget->Invalidate( + LayoutDeviceIntRect(invalidRect->left, invalidRect->top, + invalidRect->right - invalidRect->left, + invalidRect->bottom - invalidRect->top)); + // Plugin instances also call invalidate when plugin windows are hidden + // during scrolling. In this case fall through so we invalidate the + // underlying layer. + if (!NeedsScrollImageLayer()) { + return NS_OK; + } + } +#endif + nsIntRect rect(invalidRect->left, + invalidRect->top, + invalidRect->right - invalidRect->left, + invalidRect->bottom - invalidRect->top); + // invalidRect is in "display pixels". In non-HiDPI modes "display pixels" + // are device pixels. But in HiDPI modes each display pixel corresponds + // to more than one device pixel. + double scaleFactor = 1.0; + GetContentsScaleFactor(&scaleFactor); + rect.ScaleRoundOut(scaleFactor); + mPluginFrame->InvalidateLayer(nsDisplayItem::TYPE_PLUGIN, &rect); + return NS_OK; +} + +NS_IMETHODIMP nsPluginInstanceOwner::InvalidateRegion(NPRegion invalidRegion) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPluginInstanceOwner::RedrawPlugin() +{ + if (mPluginFrame) { + mPluginFrame->InvalidateLayer(nsDisplayItem::TYPE_PLUGIN); + } + return NS_OK; +} + +#if defined(XP_WIN) +nsIWidget* +nsPluginInstanceOwner::GetContainingWidgetIfOffset() +{ + MOZ_ASSERT(mPluginFrame, "Caller should have checked for null mPluginFrame."); + + // This property is provided to allow a "windowless" plugin to determine the window it is drawing + // in, so it can translate mouse coordinates it receives directly from the operating system + // to coordinates relative to itself. + + // The original code returns the document's window, which is OK if the window the "windowless" plugin + // is drawing into has the same origin as the document's window, but this is not the case for "windowless" plugins inside of scrolling DIVs etc + + // To make sure "windowless" plugins always get the right origin for translating mouse coordinates, this code + // determines the window handle of the mozilla window containing the "windowless" plugin. + + // Given that this HWND may not be that of the document's window, there is a slight risk + // of confusing a plugin that is using this HWND for illicit purposes, but since the documentation + // does not suggest this HWND IS that of the document window, rather that of the window + // the plugin is drawn in, this seems like a safe fix. + + // we only attempt to get the nearest window if this really is a "windowless" plugin so as not + // to change any behaviour for the much more common windowed plugins, + // though why this method would even be being called for a windowed plugin escapes me. + if (!XRE_IsContentProcess() && + mPluginWindow && mPluginWindow->type == NPWindowTypeDrawable) { + // it turns out that flash also uses this window for determining focus, and is currently + // unable to show a caret correctly if we return the enclosing window. Therefore for + // now we only return the enclosing window when there is an actual offset which + // would otherwise cause coordinates to be offset incorrectly. (i.e. + // if the enclosing window if offset from the document window) + // + // fixing both the caret and ability to interact issues for a windowless control in a non document aligned windw + // does not seem to be possible without a change to the flash plugin + + nsIWidget* win = mPluginFrame->GetNearestWidget(); + if (win) { + nsView *view = nsView::GetViewFor(win); + NS_ASSERTION(view, "No view for widget"); + nsPoint offset = view->GetOffsetTo(nullptr); + + if (offset.x || offset.y) { + // in the case the two windows are offset from eachother, we do go ahead and return the correct enclosing window + // so that mouse co-ordinates are not messed up. + return win; + } + } + } + + return nullptr; +} + +static already_AddRefed<nsIWidget> +GetRootWidgetForPluginFrame(const nsPluginFrame* aPluginFrame) +{ + MOZ_ASSERT(aPluginFrame); + + nsViewManager* vm = + aPluginFrame->PresContext()->GetPresShell()->GetViewManager(); + if (!vm) { + NS_WARNING("Could not find view manager for plugin frame."); + return nullptr; + } + + nsCOMPtr<nsIWidget> rootWidget; + vm->GetRootWidget(getter_AddRefs(rootWidget)); + return rootWidget.forget(); +} +#endif + +NS_IMETHODIMP nsPluginInstanceOwner::GetNetscapeWindow(void *value) +{ + if (!mPluginFrame) { + NS_WARNING("plugin owner has no owner in getting doc's window handle"); + return NS_ERROR_FAILURE; + } + +#if defined(XP_WIN) + void** pvalue = (void**)value; + nsIWidget* offsetContainingWidget = GetContainingWidgetIfOffset(); + if (offsetContainingWidget) { + *pvalue = (void*)offsetContainingWidget->GetNativeData(NS_NATIVE_WINDOW); + if (*pvalue) { + return NS_OK; + } + } + + // simply return the topmost document window + nsCOMPtr<nsIWidget> widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (widget) { + *pvalue = widget->GetNativeData(NS_NATIVE_SHAREABLE_WINDOW); + } else { + NS_ASSERTION(widget, "couldn't get doc's widget in getting doc's window handle"); + } + + return NS_OK; +#elif defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) + // X11 window managers want the toplevel window for WM_TRANSIENT_FOR. + nsIWidget* win = mPluginFrame->GetNearestWidget(); + if (!win) + return NS_ERROR_FAILURE; + *static_cast<Window*>(value) = (long unsigned int)win->GetNativeData(NS_NATIVE_SHAREABLE_WINDOW); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#if defined(XP_WIN) +void +nsPluginInstanceOwner::SetWidgetWindowAsParent(HWND aWindowToAdopt) +{ + if (!mWidget) { + NS_ERROR("mWidget should exist before this gets called."); + return; + } + + mWidget->SetNativeData(NS_NATIVE_CHILD_WINDOW, + reinterpret_cast<uintptr_t>(aWindowToAdopt)); +} + +nsresult +nsPluginInstanceOwner::SetNetscapeWindowAsParent(HWND aWindowToAdopt) +{ + if (!mPluginFrame) { + NS_WARNING("Plugin owner has no plugin frame."); + return NS_ERROR_FAILURE; + } + + // If there is a containing window that is offset then ask that to adopt. + nsIWidget* offsetWidget = GetContainingWidgetIfOffset(); + if (offsetWidget) { + offsetWidget->SetNativeData(NS_NATIVE_CHILD_WINDOW, + reinterpret_cast<uintptr_t>(aWindowToAdopt)); + return NS_OK; + } + + // Otherwise ask the topmost document window to adopt. + nsCOMPtr<nsIWidget> rootWidget = GetRootWidgetForPluginFrame(mPluginFrame); + if (!rootWidget) { + NS_ASSERTION(rootWidget, "Couldn't get topmost document's widget."); + return NS_ERROR_FAILURE; + } + + rootWidget->SetNativeData(NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW, + reinterpret_cast<uintptr_t>(aWindowToAdopt)); + return NS_OK; +} + +bool +nsPluginInstanceOwner::GetCompositionString(uint32_t aType, + nsTArray<uint8_t>* aDist, + int32_t* aLength) +{ + // Mark pkugin calls ImmGetCompositionStringW correctly + mGotCompositionData = true; + + RefPtr<TextComposition> composition = GetTextComposition(); + if (NS_WARN_IF(!composition)) { + return false; + } + + switch(aType) { + case GCS_COMPSTR: { + if (!composition->IsComposing()) { + *aLength = 0; + return true; + } + + uint32_t len = composition->LastData().Length() * sizeof(char16_t); + if (len) { + aDist->SetLength(len); + memcpy(aDist->Elements(), composition->LastData().get(), len); + } + *aLength = len; + return true; + } + + case GCS_RESULTSTR: { + if (composition->IsComposing()) { + *aLength = 0; + return true; + } + + uint32_t len = composition->LastData().Length() * sizeof(char16_t); + if (len) { + aDist->SetLength(len); + memcpy(aDist->Elements(), composition->LastData().get(), len); + } + *aLength = len; + return true; + } + + case GCS_CURSORPOS: { + *aLength = 0; + TextRangeArray* ranges = composition->GetLastRanges(); + if (!ranges) { + return true; + } + *aLength = ranges->GetCaretPosition(); + if (*aLength < 0) { + return false; + } + return true; + } + + case GCS_COMPATTR: { + TextRangeArray* ranges = composition->GetLastRanges(); + if (!ranges || ranges->IsEmpty()) { + *aLength = 0; + return true; + } + + aDist->SetLength(composition->LastData().Length()); + memset(aDist->Elements(), ATTR_INPUT, aDist->Length()); + + for (TextRange& range : *ranges) { + uint8_t type = ATTR_INPUT; + switch(range.mRangeType) { + case TextRangeType::eRawClause: + type = ATTR_INPUT; + break; + case TextRangeType::eSelectedRawClause: + type = ATTR_TARGET_NOTCONVERTED; + break; + case TextRangeType::eConvertedClause: + type = ATTR_CONVERTED; + break; + case TextRangeType::eSelectedClause: + type = ATTR_TARGET_CONVERTED; + break; + default: + continue; + } + + size_t minLen = std::min<size_t>(range.mEndOffset, aDist->Length()); + for (size_t i = range.mStartOffset; i < minLen; i++) { + (*aDist)[i] = type; + } + } + *aLength = aDist->Length(); + return true; + } + + case GCS_COMPCLAUSE: { + RefPtr<TextRangeArray> ranges = composition->GetLastRanges(); + if (!ranges || ranges->IsEmpty()) { + aDist->SetLength(sizeof(uint32_t)); + memset(aDist->Elements(), 0, sizeof(uint32_t)); + *aLength = aDist->Length(); + return true; + } + AutoTArray<uint32_t, 16> clauses; + clauses.AppendElement(0); + for (TextRange& range : *ranges) { + if (!range.IsClause()) { + continue; + } + clauses.AppendElement(range.mEndOffset); + } + + aDist->SetLength(clauses.Length() * sizeof(uint32_t)); + memcpy(aDist->Elements(), clauses.Elements(), aDist->Length()); + *aLength = aDist->Length(); + return true; + } + + case GCS_RESULTREADSTR: { + // When returning error causes unexpected error, so we return 0 instead. + *aLength = 0; + return true; + } + + case GCS_RESULTCLAUSE: { + // When returning error causes unexpected error, so we return 0 instead. + *aLength = 0; + return true; + } + + default: + NS_WARNING( + nsPrintfCString("Unsupported type %x of ImmGetCompositionStringW hook", + aType).get()); + break; + } + + return false; +} + +bool +nsPluginInstanceOwner::SetCandidateWindow( + const widget::CandidateWindowPosition& aPosition) +{ + if (NS_WARN_IF(!mPluginFrame)) { + return false; + } + + nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset(); + if (!widget) { + widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (NS_WARN_IF(!widget)) { + return false; + } + } + + widget->SetCandidateWindowForPlugin(aPosition); + return true; +} + +bool +nsPluginInstanceOwner::RequestCommitOrCancel(bool aCommitted) +{ + nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset(); + if (!widget) { + widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (NS_WARN_IF(!widget)) { + return false; + } + } + + if (aCommitted) { + widget->NotifyIME(widget::REQUEST_TO_COMMIT_COMPOSITION); + } else { + widget->NotifyIME(widget::REQUEST_TO_CANCEL_COMPOSITION); + } + return true; +} + +#endif // #ifdef XP_WIN + +void +nsPluginInstanceOwner::HandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, + bool aIsConsumed) +{ + if (NS_WARN_IF(!mInstance)) { + return; + } + DebugOnly<nsresult> rv = + mInstance->HandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HandledWindowedPluginKeyEvent fail"); +} + +void +nsPluginInstanceOwner::OnWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData) +{ + if (NS_WARN_IF(!mPluginFrame)) { + // Notifies the plugin process of the key event being not consumed by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return; + } + + nsCOMPtr<nsIWidget> widget = mPluginFrame->PresContext()->GetRootWidget(); + if (NS_WARN_IF(!widget)) { + // Notifies the plugin process of the key event being not consumed by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return; + } + + nsresult rv = widget->OnWindowedPluginKeyEvent(aKeyEventData, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Notifies the plugin process of the key event being not consumed by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return; + } + + // If the key event is posted to another process, we need to wait a call + // of HandledWindowedPluginKeyEvent(). So, nothing to do here in this case. + if (rv == NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY) { + return; + } + + // Otherwise, the key event is handled synchronously. Let's notify the + // plugin process of the key event's result. + bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED); + HandledWindowedPluginKeyEvent(aKeyEventData, consumed); +} + +NS_IMETHODIMP nsPluginInstanceOwner::SetEventModel(int32_t eventModel) +{ +#ifdef XP_MACOSX + mEventModel = static_cast<NPEventModel>(eventModel); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#ifdef XP_MACOSX +NPBool nsPluginInstanceOwner::ConvertPointPuppet(PuppetWidget *widget, + nsPluginFrame* pluginFrame, + double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, + double *destX, double *destY, + NPCoordinateSpace destSpace) +{ + NS_ENSURE_TRUE(widget && widget->GetOwningTabChild() && pluginFrame, false); + // Caller has to want a result. + NS_ENSURE_TRUE(destX || destY, false); + + if (sourceSpace == destSpace) { + if (destX) { + *destX = sourceX; + } + if (destY) { + *destY = sourceY; + } + return true; + } + + nsPresContext* presContext = pluginFrame->PresContext(); + double scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/ + presContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom(); + + PuppetWidget *puppetWidget = static_cast<PuppetWidget*>(widget); + PuppetWidget *rootWidget = static_cast<PuppetWidget*>(widget->GetTopLevelWidget()); + if (!rootWidget) { + return false; + } + nsPoint chromeSize = AsNsPoint(rootWidget->GetChromeDimensions()) / scaleFactor; + nsIntSize intScreenDims = rootWidget->GetScreenDimensions(); + nsSize screenDims = nsSize(intScreenDims.width / scaleFactor, + intScreenDims.height / scaleFactor); + int32_t screenH = screenDims.height; + nsPoint windowPosition = AsNsPoint(rootWidget->GetWindowPosition()) / scaleFactor; + + // Window size is tab size + chrome size. + LayoutDeviceIntRect tabContentBounds = puppetWidget->GetBounds(); + tabContentBounds.ScaleInverseRoundOut(scaleFactor); + int32_t windowH = tabContentBounds.height + int(chromeSize.y); + + nsPoint pluginPosition = AsNsPoint(pluginFrame->GetScreenRect().TopLeft()); + + // Convert (sourceX, sourceY) to 'real' (not PuppetWidget) screen space. + // In OSX, the Y-axis increases upward, which is the reverse of ours. + // We want OSX coordinates for window and screen so those equations are swapped. + nsPoint sourcePoint(sourceX, sourceY); + nsPoint screenPoint; + switch (sourceSpace) { + case NPCoordinateSpacePlugin: + screenPoint = sourcePoint + pluginPosition + + pluginFrame->GetContentRectRelativeToSelf().TopLeft() / nsPresContext::AppUnitsPerCSSPixel(); + break; + case NPCoordinateSpaceWindow: + screenPoint = nsPoint(sourcePoint.x, windowH-sourcePoint.y) + + windowPosition; + break; + case NPCoordinateSpaceFlippedWindow: + screenPoint = sourcePoint + windowPosition; + break; + case NPCoordinateSpaceScreen: + screenPoint = nsPoint(sourcePoint.x, screenH-sourcePoint.y); + break; + case NPCoordinateSpaceFlippedScreen: + screenPoint = sourcePoint; + break; + default: + return false; + } + + // Convert from screen to dest space. + nsPoint destPoint; + switch (destSpace) { + case NPCoordinateSpacePlugin: + destPoint = screenPoint - pluginPosition - + pluginFrame->GetContentRectRelativeToSelf().TopLeft() / nsPresContext::AppUnitsPerCSSPixel(); + break; + case NPCoordinateSpaceWindow: + destPoint = screenPoint - windowPosition; + destPoint.y = windowH - destPoint.y; + break; + case NPCoordinateSpaceFlippedWindow: + destPoint = screenPoint - windowPosition; + break; + case NPCoordinateSpaceScreen: + destPoint = nsPoint(screenPoint.x, screenH-screenPoint.y); + break; + case NPCoordinateSpaceFlippedScreen: + destPoint = screenPoint; + break; + default: + return false; + } + + if (destX) { + *destX = destPoint.x; + } + if (destY) { + *destY = destPoint.y; + } + + return true; +} + +NPBool nsPluginInstanceOwner::ConvertPointNoPuppet(nsIWidget *widget, + nsPluginFrame* pluginFrame, + double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, + double *destX, double *destY, + NPCoordinateSpace destSpace) +{ + NS_ENSURE_TRUE(widget && pluginFrame, false); + // Caller has to want a result. + NS_ENSURE_TRUE(destX || destY, false); + + if (sourceSpace == destSpace) { + if (destX) { + *destX = sourceX; + } + if (destY) { + *destY = sourceY; + } + return true; + } + + nsPresContext* presContext = pluginFrame->PresContext(); + double scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/ + presContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom(); + + nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1"); + if (!screenMgr) { + return false; + } + nsCOMPtr<nsIScreen> screen; + screenMgr->ScreenForNativeWidget(widget->GetNativeData(NS_NATIVE_WINDOW), getter_AddRefs(screen)); + if (!screen) { + return false; + } + + int32_t screenX, screenY, screenWidth, screenHeight; + screen->GetRect(&screenX, &screenY, &screenWidth, &screenHeight); + screenHeight /= scaleFactor; + + LayoutDeviceIntRect windowScreenBounds = widget->GetScreenBounds(); + windowScreenBounds.ScaleInverseRoundOut(scaleFactor); + int32_t windowX = windowScreenBounds.x; + int32_t windowY = windowScreenBounds.y; + int32_t windowHeight = windowScreenBounds.height; + + nsIntRect pluginScreenRect = pluginFrame->GetScreenRect(); + + double screenXGecko, screenYGecko; + switch (sourceSpace) { + case NPCoordinateSpacePlugin: + screenXGecko = pluginScreenRect.x + sourceX; + screenYGecko = pluginScreenRect.y + sourceY; + break; + case NPCoordinateSpaceWindow: + screenXGecko = windowX + sourceX; + screenYGecko = windowY + (windowHeight - sourceY); + break; + case NPCoordinateSpaceFlippedWindow: + screenXGecko = windowX + sourceX; + screenYGecko = windowY + sourceY; + break; + case NPCoordinateSpaceScreen: + screenXGecko = sourceX; + screenYGecko = screenHeight - sourceY; + break; + case NPCoordinateSpaceFlippedScreen: + screenXGecko = sourceX; + screenYGecko = sourceY; + break; + default: + return false; + } + + double destXCocoa, destYCocoa; + switch (destSpace) { + case NPCoordinateSpacePlugin: + destXCocoa = screenXGecko - pluginScreenRect.x; + destYCocoa = screenYGecko - pluginScreenRect.y; + break; + case NPCoordinateSpaceWindow: + destXCocoa = screenXGecko - windowX; + destYCocoa = windowHeight - (screenYGecko - windowY); + break; + case NPCoordinateSpaceFlippedWindow: + destXCocoa = screenXGecko - windowX; + destYCocoa = screenYGecko - windowY; + break; + case NPCoordinateSpaceScreen: + destXCocoa = screenXGecko; + destYCocoa = screenHeight - screenYGecko; + break; + case NPCoordinateSpaceFlippedScreen: + destXCocoa = screenXGecko; + destYCocoa = screenYGecko; + break; + default: + return false; + } + + if (destX) { + *destX = destXCocoa; + } + if (destY) { + *destY = destYCocoa; + } + + return true; +} +#endif // XP_MACOSX + +NPBool nsPluginInstanceOwner::ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace, + double *destX, double *destY, NPCoordinateSpace destSpace) +{ +#ifdef XP_MACOSX + if (!mPluginFrame) { + return false; + } + + MOZ_ASSERT(mPluginFrame->GetNearestWidget()); + + if (nsIWidget::UsePuppetWidgets()) { + return ConvertPointPuppet(static_cast<PuppetWidget*>(mPluginFrame->GetNearestWidget()), + mPluginFrame, sourceX, sourceY, sourceSpace, + destX, destY, destSpace); + } + + return ConvertPointNoPuppet(mPluginFrame->GetNearestWidget(), + mPluginFrame, sourceX, sourceY, sourceSpace, + destX, destY, destSpace); +#else + return false; +#endif +} + +NPError nsPluginInstanceOwner::InitAsyncSurface(NPSize *size, NPImageFormat format, + void *initData, NPAsyncSurface *surface) +{ + return NPERR_INCOMPATIBLE_VERSION_ERROR; +} + +NPError nsPluginInstanceOwner::FinalizeAsyncSurface(NPAsyncSurface *) +{ + return NPERR_INCOMPATIBLE_VERSION_ERROR; +} + +void nsPluginInstanceOwner::SetCurrentAsyncSurface(NPAsyncSurface *, NPRect*) +{ +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetTagType(nsPluginTagType *result) +{ + NS_ENSURE_ARG_POINTER(result); + + *result = nsPluginTagType_Unknown; + + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (content->IsHTMLElement(nsGkAtoms::applet)) + *result = nsPluginTagType_Applet; + else if (content->IsHTMLElement(nsGkAtoms::embed)) + *result = nsPluginTagType_Embed; + else if (content->IsHTMLElement(nsGkAtoms::object)) + *result = nsPluginTagType_Object; + + return NS_OK; +} + +void nsPluginInstanceOwner::GetParameters(nsTArray<MozPluginParameter>& parameters) +{ + nsCOMPtr<nsIObjectLoadingContent> content = do_QueryReferent(mContent); + nsObjectLoadingContent *loadingContent = + static_cast<nsObjectLoadingContent*>(content.get()); + + loadingContent->GetPluginParameters(parameters); +} + +#ifdef XP_MACOSX + +static void InitializeNPCocoaEvent(NPCocoaEvent* event) +{ + memset(event, 0, sizeof(NPCocoaEvent)); +} + +NPDrawingModel nsPluginInstanceOwner::GetDrawingModel() +{ +#ifndef NP_NO_QUICKDRAW + // We don't support the Quickdraw drawing model any more but it's still + // the default model for i386 per NPAPI. + NPDrawingModel drawingModel = NPDrawingModelQuickDraw; +#else + NPDrawingModel drawingModel = NPDrawingModelCoreGraphics; +#endif + + if (!mInstance) + return drawingModel; + + mInstance->GetDrawingModel((int32_t*)&drawingModel); + return drawingModel; +} + +bool nsPluginInstanceOwner::IsRemoteDrawingCoreAnimation() +{ + if (!mInstance) + return false; + + bool coreAnimation; + if (!NS_SUCCEEDED(mInstance->IsRemoteDrawingCoreAnimation(&coreAnimation))) + return false; + + return coreAnimation; +} + +NPEventModel nsPluginInstanceOwner::GetEventModel() +{ + return mEventModel; +} + +#define DEFAULT_REFRESH_RATE 20 // 50 FPS + +nsCOMPtr<nsITimer> *nsPluginInstanceOwner::sCATimer = nullptr; +nsTArray<nsPluginInstanceOwner*> *nsPluginInstanceOwner::sCARefreshListeners = nullptr; + +void nsPluginInstanceOwner::CARefresh(nsITimer *aTimer, void *aClosure) { + if (!sCARefreshListeners) { + return; + } + for (size_t i = 0; i < sCARefreshListeners->Length(); i++) { + nsPluginInstanceOwner* instanceOwner = (*sCARefreshListeners)[i]; + NPWindow *window; + instanceOwner->GetWindow(window); + if (!window) { + continue; + } + NPRect r; + r.left = 0; + r.top = 0; + r.right = window->width; + r.bottom = window->height; + instanceOwner->InvalidateRect(&r); + } +} + +void nsPluginInstanceOwner::AddToCARefreshTimer() { + if (!mInstance) { + return; + } + + // Flash invokes InvalidateRect for us. + const char* mime = nullptr; + if (NS_SUCCEEDED(mInstance->GetMIMEType(&mime)) && mime && + nsPluginHost::GetSpecialType(nsDependentCString(mime)) == + nsPluginHost::eSpecialType_Flash) { + return; + } + + if (!sCARefreshListeners) { + sCARefreshListeners = new nsTArray<nsPluginInstanceOwner*>(); + } + + if (sCARefreshListeners->Contains(this)) { + return; + } + + sCARefreshListeners->AppendElement(this); + + if (!sCATimer) { + sCATimer = new nsCOMPtr<nsITimer>(); + } + + if (sCARefreshListeners->Length() == 1) { + *sCATimer = do_CreateInstance("@mozilla.org/timer;1"); + (*sCATimer)->InitWithFuncCallback(CARefresh, nullptr, + DEFAULT_REFRESH_RATE, nsITimer::TYPE_REPEATING_SLACK); + } +} + +void nsPluginInstanceOwner::RemoveFromCARefreshTimer() { + if (!sCARefreshListeners || sCARefreshListeners->Contains(this) == false) { + return; + } + + sCARefreshListeners->RemoveElement(this); + + if (sCARefreshListeners->Length() == 0) { + if (sCATimer) { + (*sCATimer)->Cancel(); + delete sCATimer; + sCATimer = nullptr; + } + delete sCARefreshListeners; + sCARefreshListeners = nullptr; + } +} + +void nsPluginInstanceOwner::SetPluginPort() +{ + void* pluginPort = GetPluginPort(); + if (!pluginPort || !mPluginWindow) + return; + mPluginWindow->window = pluginPort; +} +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +nsresult nsPluginInstanceOwner::ContentsScaleFactorChanged(double aContentsScaleFactor) +{ + if (!mInstance) { + return NS_ERROR_NULL_POINTER; + } + return mInstance->ContentsScaleFactorChanged(aContentsScaleFactor); +} +#endif + + +// static +uint32_t +nsPluginInstanceOwner::GetEventloopNestingLevel() +{ + nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID); + uint32_t currentLevel = 0; + if (appShell) { + appShell->GetEventloopNestingLevel(¤tLevel); +#ifdef XP_MACOSX + // Cocoa widget code doesn't process UI events through the normal + // appshell event loop, so it needs an additional count here. + currentLevel++; +#endif + } + + // No idea how this happens... but Linux doesn't consistently + // process UI events through the appshell event loop. If we get a 0 + // here on any platform we increment the level just in case so that + // we make sure we always tear the plugin down eventually. + if (!currentLevel) { + currentLevel++; + } + + return currentLevel; +} + +#ifdef MOZ_WIDGET_ANDROID + +// Modified version of nsFrame::GetOffsetToCrossDoc that stops when it +// hits an element with a displayport (or runs out of frames). This is +// not really the right thing to do, but it's better than what was here before. +static nsPoint +GetOffsetRootContent(nsIFrame* aFrame) +{ + // offset will hold the final offset + // docOffset holds the currently accumulated offset at the current APD, it + // will be converted and added to offset when the current APD changes. + nsPoint offset(0, 0), docOffset(0, 0); + const nsIFrame* f = aFrame; + int32_t currAPD = aFrame->PresContext()->AppUnitsPerDevPixel(); + int32_t apd = currAPD; + while (f) { + if (f->GetContent() && nsLayoutUtils::HasDisplayPort(f->GetContent())) + break; + + docOffset += f->GetPosition(); + nsIFrame* parent = f->GetParent(); + if (parent) { + f = parent; + } else { + nsPoint newOffset(0, 0); + f = nsLayoutUtils::GetCrossDocParentFrame(f, &newOffset); + int32_t newAPD = f ? f->PresContext()->AppUnitsPerDevPixel() : 0; + if (!f || newAPD != currAPD) { + // Convert docOffset to the right APD and add it to offset. + offset += docOffset.ScaleToOtherAppUnits(currAPD, apd); + docOffset.x = docOffset.y = 0; + } + currAPD = newAPD; + docOffset += newOffset; + } + } + + offset += docOffset.ScaleToOtherAppUnits(currAPD, apd); + + return offset; +} + +LayoutDeviceRect nsPluginInstanceOwner::GetPluginRect() +{ + // Get the offset of the content relative to the page + nsRect bounds = mPluginFrame->GetContentRectRelativeToSelf() + GetOffsetRootContent(mPluginFrame); + LayoutDeviceIntRect rect = LayoutDeviceIntRect::FromAppUnitsToNearest(bounds, mPluginFrame->PresContext()->AppUnitsPerDevPixel()); + return LayoutDeviceRect(rect); +} + +bool nsPluginInstanceOwner::AddPluginView(const LayoutDeviceRect& aRect /* = LayoutDeviceRect(0, 0, 0, 0) */) +{ + if (!mJavaView) { + mJavaView = mInstance->GetJavaSurface(); + + if (!mJavaView) + return false; + + mJavaView = (void*)jni::GetGeckoThreadEnv()->NewGlobalRef((jobject)mJavaView); + } + + if (mFullScreen) { + java::GeckoAppShell::AddFullScreenPluginView(jni::Object::Ref::From(jobject(mJavaView))); + sFullScreenInstance = this; + } + + return true; +} + +void nsPluginInstanceOwner::RemovePluginView() +{ + if (!mInstance || !mJavaView) + return; + + if (mFullScreen) { + java::GeckoAppShell::RemoveFullScreenPluginView(jni::Object::Ref::From(jobject(mJavaView))); + } + jni::GetGeckoThreadEnv()->DeleteGlobalRef((jobject)mJavaView); + mJavaView = nullptr; + + if (mFullScreen) + sFullScreenInstance = nullptr; +} + +void +nsPluginInstanceOwner::GetVideos(nsTArray<nsNPAPIPluginInstance::VideoInfo*>& aVideos) +{ + if (!mInstance) + return; + + mInstance->GetVideos(aVideos); +} + +already_AddRefed<ImageContainer> +nsPluginInstanceOwner::GetImageContainerForVideo(nsNPAPIPluginInstance::VideoInfo* aVideoInfo) +{ + RefPtr<ImageContainer> container = LayerManager::CreateImageContainer(); + + if (aVideoInfo->mDimensions.width && aVideoInfo->mDimensions.height) { + RefPtr<Image> img = new SurfaceTextureImage( + aVideoInfo->mSurfaceTexture, + gfx::IntSize::Truncate(aVideoInfo->mDimensions.width, aVideoInfo->mDimensions.height), + gl::OriginPos::BottomLeft); + container->SetCurrentImageInTransaction(img); + } + + return container.forget(); +} + +void nsPluginInstanceOwner::Invalidate() { + NPRect rect; + rect.left = rect.top = 0; + rect.right = mPluginWindow->width; + rect.bottom = mPluginWindow->height; + InvalidateRect(&rect); +} + +void nsPluginInstanceOwner::Recomposite() { + nsIWidget* const widget = mPluginFrame->GetNearestWidget(); + NS_ENSURE_TRUE_VOID(widget); + + LayerManager* const lm = widget->GetLayerManager(); + NS_ENSURE_TRUE_VOID(lm); + + ClientLayerManager* const clm = lm->AsClientLayerManager(); + NS_ENSURE_TRUE_VOID(clm && clm->GetRoot()); + + clm->SendInvalidRegion( + clm->GetRoot()->GetLocalVisibleRegion().ToUnknownRegion().GetBounds()); + clm->Composite(); +} + +void nsPluginInstanceOwner::RequestFullScreen() { + if (mFullScreen) + return; + + // Remove whatever view we currently have (if any, fullscreen or otherwise) + RemovePluginView(); + + mFullScreen = true; + AddPluginView(); + + mInstance->NotifyFullScreen(mFullScreen); +} + +void nsPluginInstanceOwner::ExitFullScreen() { + if (!mFullScreen) + return; + + RemovePluginView(); + + mFullScreen = false; + + int32_t model = mInstance->GetANPDrawingModel(); + + if (model == kSurface_ANPDrawingModel) { + // We need to do this immediately, otherwise Flash + // sometimes causes a deadlock (bug 762407) + AddPluginView(GetPluginRect()); + } + + mInstance->NotifyFullScreen(mFullScreen); + + // This will cause Paint() to be called, which is where + // we normally add/update views and layers + Invalidate(); +} + +void nsPluginInstanceOwner::ExitFullScreen(jobject view) { + JNIEnv* env = jni::GetGeckoThreadEnv(); + + if (sFullScreenInstance && sFullScreenInstance->mInstance && + env->IsSameObject(view, (jobject)sFullScreenInstance->mInstance->GetJavaSurface())) { + sFullScreenInstance->ExitFullScreen(); + } +} + +#endif + +void +nsPluginInstanceOwner::NotifyHostAsyncInitFailed() +{ + nsCOMPtr<nsIObjectLoadingContent> content = do_QueryReferent(mContent); + content->StopPluginInstance(); +} + +void +nsPluginInstanceOwner::NotifyHostCreateWidget() +{ + mPluginHost->CreateWidget(this); +#ifdef XP_MACOSX + FixUpPluginWindow(ePluginPaintEnable); +#else + if (mPluginFrame) { + mPluginFrame->InvalidateFrame(); + } else { + CallSetWindow(); + } +#endif +} + +void +nsPluginInstanceOwner::NotifyDestroyPending() +{ + if (!mInstance) { + return; + } + bool isOOP = false; + if (NS_FAILED(mInstance->GetIsOOP(&isOOP)) || !isOOP) { + return; + } + NPP npp = nullptr; + if (NS_FAILED(mInstance->GetNPP(&npp)) || !npp) { + return; + } + PluginAsyncSurrogate::NotifyDestroyPending(npp); +} + +nsresult nsPluginInstanceOwner::DispatchFocusToPlugin(nsIDOMEvent* aFocusEvent) +{ +#ifdef MOZ_WIDGET_ANDROID + if (mInstance) { + ANPEvent event; + event.inSize = sizeof(ANPEvent); + event.eventType = kLifecycle_ANPEventType; + + nsAutoString eventType; + aFocusEvent->GetType(eventType); + if (eventType.EqualsLiteral("focus")) { + event.data.lifecycle.action = kGainFocus_ANPLifecycleAction; + } + else if (eventType.EqualsLiteral("blur")) { + event.data.lifecycle.action = kLoseFocus_ANPLifecycleAction; + } + else { + NS_ASSERTION(false, "nsPluginInstanceOwner::DispatchFocusToPlugin, wierd eventType"); + } + mInstance->HandleEvent(&event, nullptr); + } +#endif + +#ifndef XP_MACOSX + if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) { + // continue only for cases without child window + return aFocusEvent->PreventDefault(); // consume event + } +#endif + + WidgetEvent* theEvent = aFocusEvent->WidgetEventPtr(); + if (theEvent) { + WidgetGUIEvent focusEvent(theEvent->IsTrusted(), theEvent->mMessage, + nullptr); + nsEventStatus rv = ProcessEvent(focusEvent); + if (nsEventStatus_eConsumeNoDefault == rv) { + aFocusEvent->PreventDefault(); + aFocusEvent->StopPropagation(); + } + } + + return NS_OK; +} + +nsresult nsPluginInstanceOwner::ProcessKeyPress(nsIDOMEvent* aKeyEvent) +{ +#ifdef XP_MACOSX + return DispatchKeyToPlugin(aKeyEvent); +#else + if (SendNativeEvents()) + DispatchKeyToPlugin(aKeyEvent); + + if (mInstance) { + // If this event is going to the plugin, we want to kill it. + // Not actually sending keypress to the plugin, since we didn't before. + aKeyEvent->PreventDefault(); + aKeyEvent->StopPropagation(); + } + return NS_OK; +#endif +} + +nsresult nsPluginInstanceOwner::DispatchKeyToPlugin(nsIDOMEvent* aKeyEvent) +{ +#if !defined(XP_MACOSX) + if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) + return aKeyEvent->PreventDefault(); // consume event + // continue only for cases without child window +#endif + + if (mInstance) { + WidgetKeyboardEvent* keyEvent = + aKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); + if (keyEvent && keyEvent->mClass == eKeyboardEventClass) { + nsEventStatus rv = ProcessEvent(*keyEvent); + if (nsEventStatus_eConsumeNoDefault == rv) { + aKeyEvent->PreventDefault(); + aKeyEvent->StopPropagation(); + } + } + } + + return NS_OK; +} + +nsresult +nsPluginInstanceOwner::ProcessMouseDown(nsIDOMEvent* aMouseEvent) +{ +#if !defined(XP_MACOSX) + if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) + return aMouseEvent->PreventDefault(); // consume event + // continue only for cases without child window +#endif + + // if the plugin is windowless, we need to set focus ourselves + // otherwise, we might not get key events + if (mPluginFrame && mPluginWindow && + mPluginWindow->type == NPWindowTypeDrawable) { + + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsCOMPtr<nsIDOMElement> elem = do_QueryReferent(mContent); + fm->SetFocus(elem, 0); + } + } + + WidgetMouseEvent* mouseEvent = + aMouseEvent->WidgetEventPtr()->AsMouseEvent(); + if (mouseEvent && mouseEvent->mClass == eMouseEventClass) { + mLastMouseDownButtonType = mouseEvent->button; + nsEventStatus rv = ProcessEvent(*mouseEvent); + if (nsEventStatus_eConsumeNoDefault == rv) { + return aMouseEvent->PreventDefault(); // consume event + } + } + + return NS_OK; +} + +nsresult nsPluginInstanceOwner::DispatchMouseToPlugin(nsIDOMEvent* aMouseEvent, + bool aAllowPropagate) +{ +#if !defined(XP_MACOSX) + if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) + return aMouseEvent->PreventDefault(); // consume event + // continue only for cases without child window +#endif + // don't send mouse events if we are hidden + if (!mWidgetVisible) + return NS_OK; + + WidgetMouseEvent* mouseEvent = + aMouseEvent->WidgetEventPtr()->AsMouseEvent(); + if (mouseEvent && mouseEvent->mClass == eMouseEventClass) { + nsEventStatus rv = ProcessEvent(*mouseEvent); + if (nsEventStatus_eConsumeNoDefault == rv) { + aMouseEvent->PreventDefault(); + if (!aAllowPropagate) { + aMouseEvent->StopPropagation(); + } + } + if (mouseEvent->mMessage == eMouseUp) { + mLastMouseDownButtonType = -1; + } + } + return NS_OK; +} + +#ifdef XP_WIN +void +nsPluginInstanceOwner::CallDefaultProc(const WidgetGUIEvent* aEvent) +{ + nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset(); + if (!widget) { + widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (NS_WARN_IF(!widget)) { + return; + } + } + + const NPEvent* npEvent = + static_cast<const NPEvent*>(aEvent->mPluginEvent); + if (NS_WARN_IF(!npEvent)) { + return; + } + + WidgetPluginEvent pluginEvent(true, ePluginInputEvent, widget); + pluginEvent.mPluginEvent.Copy(*npEvent); + widget->DefaultProcOfPluginEvent(pluginEvent); +} + +already_AddRefed<TextComposition> +nsPluginInstanceOwner::GetTextComposition() +{ + if (NS_WARN_IF(!mPluginFrame)) { + return nullptr; + } + + nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset(); + if (!widget) { + widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (NS_WARN_IF(!widget)) { + return nullptr; + } + } + + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(widget); + if (NS_WARN_IF(!composition)) { + return nullptr; + } + + return composition.forget(); +} + +void +nsPluginInstanceOwner::HandleNoConsumedCompositionMessage( + WidgetCompositionEvent* aCompositionEvent, + const NPEvent* aPluginEvent) +{ + nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset(); + if (!widget) { + widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (NS_WARN_IF(!widget)) { + return; + } + } + + NPEvent npevent; + if (aPluginEvent->lParam & GCS_RESULTSTR) { + // GCS_RESULTSTR's default proc will generate WM_CHAR. So emulate it. + for (size_t i = 0; i < aCompositionEvent->mData.Length(); i++) { + WidgetPluginEvent charEvent(true, ePluginInputEvent, widget); + npevent.event = WM_CHAR; + npevent.wParam = aCompositionEvent->mData[i]; + npevent.lParam = 0; + charEvent.mPluginEvent.Copy(npevent); + ProcessEvent(charEvent); + } + return; + } + if (!mSentStartComposition) { + // We post WM_IME_COMPOSITION to default proc, but + // WM_IME_STARTCOMPOSITION isn't post yet. We should post it at first. + WidgetPluginEvent startEvent(true, ePluginInputEvent, widget); + npevent.event = WM_IME_STARTCOMPOSITION; + npevent.wParam = 0; + npevent.lParam = 0; + startEvent.mPluginEvent.Copy(npevent); + CallDefaultProc(&startEvent); + mSentStartComposition = true; + } + + CallDefaultProc(aCompositionEvent); +} +#endif + +nsresult +nsPluginInstanceOwner::DispatchCompositionToPlugin(nsIDOMEvent* aEvent) +{ +#ifdef XP_WIN + if (!mPluginWindow) { + // CompositionEvent isn't cancellable. So it is unnecessary to call + // PreventDefaults() to consume event + return NS_OK; + } + WidgetCompositionEvent* compositionEvent = + aEvent->WidgetEventPtr()->AsCompositionEvent(); + if (NS_WARN_IF(!compositionEvent)) { + return NS_ERROR_INVALID_ARG; + } + + if (compositionEvent->mMessage == eCompositionChange) { + RefPtr<TextComposition> composition = GetTextComposition(); + if (NS_WARN_IF(!composition)) { + return NS_ERROR_FAILURE; + } + TextComposition::CompositionChangeEventHandlingMarker + compositionChangeEventHandlingMarker(composition, compositionEvent); + } + + const NPEvent* pPluginEvent = + static_cast<const NPEvent*>(compositionEvent->mPluginEvent); + if (pPluginEvent && pPluginEvent->event == WM_IME_COMPOSITION && + mPluginDidNotHandleIMEComposition) { + // This is a workaround when running windowed and windowless Flash on + // same process. + // Flash with protected mode calls IMM APIs on own render process. This + // is a bug of Flash's protected mode. + // ImmGetCompositionString with GCS_RESULTSTR returns *LAST* committed + // string. So when windowed mode Flash handles IME composition, + // windowless plugin can get windowed mode's commited string by that API. + // So we never post WM_IME_COMPOSITION when plugin doesn't call + // ImmGetCompositionString() during WM_IME_COMPOSITION correctly. + HandleNoConsumedCompositionMessage(compositionEvent, pPluginEvent); + aEvent->StopImmediatePropagation(); + return NS_OK; + } + + // Protected mode Flash returns noDefault by NPP_HandleEvent, but + // composition information into plugin is invalid because plugin's bug. + // So if plugin doesn't get composition data by WM_IME_COMPOSITION, we + // recongnize it isn't handled + AutoRestore<bool> restore(mGotCompositionData); + mGotCompositionData = false; + + nsEventStatus status = ProcessEvent(*compositionEvent); + aEvent->StopImmediatePropagation(); + + // Composition event isn't handled by plugin, so we have to call default proc. + + if (NS_WARN_IF(!pPluginEvent)) { + return NS_OK; + } + + if (pPluginEvent->event == WM_IME_STARTCOMPOSITION) { + // Flash's protected mode lies that composition event is handled, but it + // cannot do it well. So even if handled, we should post this message when + // no IMM API calls during WM_IME_COMPOSITION. + if (nsEventStatus_eConsumeNoDefault != status) { + CallDefaultProc(compositionEvent); + mSentStartComposition = true; + } else { + mSentStartComposition = false; + } + mPluginDidNotHandleIMEComposition = false; + return NS_OK; + } + + if (pPluginEvent->event == WM_IME_ENDCOMPOSITION) { + // Always post WM_END_COMPOSITION to default proc. Because Flash may lie + // that it doesn't handle composition well, but event is handled. + // Even if posting this message, default proc do nothing if unnecessary. + CallDefaultProc(compositionEvent); + return NS_OK; + } + + if (pPluginEvent->event == WM_IME_COMPOSITION && !mGotCompositionData) { + // If plugin doesn't handle WM_IME_COMPOSITION correctly, we don't send + // composition event until end composition. + mPluginDidNotHandleIMEComposition = true; + + HandleNoConsumedCompositionMessage(compositionEvent, pPluginEvent); + } +#endif // #ifdef XP_WIN + return NS_OK; +} + +nsresult +nsPluginInstanceOwner::HandleEvent(nsIDOMEvent* aEvent) +{ + NS_ASSERTION(mInstance, "Should have a valid plugin instance or not receive events."); + + nsAutoString eventType; + aEvent->GetType(eventType); + +#ifdef XP_MACOSX + if (eventType.EqualsLiteral("activate") || + eventType.EqualsLiteral("deactivate")) { + WindowFocusMayHaveChanged(); + return NS_OK; + } + if (eventType.EqualsLiteral("MozPerformDelayedBlur")) { + if (mShouldBlurOnActivate) { + WidgetGUIEvent blurEvent(true, eBlur, nullptr); + ProcessEvent(blurEvent); + mShouldBlurOnActivate = false; + } + return NS_OK; + } +#endif + + if (eventType.EqualsLiteral("focus")) { + mContentFocused = true; + return DispatchFocusToPlugin(aEvent); + } + if (eventType.EqualsLiteral("blur")) { + mContentFocused = false; + return DispatchFocusToPlugin(aEvent); + } + if (eventType.EqualsLiteral("mousedown")) { + return ProcessMouseDown(aEvent); + } + if (eventType.EqualsLiteral("mouseup")) { + return DispatchMouseToPlugin(aEvent); + } + if (eventType.EqualsLiteral("mousemove")) { + return DispatchMouseToPlugin(aEvent, true); + } + if (eventType.EqualsLiteral("click") || + eventType.EqualsLiteral("dblclick") || + eventType.EqualsLiteral("mouseover") || + eventType.EqualsLiteral("mouseout")) { + return DispatchMouseToPlugin(aEvent); + } + if (eventType.EqualsLiteral("keydown") || + eventType.EqualsLiteral("keyup")) { + return DispatchKeyToPlugin(aEvent); + } + if (eventType.EqualsLiteral("keypress")) { + return ProcessKeyPress(aEvent); + } + if (eventType.EqualsLiteral("compositionstart") || + eventType.EqualsLiteral("compositionend") || + eventType.EqualsLiteral("text")) { + return DispatchCompositionToPlugin(aEvent); + } + + nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aEvent)); + if (dragEvent && mInstance) { + WidgetEvent* ievent = aEvent->WidgetEventPtr(); + if (ievent && ievent->IsTrusted() && + ievent->mMessage != eDragEnter && ievent->mMessage != eDragOver) { + aEvent->PreventDefault(); + } + + // Let the plugin handle drag events. + aEvent->StopPropagation(); + } + return NS_OK; +} + +#ifdef MOZ_X11 +static unsigned int XInputEventState(const WidgetInputEvent& anEvent) +{ + unsigned int state = 0; + if (anEvent.IsShift()) state |= ShiftMask; + if (anEvent.IsControl()) state |= ControlMask; + if (anEvent.IsAlt()) state |= Mod1Mask; + if (anEvent.IsMeta()) state |= Mod4Mask; + return state; +} +#endif + +#ifdef XP_MACOSX + +// Returns whether or not content is the content that is or would be +// focused if the top-level chrome window was active. +static bool +ContentIsFocusedWithinWindow(nsIContent* aContent) +{ + nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow(); + if (!outerWindow) { + return false; + } + + nsPIDOMWindowOuter* rootWindow = outerWindow->GetPrivateRoot(); + if (!rootWindow) { + return false; + } + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return false; + } + + nsCOMPtr<nsPIDOMWindowOuter> focusedFrame; + nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedDescendant(rootWindow, true, getter_AddRefs(focusedFrame)); + return (focusedContent.get() == aContent); +} + +static NPCocoaEventType +CocoaEventTypeForEvent(const WidgetGUIEvent& anEvent, nsIFrame* aObjectFrame) +{ + const NPCocoaEvent* event = static_cast<const NPCocoaEvent*>(anEvent.mPluginEvent); + if (event) { + return event->type; + } + + switch (anEvent.mMessage) { + case eMouseOver: + return NPCocoaEventMouseEntered; + case eMouseOut: + return NPCocoaEventMouseExited; + case eMouseMove: { + // We don't know via information on events from the widget code whether or not + // we're dragging. The widget code just generates mouse move events from native + // drag events. If anybody is capturing, this is a drag event. + if (nsIPresShell::GetCapturingContent()) { + return NPCocoaEventMouseDragged; + } + + return NPCocoaEventMouseMoved; + } + case eMouseDown: + return NPCocoaEventMouseDown; + case eMouseUp: + return NPCocoaEventMouseUp; + case eKeyDown: + return NPCocoaEventKeyDown; + case eKeyUp: + return NPCocoaEventKeyUp; + case eFocus: + case eBlur: + return NPCocoaEventFocusChanged; + case eLegacyMouseLineOrPageScroll: + return NPCocoaEventScrollWheel; + default: + return (NPCocoaEventType)0; + } +} + +static NPCocoaEvent +TranslateToNPCocoaEvent(WidgetGUIEvent* anEvent, nsIFrame* aObjectFrame) +{ + NPCocoaEvent cocoaEvent; + InitializeNPCocoaEvent(&cocoaEvent); + cocoaEvent.type = CocoaEventTypeForEvent(*anEvent, aObjectFrame); + + if (anEvent->mMessage == eMouseMove || + anEvent->mMessage == eMouseDown || + anEvent->mMessage == eMouseUp || + anEvent->mMessage == eLegacyMouseLineOrPageScroll || + anEvent->mMessage == eMouseOver || + anEvent->mMessage == eMouseOut) + { + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(anEvent, aObjectFrame) - + aObjectFrame->GetContentRectRelativeToSelf().TopLeft(); + nsPresContext* presContext = aObjectFrame->PresContext(); + // Plugin event coordinates need to be translated from device pixels + // into "display pixels" in HiDPI modes. + double scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/ + aObjectFrame->PresContext()->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom(); + size_t intScaleFactor = ceil(scaleFactor); + nsIntPoint ptPx(presContext->AppUnitsToDevPixels(pt.x) / intScaleFactor, + presContext->AppUnitsToDevPixels(pt.y) / intScaleFactor); + cocoaEvent.data.mouse.pluginX = double(ptPx.x); + cocoaEvent.data.mouse.pluginY = double(ptPx.y); + } + + switch (anEvent->mMessage) { + case eMouseDown: + case eMouseUp: { + WidgetMouseEvent* mouseEvent = anEvent->AsMouseEvent(); + if (mouseEvent) { + switch (mouseEvent->button) { + case WidgetMouseEvent::eLeftButton: + cocoaEvent.data.mouse.buttonNumber = 0; + break; + case WidgetMouseEvent::eRightButton: + cocoaEvent.data.mouse.buttonNumber = 1; + break; + case WidgetMouseEvent::eMiddleButton: + cocoaEvent.data.mouse.buttonNumber = 2; + break; + default: + NS_WARNING("Mouse button we don't know about?"); + } + cocoaEvent.data.mouse.clickCount = mouseEvent->mClickCount; + } else { + NS_WARNING("eMouseUp/DOWN is not a WidgetMouseEvent?"); + } + break; + } + case eLegacyMouseLineOrPageScroll: { + WidgetWheelEvent* wheelEvent = anEvent->AsWheelEvent(); + if (wheelEvent) { + cocoaEvent.data.mouse.deltaX = wheelEvent->mLineOrPageDeltaX; + cocoaEvent.data.mouse.deltaY = wheelEvent->mLineOrPageDeltaY; + } else { + NS_WARNING("eLegacyMouseLineOrPageScroll is not a WidgetWheelEvent? " + "(could be, haven't checked)"); + } + break; + } + case eKeyDown: + case eKeyUp: + { + WidgetKeyboardEvent* keyEvent = anEvent->AsKeyboardEvent(); + + // That keyEvent->mPluginTextEventString is non-empty is a signal that we should + // create a text event for the plugin, instead of a key event. + if (anEvent->mMessage == eKeyDown && + !keyEvent->mPluginTextEventString.IsEmpty()) { + cocoaEvent.type = NPCocoaEventTextInput; + const char16_t* pluginTextEventString = keyEvent->mPluginTextEventString.get(); + cocoaEvent.data.text.text = (NPNSString*) + ::CFStringCreateWithCharacters(NULL, + reinterpret_cast<const UniChar*>(pluginTextEventString), + keyEvent->mPluginTextEventString.Length()); + } else { + cocoaEvent.data.key.keyCode = keyEvent->mNativeKeyCode; + cocoaEvent.data.key.isARepeat = keyEvent->mIsRepeat; + cocoaEvent.data.key.modifierFlags = keyEvent->mNativeModifierFlags; + const char16_t* nativeChars = keyEvent->mNativeCharacters.get(); + cocoaEvent.data.key.characters = (NPNSString*) + ::CFStringCreateWithCharacters(NULL, + reinterpret_cast<const UniChar*>(nativeChars), + keyEvent->mNativeCharacters.Length()); + const char16_t* nativeCharsIgnoringModifiers = keyEvent->mNativeCharactersIgnoringModifiers.get(); + cocoaEvent.data.key.charactersIgnoringModifiers = (NPNSString*) + ::CFStringCreateWithCharacters(NULL, + reinterpret_cast<const UniChar*>(nativeCharsIgnoringModifiers), + keyEvent->mNativeCharactersIgnoringModifiers.Length()); + } + break; + } + case eFocus: + case eBlur: + cocoaEvent.data.focus.hasFocus = (anEvent->mMessage == eFocus); + break; + default: + break; + } + return cocoaEvent; +} + +void nsPluginInstanceOwner::PerformDelayedBlurs() +{ + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + nsCOMPtr<EventTarget> windowRoot = content->OwnerDoc()->GetWindow()->GetTopWindowRoot(); + nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), + windowRoot, + NS_LITERAL_STRING("MozPerformDelayedBlur"), + false, false, nullptr); +} + +#endif + +nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent) +{ + nsEventStatus rv = nsEventStatus_eIgnore; + + if (!mInstance || !mPluginFrame) { + return nsEventStatus_eIgnore; + } + +#ifdef XP_MACOSX + NPEventModel eventModel = GetEventModel(); + if (eventModel != NPEventModelCocoa) { + return nsEventStatus_eIgnore; + } + + // In the Cocoa event model, focus is per-window. Don't tell a plugin it lost + // focus unless it lost focus within the window. For example, ignore a blur + // event if it's coming due to the plugin's window deactivating. + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (anEvent.mMessage == eBlur && ContentIsFocusedWithinWindow(content)) { + mShouldBlurOnActivate = true; + return nsEventStatus_eIgnore; + } + + // Also, don't tell the plugin it gained focus again after we've already given + // it focus. This might happen if it has focus, its window is blurred, then the + // window is made active again. The plugin never lost in-window focus, so it + // shouldn't get a focus event again. + if (anEvent.mMessage == eFocus && mLastContentFocused == true) { + mShouldBlurOnActivate = false; + return nsEventStatus_eIgnore; + } + + // Now, if we're going to send a focus event, update mLastContentFocused and + // tell any plugins in our window that we have taken focus, so they should + // perform any delayed blurs. + if (anEvent.mMessage == eFocus || anEvent.mMessage == eBlur) { + mLastContentFocused = (anEvent.mMessage == eFocus); + mShouldBlurOnActivate = false; + PerformDelayedBlurs(); + } + + NPCocoaEvent cocoaEvent = TranslateToNPCocoaEvent(const_cast<WidgetGUIEvent*>(&anEvent), mPluginFrame); + if (cocoaEvent.type == (NPCocoaEventType)0) { + return nsEventStatus_eIgnore; + } + + if (cocoaEvent.type == NPCocoaEventTextInput) { + mInstance->HandleEvent(&cocoaEvent, nullptr); + return nsEventStatus_eConsumeNoDefault; + } + + int16_t response = kNPEventNotHandled; + mInstance->HandleEvent(&cocoaEvent, + &response, + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + if ((response == kNPEventStartIME) && (cocoaEvent.type == NPCocoaEventKeyDown)) { + nsIWidget* widget = mPluginFrame->GetNearestWidget(); + if (widget) { + const WidgetKeyboardEvent* keyEvent = anEvent.AsKeyboardEvent(); + double screenX, screenY; + ConvertPoint(0.0, mPluginFrame->GetScreenRect().height, + NPCoordinateSpacePlugin, &screenX, &screenY, + NPCoordinateSpaceScreen); + nsAutoString outText; + if (NS_SUCCEEDED(widget->StartPluginIME(*keyEvent, screenX, screenY, outText)) && + !outText.IsEmpty()) { + CFStringRef cfString = + ::CFStringCreateWithCharacters(kCFAllocatorDefault, + reinterpret_cast<const UniChar*>(outText.get()), + outText.Length()); + NPCocoaEvent textEvent; + InitializeNPCocoaEvent(&textEvent); + textEvent.type = NPCocoaEventTextInput; + textEvent.data.text.text = (NPNSString*)cfString; + mInstance->HandleEvent(&textEvent, nullptr); + } + } + } + + bool handled = (response == kNPEventHandled || response == kNPEventStartIME); + bool leftMouseButtonDown = (anEvent.mMessage == eMouseDown) && + (anEvent.AsMouseEvent()->button == WidgetMouseEvent::eLeftButton); + if (handled && !(leftMouseButtonDown && !mContentFocused)) { + rv = nsEventStatus_eConsumeNoDefault; + } +#endif + +#ifdef XP_WIN + // this code supports windowless plugins + const NPEvent *pPluginEvent = static_cast<const NPEvent*>(anEvent.mPluginEvent); + // we can get synthetic events from the EventStateManager... these + // have no pluginEvent + NPEvent pluginEvent; + if (anEvent.mClass == eMouseEventClass || + anEvent.mClass == eWheelEventClass) { + if (!pPluginEvent) { + // XXX Should extend this list to synthesize events for more event + // types + pluginEvent.event = 0; + bool initWParamWithCurrentState = true; + switch (anEvent.mMessage) { + case eMouseMove: { + pluginEvent.event = WM_MOUSEMOVE; + break; + } + case eMouseDown: { + static const int downMsgs[] = + { WM_LBUTTONDOWN, WM_MBUTTONDOWN, WM_RBUTTONDOWN }; + static const int dblClickMsgs[] = + { WM_LBUTTONDBLCLK, WM_MBUTTONDBLCLK, WM_RBUTTONDBLCLK }; + const WidgetMouseEvent* mouseEvent = anEvent.AsMouseEvent(); + if (mouseEvent->mClickCount == 2) { + pluginEvent.event = dblClickMsgs[mouseEvent->button]; + } else { + pluginEvent.event = downMsgs[mouseEvent->button]; + } + break; + } + case eMouseUp: { + static const int upMsgs[] = + { WM_LBUTTONUP, WM_MBUTTONUP, WM_RBUTTONUP }; + const WidgetMouseEvent* mouseEvent = anEvent.AsMouseEvent(); + pluginEvent.event = upMsgs[mouseEvent->button]; + break; + } + // For plugins which don't support high-resolution scroll, we should + // generate legacy resolution wheel messages. I.e., the delta value + // should be WHEEL_DELTA * n. + case eWheel: { + const WidgetWheelEvent* wheelEvent = anEvent.AsWheelEvent(); + int32_t delta = 0; + if (wheelEvent->mLineOrPageDeltaY) { + switch (wheelEvent->mDeltaMode) { + case nsIDOMWheelEvent::DOM_DELTA_PAGE: + pluginEvent.event = WM_MOUSEWHEEL; + delta = -WHEEL_DELTA * wheelEvent->mLineOrPageDeltaY; + break; + case nsIDOMWheelEvent::DOM_DELTA_LINE: { + UINT linesPerWheelDelta = 0; + if (NS_WARN_IF(!::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, + &linesPerWheelDelta, 0))) { + // Use system default scroll amount, 3, when + // SPI_GETWHEELSCROLLLINES isn't available. + linesPerWheelDelta = 3; + } + if (!linesPerWheelDelta) { + break; + } + pluginEvent.event = WM_MOUSEWHEEL; + delta = -WHEEL_DELTA / linesPerWheelDelta; + delta *= wheelEvent->mLineOrPageDeltaY; + break; + } + case nsIDOMWheelEvent::DOM_DELTA_PIXEL: + default: + // We don't support WM_GESTURE with this path. + MOZ_ASSERT(!pluginEvent.event); + break; + } + } else if (wheelEvent->mLineOrPageDeltaX) { + switch (wheelEvent->mDeltaMode) { + case nsIDOMWheelEvent::DOM_DELTA_PAGE: + pluginEvent.event = WM_MOUSEHWHEEL; + delta = -WHEEL_DELTA * wheelEvent->mLineOrPageDeltaX; + break; + case nsIDOMWheelEvent::DOM_DELTA_LINE: { + pluginEvent.event = WM_MOUSEHWHEEL; + UINT charsPerWheelDelta = 0; + // FYI: SPI_GETWHEELSCROLLCHARS is available on Vista or later. + if (::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, + &charsPerWheelDelta, 0)) { + // Use system default scroll amount, 3, when + // SPI_GETWHEELSCROLLCHARS isn't available. + charsPerWheelDelta = 3; + } + if (!charsPerWheelDelta) { + break; + } + delta = WHEEL_DELTA / charsPerWheelDelta; + delta *= wheelEvent->mLineOrPageDeltaX; + break; + } + case nsIDOMWheelEvent::DOM_DELTA_PIXEL: + default: + // We don't support WM_GESTURE with this path. + MOZ_ASSERT(!pluginEvent.event); + break; + } + } + + if (!pluginEvent.event) { + break; + } + + initWParamWithCurrentState = false; + int32_t modifiers = + (wheelEvent->IsControl() ? MK_CONTROL : 0) | + (wheelEvent->IsShift() ? MK_SHIFT : 0) | + (wheelEvent->IsLeftButtonPressed() ? MK_LBUTTON : 0) | + (wheelEvent->IsMiddleButtonPressed() ? MK_MBUTTON : 0) | + (wheelEvent->IsRightButtonPressed() ? MK_RBUTTON : 0) | + (wheelEvent->Is4thButtonPressed() ? MK_XBUTTON1 : 0) | + (wheelEvent->Is5thButtonPressed() ? MK_XBUTTON2 : 0); + pluginEvent.wParam = MAKEWPARAM(modifiers, delta); + pPluginEvent = &pluginEvent; + break; + } + // don't synthesize anything for eMouseDoubleClick, since that + // is a synthetic event generated on mouse-up, and Windows WM_*DBLCLK + // messages are sent on mouse-down + default: + break; + } + if (pluginEvent.event && initWParamWithCurrentState) { + pPluginEvent = &pluginEvent; + pluginEvent.wParam = + (::GetKeyState(VK_CONTROL) ? MK_CONTROL : 0) | + (::GetKeyState(VK_SHIFT) ? MK_SHIFT : 0) | + (::GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) | + (::GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) | + (::GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0) | + (::GetKeyState(VK_XBUTTON1) ? MK_XBUTTON1 : 0) | + (::GetKeyState(VK_XBUTTON2) ? MK_XBUTTON2 : 0); + } + } + if (pPluginEvent) { + // Make event coordinates relative to our enclosing widget, + // not the widget they were received on. + // See use of NPEvent in widget/windows/nsWindow.cpp + // for why this assert should be safe + NS_ASSERTION(anEvent.mMessage == eMouseDown || + anEvent.mMessage == eMouseUp || + anEvent.mMessage == eMouseDoubleClick || + anEvent.mMessage == eMouseOver || + anEvent.mMessage == eMouseOut || + anEvent.mMessage == eMouseMove || + anEvent.mMessage == eWheel, + "Incorrect event type for coordinate translation"); + nsPoint pt = + nsLayoutUtils::GetEventCoordinatesRelativeTo(&anEvent, mPluginFrame) - + mPluginFrame->GetContentRectRelativeToSelf().TopLeft(); + nsPresContext* presContext = mPluginFrame->PresContext(); + nsIntPoint ptPx(presContext->AppUnitsToDevPixels(pt.x), + presContext->AppUnitsToDevPixels(pt.y)); + nsIntPoint widgetPtPx = ptPx + mPluginFrame->GetWindowOriginInPixels(true); + const_cast<NPEvent*>(pPluginEvent)->lParam = MAKELPARAM(widgetPtPx.x, widgetPtPx.y); + } + } + else if (!pPluginEvent) { + switch (anEvent.mMessage) { + case eFocus: + pluginEvent.event = WM_SETFOCUS; + pluginEvent.wParam = 0; + pluginEvent.lParam = 0; + pPluginEvent = &pluginEvent; + break; + case eBlur: + pluginEvent.event = WM_KILLFOCUS; + pluginEvent.wParam = 0; + pluginEvent.lParam = 0; + pPluginEvent = &pluginEvent; + break; + default: + break; + } + } + + if (pPluginEvent && !pPluginEvent->event) { + // Don't send null events to plugins. + NS_WARNING("nsPluginFrame ProcessEvent: trying to send null event to plugin."); + return rv; + } + + if (pPluginEvent) { + int16_t response = kNPEventNotHandled; + mInstance->HandleEvent(const_cast<NPEvent*>(pPluginEvent), + &response, + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + if (response == kNPEventHandled) + rv = nsEventStatus_eConsumeNoDefault; + } +#endif + +#ifdef MOZ_X11 + // this code supports windowless plugins + nsIWidget* widget = anEvent.mWidget; + XEvent pluginEvent = XEvent(); + pluginEvent.type = 0; + + switch(anEvent.mClass) { + case eMouseEventClass: + { + switch (anEvent.mMessage) { + case eMouseClick: + case eMouseDoubleClick: + // Button up/down events sent instead. + return rv; + default: + break; + } + + // Get reference point relative to plugin origin. + const nsPresContext* presContext = mPluginFrame->PresContext(); + nsPoint appPoint = + nsLayoutUtils::GetEventCoordinatesRelativeTo(&anEvent, mPluginFrame) - + mPluginFrame->GetContentRectRelativeToSelf().TopLeft(); + nsIntPoint pluginPoint(presContext->AppUnitsToDevPixels(appPoint.x), + presContext->AppUnitsToDevPixels(appPoint.y)); + const WidgetMouseEvent& mouseEvent = *anEvent.AsMouseEvent(); + // Get reference point relative to screen: + LayoutDeviceIntPoint rootPoint(-1, -1); + if (widget) { + rootPoint = anEvent.mRefPoint + widget->WidgetToScreenOffset(); + } +#ifdef MOZ_WIDGET_GTK + Window root = GDK_ROOT_WINDOW(); +#else + Window root = X11None; // Could XQueryTree, but this is not important. +#endif + + switch (anEvent.mMessage) { + case eMouseOver: + case eMouseOut: + { + XCrossingEvent& event = pluginEvent.xcrossing; + event.type = anEvent.mMessage == eMouseOver ? + EnterNotify : LeaveNotify; + event.root = root; + event.time = anEvent.mTime; + event.x = pluginPoint.x; + event.y = pluginPoint.y; + event.x_root = rootPoint.x; + event.y_root = rootPoint.y; + event.state = XInputEventState(mouseEvent); + // information lost + event.subwindow = X11None; + event.mode = -1; + event.detail = NotifyDetailNone; + event.same_screen = True; + event.focus = mContentFocused; + } + break; + case eMouseMove: + { + XMotionEvent& event = pluginEvent.xmotion; + event.type = MotionNotify; + event.root = root; + event.time = anEvent.mTime; + event.x = pluginPoint.x; + event.y = pluginPoint.y; + event.x_root = rootPoint.x; + event.y_root = rootPoint.y; + event.state = XInputEventState(mouseEvent); + // information lost + event.subwindow = X11None; + event.is_hint = NotifyNormal; + event.same_screen = True; + } + break; + case eMouseDown: + case eMouseUp: + { + XButtonEvent& event = pluginEvent.xbutton; + event.type = anEvent.mMessage == eMouseDown ? + ButtonPress : ButtonRelease; + event.root = root; + event.time = anEvent.mTime; + event.x = pluginPoint.x; + event.y = pluginPoint.y; + event.x_root = rootPoint.x; + event.y_root = rootPoint.y; + event.state = XInputEventState(mouseEvent); + switch (mouseEvent.button) + { + case WidgetMouseEvent::eMiddleButton: + event.button = 2; + break; + case WidgetMouseEvent::eRightButton: + event.button = 3; + break; + default: // WidgetMouseEvent::eLeftButton; + event.button = 1; + break; + } + // information lost: + event.subwindow = X11None; + event.same_screen = True; + } + break; + default: + break; + } + } + break; + + //XXX case eMouseScrollEventClass: not received. + + case eKeyboardEventClass: + if (anEvent.mPluginEvent) + { + XKeyEvent &event = pluginEvent.xkey; +#ifdef MOZ_WIDGET_GTK + event.root = GDK_ROOT_WINDOW(); + event.time = anEvent.mTime; + const GdkEventKey* gdkEvent = + static_cast<const GdkEventKey*>(anEvent.mPluginEvent); + event.keycode = gdkEvent->hardware_keycode; + event.state = gdkEvent->state; + switch (anEvent.mMessage) + { + case eKeyDown: + // Handle eKeyDown for modifier key presses + // For non-modifiers we get eKeyPress + if (gdkEvent->is_modifier) + event.type = XKeyPress; + break; + case eKeyPress: + event.type = XKeyPress; + break; + case eKeyUp: + event.type = KeyRelease; + break; + default: + break; + } +#endif + + // Information that could be obtained from pluginEvent but we may not + // want to promise to provide: + event.subwindow = X11None; + event.x = 0; + event.y = 0; + event.x_root = -1; + event.y_root = -1; + event.same_screen = False; + } + else + { + // If we need to send synthesized key events, then + // DOMKeyCodeToGdkKeyCode(keyEvent.keyCode) and + // gdk_keymap_get_entries_for_keyval will be useful, but the + // mappings will not be unique. + NS_WARNING("Synthesized key event not sent to plugin"); + } + break; + + default: + switch (anEvent.mMessage) { + case eFocus: + case eBlur: + { + XFocusChangeEvent &event = pluginEvent.xfocus; + event.type = anEvent.mMessage == eFocus ? FocusIn : FocusOut; + // information lost: + event.mode = -1; + event.detail = NotifyDetailNone; + } + break; + default: + break; + } + } + + if (!pluginEvent.type) { + return rv; + } + + // Fill in (useless) generic event information. + XAnyEvent& event = pluginEvent.xany; + event.display = widget ? + static_cast<Display*>(widget->GetNativeData(NS_NATIVE_DISPLAY)) : nullptr; + event.window = X11None; // not a real window + // information lost: + event.serial = 0; + event.send_event = False; + + int16_t response = kNPEventNotHandled; + mInstance->HandleEvent(&pluginEvent, &response, NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + if (response == kNPEventHandled) + rv = nsEventStatus_eConsumeNoDefault; +#endif + +#ifdef MOZ_WIDGET_ANDROID + // this code supports windowless plugins + { + // The plugin needs focus to receive keyboard and touch events + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsCOMPtr<nsIDOMElement> elem = do_QueryReferent(mContent); + fm->SetFocus(elem, 0); + } + } + switch(anEvent.mClass) { + case eMouseEventClass: + { + switch (anEvent.mMessage) { + case eMouseClick: + case eMouseDoubleClick: + // Button up/down events sent instead. + return rv; + default: + break; + } + + // Get reference point relative to plugin origin. + const nsPresContext* presContext = mPluginFrame->PresContext(); + nsPoint appPoint = + nsLayoutUtils::GetEventCoordinatesRelativeTo(&anEvent, mPluginFrame) - + mPluginFrame->GetContentRectRelativeToSelf().TopLeft(); + nsIntPoint pluginPoint(presContext->AppUnitsToDevPixels(appPoint.x), + presContext->AppUnitsToDevPixels(appPoint.y)); + + switch (anEvent.mMessage) { + case eMouseMove: + { + // are these going to be touch events? + // pluginPoint.x; + // pluginPoint.y; + } + break; + case eMouseDown: + { + ANPEvent event; + event.inSize = sizeof(ANPEvent); + event.eventType = kMouse_ANPEventType; + event.data.mouse.action = kDown_ANPMouseAction; + event.data.mouse.x = pluginPoint.x; + event.data.mouse.y = pluginPoint.y; + mInstance->HandleEvent(&event, nullptr, NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + } + break; + case eMouseUp: + { + ANPEvent event; + event.inSize = sizeof(ANPEvent); + event.eventType = kMouse_ANPEventType; + event.data.mouse.action = kUp_ANPMouseAction; + event.data.mouse.x = pluginPoint.x; + event.data.mouse.y = pluginPoint.y; + mInstance->HandleEvent(&event, nullptr, NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + } + break; + default: + break; + } + } + break; + + case eKeyboardEventClass: + { + const WidgetKeyboardEvent& keyEvent = *anEvent.AsKeyboardEvent(); + LOG("Firing eKeyboardEventClass %d %d\n", + keyEvent.mKeyCode, keyEvent.mCharCode); + // pluginEvent is initialized by nsWindow::InitKeyEvent(). + const ANPEvent* pluginEvent = static_cast<const ANPEvent*>(keyEvent.mPluginEvent); + if (pluginEvent) { + MOZ_ASSERT(pluginEvent->inSize == sizeof(ANPEvent)); + MOZ_ASSERT(pluginEvent->eventType == kKey_ANPEventType); + mInstance->HandleEvent(const_cast<ANPEvent*>(pluginEvent), + nullptr, + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + } + } + break; + + default: + break; + } + rv = nsEventStatus_eConsumeNoDefault; +#endif + + return rv; +} + +nsresult +nsPluginInstanceOwner::Destroy() +{ + SetFrame(nullptr); + +#ifdef XP_MACOSX + RemoveFromCARefreshTimer(); +#endif + + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + + // unregister context menu listener + if (mCXMenuListener) { + mCXMenuListener->Destroy(content); + mCXMenuListener = nullptr; + } + + content->RemoveEventListener(NS_LITERAL_STRING("focus"), this, false); + content->RemoveEventListener(NS_LITERAL_STRING("blur"), this, false); + content->RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, false); + content->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, false); + content->RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, false); + content->RemoveEventListener(NS_LITERAL_STRING("click"), this, false); + content->RemoveEventListener(NS_LITERAL_STRING("dblclick"), this, false); + content->RemoveEventListener(NS_LITERAL_STRING("mouseover"), this, false); + content->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, false); + content->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("drop"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("drag"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("dragenter"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("dragover"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("dragleave"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("dragexit"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("dragstart"), this, true); + content->RemoveEventListener(NS_LITERAL_STRING("dragend"), this, true); + content->RemoveSystemEventListener(NS_LITERAL_STRING("compositionstart"), + this, true); + content->RemoveSystemEventListener(NS_LITERAL_STRING("compositionend"), + this, true); + content->RemoveSystemEventListener(NS_LITERAL_STRING("text"), this, true); + +#if MOZ_WIDGET_ANDROID + RemovePluginView(); +#endif + + if (mWidget) { + if (mPluginWindow) { + mPluginWindow->SetPluginWidget(nullptr); + } + + nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget); + if (pluginWidget) { + pluginWidget->SetPluginInstanceOwner(nullptr); + } + mWidget->Destroy(); + } + + return NS_OK; +} + +// Paints are handled differently, so we just simulate an update event. + +#ifdef XP_MACOSX +void nsPluginInstanceOwner::Paint(const gfxRect& aDirtyRect, CGContextRef cgContext) +{ + if (!mInstance || !mPluginFrame) + return; + + gfxRect dirtyRectCopy = aDirtyRect; + double scaleFactor = 1.0; + GetContentsScaleFactor(&scaleFactor); + if (scaleFactor != 1.0) { + ::CGContextScaleCTM(cgContext, scaleFactor, scaleFactor); + // Convert aDirtyRect from device pixels to "display pixels" + // for HiDPI modes + dirtyRectCopy.ScaleRoundOut(1.0 / scaleFactor); + } + + DoCocoaEventDrawRect(dirtyRectCopy, cgContext); +} + +void nsPluginInstanceOwner::DoCocoaEventDrawRect(const gfxRect& aDrawRect, CGContextRef cgContext) +{ + if (!mInstance || !mPluginFrame) + return; + + // The context given here is only valid during the HandleEvent call. + NPCocoaEvent updateEvent; + InitializeNPCocoaEvent(&updateEvent); + updateEvent.type = NPCocoaEventDrawRect; + updateEvent.data.draw.context = cgContext; + updateEvent.data.draw.x = aDrawRect.X(); + updateEvent.data.draw.y = aDrawRect.Y(); + updateEvent.data.draw.width = aDrawRect.Width(); + updateEvent.data.draw.height = aDrawRect.Height(); + + mInstance->HandleEvent(&updateEvent, nullptr); +} +#endif + +#ifdef XP_WIN +void nsPluginInstanceOwner::Paint(const RECT& aDirty, HDC aDC) +{ + if (!mInstance || !mPluginFrame) + return; + + NPEvent pluginEvent; + pluginEvent.event = WM_PAINT; + pluginEvent.wParam = WPARAM(aDC); + pluginEvent.lParam = LPARAM(&aDirty); + mInstance->HandleEvent(&pluginEvent, nullptr); +} +#endif + +#ifdef MOZ_WIDGET_ANDROID + +void nsPluginInstanceOwner::Paint(gfxContext* aContext, + const gfxRect& aFrameRect, + const gfxRect& aDirtyRect) +{ + if (!mInstance || !mPluginFrame || !mPluginDocumentActiveState || mFullScreen) + return; + + int32_t model = mInstance->GetANPDrawingModel(); + + if (model == kSurface_ANPDrawingModel) { + if (!AddPluginView(GetPluginRect())) { + Invalidate(); + } + return; + } + + if (model != kBitmap_ANPDrawingModel) + return; + +#ifdef ANP_BITMAP_DRAWING_MODEL + static RefPtr<gfxImageSurface> pluginSurface; + + if (pluginSurface == nullptr || + aFrameRect.width != pluginSurface->Width() || + aFrameRect.height != pluginSurface->Height()) { + + pluginSurface = new gfxImageSurface(gfx::IntSize(aFrameRect.width, aFrameRect.height), + SurfaceFormat::A8R8G8B8_UINT32); + if (!pluginSurface) + return; + } + + // Clears buffer. I think this is needed. + gfxUtils::ClearThebesSurface(pluginSurface); + + ANPEvent event; + event.inSize = sizeof(ANPEvent); + event.eventType = 4; + event.data.draw.model = 1; + + event.data.draw.clip.top = 0; + event.data.draw.clip.left = 0; + event.data.draw.clip.bottom = aFrameRect.width; + event.data.draw.clip.right = aFrameRect.height; + + event.data.draw.data.bitmap.format = kRGBA_8888_ANPBitmapFormat; + event.data.draw.data.bitmap.width = aFrameRect.width; + event.data.draw.data.bitmap.height = aFrameRect.height; + event.data.draw.data.bitmap.baseAddr = pluginSurface->Data(); + event.data.draw.data.bitmap.rowBytes = aFrameRect.width * 4; + + if (!mInstance) + return; + + mInstance->HandleEvent(&event, nullptr); + + aContext->SetOp(gfx::CompositionOp::OP_SOURCE); + aContext->SetSource(pluginSurface, gfxPoint(aFrameRect.x, aFrameRect.y)); + aContext->Clip(aFrameRect); + aContext->Paint(); +#endif +} +#endif + +#if defined(MOZ_X11) +void nsPluginInstanceOwner::Paint(gfxContext* aContext, + const gfxRect& aFrameRect, + const gfxRect& aDirtyRect) +{ + if (!mInstance || !mPluginFrame) + return; + + // to provide crisper and faster drawing. + gfxRect pluginRect = aFrameRect; + if (aContext->UserToDevicePixelSnapped(pluginRect)) { + pluginRect = aContext->DeviceToUser(pluginRect); + } + + // Round out the dirty rect to plugin pixels to ensure the plugin draws + // enough pixels for interpolation to device pixels. + gfxRect dirtyRect = aDirtyRect - pluginRect.TopLeft(); + dirtyRect.RoundOut(); + + // Plugins can only draw an integer number of pixels. + // + // With translation-only transformation matrices, pluginRect is already + // pixel-aligned. + // + // With more complex transformations, modifying the scales in the + // transformation matrix could retain subpixel accuracy and let the plugin + // draw a suitable number of pixels for interpolation to device pixels in + // Renderer::Draw, but such cases are not common enough to warrant the + // effort now. + nsIntSize pluginSize(NS_lround(pluginRect.width), + NS_lround(pluginRect.height)); + + // Determine what the plugin needs to draw. + nsIntRect pluginDirtyRect(int32_t(dirtyRect.x), + int32_t(dirtyRect.y), + int32_t(dirtyRect.width), + int32_t(dirtyRect.height)); + if (!pluginDirtyRect. + IntersectRect(nsIntRect(0, 0, pluginSize.width, pluginSize.height), + pluginDirtyRect)) + return; + + NPWindow* window; + GetWindow(window); + + uint32_t rendererFlags = 0; + if (!mFlash10Quirks) { + rendererFlags |= + Renderer::DRAW_SUPPORTS_CLIP_RECT | + Renderer::DRAW_SUPPORTS_ALTERNATE_VISUAL; + } + + bool transparent; + mInstance->IsTransparent(&transparent); + if (!transparent) + rendererFlags |= Renderer::DRAW_IS_OPAQUE; + + // Renderer::Draw() draws a rectangle with top-left at the aContext origin. + gfxContextAutoSaveRestore autoSR(aContext); + aContext->SetMatrix( + aContext->CurrentMatrix().Translate(pluginRect.TopLeft())); + + Renderer renderer(window, this, pluginSize, pluginDirtyRect); + + Display* dpy = mozilla::DefaultXDisplay(); + Screen* screen = DefaultScreenOfDisplay(dpy); + Visual* visual = DefaultVisualOfScreen(screen); + + renderer.Draw(aContext, nsIntSize(window->width, window->height), + rendererFlags, screen, visual); +} +nsresult +nsPluginInstanceOwner::Renderer::DrawWithXlib(cairo_surface_t* xsurface, + nsIntPoint offset, + nsIntRect *clipRects, + uint32_t numClipRects) +{ + Screen *screen = cairo_xlib_surface_get_screen(xsurface); + Colormap colormap; + Visual* visual; + if (!gfxXlibSurface::GetColormapAndVisual(xsurface, &colormap, &visual)) { + NS_ERROR("Failed to get visual and colormap"); + return NS_ERROR_UNEXPECTED; + } + + nsNPAPIPluginInstance *instance = mInstanceOwner->mInstance; + if (!instance) + return NS_ERROR_FAILURE; + + // See if the plugin must be notified of new window parameters. + bool doupdatewindow = false; + + if (mWindow->x != offset.x || mWindow->y != offset.y) { + mWindow->x = offset.x; + mWindow->y = offset.y; + doupdatewindow = true; + } + + if (nsIntSize(mWindow->width, mWindow->height) != mPluginSize) { + mWindow->width = mPluginSize.width; + mWindow->height = mPluginSize.height; + doupdatewindow = true; + } + + // The clip rect is relative to drawable top-left. + NS_ASSERTION(numClipRects <= 1, "We don't support multiple clip rectangles!"); + nsIntRect clipRect; + if (numClipRects) { + clipRect.x = clipRects[0].x; + clipRect.y = clipRects[0].y; + clipRect.width = clipRects[0].width; + clipRect.height = clipRects[0].height; + // NPRect members are unsigned, but clip rectangles should be contained by + // the surface. + NS_ASSERTION(clipRect.x >= 0 && clipRect.y >= 0, + "Clip rectangle offsets are negative!"); + } + else { + clipRect.x = offset.x; + clipRect.y = offset.y; + clipRect.width = mWindow->width; + clipRect.height = mWindow->height; + // Don't ask the plugin to draw outside the drawable. + // This also ensures that the unsigned clip rectangle offsets won't be -ve. + clipRect.IntersectRect(clipRect, + nsIntRect(0, 0, + cairo_xlib_surface_get_width(xsurface), + cairo_xlib_surface_get_height(xsurface))); + } + + NPRect newClipRect; + newClipRect.left = clipRect.x; + newClipRect.top = clipRect.y; + newClipRect.right = clipRect.XMost(); + newClipRect.bottom = clipRect.YMost(); + if (mWindow->clipRect.left != newClipRect.left || + mWindow->clipRect.top != newClipRect.top || + mWindow->clipRect.right != newClipRect.right || + mWindow->clipRect.bottom != newClipRect.bottom) { + mWindow->clipRect = newClipRect; + doupdatewindow = true; + } + + NPSetWindowCallbackStruct* ws_info = + static_cast<NPSetWindowCallbackStruct*>(mWindow->ws_info); +#ifdef MOZ_X11 + if (ws_info->visual != visual || ws_info->colormap != colormap) { + ws_info->visual = visual; + ws_info->colormap = colormap; + ws_info->depth = gfxXlibSurface::DepthOfVisual(screen, visual); + doupdatewindow = true; + } +#endif + + { + if (doupdatewindow) + instance->SetWindow(mWindow); + } + + // Translate the dirty rect to drawable coordinates. + nsIntRect dirtyRect = mDirtyRect + offset; + if (mInstanceOwner->mFlash10Quirks) { + // Work around a bug in Flash up to 10.1 d51 at least, where expose event + // top left coordinates within the plugin-rect and not at the drawable + // origin are misinterpreted. (We can move the top left coordinate + // provided it is within the clipRect.) + dirtyRect.SetRect(offset.x, offset.y, + mDirtyRect.XMost(), mDirtyRect.YMost()); + } + // Intersect the dirty rect with the clip rect to ensure that it lies within + // the drawable. + if (!dirtyRect.IntersectRect(dirtyRect, clipRect)) + return NS_OK; + + { + XEvent pluginEvent = XEvent(); + XGraphicsExposeEvent& exposeEvent = pluginEvent.xgraphicsexpose; + // set the drawing info + exposeEvent.type = GraphicsExpose; + exposeEvent.display = DisplayOfScreen(screen); + exposeEvent.drawable = cairo_xlib_surface_get_drawable(xsurface); + exposeEvent.x = dirtyRect.x; + exposeEvent.y = dirtyRect.y; + exposeEvent.width = dirtyRect.width; + exposeEvent.height = dirtyRect.height; + exposeEvent.count = 0; + // information not set: + exposeEvent.serial = 0; + exposeEvent.send_event = False; + exposeEvent.major_code = 0; + exposeEvent.minor_code = 0; + + instance->HandleEvent(&pluginEvent, nullptr); + } + return NS_OK; +} +#endif + +nsresult nsPluginInstanceOwner::Init(nsIContent* aContent) +{ + mLastEventloopNestingLevel = GetEventloopNestingLevel(); + + mContent = do_GetWeakReference(aContent); + + // Get a frame, don't reflow. If a reflow was necessary it should have been + // done at a higher level than this (content). + nsIFrame* frame = aContent->GetPrimaryFrame(); + nsIObjectFrame* iObjFrame = do_QueryFrame(frame); + nsPluginFrame* objFrame = static_cast<nsPluginFrame*>(iObjFrame); + if (objFrame) { + SetFrame(objFrame); + // Some plugins require a specific sequence of shutdown and startup when + // a page is reloaded. Shutdown happens usually when the last instance + // is destroyed. Here we make sure the plugin instance in the old + // document is destroyed before we try to create the new one. + objFrame->PresContext()->EnsureVisible(); + } else { + NS_NOTREACHED("Should not be initializing plugin without a frame"); + return NS_ERROR_FAILURE; + } + + // register context menu listener + mCXMenuListener = new nsPluginDOMContextMenuListener(aContent); + + aContent->AddEventListener(NS_LITERAL_STRING("focus"), this, false, + false); + aContent->AddEventListener(NS_LITERAL_STRING("blur"), this, false, + false); + aContent->AddEventListener(NS_LITERAL_STRING("mouseup"), this, false, + false); + aContent->AddEventListener(NS_LITERAL_STRING("mousedown"), this, false, + false); + aContent->AddEventListener(NS_LITERAL_STRING("mousemove"), this, false, + false); + aContent->AddEventListener(NS_LITERAL_STRING("click"), this, false, + false); + aContent->AddEventListener(NS_LITERAL_STRING("dblclick"), this, false, + false); + aContent->AddEventListener(NS_LITERAL_STRING("mouseover"), this, false, + false); + aContent->AddEventListener(NS_LITERAL_STRING("mouseout"), this, false, + false); + aContent->AddEventListener(NS_LITERAL_STRING("keypress"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("keydown"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("keyup"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("drop"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("drag"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("dragenter"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("dragover"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("dragleave"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("dragexit"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("dragstart"), this, true); + aContent->AddEventListener(NS_LITERAL_STRING("dragend"), this, true); + aContent->AddSystemEventListener(NS_LITERAL_STRING("compositionstart"), + this, true); + aContent->AddSystemEventListener(NS_LITERAL_STRING("compositionend"), this, + true); + aContent->AddSystemEventListener(NS_LITERAL_STRING("text"), this, true); + + return NS_OK; +} + +void* nsPluginInstanceOwner::GetPluginPort() +{ + void* result = nullptr; + if (mWidget) { +#ifdef XP_WIN + if (!mPluginWindow || mPluginWindow->type == NPWindowTypeWindow) +#endif + result = mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT); // HWND/gdk window + } + + return result; +} + +void nsPluginInstanceOwner::ReleasePluginPort(void * pluginPort) +{ +} + +NS_IMETHODIMP nsPluginInstanceOwner::CreateWidget(void) +{ + NS_ENSURE_TRUE(mPluginWindow, NS_ERROR_NULL_POINTER); + + nsresult rv = NS_ERROR_FAILURE; + + // Can't call this twice! + if (mWidget) { + NS_WARNING("Trying to create a plugin widget twice!"); + return NS_ERROR_FAILURE; + } + + bool windowless = false; + mInstance->IsWindowless(&windowless); + if (!windowless) { + // Try to get a parent widget, on some platforms widget creation will fail without + // a parent. + nsCOMPtr<nsIWidget> parentWidget; + nsIDocument *doc = nullptr; + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (content) { + doc = content->OwnerDoc(); + parentWidget = nsContentUtils::WidgetForDocument(doc); +#ifndef XP_MACOSX + // If we're running in the content process, we need a remote widget created in chrome. + if (XRE_IsContentProcess()) { + if (nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow()) { + if (nsCOMPtr<nsPIDOMWindowOuter> topWindow = window->GetTop()) { + dom::TabChild* tc = dom::TabChild::GetFrom(topWindow); + if (tc) { + // This returns a PluginWidgetProxy which remotes a number of calls. + rv = tc->CreatePluginWidget(parentWidget.get(), getter_AddRefs(mWidget)); + if (NS_FAILED(rv)) { + return rv; + } + } + } + } + } +#endif // XP_MACOSX + } + +#ifndef XP_MACOSX + // A failure here is terminal since we can't fall back on the non-e10s code + // path below. + if (!mWidget && XRE_IsContentProcess()) { + return NS_ERROR_UNEXPECTED; + } +#endif // XP_MACOSX + + if (!mWidget) { + // native (single process) + mWidget = do_CreateInstance(kWidgetCID, &rv); + nsWidgetInitData initData; + initData.mWindowType = eWindowType_plugin; + initData.mUnicode = false; + initData.clipChildren = true; + initData.clipSiblings = true; + rv = mWidget->Create(parentWidget.get(), nullptr, + LayoutDeviceIntRect(0, 0, 0, 0), &initData); + if (NS_FAILED(rv)) { + mWidget->Destroy(); + mWidget = nullptr; + return rv; + } + } + + mWidget->EnableDragDrop(true); + mWidget->Show(false); + mWidget->Enable(false); + } + + if (mPluginFrame) { + // nullptr widget is fine, will result in windowless setup. + mPluginFrame->PrepForDrawing(mWidget); + } + + if (windowless) { + mPluginWindow->type = NPWindowTypeDrawable; + + // this needs to be a HDC according to the spec, but I do + // not see the right way to release it so let's postpone + // passing HDC till paint event when it is really + // needed. Change spec? + mPluginWindow->window = nullptr; +#ifdef MOZ_X11 + // Fill in the display field. + NPSetWindowCallbackStruct* ws_info = + static_cast<NPSetWindowCallbackStruct*>(mPluginWindow->ws_info); + ws_info->display = DefaultXDisplay(); + + nsAutoCString description; + GetPluginDescription(description); + NS_NAMED_LITERAL_CSTRING(flash10Head, "Shockwave Flash 10."); + mFlash10Quirks = StringBeginsWith(description, flash10Head); +#endif + } else if (mWidget) { + // mPluginWindow->type is used in |GetPluginPort| so it must + // be initialized first + mPluginWindow->type = NPWindowTypeWindow; + mPluginWindow->window = GetPluginPort(); + // tell the plugin window about the widget + mPluginWindow->SetPluginWidget(mWidget); + + // tell the widget about the current plugin instance owner. + nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget); + if (pluginWidget) { + pluginWidget->SetPluginInstanceOwner(this); + } + } + +#ifdef XP_MACOSX + if (GetDrawingModel() == NPDrawingModelCoreAnimation) { + AddToCARefreshTimer(); + } +#endif + + mWidgetCreationComplete = true; + + return NS_OK; +} + +// Mac specific code to fix up the port location and clipping region +#ifdef XP_MACOSX + +void nsPluginInstanceOwner::FixUpPluginWindow(int32_t inPaintState) +{ + if (!mPluginWindow || !mInstance || !mPluginFrame) { + return; + } + + SetPluginPort(); + + LayoutDeviceIntSize widgetClip = mPluginFrame->GetWidgetlessClipRect().Size(); + + mPluginWindow->x = 0; + mPluginWindow->y = 0; + + NPRect oldClipRect = mPluginWindow->clipRect; + + // fix up the clipping region + mPluginWindow->clipRect.top = 0; + mPluginWindow->clipRect.left = 0; + + if (inPaintState == ePluginPaintDisable) { + mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top; + mPluginWindow->clipRect.right = mPluginWindow->clipRect.left; + } + else if (inPaintState == ePluginPaintEnable) + { + mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top + widgetClip.height; + mPluginWindow->clipRect.right = mPluginWindow->clipRect.left + widgetClip.width; + } + + // if the clip rect changed, call SetWindow() + // (RealPlayer needs this to draw correctly) + if (mPluginWindow->clipRect.left != oldClipRect.left || + mPluginWindow->clipRect.top != oldClipRect.top || + mPluginWindow->clipRect.right != oldClipRect.right || + mPluginWindow->clipRect.bottom != oldClipRect.bottom) + { + if (UseAsyncRendering()) { + mInstance->AsyncSetWindow(mPluginWindow); + } + else { + mPluginWindow->CallSetWindow(mInstance); + } + } + + // After the first NPP_SetWindow call we need to send an initial + // top-level window focus event. + if (!mSentInitialTopLevelWindowEvent) { + // Set this before calling ProcessEvent to avoid endless recursion. + mSentInitialTopLevelWindowEvent = true; + + bool isActive = WindowIsActive(); + SendWindowFocusChanged(isActive); + mLastWindowIsActive = isActive; + } +} + +void +nsPluginInstanceOwner::WindowFocusMayHaveChanged() +{ + if (!mSentInitialTopLevelWindowEvent) { + return; + } + + bool isActive = WindowIsActive(); + if (isActive != mLastWindowIsActive) { + SendWindowFocusChanged(isActive); + mLastWindowIsActive = isActive; + } +} + +bool +nsPluginInstanceOwner::WindowIsActive() +{ + if (!mPluginFrame) { + return false; + } + + EventStates docState = mPluginFrame->GetContent()->OwnerDoc()->GetDocumentState(); + return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE); +} + +void +nsPluginInstanceOwner::SendWindowFocusChanged(bool aIsActive) +{ + if (!mInstance) { + return; + } + + NPCocoaEvent cocoaEvent; + InitializeNPCocoaEvent(&cocoaEvent); + cocoaEvent.type = NPCocoaEventWindowFocusChanged; + cocoaEvent.data.focus.hasFocus = aIsActive; + mInstance->HandleEvent(&cocoaEvent, + nullptr, + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); +} + +void +nsPluginInstanceOwner::HidePluginWindow() +{ + if (!mPluginWindow || !mInstance) { + return; + } + + mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top; + mPluginWindow->clipRect.right = mPluginWindow->clipRect.left; + mWidgetVisible = false; + if (UseAsyncRendering()) { + mInstance->AsyncSetWindow(mPluginWindow); + } else { + mInstance->SetWindow(mPluginWindow); + } +} + +#else // XP_MACOSX + +void nsPluginInstanceOwner::UpdateWindowPositionAndClipRect(bool aSetWindow) +{ + if (!mPluginWindow) + return; + + // For windowless plugins a non-empty clip rectangle will be + // passed to the plugin during paint, an additional update + // of the the clip rectangle here is not required + if (aSetWindow && !mWidget && mPluginWindowVisible && !UseAsyncRendering()) + return; + + const NPWindow oldWindow = *mPluginWindow; + + bool windowless = (mPluginWindow->type == NPWindowTypeDrawable); + nsIntPoint origin = mPluginFrame->GetWindowOriginInPixels(windowless); + + mPluginWindow->x = origin.x; + mPluginWindow->y = origin.y; + + mPluginWindow->clipRect.left = 0; + mPluginWindow->clipRect.top = 0; + + if (mPluginWindowVisible && mPluginDocumentActiveState) { + mPluginWindow->clipRect.right = mPluginWindow->width; + mPluginWindow->clipRect.bottom = mPluginWindow->height; + } else { + mPluginWindow->clipRect.right = 0; + mPluginWindow->clipRect.bottom = 0; + } + + if (!aSetWindow) + return; + + if (mPluginWindow->x != oldWindow.x || + mPluginWindow->y != oldWindow.y || + mPluginWindow->clipRect.left != oldWindow.clipRect.left || + mPluginWindow->clipRect.top != oldWindow.clipRect.top || + mPluginWindow->clipRect.right != oldWindow.clipRect.right || + mPluginWindow->clipRect.bottom != oldWindow.clipRect.bottom) { + CallSetWindow(); + } +} + +void +nsPluginInstanceOwner::UpdateWindowVisibility(bool aVisible) +{ + mPluginWindowVisible = aVisible; + UpdateWindowPositionAndClipRect(true); +} +#endif // XP_MACOSX + +void +nsPluginInstanceOwner::ResolutionMayHaveChanged() +{ +#if defined(XP_MACOSX) || defined(XP_WIN) + double scaleFactor = 1.0; + GetContentsScaleFactor(&scaleFactor); + if (scaleFactor != mLastScaleFactor) { + ContentsScaleFactorChanged(scaleFactor); + mLastScaleFactor = scaleFactor; + } +#endif + float zoomFactor = 1.0; + GetCSSZoomFactor(&zoomFactor); + if (zoomFactor != mLastCSSZoomFactor) { + if (mInstance) { + mInstance->CSSZoomFactorChanged(zoomFactor); + } + mLastCSSZoomFactor = zoomFactor; + } + +} + +void +nsPluginInstanceOwner::UpdateDocumentActiveState(bool aIsActive) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + + mPluginDocumentActiveState = aIsActive; +#ifndef XP_MACOSX + UpdateWindowPositionAndClipRect(true); + +#ifdef MOZ_WIDGET_ANDROID + if (mInstance) { + if (!mPluginDocumentActiveState) { + RemovePluginView(); + } + + mInstance->NotifyOnScreen(mPluginDocumentActiveState); + + // This is, perhaps, incorrect. It is supposed to be sent + // when "the webview has paused or resumed". The side effect + // is that Flash video players pause or resume (if they were + // playing before) based on the value here. I personally think + // we want that on Android when switching to another tab, so + // that's why we call it here. + mInstance->NotifyForeground(mPluginDocumentActiveState); + } +#endif // #ifdef MOZ_WIDGET_ANDROID + + // We don't have a connection to PluginWidgetParent in the chrome + // process when dealing with tab visibility changes, so this needs + // to be forwarded over after the active state is updated. If we + // don't hide plugin widgets in hidden tabs, the native child window + // in chrome will remain visible after a tab switch. + if (mWidget && XRE_IsContentProcess()) { + mWidget->Show(aIsActive); + mWidget->Enable(aIsActive); + } +#endif // #ifndef XP_MACOSX +} + +NS_IMETHODIMP +nsPluginInstanceOwner::CallSetWindow() +{ + if (!mWidgetCreationComplete) { + // No widget yet, we can't run this code + return NS_OK; + } + if (mPluginFrame) { + mPluginFrame->CallSetWindow(false); + } else if (mInstance) { + if (UseAsyncRendering()) { + mInstance->AsyncSetWindow(mPluginWindow); + } else { + mInstance->SetWindow(mPluginWindow); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPluginInstanceOwner::GetContentsScaleFactor(double *result) +{ + NS_ENSURE_ARG_POINTER(result); + double scaleFactor = 1.0; + // On Mac, device pixels need to be translated to (and from) "display pixels" + // for plugins. On other platforms, plugin coordinates are always in device + // pixels. +#if defined(XP_MACOSX) || defined(XP_WIN) + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + nsIPresShell* presShell = nsContentUtils::FindPresShellForDocument(content->OwnerDoc()); + if (presShell) { + scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/ + presShell->GetPresContext()->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom(); + } +#endif + *result = scaleFactor; + return NS_OK; +} + +void +nsPluginInstanceOwner::GetCSSZoomFactor(float *result) +{ + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + nsIPresShell* presShell = nsContentUtils::FindPresShellForDocument(content->OwnerDoc()); + if (presShell) { + *result = presShell->GetPresContext()->DeviceContext()->GetFullZoom(); + } else { + *result = 1.0; + } +} + +void nsPluginInstanceOwner::SetFrame(nsPluginFrame *aFrame) +{ + // Don't do anything if the frame situation hasn't changed. + if (mPluginFrame == aFrame) { + return; + } + + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + + // If we already have a frame that is changing or going away... + if (mPluginFrame) { + if (content && content->OwnerDoc() && content->OwnerDoc()->GetWindow()) { + nsCOMPtr<EventTarget> windowRoot = content->OwnerDoc()->GetWindow()->GetTopWindowRoot(); + if (windowRoot) { + windowRoot->RemoveEventListener(NS_LITERAL_STRING("activate"), + this, false); + windowRoot->RemoveEventListener(NS_LITERAL_STRING("deactivate"), + this, false); + windowRoot->RemoveEventListener(NS_LITERAL_STRING("MozPerformDelayedBlur"), + this, false); + } + } + + // Make sure the old frame isn't holding a reference to us. + mPluginFrame->SetInstanceOwner(nullptr); + } + + // Swap in the new frame (or no frame) + mPluginFrame = aFrame; + + // Set up a new frame + if (mPluginFrame) { + mPluginFrame->SetInstanceOwner(this); + // Can only call PrepForDrawing on an object frame once. Don't do it here unless + // widget creation is complete. Doesn't matter if we actually have a widget. + if (mWidgetCreationComplete) { + mPluginFrame->PrepForDrawing(mWidget); + } + mPluginFrame->FixupWindow(mPluginFrame->GetContentRectRelativeToSelf().Size()); + mPluginFrame->InvalidateFrame(); + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + const nsIContent* content = aFrame->GetContent(); + if (fm && content) { + mContentFocused = (content == fm->GetFocusedContent()); + } + + // Register for widget-focus events on the window root. + if (content && content->OwnerDoc() && content->OwnerDoc()->GetWindow()) { + nsCOMPtr<EventTarget> windowRoot = content->OwnerDoc()->GetWindow()->GetTopWindowRoot(); + if (windowRoot) { + windowRoot->AddEventListener(NS_LITERAL_STRING("activate"), + this, false, false); + windowRoot->AddEventListener(NS_LITERAL_STRING("deactivate"), + this, false, false); + windowRoot->AddEventListener(NS_LITERAL_STRING("MozPerformDelayedBlur"), + this, false, false); + } + } + } +} + +nsPluginFrame* nsPluginInstanceOwner::GetFrame() +{ + return mPluginFrame; +} + +NS_IMETHODIMP nsPluginInstanceOwner::PrivateModeChanged(bool aEnabled) +{ + return mInstance ? mInstance->PrivateModeStateChanged(aEnabled) : NS_OK; +} + +already_AddRefed<nsIURI> nsPluginInstanceOwner::GetBaseURI() const +{ + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (!content) { + return nullptr; + } + return content->GetBaseURI(); +} + +// static +void +nsPluginInstanceOwner::GeneratePluginEvent( + const WidgetCompositionEvent* aSrcCompositionEvent, + WidgetCompositionEvent* aDistCompositionEvent) +{ +#ifdef XP_WIN + NPEvent newEvent; + switch (aDistCompositionEvent->mMessage) { + case eCompositionChange: { + newEvent.event = WM_IME_COMPOSITION; + newEvent.wParam = 0; + if (aSrcCompositionEvent && + (aSrcCompositionEvent->mMessage == eCompositionCommit || + aSrcCompositionEvent->mMessage == eCompositionCommitAsIs)) { + newEvent.lParam = GCS_RESULTSTR; + } else { + newEvent.lParam = GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE; + } + TextRangeArray* ranges = aDistCompositionEvent->mRanges; + if (ranges && ranges->HasCaret()) { + newEvent.lParam |= GCS_CURSORPOS; + } + break; + } + + case eCompositionStart: + newEvent.event = WM_IME_STARTCOMPOSITION; + newEvent.wParam = 0; + newEvent.lParam = 0; + break; + + case eCompositionEnd: + newEvent.event = WM_IME_ENDCOMPOSITION; + newEvent.wParam = 0; + newEvent.lParam = 0; + break; + + default: + return; + } + aDistCompositionEvent->mPluginEvent.Copy(newEvent); +#endif +} + +// nsPluginDOMContextMenuListener class implementation + +nsPluginDOMContextMenuListener::nsPluginDOMContextMenuListener(nsIContent* aContent) +{ + aContent->AddEventListener(NS_LITERAL_STRING("contextmenu"), this, true); +} + +nsPluginDOMContextMenuListener::~nsPluginDOMContextMenuListener() +{ +} + +NS_IMPL_ISUPPORTS(nsPluginDOMContextMenuListener, + nsIDOMEventListener) + +NS_IMETHODIMP +nsPluginDOMContextMenuListener::HandleEvent(nsIDOMEvent* aEvent) +{ + aEvent->PreventDefault(); // consume event + + return NS_OK; +} + +void nsPluginDOMContextMenuListener::Destroy(nsIContent* aContent) +{ + // Unregister context menu listener + aContent->RemoveEventListener(NS_LITERAL_STRING("contextmenu"), this, true); +} diff --git a/dom/plugins/base/nsPluginInstanceOwner.h b/dom/plugins/base/nsPluginInstanceOwner.h new file mode 100644 index 000000000..589bcb02c --- /dev/null +++ b/dom/plugins/base/nsPluginInstanceOwner.h @@ -0,0 +1,420 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* 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/. */ + +#ifndef nsPluginInstanceOwner_h_ +#define nsPluginInstanceOwner_h_ + +#include "mozilla/Attributes.h" +#include "npapi.h" +#include "nsCOMPtr.h" +#include "nsIKeyEventInPluginCallback.h" +#include "nsIPluginInstanceOwner.h" +#include "nsIPrivacyTransitionObserver.h" +#include "nsIDOMEventListener.h" +#include "nsPluginHost.h" +#include "nsPluginNativeWindow.h" +#include "nsWeakReference.h" +#include "gfxRect.h" + +#ifdef XP_MACOSX +#include "mozilla/gfx/QuartzSupport.h" +#include <ApplicationServices/ApplicationServices.h> +#endif + +class nsIInputStream; +class nsPluginDOMContextMenuListener; +class nsPluginFrame; +class nsDisplayListBuilder; + +#if defined(MOZ_X11) || defined(ANDROID) +class gfxContext; +#endif + +namespace mozilla { +class TextComposition; +namespace dom { +struct MozPluginParameter; +} // namespace dom +namespace widget { +class PuppetWidget; +} // namespace widget +} // namespace mozilla + +using mozilla::widget::PuppetWidget; + +#ifdef MOZ_X11 +#include "gfxXlibNativeRenderer.h" +#endif + +class nsPluginInstanceOwner final : public nsIPluginInstanceOwner + , public nsIDOMEventListener + , public nsIPrivacyTransitionObserver + , public nsIKeyEventInPluginCallback + , public nsSupportsWeakReference +{ +public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + nsPluginInstanceOwner(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPLUGININSTANCEOWNER + NS_DECL_NSIPRIVACYTRANSITIONOBSERVER + + NS_IMETHOD GetURL(const char *aURL, const char *aTarget, + nsIInputStream *aPostStream, + void *aHeadersData, uint32_t aHeadersDataLen, + bool aDoCheckLoadURIChecks) override; + + NPBool ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace, + double *destX, double *destY, NPCoordinateSpace destSpace) override; + + NPError InitAsyncSurface(NPSize *size, NPImageFormat format, + void *initData, NPAsyncSurface *surface) override; + NPError FinalizeAsyncSurface(NPAsyncSurface *surface) override; + void SetCurrentAsyncSurface(NPAsyncSurface *surface, NPRect *changed) override; + + /** + * Get the type of the HTML tag that was used ot instantiate this + * plugin. Currently supported tags are EMBED, OBJECT and APPLET. + */ + NS_IMETHOD GetTagType(nsPluginTagType *aResult); + + void GetParameters(nsTArray<mozilla::dom::MozPluginParameter>& parameters); + void GetAttributes(nsTArray<mozilla::dom::MozPluginParameter>& attributes); + + /** + * Returns the DOM element corresponding to the tag which references + * this plugin in the document. + * + * @param aDOMElement - resulting DOM element + * @result - NS_OK if this operation was successful + */ + NS_IMETHOD GetDOMElement(nsIDOMElement* * aResult); + + // nsIDOMEventListener interfaces + NS_DECL_NSIDOMEVENTLISTENER + + nsresult ProcessMouseDown(nsIDOMEvent* aKeyEvent); + nsresult ProcessKeyPress(nsIDOMEvent* aKeyEvent); + nsresult Destroy(); + +#ifdef XP_WIN + void Paint(const RECT& aDirty, HDC aDC); +#elif defined(XP_MACOSX) + void Paint(const gfxRect& aDirtyRect, CGContextRef cgContext); + void RenderCoreAnimation(CGContextRef aCGContext, int aWidth, int aHeight); + void DoCocoaEventDrawRect(const gfxRect& aDrawRect, CGContextRef cgContext); +#elif defined(MOZ_X11) || defined(ANDROID) + void Paint(gfxContext* aContext, + const gfxRect& aFrameRect, + const gfxRect& aDirtyRect); +#endif + + //locals + + nsresult Init(nsIContent* aContent); + + void* GetPluginPort(); + void ReleasePluginPort(void* pluginPort); + + nsEventStatus ProcessEvent(const mozilla::WidgetGUIEvent& anEvent); + + static void GeneratePluginEvent( + const mozilla::WidgetCompositionEvent* aSrcCompositionEvent, + mozilla::WidgetCompositionEvent* aDistCompositionEvent); + +#if defined(XP_WIN) + void SetWidgetWindowAsParent(HWND aWindowToAdopt); + nsresult SetNetscapeWindowAsParent(HWND aWindowToAdopt); +#endif + +#ifdef XP_MACOSX + enum { ePluginPaintEnable, ePluginPaintDisable }; + + void WindowFocusMayHaveChanged(); + + bool WindowIsActive(); + void SendWindowFocusChanged(bool aIsActive); + NPDrawingModel GetDrawingModel(); + bool IsRemoteDrawingCoreAnimation(); + + NPEventModel GetEventModel(); + static void CARefresh(nsITimer *aTimer, void *aClosure); + void AddToCARefreshTimer(); + void RemoveFromCARefreshTimer(); + // This calls into the plugin (NPP_SetWindow) and can run script. + void FixUpPluginWindow(int32_t inPaintState); + void HidePluginWindow(); + // Set plugin port info in the plugin (in the 'window' member of the + // NPWindow structure passed to the plugin by SetWindow()). + void SetPluginPort(); +#else // XP_MACOSX + void UpdateWindowPositionAndClipRect(bool aSetWindow); + void UpdateWindowVisibility(bool aVisible); +#endif // XP_MACOSX + + void ResolutionMayHaveChanged(); +#if defined(XP_MACOSX) || defined(XP_WIN) + nsresult ContentsScaleFactorChanged(double aContentsScaleFactor); +#endif + + void UpdateDocumentActiveState(bool aIsActive); + + void SetFrame(nsPluginFrame *aFrame); + nsPluginFrame* GetFrame(); + + uint32_t GetLastEventloopNestingLevel() const { + return mLastEventloopNestingLevel; + } + + static uint32_t GetEventloopNestingLevel(); + + void ConsiderNewEventloopNestingLevel() { + uint32_t currentLevel = GetEventloopNestingLevel(); + + if (currentLevel < mLastEventloopNestingLevel) { + mLastEventloopNestingLevel = currentLevel; + } + } + + const char* GetPluginName() + { + if (mInstance && mPluginHost) { + const char* name = nullptr; + if (NS_SUCCEEDED(mPluginHost->GetPluginName(mInstance, &name)) && name) + return name; + } + return ""; + } + +#ifdef MOZ_X11 + void GetPluginDescription(nsACString& aDescription) + { + aDescription.Truncate(); + if (mInstance && mPluginHost) { + nsCOMPtr<nsIPluginTag> pluginTag; + + mPluginHost->GetPluginTagForInstance(mInstance, + getter_AddRefs(pluginTag)); + if (pluginTag) { + pluginTag->GetDescription(aDescription); + } + } + } +#endif + + bool SendNativeEvents() + { +#ifdef XP_WIN + // XXX we should remove the plugin name check + return mPluginWindow->type == NPWindowTypeDrawable && + (MatchPluginName("Shockwave Flash") || + MatchPluginName("Test Plug-in")); +#elif defined(MOZ_X11) || defined(XP_MACOSX) + return true; +#else + return false; +#endif + } + + bool MatchPluginName(const char *aPluginName) + { + return strncmp(GetPluginName(), aPluginName, strlen(aPluginName)) == 0; + } + + void NotifyPaintWaiter(nsDisplayListBuilder* aBuilder); + + // Returns the image container that has our currently displayed image. + already_AddRefed<mozilla::layers::ImageContainer> GetImageContainer(); + // Returns true if this is windowed plugin that can return static captures + // for scroll operations. + bool NeedsScrollImageLayer(); + + void DidComposite(); + + /** + * Returns the bounds of the current async-rendered surface. This can only + * change in response to messages received by the event loop (i.e. not during + * painting). + */ + nsIntSize GetCurrentImageSize(); + + // Methods to update the background image we send to async plugins. + // The eventual target of these operations is PluginInstanceParent, + // but it takes several hops to get there. + void SetBackgroundUnknown(); + already_AddRefed<DrawTarget> BeginUpdateBackground(const nsIntRect& aRect); + void EndUpdateBackground(const nsIntRect& aRect); + + bool UseAsyncRendering(); + + already_AddRefed<nsIURI> GetBaseURI() const; + +#ifdef MOZ_WIDGET_ANDROID + // Returns the image container for the specified VideoInfo + void GetVideos(nsTArray<nsNPAPIPluginInstance::VideoInfo*>& aVideos); + already_AddRefed<mozilla::layers::ImageContainer> GetImageContainerForVideo(nsNPAPIPluginInstance::VideoInfo* aVideoInfo); + + void Invalidate(); + void Recomposite(); + + void RequestFullScreen(); + void ExitFullScreen(); + + // Called from nsAppShell when we removed the fullscreen view. + static void ExitFullScreen(jobject view); +#endif + + void NotifyHostAsyncInitFailed(); + void NotifyHostCreateWidget(); + void NotifyDestroyPending(); + + bool GetCompositionString(uint32_t aIndex, nsTArray<uint8_t>* aString, + int32_t* aLength); + bool SetCandidateWindow( + const mozilla::widget::CandidateWindowPosition& aPosition); + bool RequestCommitOrCancel(bool aCommitted); + + // See nsIKeyEventInPluginCallback + virtual void HandledWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, + bool aIsConsumed) override; + + /** + * OnWindowedPluginKeyEvent() is called when the plugin process receives + * native key event directly. + * + * @param aNativeKeyData The key event which was received by the + * plugin process directly. + */ + void OnWindowedPluginKeyEvent( + const mozilla::NativeEventData& aNativeKeyData); + + void GetCSSZoomFactor(float *result); +private: + virtual ~nsPluginInstanceOwner(); + + // return FALSE if LayerSurface dirty (newly created and don't have valid plugin content yet) + bool IsUpToDate() + { + nsIntSize size; + return NS_SUCCEEDED(mInstance->GetImageSize(&size)) && + size == nsIntSize(mPluginWindow->width, mPluginWindow->height); + } + +#ifdef MOZ_WIDGET_ANDROID + mozilla::LayoutDeviceRect GetPluginRect(); + bool AddPluginView(const mozilla::LayoutDeviceRect& aRect = mozilla::LayoutDeviceRect(0, 0, 0, 0)); + void RemovePluginView(); + + bool mFullScreen; + void* mJavaView; +#endif + +#if defined(XP_WIN) + nsIWidget* GetContainingWidgetIfOffset(); + already_AddRefed<mozilla::TextComposition> GetTextComposition(); + void HandleNoConsumedCompositionMessage( + mozilla::WidgetCompositionEvent* aCompositionEvent, + const NPEvent* aPluginEvent); + bool mGotCompositionData; + bool mSentStartComposition; + bool mPluginDidNotHandleIMEComposition; +#endif + + nsPluginNativeWindow *mPluginWindow; + RefPtr<nsNPAPIPluginInstance> mInstance; + nsPluginFrame *mPluginFrame; + nsWeakPtr mContent; // WEAK, content owns us + nsCString mDocumentBase; + bool mWidgetCreationComplete; + nsCOMPtr<nsIWidget> mWidget; + RefPtr<nsPluginHost> mPluginHost; + +#ifdef XP_MACOSX + static nsCOMPtr<nsITimer> *sCATimer; + static nsTArray<nsPluginInstanceOwner*> *sCARefreshListeners; + bool mSentInitialTopLevelWindowEvent; + bool mLastWindowIsActive; + bool mLastContentFocused; + // True if, the next time the window is activated, we should blur ourselves. + bool mShouldBlurOnActivate; +#endif + double mLastScaleFactor; + double mLastCSSZoomFactor; + // Initially, the event loop nesting level we were created on, it's updated + // if we detect the appshell is on a lower level as long as we're not stopped. + // We delay DoStopPlugin() until the appshell reaches this level or lower. + uint32_t mLastEventloopNestingLevel; + bool mContentFocused; + bool mWidgetVisible; // used on Mac to store our widget's visible state +#ifdef MOZ_X11 + // Used with windowless plugins only, initialized in CreateWidget(). + bool mFlash10Quirks; +#endif + bool mPluginWindowVisible; + bool mPluginDocumentActiveState; + +#ifdef XP_MACOSX + NPEventModel mEventModel; + // This is a hack! UseAsyncRendering() can incorrectly return false + // when we don't have an object frame (possible as of bug 90268). + // We hack around this by always returning true if we've ever + // returned true. + bool mUseAsyncRendering; +#endif + + // pointer to wrapper for nsIDOMContextMenuListener + RefPtr<nsPluginDOMContextMenuListener> mCXMenuListener; + + nsresult DispatchKeyToPlugin(nsIDOMEvent* aKeyEvent); + nsresult DispatchMouseToPlugin(nsIDOMEvent* aMouseEvent, + bool aAllowPropagate = false); + nsresult DispatchFocusToPlugin(nsIDOMEvent* aFocusEvent); + nsresult DispatchCompositionToPlugin(nsIDOMEvent* aEvent); + +#ifdef XP_WIN + void CallDefaultProc(const mozilla::WidgetGUIEvent* aEvent); +#endif + +#ifdef XP_MACOSX + static NPBool ConvertPointPuppet(PuppetWidget *widget, nsPluginFrame* pluginFrame, + double sourceX, double sourceY, NPCoordinateSpace sourceSpace, + double *destX, double *destY, NPCoordinateSpace destSpace); + static NPBool ConvertPointNoPuppet(nsIWidget *widget, nsPluginFrame* pluginFrame, + double sourceX, double sourceY, NPCoordinateSpace sourceSpace, + double *destX, double *destY, NPCoordinateSpace destSpace); + void PerformDelayedBlurs(); +#endif // XP_MACOSX + + int mLastMouseDownButtonType; + +#ifdef MOZ_X11 + class Renderer : public gfxXlibNativeRenderer + { + public: + Renderer(NPWindow* aWindow, nsPluginInstanceOwner* aInstanceOwner, + const nsIntSize& aPluginSize, const nsIntRect& aDirtyRect) + : mWindow(aWindow), mInstanceOwner(aInstanceOwner), + mPluginSize(aPluginSize), mDirtyRect(aDirtyRect) + {} + virtual nsresult DrawWithXlib(cairo_surface_t* surface, + nsIntPoint offset, + nsIntRect* clipRects, uint32_t numClipRects) override; + private: + NPWindow* mWindow; + nsPluginInstanceOwner* mInstanceOwner; + const nsIntSize& mPluginSize; + const nsIntRect& mDirtyRect; + }; +#endif + + bool mWaitingForPaint; +}; + +#endif // nsPluginInstanceOwner_h_ + diff --git a/dom/plugins/base/nsPluginLogging.h b/dom/plugins/base/nsPluginLogging.h new file mode 100644 index 000000000..b51d3531e --- /dev/null +++ b/dom/plugins/base/nsPluginLogging.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* Plugin Module Logging usage instructions and includes */ +//////////////////////////////////////////////////////////////////////////////// +#ifndef nsPluginLogging_h__ +#define nsPluginLogging_h__ + +#include "mozilla/Logging.h" + +#ifndef PLUGIN_LOGGING // allow external override +#define PLUGIN_LOGGING 1 // master compile-time switch for pluging logging +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Basic Plugin Logging Usage Instructions +// +// 1. Set this environment variable: MOZ_LOG=<name>:<level> + +// Choose the <name> and <level> from this list (no quotes): + +// Log Names <name> +#define NPN_LOG_NAME "PluginNPN" +#define NPP_LOG_NAME "PluginNPP" +#define PLUGIN_LOG_NAME "Plugin" + +// Levels <level> +#define PLUGIN_LOG_ALWAYS mozilla::LogLevel::Error +#define PLUGIN_LOG_BASIC mozilla::LogLevel::Info +#define PLUGIN_LOG_NORMAL mozilla::LogLevel::Debug +#define PLUGIN_LOG_NOISY mozilla::LogLevel::Verbose + +// 2. You can combine logs and levels by separating them with a comma: +// My favorite Win32 Example: SET MOZ_LOG=Plugin:5,PluginNPP:5,PluginNPN:5 + +// 3. Instead of output going to the console, you can log to a file. Additionally, set the +// MOZ_LOG_FILE environment variable to point to the full path of a file. +// My favorite Win32 Example: SET MOZ_LOG_FILE=c:\temp\pluginLog.txt + +// 4. For complete information see the Gecko Developer guide: +// https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Gecko_Logging + + +#ifdef PLUGIN_LOGGING + +class nsPluginLogging +{ +public: + static mozilla::LazyLogModule gNPNLog; // 4.x NP API, calls into navigator + static mozilla::LazyLogModule gNPPLog; // 4.x NP API, calls into plugin + static mozilla::LazyLogModule gPluginLog; // general plugin log +}; + +#endif // PLUGIN_LOGGING + +// Quick-use macros +#ifdef PLUGIN_LOGGING + #define NPN_PLUGIN_LOG(a, b) \ + PR_BEGIN_MACRO \ + MOZ_LOG(nsPluginLogging::gNPNLog, a, b); \ + PR_LogFlush(); \ + PR_END_MACRO +#else + #define NPN_PLUGIN_LOG(a, b) +#endif + +#ifdef PLUGIN_LOGGING + #define NPP_PLUGIN_LOG(a, b) \ + PR_BEGIN_MACRO \ + MOZ_LOG(nsPluginLogging::gNPPLog, a, b); \ + PR_LogFlush(); \ + PR_END_MACRO +#else + #define NPP_PLUGIN_LOG(a, b) +#endif + +#ifdef PLUGIN_LOGGING + #define PLUGIN_LOG(a, b) \ + PR_BEGIN_MACRO \ + MOZ_LOG(nsPluginLogging::gPluginLog, a, b); \ + PR_LogFlush(); \ + PR_END_MACRO +#else + #define PLUGIN_LOG(a, b) +#endif + +#endif // nsPluginLogging_h__ + diff --git a/dom/plugins/base/nsPluginManifestLineReader.h b/dom/plugins/base/nsPluginManifestLineReader.h new file mode 100644 index 000000000..be7d77044 --- /dev/null +++ b/dom/plugins/base/nsPluginManifestLineReader.h @@ -0,0 +1,102 @@ +/* -*- 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/. */ + +#ifndef nsPluginManifestLineReader_h_ +#define nsPluginManifestLineReader_h_ + +#include "nspr.h" +#include "nsDebug.h" + +#ifdef XP_WIN +#define PLUGIN_REGISTRY_FIELD_DELIMITER '|' +#else +#define PLUGIN_REGISTRY_FIELD_DELIMITER ':' +#endif + +#define PLUGIN_REGISTRY_END_OF_LINE_MARKER '$' + +class nsPluginManifestLineReader +{ + public: + nsPluginManifestLineReader() {mBase = mCur = mNext = mLimit = 0;} + ~nsPluginManifestLineReader() { if (mBase) delete[] mBase; mBase=0;} + + char* Init(uint32_t flen) + { + mBase = mCur = mNext = new char[flen + 1]; + if (mBase) { + mLimit = mBase + flen; + *mLimit = 0; + } + mLength = 0; + return mBase; + } + + bool NextLine() + { + if (mNext >= mLimit) + return false; + + mCur = mNext; + mLength = 0; + + char *lastDelimiter = 0; + while(mNext < mLimit) { + if (IsEOL(*mNext)) { + if (lastDelimiter) { + if (lastDelimiter && *(mNext - 1) != PLUGIN_REGISTRY_END_OF_LINE_MARKER) + return false; + *lastDelimiter = '\0'; + } else { + *mNext = '\0'; + } + + for (++mNext; mNext < mLimit; ++mNext) { + if (!IsEOL(*mNext)) + break; + } + return true; + } + if (*mNext == PLUGIN_REGISTRY_FIELD_DELIMITER) + lastDelimiter = mNext; + ++mNext; + ++mLength; + } + return false; + } + + int ParseLine(char** chunks, int maxChunks) + { + NS_ASSERTION(mCur && maxChunks && chunks, "bad call to ParseLine"); + int found = 0; + chunks[found++] = mCur; + + if (found < maxChunks) { + for (char* cur = mCur; *cur; cur++) { + if (*cur == PLUGIN_REGISTRY_FIELD_DELIMITER) { + *cur = 0; + chunks[found++] = cur + 1; + if (found == maxChunks) + break; + } + } + } + return found; + } + + char* LinePtr() { return mCur; } + uint32_t LineLength() { return mLength; } + + bool IsEOL(char c) {return c == '\n' || c == '\r';} + + char* mBase; + private: + char* mCur; + uint32_t mLength; + char* mNext; + char* mLimit; +}; + +#endif diff --git a/dom/plugins/base/nsPluginModule.cpp b/dom/plugins/base/nsPluginModule.cpp new file mode 100644 index 000000000..539db8852 --- /dev/null +++ b/dom/plugins/base/nsPluginModule.cpp @@ -0,0 +1,29 @@ +/* -*- 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/ModuleUtils.h" +#include "nsPluginHost.h" +#include "nsPluginsCID.h" + +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPluginHost, nsPluginHost::GetInst) +NS_DEFINE_NAMED_CID(NS_PLUGIN_HOST_CID); + +static const mozilla::Module::CIDEntry kPluginCIDs[] = { + { &kNS_PLUGIN_HOST_CID, false, nullptr, nsPluginHostConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kPluginContracts[] = { + { MOZ_PLUGIN_HOST_CONTRACTID, &kNS_PLUGIN_HOST_CID }, + { nullptr } +}; + +static const mozilla::Module kPluginModule = { + mozilla::Module::kVersion, + kPluginCIDs, + kPluginContracts +}; + +NSMODULE_DEFN(nsPluginModule) = &kPluginModule; diff --git a/dom/plugins/base/nsPluginNativeWindow.cpp b/dom/plugins/base/nsPluginNativeWindow.cpp new file mode 100644 index 000000000..f9baf5b81 --- /dev/null +++ b/dom/plugins/base/nsPluginNativeWindow.cpp @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +/** + * This file is the default implementation of plugin native window + * empty stubs, it should be replaced with real platform implementation + * for every platform + */ + +#include "nsDebug.h" +#include "nsPluginNativeWindow.h" + +class nsPluginNativeWindowPLATFORM : public nsPluginNativeWindow { +public: + nsPluginNativeWindowPLATFORM(); + virtual ~nsPluginNativeWindowPLATFORM(); +}; + +nsPluginNativeWindowPLATFORM::nsPluginNativeWindowPLATFORM() : nsPluginNativeWindow() +{ + // initialize the struct fields + window = nullptr; + x = 0; + y = 0; + width = 0; + height = 0; + memset(&clipRect, 0, sizeof(clipRect)); +#if defined(XP_UNIX) && !defined(XP_MACOSX) + ws_info = nullptr; +#endif + type = NPWindowTypeWindow; +} + +nsPluginNativeWindowPLATFORM::~nsPluginNativeWindowPLATFORM() +{ +} + +nsresult PLUG_NewPluginNativeWindow(nsPluginNativeWindow ** aPluginNativeWindow) +{ + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + *aPluginNativeWindow = new nsPluginNativeWindowPLATFORM(); + return NS_OK; +} + +nsresult PLUG_DeletePluginNativeWindow(nsPluginNativeWindow * aPluginNativeWindow) +{ + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + nsPluginNativeWindowPLATFORM *p = (nsPluginNativeWindowPLATFORM *)aPluginNativeWindow; + delete p; + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginNativeWindow.h b/dom/plugins/base/nsPluginNativeWindow.h new file mode 100644 index 000000000..7c54d8a97 --- /dev/null +++ b/dom/plugins/base/nsPluginNativeWindow.h @@ -0,0 +1,80 @@ +/* -*- 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/. */ + +#ifndef _nsPluginNativeWindow_h_ +#define _nsPluginNativeWindow_h_ + +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsISupportsImpl.h" +#include "nsNPAPIPluginInstance.h" +#include "npapi.h" +#include "nsIWidget.h" + +/** + * base class for native plugin window implementations + */ +class nsPluginNativeWindow : public NPWindow +{ +public: + nsPluginNativeWindow() : NPWindow() { + MOZ_COUNT_CTOR(nsPluginNativeWindow); + } + + virtual ~nsPluginNativeWindow() { + MOZ_COUNT_DTOR(nsPluginNativeWindow); + } + + /** + * !!! CAUTION !!! + * + * The base class |nsPluginWindow| is defined as a struct in nsplugindefs.h, + * thus it does not have a destructor of its own. + * One should never attempt to delete |nsPluginNativeWindow| object instance + * (or derivatives) using a pointer of |nsPluginWindow *| type. Should such + * necessity occur it must be properly casted first. + */ + +public: + nsresult GetPluginInstance(RefPtr<nsNPAPIPluginInstance> &aPluginInstance) { + aPluginInstance = mPluginInstance; + return NS_OK; + } + nsresult SetPluginInstance(nsNPAPIPluginInstance *aPluginInstance) { + if (mPluginInstance != aPluginInstance) + mPluginInstance = aPluginInstance; + return NS_OK; + } + + nsresult GetPluginWidget(nsIWidget **aWidget) const { + NS_IF_ADDREF(*aWidget = mWidget); + return NS_OK; + } + nsresult SetPluginWidget(nsIWidget *aWidget) { + mWidget = aWidget; + return NS_OK; + } + +public: + virtual nsresult CallSetWindow(RefPtr<nsNPAPIPluginInstance> &aPluginInstance) { + // null aPluginInstance means that we want to call SetWindow(null) + if (aPluginInstance) + aPluginInstance->SetWindow(this); + else if (mPluginInstance) + mPluginInstance->SetWindow(nullptr); + + SetPluginInstance(aPluginInstance); + return NS_OK; + } + +protected: + RefPtr<nsNPAPIPluginInstance> mPluginInstance; + nsCOMPtr<nsIWidget> mWidget; +}; + +nsresult PLUG_NewPluginNativeWindow(nsPluginNativeWindow ** aPluginNativeWindow); +nsresult PLUG_DeletePluginNativeWindow(nsPluginNativeWindow * aPluginNativeWindow); + +#endif //_nsPluginNativeWindow_h_ diff --git a/dom/plugins/base/nsPluginNativeWindowGtk.cpp b/dom/plugins/base/nsPluginNativeWindowGtk.cpp new file mode 100644 index 000000000..bfb9510e0 --- /dev/null +++ b/dom/plugins/base/nsPluginNativeWindowGtk.cpp @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=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/. */ + +/** + * This file is the Gtk2 implementation of plugin native window. + */ + +#include "nsDebug.h" +#include "nsPluginNativeWindowGtk.h" +#include "nsNPAPIPlugin.h" +#include "npapi.h" +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include <gdk/gdk.h> + +#if (GTK_MAJOR_VERSION == 3) +#include <gtk/gtkx.h> +#else +#include "gtk2xtbin.h" +#endif +#include "mozilla/X11Util.h" + +static void plug_added_cb(GtkWidget *widget, gpointer data); +static gboolean plug_removed_cb (GtkWidget *widget, gpointer data); +static void socket_unrealize_cb (GtkWidget *widget, gpointer data); + +nsPluginNativeWindowGtk::nsPluginNativeWindowGtk() : nsPluginNativeWindow() +{ + // initialize the struct fields + window = nullptr; + x = 0; + y = 0; + width = 0; + height = 0; + memset(&clipRect, 0, sizeof(clipRect)); + ws_info = &mWsInfo; + type = NPWindowTypeWindow; + mSocketWidget = 0; + mWsInfo.type = 0; + mWsInfo.display = nullptr; + mWsInfo.visual = nullptr; + mWsInfo.colormap = 0; + mWsInfo.depth = 0; +} + +nsPluginNativeWindowGtk::~nsPluginNativeWindowGtk() +{ + if(mSocketWidget) { + gtk_widget_destroy(mSocketWidget); + } +} + +nsresult PLUG_NewPluginNativeWindow(nsPluginNativeWindow ** aPluginNativeWindow) +{ + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + *aPluginNativeWindow = new nsPluginNativeWindowGtk(); + return NS_OK; +} + +nsresult PLUG_DeletePluginNativeWindow(nsPluginNativeWindow * aPluginNativeWindow) +{ + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + nsPluginNativeWindowGtk *p = (nsPluginNativeWindowGtk *)aPluginNativeWindow; + delete p; + return NS_OK; +} + +nsresult nsPluginNativeWindowGtk::CallSetWindow(RefPtr<nsNPAPIPluginInstance> &aPluginInstance) +{ + if (aPluginInstance) { + if (type == NPWindowTypeWindow && + XRE_IsContentProcess()) { + // In this case, most of the initialization code here has already happened + // in the chrome process. The window we have in content is the XID of the + // socket widget we need to hand to plugins. + SetWindow((XID)window); + } else if (type == NPWindowTypeWindow) { + if (!mSocketWidget) { + nsresult rv; + + // The documentation on the types for many variables in NP(N|P)_GetValue + // is vague. Often boolean values are NPBool (1 byte), but + // https://developer.mozilla.org/en/XEmbed_Extension_for_Mozilla_Plugins + // treats NPPVpluginNeedsXEmbed as PRBool (int), and + // on x86/32-bit, flash stores to this using |movl 0x1,&needsXEmbed|. + // thus we can't use NPBool for needsXEmbed, or the three bytes above + // it on the stack would get clobbered. so protect with the larger bool. + int needsXEmbed = 0; + rv = aPluginInstance->GetValueFromPlugin(NPPVpluginNeedsXEmbed, &needsXEmbed); + // If the call returned an error code make sure we still use our default value. + if (NS_FAILED(rv)) { + needsXEmbed = 0; + } +#ifdef DEBUG + printf("nsPluginNativeWindowGtk: NPPVpluginNeedsXEmbed=%d\n", needsXEmbed); +#endif + + bool isOOPPlugin = aPluginInstance->GetPlugin()->GetLibrary()->IsOOP(); + if (needsXEmbed || isOOPPlugin) { + bool enableXtFocus = !needsXEmbed; + rv = CreateXEmbedWindow(enableXtFocus); + } + else { +#if (MOZ_WIDGET_GTK == 2) + rv = CreateXtWindow(); +#else + return NS_ERROR_FAILURE; +#endif + } + + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + + if (!mSocketWidget) { + return NS_ERROR_FAILURE; + } + + // Make sure to resize and re-place the window if required. + SetAllocation(); + // Need to reset "window" each time as nsPluginFrame::DidReflow sets it + // to the ancestor window. +#if (MOZ_WIDGET_GTK == 2) + if (GTK_IS_XTBIN(mSocketWidget)) { + // Point the NPWindow structures window to the actual X window + SetWindow(GTK_XTBIN(mSocketWidget)->xtwindow); + } + else { // XEmbed or OOP&Xt + SetWindow(gtk_socket_get_id(GTK_SOCKET(mSocketWidget))); + } +#else + // Gtk3 supports only OOP by GtkSocket + SetWindow(gtk_socket_get_id(GTK_SOCKET(mSocketWidget))); +#endif + +#ifdef DEBUG + printf("nsPluginNativeWindowGtk: call SetWindow with xid=%p\n", (void *)window); +#endif + } // NPWindowTypeWindow + aPluginInstance->SetWindow(this); + } else if (mPluginInstance) { + mPluginInstance->SetWindow(nullptr); + } + + SetPluginInstance(aPluginInstance); + return NS_OK; +} + +nsresult nsPluginNativeWindowGtk::CreateXEmbedWindow(bool aEnableXtFocus) { + NS_ASSERTION(!mSocketWidget,"Already created a socket widget!"); + GdkDisplay *display = gdk_display_get_default(); + GdkWindow *parent_win = gdk_x11_window_lookup_for_display(display, GetWindow()); + mSocketWidget = gtk_socket_new(); + + //attach the socket to the container widget + gtk_widget_set_parent_window(mSocketWidget, parent_win); + + // enable/disable focus event handlers, + // see plugin_window_filter_func() for details + g_object_set_data(G_OBJECT(mSocketWidget), "enable-xt-focus", (void *)aEnableXtFocus); + + g_signal_connect(mSocketWidget, "plug_added", + G_CALLBACK(plug_added_cb), nullptr); + + // Make sure to handle the plug_removed signal. If we don't the + // socket will automatically be destroyed when the plug is + // removed, which means we're destroying it more than once. + // SYNTAX ERROR. + g_signal_connect(mSocketWidget, "plug_removed", + G_CALLBACK(plug_removed_cb), nullptr); + + g_signal_connect(mSocketWidget, "unrealize", + G_CALLBACK(socket_unrealize_cb), nullptr); + + g_signal_connect(mSocketWidget, "destroy", + G_CALLBACK(gtk_widget_destroyed), &mSocketWidget); + + gpointer user_data = nullptr; + gdk_window_get_user_data(parent_win, &user_data); + + GtkContainer *container = GTK_CONTAINER(user_data); + gtk_container_add(container, mSocketWidget); + gtk_widget_realize(mSocketWidget); + + // The GtkSocket has a visible window, but the plugin's XEmbed plug will + // cover this window. Normally GtkSockets let the X server paint their + // background and this would happen immediately (before the plug is + // created). Setting the background to None prevents the server from + // painting this window, avoiding flicker. + // TODO GTK3 +#if (MOZ_WIDGET_GTK == 2) + gdk_window_set_back_pixmap(gtk_widget_get_window(mSocketWidget), nullptr, FALSE); +#endif + + // Resize before we show + SetAllocation(); + + gtk_widget_show(mSocketWidget); + + gdk_flush(); + SetWindow(gtk_socket_get_id(GTK_SOCKET(mSocketWidget))); + + // Fill out the ws_info structure. + // (The windowless case is done in nsPluginFrame.cpp.) + GdkWindow *gdkWindow = gdk_x11_window_lookup_for_display(display, GetWindow()); + if(!gdkWindow) + return NS_ERROR_FAILURE; + + mWsInfo.display = GDK_WINDOW_XDISPLAY(gdkWindow); +#if (MOZ_WIDGET_GTK == 2) + mWsInfo.colormap = GDK_COLORMAP_XCOLORMAP(gdk_drawable_get_colormap(gdkWindow)); + GdkVisual* gdkVisual = gdk_drawable_get_visual(gdkWindow); + mWsInfo.depth = gdkVisual->depth; +#else + mWsInfo.colormap = X11None; + GdkVisual* gdkVisual = gdk_window_get_visual(gdkWindow); + mWsInfo.depth = gdk_visual_get_depth(gdkVisual); +#endif + mWsInfo.visual = GDK_VISUAL_XVISUAL(gdkVisual); + + return NS_OK; +} + +void nsPluginNativeWindowGtk::SetAllocation() { + if (!mSocketWidget) + return; + + GtkAllocation new_allocation; + new_allocation.x = 0; + new_allocation.y = 0; + new_allocation.width = width; + new_allocation.height = height; + gtk_widget_size_allocate(mSocketWidget, &new_allocation); +} + +#if (MOZ_WIDGET_GTK == 2) +nsresult nsPluginNativeWindowGtk::CreateXtWindow() { + NS_ASSERTION(!mSocketWidget,"Already created a socket widget!"); + +#ifdef DEBUG + printf("About to create new xtbin of %i X %i from %p...\n", + width, height, (void*)window); +#endif + GdkDisplay *display = gdk_display_get_default(); + GdkWindow *gdkWindow = gdk_x11_window_lookup_for_display(display, GetWindow()); + mSocketWidget = gtk_xtbin_new(gdkWindow, 0); + // Check to see if creating the xtbin failed for some reason. + // if it did, we can't go any further. + if (!mSocketWidget) + return NS_ERROR_FAILURE; + + g_signal_connect(mSocketWidget, "destroy", + G_CALLBACK(gtk_widget_destroyed), &mSocketWidget); + + gtk_widget_set_size_request(mSocketWidget, width, height); + +#ifdef DEBUG + printf("About to show xtbin(%p)...\n", (void*)mSocketWidget); fflush(nullptr); +#endif + gtk_widget_show(mSocketWidget); +#ifdef DEBUG + printf("completed gtk_widget_show(%p)\n", (void*)mSocketWidget); fflush(nullptr); +#endif + + // Fill out the ws_info structure. + GtkXtBin* xtbin = GTK_XTBIN(mSocketWidget); + // The xtbin has its own Display structure. + mWsInfo.display = xtbin->xtdisplay; + mWsInfo.colormap = xtbin->xtclient.xtcolormap; + mWsInfo.visual = xtbin->xtclient.xtvisual; + mWsInfo.depth = xtbin->xtclient.xtdepth; + // Leave mWsInfo.type = 0 - Who knows what this is meant to be? + + XFlush(mWsInfo.display); + + return NS_OK; +} +#endif + +static void +plug_window_finalize_cb(gpointer socket, GObject* plug_window) +{ + g_object_unref(socket); +} + +static void +plug_added_cb(GtkWidget *socket, gpointer data) +{ + // The plug window has been embedded, and gtk_socket_add_window() has added + // a filter to the socket's plug_window, passing the socket as data for the + // filter, so the socket must live as long as events may be received on the + // plug window. + // + // https://git.gnome.org/browse/gtk+/tree/gtk/gtksocket.c?h=3.18.7#n1124 + g_object_ref(socket); + // When the socket is unrealized, perhaps during gtk_widget_destroy() from + // ~nsPluginNativeWindowGtk, the plug is removed. The plug in the child + // process then destroys its widget and window. When the browser process + // receives the DestroyNotify event for the plug window, GDK releases its + // reference to plugWindow. This is typically the last reference and so the + // weak ref callback triggers release of the socket. + GdkWindow* plugWindow = gtk_socket_get_plug_window(GTK_SOCKET(socket)); + g_object_weak_ref(G_OBJECT(plugWindow), plug_window_finalize_cb, socket); +} + +/* static */ +gboolean +plug_removed_cb (GtkWidget *widget, gpointer data) +{ + // Gee, thanks for the info! + return TRUE; +} + +static void +socket_unrealize_cb(GtkWidget *widget, gpointer data) +{ + // Unmap and reparent any child windows that GDK does not yet know about. + // (See bug 540114 comment 10.) + GdkWindow* socket_window = gtk_widget_get_window(widget); + GdkDisplay* gdkDisplay = gdk_display_get_default(); + Display* display = GDK_DISPLAY_XDISPLAY(gdkDisplay); + + // Ignore X errors that may happen if windows get destroyed (possibly + // requested by the plugin) between XQueryTree and when we operate on them. + gdk_error_trap_push(); + + Window root, parent; + Window* children; + unsigned int nchildren; + if (!XQueryTree(display, gdk_x11_window_get_xid(socket_window), + &root, &parent, &children, &nchildren)) + return; + + for (unsigned int i = 0; i < nchildren; ++i) { + Window child = children[i]; + if (!gdk_x11_window_lookup_for_display(gdkDisplay, child)) { + // This window is not known to GDK. + XUnmapWindow(display, child); + XReparentWindow(display, child, DefaultRootWindow(display), 0, 0); + } + } + + if (children) XFree(children); + + mozilla::FinishX(display); +#if (MOZ_WIDGET_GTK == 3) + gdk_error_trap_pop_ignored(); +#else + gdk_error_trap_pop(); +#endif +} diff --git a/dom/plugins/base/nsPluginNativeWindowGtk.h b/dom/plugins/base/nsPluginNativeWindowGtk.h new file mode 100644 index 000000000..872d7f017 --- /dev/null +++ b/dom/plugins/base/nsPluginNativeWindowGtk.h @@ -0,0 +1,51 @@ +/* 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/. */ + +#ifndef _nsPluginNativeWindowGdk_h_ +#define _nsPluginNativeWindowGdk_h_ + +#include "nsPluginNativeWindow.h" +#include "npapi.h" +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include <gdk/gdk.h> +#if (GTK_MAJOR_VERSION == 3) +#include <gtk/gtkx.h> +#else +#include "gtk2xtbin.h" +#endif +#include "mozilla/X11Util.h" + +class nsPluginNativeWindowGtk : public nsPluginNativeWindow { +public: + nsPluginNativeWindowGtk(); + virtual ~nsPluginNativeWindowGtk(); + + virtual nsresult CallSetWindow(RefPtr<nsNPAPIPluginInstance> &aPluginInstance); + nsresult CreateXEmbedWindow(bool aEnableXtFocus); + void SetAllocation(); + + XID GetWindow() const + { + return static_cast<XID>(reinterpret_cast<uintptr_t>(window)); + } + +private: + void SetWindow(XID aWindow) + { + window = reinterpret_cast<void*>(static_cast<uintptr_t>(aWindow)); + } + + NPSetWindowCallbackStruct mWsInfo; + /** + * Either a GtkSocket or a special GtkXtBin widget (derived from GtkSocket) + * that encapsulates the Xt toolkit within a Gtk Application. + */ + GtkWidget* mSocketWidget; +#if (MOZ_WIDGET_GTK == 2) + nsresult CreateXtWindow(); +#endif +}; + +#endif diff --git a/dom/plugins/base/nsPluginNativeWindowWin.cpp b/dom/plugins/base/nsPluginNativeWindowWin.cpp new file mode 100644 index 000000000..106dcaf77 --- /dev/null +++ b/dom/plugins/base/nsPluginNativeWindowWin.cpp @@ -0,0 +1,748 @@ +/* -*- 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/BasicEvents.h" +#include "mozilla/DebugOnly.h" + +#include "windows.h" +#include "windowsx.h" + +// XXXbz windowsx.h defines GetFirstChild, GetNextSibling, +// GetPrevSibling are macros, apparently... Eeevil. We have functions +// called that on some classes, so undef them. +#undef GetFirstChild +#undef GetNextSibling +#undef GetPrevSibling + +#include "nsDebug.h" + +#include "nsWindowsDllInterceptor.h" +#include "nsPluginNativeWindow.h" +#include "nsThreadUtils.h" +#include "nsTWeakRef.h" +#include "nsCrashOnException.h" + +using namespace mozilla; + +#define NP_POPUP_API_VERSION 16 + +#define nsMajorVersion(v) (((int32_t)(v) >> 16) & 0xffff) +#define nsMinorVersion(v) ((int32_t)(v) & 0xffff) +#define versionOK(suppliedV, requiredV) \ + (nsMajorVersion(suppliedV) == nsMajorVersion(requiredV) \ + && nsMinorVersion(suppliedV) >= nsMinorVersion(requiredV)) + + +#define NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION TEXT("MozillaPluginWindowPropertyAssociation") +#define NS_PLUGIN_CUSTOM_MSG_ID TEXT("MozFlashUserRelay") +#define WM_USER_FLASH WM_USER+1 +static UINT sWM_FLASHBOUNCEMSG = 0; + +typedef nsTWeakRef<class nsPluginNativeWindowWin> PluginWindowWeakRef; + +/** + * PLEvent handling code + */ +class PluginWindowEvent : public Runnable { +public: + PluginWindowEvent(); + void Init(const PluginWindowWeakRef &ref, HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam); + void Clear(); + HWND GetWnd() { return mWnd; }; + UINT GetMsg() { return mMsg; }; + WPARAM GetWParam() { return mWParam; }; + LPARAM GetLParam() { return mLParam; }; + bool InUse() { return mWnd != nullptr; }; + + NS_DECL_NSIRUNNABLE + +protected: + PluginWindowWeakRef mPluginWindowRef; + HWND mWnd; + UINT mMsg; + WPARAM mWParam; + LPARAM mLParam; +}; + +PluginWindowEvent::PluginWindowEvent() +{ + Clear(); +} + +void PluginWindowEvent::Clear() +{ + mWnd = nullptr; + mMsg = 0; + mWParam = 0; + mLParam = 0; +} + +void PluginWindowEvent::Init(const PluginWindowWeakRef &ref, HWND aWnd, + UINT aMsg, WPARAM aWParam, LPARAM aLParam) +{ + NS_ASSERTION(aWnd != nullptr, "invalid plugin event value"); + NS_ASSERTION(mWnd == nullptr, "event already in use"); + mPluginWindowRef = ref; + mWnd = aWnd; + mMsg = aMsg; + mWParam = aWParam; + mLParam = aLParam; +} + +/** + * nsPluginNativeWindow Windows specific class declaration + */ + +class nsPluginNativeWindowWin : public nsPluginNativeWindow { +public: + nsPluginNativeWindowWin(); + virtual ~nsPluginNativeWindowWin(); + + virtual nsresult CallSetWindow(RefPtr<nsNPAPIPluginInstance> &aPluginInstance); + +private: + nsresult SubclassAndAssociateWindow(); + nsresult UndoSubclassAndAssociateWindow(); + +public: + // locals + WNDPROC GetPrevWindowProc(); + void SetPrevWindowProc(WNDPROC proc) { mPluginWinProc = proc; } + WNDPROC GetWindowProc(); + PluginWindowEvent * GetPluginWindowEvent(HWND aWnd, + UINT aMsg, + WPARAM aWParam, + LPARAM aLParam); + +private: + WNDPROC mPluginWinProc; + WNDPROC mPrevWinProc; + PluginWindowWeakRef mWeakRef; + RefPtr<PluginWindowEvent> mCachedPluginWindowEvent; + + HWND mParentWnd; + LONG_PTR mParentProc; +public: + nsPluginHost::SpecialType mPluginType; +}; + +static bool sInMessageDispatch = false; +static bool sInPreviousMessageDispatch = false; +static UINT sLastMsg = 0; + +static bool ProcessFlashMessageDelayed(nsPluginNativeWindowWin * aWin, nsNPAPIPluginInstance * aInst, + HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + NS_ENSURE_TRUE(aWin, false); + NS_ENSURE_TRUE(aInst, false); + + if (msg == sWM_FLASHBOUNCEMSG) { + // See PluginWindowEvent::Run() below. + NS_ASSERTION((sWM_FLASHBOUNCEMSG != 0), "RegisterWindowMessage failed in flash plugin WM_USER message handling!"); + ::CallWindowProc((WNDPROC)aWin->GetWindowProc(), hWnd, WM_USER_FLASH, wParam, lParam); + return true; + } + + if (msg != WM_USER_FLASH) + return false; // no need to delay + + // do stuff + nsCOMPtr<nsIRunnable> pwe = aWin->GetPluginWindowEvent(hWnd, msg, wParam, lParam); + if (pwe) { + NS_DispatchToCurrentThread(pwe); + return true; + } + return false; +} + +class nsDelayedPopupsEnabledEvent : public Runnable +{ +public: + nsDelayedPopupsEnabledEvent(nsNPAPIPluginInstance *inst) + : mInst(inst) + {} + + NS_DECL_NSIRUNNABLE + +private: + RefPtr<nsNPAPIPluginInstance> mInst; +}; + +NS_IMETHODIMP nsDelayedPopupsEnabledEvent::Run() +{ + mInst->PushPopupsEnabledState(false); + return NS_OK; +} + +static LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +/** + * New plugin window procedure + * + * e10s note - this subclass, and the hooks we set below using WindowsDllInterceptor + * are currently not in use when running with e10s. (Utility calls like CallSetWindow + * are still in use in the content process.) We would like to keep things this away, + * essentially making all the hacks here obsolete. Some of the mitigation work here has + * already been supplanted by code in PluginInstanceChild. The rest we eventually want + * to rip out. + */ +static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + nsPluginNativeWindowWin * win = (nsPluginNativeWindowWin *)::GetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + if (!win) + return TRUE; + + // The DispatchEvent(ePluginActivate) below can trigger a reentrant focus + // event which might destroy us. Hold a strong ref on the plugin instance + // to prevent that, bug 374229. + RefPtr<nsNPAPIPluginInstance> inst; + win->GetPluginInstance(inst); + + // Real may go into a state where it recursivly dispatches the same event + // when subclassed. If this is Real, lets examine the event and drop it + // on the floor if we get into this recursive situation. See bug 192914. + if (win->mPluginType == nsPluginHost::eSpecialType_RealPlayer) { + if (sInMessageDispatch && msg == sLastMsg) + return true; + // Cache the last message sent + sLastMsg = msg; + } + + bool enablePopups = false; + + // Activate/deactivate mouse capture on the plugin widget + // here, before we pass the Windows event to the plugin + // because its possible our widget won't get paired events + // (see bug 131007) and we'll look frozen. Note that this + // is also done in ChildWindow::DispatchMouseEvent. + switch (msg) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: { + nsCOMPtr<nsIWidget> widget; + win->GetPluginWidget(getter_AddRefs(widget)); + if (widget) + widget->CaptureMouse(true); + break; + } + case WM_LBUTTONUP: + enablePopups = true; + + // fall through + case WM_MBUTTONUP: + case WM_RBUTTONUP: { + nsCOMPtr<nsIWidget> widget; + win->GetPluginWidget(getter_AddRefs(widget)); + if (widget) + widget->CaptureMouse(false); + break; + } + case WM_KEYDOWN: + // Ignore repeating keydown messages... + if ((lParam & 0x40000000) != 0) { + break; + } + + // fall through + case WM_KEYUP: + enablePopups = true; + + break; + + case WM_MOUSEACTIVATE: { + // If a child window of this plug-in is already focused, + // don't focus the parent to avoid focus dance. We'll + // receive a follow up WM_SETFOCUS which will notify + // the appropriate window anyway. + HWND focusedWnd = ::GetFocus(); + if (!::IsChild((HWND)win->window, focusedWnd)) { + // Notify the dom / focus manager the plugin has focus when one of + // it's child windows receives it. OOPP specific - this code is + // critical in notifying the dom of focus changes when the plugin + // window in the child process receives focus via a mouse click. + // WM_MOUSEACTIVATE is sent by nsWindow via a custom window event + // sent from PluginInstanceParent in response to focus events sent + // from the child. (bug 540052) Note, this gui event could also be + // sent directly from widget. + nsCOMPtr<nsIWidget> widget; + win->GetPluginWidget(getter_AddRefs(widget)); + if (widget) { + WidgetGUIEvent event(true, ePluginActivate, widget); + nsEventStatus status; + widget->DispatchEvent(&event, status); + } + } + } + break; + + case WM_SETFOCUS: + case WM_KILLFOCUS: { + // RealPlayer can crash, don't process the message for those, + // see bug 328675. + if (win->mPluginType == nsPluginHost::eSpecialType_RealPlayer && msg == sLastMsg) + return TRUE; + // Make sure setfocus and killfocus get through to the widget procedure + // even if they are eaten by the plugin. Also make sure we aren't calling + // recursively. + WNDPROC prevWndProc = win->GetPrevWindowProc(); + if (prevWndProc && !sInPreviousMessageDispatch) { + sInPreviousMessageDispatch = true; + ::CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam); + sInPreviousMessageDispatch = false; + } + break; + } + } + + // Macromedia Flash plugin may flood the message queue with some special messages + // (WM_USER+1) causing 100% CPU consumption and GUI freeze, see mozilla bug 132759; + // we can prevent this from happening by delaying the processing such messages; + if (win->mPluginType == nsPluginHost::eSpecialType_Flash) { + if (ProcessFlashMessageDelayed(win, inst, hWnd, msg, wParam, lParam)) + return TRUE; + } + + if (enablePopups && inst) { + uint16_t apiVersion; + if (NS_SUCCEEDED(inst->GetPluginAPIVersion(&apiVersion)) && + !versionOK(apiVersion, NP_POPUP_API_VERSION)) { + inst->PushPopupsEnabledState(true); + } + } + + sInMessageDispatch = true; + LRESULT res; + WNDPROC proc = (WNDPROC)win->GetWindowProc(); + if (PluginWndProc == proc) { + NS_WARNING("Previous plugin window procedure references PluginWndProc! " + "Report this bug!"); + res = CallWindowProc(DefWindowProc, hWnd, msg, wParam, lParam); + } else { + res = CallWindowProc(proc, hWnd, msg, wParam, lParam); + } + sInMessageDispatch = false; + + if (inst) { + // Popups are enabled (were enabled before the call to + // CallWindowProc()). Some plugins (at least the flash player) + // post messages from their key handlers etc that delay the actual + // processing, so we need to delay the disabling of popups so that + // popups remain enabled when the flash player ends up processing + // the actual key handlers. We do this by posting an event that + // does the disabling, this way our disabling will happen after + // the handlers in the plugin are done. + + // Note that it's not fatal if any of this fails (which won't + // happen unless we're out of memory anyways) since the plugin + // code will pop any popup state pushed by this plugin on + // destruction. + + nsCOMPtr<nsIRunnable> event = new nsDelayedPopupsEnabledEvent(inst); + if (event) + NS_DispatchToCurrentThread(event); + } + + return res; +} + +static LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + return mozilla::CallWindowProcCrashProtected(PluginWndProcInternal, hWnd, msg, wParam, lParam); +} + +/* + * Flash will reset the subclass of our widget at various times. + * (Notably when entering and exiting full screen mode.) This + * occurs independent of the main plugin window event procedure. + * We trap these subclass calls to prevent our subclass hook from + * getting dropped. + * Note, ascii versions can be nixed once flash versions < 10.1 + * are considered obsolete. + */ +static WindowsDllInterceptor sUser32Intercept; + +#ifdef _WIN64 +typedef LONG_PTR + (WINAPI *User32SetWindowLongPtrA)(HWND hWnd, + int nIndex, + LONG_PTR dwNewLong); +typedef LONG_PTR + (WINAPI *User32SetWindowLongPtrW)(HWND hWnd, + int nIndex, + LONG_PTR dwNewLong); +static User32SetWindowLongPtrA sUser32SetWindowLongAHookStub = nullptr; +static User32SetWindowLongPtrW sUser32SetWindowLongWHookStub = nullptr; +#else +typedef LONG +(WINAPI *User32SetWindowLongA)(HWND hWnd, + int nIndex, + LONG dwNewLong); +typedef LONG +(WINAPI *User32SetWindowLongW)(HWND hWnd, + int nIndex, + LONG dwNewLong); +static User32SetWindowLongA sUser32SetWindowLongAHookStub = nullptr; +static User32SetWindowLongW sUser32SetWindowLongWHookStub = nullptr; +#endif +static inline bool +SetWindowLongHookCheck(HWND hWnd, + int nIndex, + LONG_PTR newLong) +{ + nsPluginNativeWindowWin * win = + (nsPluginNativeWindowWin *)GetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + if (!win || (win && win->mPluginType != nsPluginHost::eSpecialType_Flash) || + (nIndex == GWLP_WNDPROC && + newLong == reinterpret_cast<LONG_PTR>(PluginWndProc))) + return true; + return false; +} + +#ifdef _WIN64 +LONG_PTR WINAPI +SetWindowLongPtrAHook(HWND hWnd, + int nIndex, + LONG_PTR newLong) +#else +LONG WINAPI +SetWindowLongAHook(HWND hWnd, + int nIndex, + LONG newLong) +#endif +{ + if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) + return sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); + + // Set flash's new subclass to get the result. + LONG_PTR proc = sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); + + // We already checked this in SetWindowLongHookCheck + nsPluginNativeWindowWin * win = + (nsPluginNativeWindowWin *)GetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + + // Hook our subclass back up, just like we do on setwindow. + win->SetPrevWindowProc( + reinterpret_cast<WNDPROC>(sUser32SetWindowLongWHookStub(hWnd, nIndex, + reinterpret_cast<LONG_PTR>(PluginWndProc)))); + return proc; +} + +#ifdef _WIN64 +LONG_PTR WINAPI +SetWindowLongPtrWHook(HWND hWnd, + int nIndex, + LONG_PTR newLong) +#else +LONG WINAPI +SetWindowLongWHook(HWND hWnd, + int nIndex, + LONG newLong) +#endif +{ + if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) + return sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); + + // Set flash's new subclass to get the result. + LONG_PTR proc = sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); + + // We already checked this in SetWindowLongHookCheck + nsPluginNativeWindowWin * win = + (nsPluginNativeWindowWin *)GetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + + // Hook our subclass back up, just like we do on setwindow. + win->SetPrevWindowProc( + reinterpret_cast<WNDPROC>(sUser32SetWindowLongWHookStub(hWnd, nIndex, + reinterpret_cast<LONG_PTR>(PluginWndProc)))); + return proc; +} + +static void +HookSetWindowLongPtr() +{ + sUser32Intercept.Init("user32.dll"); +#ifdef _WIN64 + if (!sUser32SetWindowLongAHookStub) + sUser32Intercept.AddHook("SetWindowLongPtrA", + reinterpret_cast<intptr_t>(SetWindowLongPtrAHook), + (void**) &sUser32SetWindowLongAHookStub); + if (!sUser32SetWindowLongWHookStub) + sUser32Intercept.AddHook("SetWindowLongPtrW", + reinterpret_cast<intptr_t>(SetWindowLongPtrWHook), + (void**) &sUser32SetWindowLongWHookStub); +#else + if (!sUser32SetWindowLongAHookStub) + sUser32Intercept.AddHook("SetWindowLongA", + reinterpret_cast<intptr_t>(SetWindowLongAHook), + (void**) &sUser32SetWindowLongAHookStub); + if (!sUser32SetWindowLongWHookStub) + sUser32Intercept.AddHook("SetWindowLongW", + reinterpret_cast<intptr_t>(SetWindowLongWHook), + (void**) &sUser32SetWindowLongWHookStub); +#endif +} + +/** + * nsPluginNativeWindowWin implementation + */ +nsPluginNativeWindowWin::nsPluginNativeWindowWin() : nsPluginNativeWindow() +{ + // initialize the struct fields + window = nullptr; + x = 0; + y = 0; + width = 0; + height = 0; + + mPrevWinProc = nullptr; + mPluginWinProc = nullptr; + mPluginType = nsPluginHost::eSpecialType_None; + + mParentWnd = nullptr; + mParentProc = 0; + + if (!sWM_FLASHBOUNCEMSG) { + sWM_FLASHBOUNCEMSG = ::RegisterWindowMessage(NS_PLUGIN_CUSTOM_MSG_ID); + } +} + +nsPluginNativeWindowWin::~nsPluginNativeWindowWin() +{ + // clear weak reference to self to prevent any pending events from + // dereferencing this. + mWeakRef.forget(); +} + +WNDPROC nsPluginNativeWindowWin::GetPrevWindowProc() +{ + return mPrevWinProc; +} + +WNDPROC nsPluginNativeWindowWin::GetWindowProc() +{ + return mPluginWinProc; +} + +NS_IMETHODIMP PluginWindowEvent::Run() +{ + nsPluginNativeWindowWin *win = mPluginWindowRef.get(); + if (!win) + return NS_OK; + + HWND hWnd = GetWnd(); + if (!hWnd) + return NS_OK; + + RefPtr<nsNPAPIPluginInstance> inst; + win->GetPluginInstance(inst); + + if (GetMsg() == WM_USER_FLASH) { + // XXX Unwind issues related to runnable event callback depth for this + // event and destruction of the plugin. (Bug 493601) + ::PostMessage(hWnd, sWM_FLASHBOUNCEMSG, GetWParam(), GetLParam()); + } + else { + // Currently not used, but added so that processing events here + // is more generic. + ::CallWindowProc(win->GetWindowProc(), + hWnd, + GetMsg(), + GetWParam(), + GetLParam()); + } + + Clear(); + return NS_OK; +} + +PluginWindowEvent * +nsPluginNativeWindowWin::GetPluginWindowEvent(HWND aWnd, UINT aMsg, WPARAM aWParam, LPARAM aLParam) +{ + if (!mWeakRef) { + mWeakRef = this; + if (!mWeakRef) + return nullptr; + } + + PluginWindowEvent *event; + + // We have the ability to alloc if needed in case in the future some plugin + // should post multiple PostMessages. However, this could lead to many + // alloc's per second which could become a performance issue. See bug 169247. + if (!mCachedPluginWindowEvent) + { + event = new PluginWindowEvent(); + mCachedPluginWindowEvent = event; + } + else if (mCachedPluginWindowEvent->InUse()) + { + event = new PluginWindowEvent(); + } + else + { + event = mCachedPluginWindowEvent; + } + + event->Init(mWeakRef, aWnd, aMsg, aWParam, aLParam); + return event; +} + +nsresult nsPluginNativeWindowWin::CallSetWindow(RefPtr<nsNPAPIPluginInstance> &aPluginInstance) +{ + // Note, 'window' can be null + + // check the incoming instance, null indicates that window is going away and we are + // not interested in subclassing business any more, undo and don't subclass + if (!aPluginInstance) { + UndoSubclassAndAssociateWindow(); + // release plugin instance + SetPluginInstance(nullptr); + nsPluginNativeWindow::CallSetWindow(aPluginInstance); + return NS_OK; + } + + // check plugin mime type and cache it if it will need special treatment later + if (mPluginType == nsPluginHost::eSpecialType_None) { + const char* mimetype = nullptr; + if (NS_SUCCEEDED(aPluginInstance->GetMIMEType(&mimetype)) && mimetype) { + mPluginType = nsPluginHost::GetSpecialType(nsDependentCString(mimetype)); + } + } + + // With e10s we execute in the content process and as such we don't + // have access to native widgets. CallSetWindow and skip native widget + // subclassing. + if (!XRE_IsParentProcess()) { + nsPluginNativeWindow::CallSetWindow(aPluginInstance); + return NS_OK; + } + + if (window) { + // grab the widget procedure before the plug-in does a subclass in + // setwindow. We'll use this in PluginWndProc for forwarding focus + // events to the widget. + WNDPROC currentWndProc = + (WNDPROC)::GetWindowLongPtr((HWND)window, GWLP_WNDPROC); + if (!mPrevWinProc && currentWndProc != PluginWndProc) + mPrevWinProc = currentWndProc; + + // PDF plugin v7.0.9, v8.1.3, and v9.0 subclass parent window, bug 531551 + // V8.2.2 and V9.1 don't have such problem. + if (mPluginType == nsPluginHost::eSpecialType_PDF) { + HWND parent = ::GetParent((HWND)window); + if (mParentWnd != parent) { + NS_ASSERTION(!mParentWnd, "Plugin's parent window changed"); + mParentWnd = parent; + mParentProc = ::GetWindowLongPtr(mParentWnd, GWLP_WNDPROC); + } + } + } + + nsPluginNativeWindow::CallSetWindow(aPluginInstance); + + SubclassAndAssociateWindow(); + + if (window && mPluginType == nsPluginHost::eSpecialType_Flash && + !GetPropW((HWND)window, L"PluginInstanceParentProperty")) { + HookSetWindowLongPtr(); + } + + return NS_OK; +} + +nsresult nsPluginNativeWindowWin::SubclassAndAssociateWindow() +{ + if (type != NPWindowTypeWindow || !window) + return NS_ERROR_FAILURE; + + HWND hWnd = (HWND)window; + + // check if we need to subclass + WNDPROC currentWndProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + if (currentWndProc == PluginWndProc) + return NS_OK; + + // If the plugin reset the subclass, set it back. + if (mPluginWinProc) { +#ifdef DEBUG + NS_WARNING("A plugin cleared our subclass - resetting."); + if (currentWndProc != mPluginWinProc) { + NS_WARNING("Procedures do not match up, discarding old subclass value."); + } + if (mPrevWinProc && currentWndProc == mPrevWinProc) { + NS_WARNING("The new procedure is our widget procedure?"); + } +#endif + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PluginWndProc); + return NS_OK; + } + + LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE); + // Out of process plugins must not have the WS_CLIPCHILDREN style set on their + // parent windows or else synchronous paints (via UpdateWindow() and others) + // will cause deadlocks. + if (::GetPropW(hWnd, L"PluginInstanceParentProperty")) + style &= ~WS_CLIPCHILDREN; + else + style |= WS_CLIPCHILDREN; + SetWindowLongPtr(hWnd, GWL_STYLE, style); + + mPluginWinProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PluginWndProc); + if (!mPluginWinProc) + return NS_ERROR_FAILURE; + + DebugOnly<nsPluginNativeWindowWin *> win = (nsPluginNativeWindowWin *)::GetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + NS_ASSERTION(!win || (win == this), "plugin window already has property and this is not us"); + + if (!::SetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION, (HANDLE)this)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult nsPluginNativeWindowWin::UndoSubclassAndAssociateWindow() +{ + // remove window property + HWND hWnd = (HWND)window; + if (IsWindow(hWnd)) + ::RemoveProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + + // restore the original win proc + // but only do this if this were us last time + if (mPluginWinProc) { + WNDPROC currentWndProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + if (currentWndProc == PluginWndProc) + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)mPluginWinProc); + mPluginWinProc = nullptr; + + LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE); + style &= ~WS_CLIPCHILDREN; + SetWindowLongPtr(hWnd, GWL_STYLE, style); + } + + if (mPluginType == nsPluginHost::eSpecialType_Flash && mParentWnd) { + ::SetWindowLongPtr(mParentWnd, GWLP_WNDPROC, mParentProc); + mParentWnd = nullptr; + mParentProc = 0; + } + + return NS_OK; +} + +nsresult PLUG_NewPluginNativeWindow(nsPluginNativeWindow ** aPluginNativeWindow) +{ + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + + *aPluginNativeWindow = new nsPluginNativeWindowWin(); + return NS_OK; +} + +nsresult PLUG_DeletePluginNativeWindow(nsPluginNativeWindow * aPluginNativeWindow) +{ + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + nsPluginNativeWindowWin *p = (nsPluginNativeWindowWin *)aPluginNativeWindow; + delete p; + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginStreamListenerPeer.cpp b/dom/plugins/base/nsPluginStreamListenerPeer.cpp new file mode 100644 index 000000000..26e0318e3 --- /dev/null +++ b/dom/plugins/base/nsPluginStreamListenerPeer.cpp @@ -0,0 +1,1424 @@ +/* -*- 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 "nsPluginStreamListenerPeer.h" +#include "nsIContentPolicy.h" +#include "nsContentPolicyUtils.h" +#include "nsIDOMElement.h" +#include "nsIStreamConverterService.h" +#include "nsIStreamLoader.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIFileChannel.h" +#include "nsMimeTypes.h" +#include "nsISupportsPrimitives.h" +#include "nsNetCID.h" +#include "nsPluginInstanceOwner.h" +#include "nsPluginLogging.h" +#include "nsIURI.h" +#include "nsIURL.h" +#include "nsPluginHost.h" +#include "nsIByteRangeRequest.h" +#include "nsIMultiPartChannel.h" +#include "nsIInputStreamTee.h" +#include "nsPrintfCString.h" +#include "nsIScriptGlobalObject.h" +#include "nsIDocument.h" +#include "nsIWebNavigation.h" +#include "nsContentUtils.h" +#include "nsNetUtil.h" +#include "nsPluginNativeWindow.h" +#include "GeckoProfiler.h" +#include "nsPluginInstanceOwner.h" +#include "nsDataHashtable.h" +#include "nsNullPrincipal.h" + +#define BYTERANGE_REQUEST_CONTEXT 0x01020304 + +// nsPluginByteRangeStreamListener + +class nsPluginByteRangeStreamListener + : public nsIStreamListener + , public nsIInterfaceRequestor +{ +public: + explicit nsPluginByteRangeStreamListener(nsIWeakReference* aWeakPtr); + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIINTERFACEREQUESTOR + +private: + virtual ~nsPluginByteRangeStreamListener(); + + nsCOMPtr<nsIStreamListener> mStreamConverter; + nsWeakPtr mWeakPtrPluginStreamListenerPeer; + bool mRemoveByteRangeRequest; +}; + +NS_IMPL_ISUPPORTS(nsPluginByteRangeStreamListener, + nsIRequestObserver, + nsIStreamListener, + nsIInterfaceRequestor) + +nsPluginByteRangeStreamListener::nsPluginByteRangeStreamListener(nsIWeakReference* aWeakPtr) +{ + mWeakPtrPluginStreamListenerPeer = aWeakPtr; + mRemoveByteRangeRequest = false; +} + +nsPluginByteRangeStreamListener::~nsPluginByteRangeStreamListener() +{ + mStreamConverter = nullptr; + mWeakPtrPluginStreamListenerPeer = nullptr; +} + +/** + * Unwrap any byte-range requests so that we can check whether the base channel + * is being tracked properly. + */ +static nsCOMPtr<nsIRequest> +GetBaseRequest(nsIRequest* r) +{ + nsCOMPtr<nsIMultiPartChannel> mp = do_QueryInterface(r); + if (!mp) + return r; + + nsCOMPtr<nsIChannel> base; + mp->GetBaseChannel(getter_AddRefs(base)); + return already_AddRefed<nsIRequest>(base.forget()); +} + +NS_IMETHODIMP +nsPluginByteRangeStreamListener::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + nsresult rv; + + nsCOMPtr<nsIStreamListener> finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer); + if (!finalStreamListener) + return NS_ERROR_FAILURE; + + nsPluginStreamListenerPeer *pslp = + static_cast<nsPluginStreamListenerPeer*>(finalStreamListener.get()); + +#ifdef DEBUG + nsCOMPtr<nsIRequest> baseRequest = GetBaseRequest(request); +#endif + NS_ASSERTION(pslp->mRequests.IndexOfObject(baseRequest) != -1, + "Untracked byte-range request?"); + + nsCOMPtr<nsIStreamConverterService> serv = do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = serv->AsyncConvertData(MULTIPART_BYTERANGES, + "*/*", + finalStreamListener, + nullptr, + getter_AddRefs(mStreamConverter)); + if (NS_SUCCEEDED(rv)) { + rv = mStreamConverter->OnStartRequest(request, ctxt); + if (NS_SUCCEEDED(rv)) + return rv; + } + } + mStreamConverter = nullptr; + + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request)); + if (!httpChannel) { + return NS_ERROR_FAILURE; + } + + uint32_t responseCode = 0; + rv = httpChannel->GetResponseStatus(&responseCode); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + if (responseCode != 200) { + uint32_t wantsAllNetworkStreams = 0; + rv = pslp->GetPluginInstance()->GetValueFromPlugin(NPPVpluginWantsAllNetworkStreams, + &wantsAllNetworkStreams); + // If the call returned an error code make sure we still use our default value. + if (NS_FAILED(rv)) { + wantsAllNetworkStreams = 0; + } + + if (!wantsAllNetworkStreams){ + return NS_ERROR_FAILURE; + } + } + + // if server cannot continue with byte range (206 status) and sending us whole object (200 status) + // reset this seekable stream & try serve it to plugin instance as a file + mStreamConverter = finalStreamListener; + mRemoveByteRangeRequest = true; + + rv = pslp->ServeStreamAsFile(request, ctxt); + return rv; +} + +NS_IMETHODIMP +nsPluginByteRangeStreamListener::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult status) +{ + if (!mStreamConverter) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIStreamListener> finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer); + if (!finalStreamListener) + return NS_ERROR_FAILURE; + + nsPluginStreamListenerPeer *pslp = + static_cast<nsPluginStreamListenerPeer*>(finalStreamListener.get()); + bool found = pslp->mRequests.RemoveObject(request); + if (!found) { + NS_ERROR("OnStopRequest received for untracked byte-range request!"); + } + + if (mRemoveByteRangeRequest) { + // remove byte range request from container + nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(ctxt); + if (container) { + uint32_t byteRangeRequest = 0; + container->GetData(&byteRangeRequest); + if (byteRangeRequest == BYTERANGE_REQUEST_CONTEXT) { + // to allow properly finish nsPluginStreamListenerPeer->OnStopRequest() + // set it to something that is not the byte range request. + container->SetData(0); + } + } else { + NS_WARNING("Bad state of nsPluginByteRangeStreamListener"); + } + } + + return mStreamConverter->OnStopRequest(request, ctxt, status); +} + +// CachedFileHolder + +CachedFileHolder::CachedFileHolder(nsIFile* cacheFile) +: mFile(cacheFile) +{ + NS_ASSERTION(mFile, "Empty CachedFileHolder"); +} + +CachedFileHolder::~CachedFileHolder() +{ + mFile->Remove(false); +} + +void +CachedFileHolder::AddRef() +{ + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "CachedFileHolder", sizeof(*this)); +} + +void +CachedFileHolder::Release() +{ + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "CachedFileHolder"); + if (0 == mRefCnt) + delete this; +} + + +NS_IMETHODIMP +nsPluginByteRangeStreamListener::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, + uint32_t count) +{ + if (!mStreamConverter) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIStreamListener> finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer); + if (!finalStreamListener) + return NS_ERROR_FAILURE; + + return mStreamConverter->OnDataAvailable(request, ctxt, inStr, sourceOffset, count); +} + +NS_IMETHODIMP +nsPluginByteRangeStreamListener::GetInterface(const nsIID& aIID, void** result) +{ + // Forward interface requests to our parent + nsCOMPtr<nsIInterfaceRequestor> finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer); + if (!finalStreamListener) + return NS_ERROR_FAILURE; + + return finalStreamListener->GetInterface(aIID, result); +} + +// nsPluginStreamListenerPeer + +NS_IMPL_ISUPPORTS(nsPluginStreamListenerPeer, + nsIStreamListener, + nsIRequestObserver, + nsIHttpHeaderVisitor, + nsISupportsWeakReference, + nsIInterfaceRequestor, + nsIChannelEventSink) + +nsPluginStreamListenerPeer::nsPluginStreamListenerPeer() +{ + mStreamType = NP_NORMAL; + mStartBinding = false; + mAbort = false; + mRequestFailed = false; + + mPendingRequests = 0; + mHaveFiredOnStartRequest = false; + mDataForwardToRequest = nullptr; + + mUseLocalCache = false; + mSeekable = false; + mModified = 0; + mStreamOffset = 0; + mStreamComplete = 0; +} + +nsPluginStreamListenerPeer::~nsPluginStreamListenerPeer() +{ +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginStreamListenerPeer::dtor this=%p, url=%s\n",this, mURLSpec.get())); +#endif + + if (mPStreamListener) { + mPStreamListener->SetStreamListenerPeer(nullptr); + } + + // close FD of mFileCacheOutputStream if it's still open + // or we won't be able to remove the cache file + if (mFileCacheOutputStream) + mFileCacheOutputStream = nullptr; + + delete mDataForwardToRequest; + + if (mPluginInstance) + mPluginInstance->FileCachedStreamListeners()->RemoveElement(this); +} + +// Called as a result of GetURL and PostURL, or by the host in the case of the +// initial plugin stream. +nsresult nsPluginStreamListenerPeer::Initialize(nsIURI *aURL, + nsNPAPIPluginInstance *aInstance, + nsNPAPIPluginStreamListener* aListener) +{ +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginStreamListenerPeer::Initialize instance=%p, url=%s\n", + aInstance, aURL ? aURL->GetSpecOrDefault().get() : "")); + + PR_LogFlush(); +#endif + + // Not gonna work out + if (!aInstance) { + return NS_ERROR_FAILURE; + } + + mURL = aURL; + + NS_ASSERTION(mPluginInstance == nullptr, + "nsPluginStreamListenerPeer::Initialize mPluginInstance != nullptr"); + mPluginInstance = aInstance; + + // If the plugin did not request this stream, e.g. the initial stream, we wont + // have a nsNPAPIPluginStreamListener yet - this will be handled by + // SetUpStreamListener + if (aListener) { + mPStreamListener = aListener; + mPStreamListener->SetStreamListenerPeer(this); + } + + mPendingRequests = 1; + + mDataForwardToRequest = new nsDataHashtable<nsUint32HashKey, uint32_t>(); + + return NS_OK; +} + +// SetupPluginCacheFile is called if we have to save the stream to disk. +// +// These files will be deleted when the host is destroyed. +// +// TODO? What if we fill up the the dest dir? +nsresult +nsPluginStreamListenerPeer::SetupPluginCacheFile(nsIChannel* channel) +{ + nsresult rv = NS_OK; + + bool useExistingCacheFile = false; + RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst(); + + // Look for an existing cache file for the URI. + nsTArray< RefPtr<nsNPAPIPluginInstance> > *instances = pluginHost->InstanceArray(); + for (uint32_t i = 0; i < instances->Length(); i++) { + // most recent streams are at the end of list + nsTArray<nsPluginStreamListenerPeer*> *streamListeners = instances->ElementAt(i)->FileCachedStreamListeners(); + for (int32_t i = streamListeners->Length() - 1; i >= 0; --i) { + nsPluginStreamListenerPeer *lp = streamListeners->ElementAt(i); + if (lp && lp->mLocalCachedFileHolder) { + useExistingCacheFile = lp->UseExistingPluginCacheFile(this); + if (useExistingCacheFile) { + mLocalCachedFileHolder = lp->mLocalCachedFileHolder; + break; + } + } + if (useExistingCacheFile) + break; + } + } + + // Create a new cache file if one could not be found. + if (!useExistingCacheFile) { + nsCOMPtr<nsIFile> pluginTmp; + rv = nsPluginHost::GetPluginTempDir(getter_AddRefs(pluginTmp)); + if (NS_FAILED(rv)) { + return rv; + } + + // Get the filename from the channel + nsCOMPtr<nsIURI> uri; + rv = channel->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIURL> url(do_QueryInterface(uri)); + if (!url) + return NS_ERROR_FAILURE; + + nsAutoCString filename; + url->GetFileName(filename); + if (NS_FAILED(rv)) + return rv; + + // Create a file to save our stream into. Should we scramble the name? + filename.Insert(NS_LITERAL_CSTRING("plugin-"), 0); + rv = pluginTmp->AppendNative(filename); + if (NS_FAILED(rv)) + return rv; + + // Yes, make it unique. + rv = pluginTmp->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_FAILED(rv)) + return rv; + + // create a file output stream to write to... + rv = NS_NewLocalFileOutputStream(getter_AddRefs(mFileCacheOutputStream), pluginTmp, -1, 00600); + if (NS_FAILED(rv)) + return rv; + + // save the file. + mLocalCachedFileHolder = new CachedFileHolder(pluginTmp); + } + + // add this listenerPeer to list of stream peers for this instance + mPluginInstance->FileCachedStreamListeners()->AppendElement(this); + + return rv; +} + +NS_IMETHODIMP +nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request, + nsISupports* aContext) +{ + nsresult rv = NS_OK; + PROFILER_LABEL("nsPluginStreamListenerPeer", "OnStartRequest", + js::ProfileEntry::Category::OTHER); + + nsCOMPtr<nsIRequest> baseRequest = GetBaseRequest(request); + if (mRequests.IndexOfObject(baseRequest) == -1) { + NS_ASSERTION(mRequests.Count() == 0, + "Only our initial stream should be unknown!"); + TrackRequest(request); + } + + if (mHaveFiredOnStartRequest) { + return NS_OK; + } + + mHaveFiredOnStartRequest = true; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + // deal with 404 (Not Found) HTTP response, + // just return, this causes the request to be ignored. + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + uint32_t responseCode = 0; + rv = httpChannel->GetResponseStatus(&responseCode); + if (NS_FAILED(rv)) { + // NPP_Notify() will be called from OnStopRequest + // in nsNPAPIPluginStreamListener::CleanUpStream + // return error will cancel this request + // ...and we also need to tell the plugin that + mRequestFailed = true; + return NS_ERROR_FAILURE; + } + + if (responseCode > 206) { // not normal + uint32_t wantsAllNetworkStreams = 0; + + // We don't always have an instance here already, but if we do, check + // to see if it wants all streams. + if (mPluginInstance) { + rv = mPluginInstance->GetValueFromPlugin(NPPVpluginWantsAllNetworkStreams, + &wantsAllNetworkStreams); + // If the call returned an error code make sure we still use our default value. + if (NS_FAILED(rv)) { + wantsAllNetworkStreams = 0; + } + } + + if (!wantsAllNetworkStreams) { + mRequestFailed = true; + return NS_ERROR_FAILURE; + } + } + } + + nsAutoCString contentType; + rv = channel->GetContentType(contentType); + if (NS_FAILED(rv)) + return rv; + + // Check ShouldProcess with content policy + RefPtr<nsPluginInstanceOwner> owner; + if (mPluginInstance) { + owner = mPluginInstance->GetOwner(); + } + nsCOMPtr<nsIDOMElement> element; + nsCOMPtr<nsIDocument> doc; + if (owner) { + owner->GetDOMElement(getter_AddRefs(element)); + owner->GetDocument(getter_AddRefs(doc)); + } + nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr; + + int16_t shouldLoad = nsIContentPolicy::ACCEPT; + rv = NS_CheckContentProcessPolicy(nsIContentPolicy::TYPE_OBJECT_SUBREQUEST, + mURL, + principal, + element, + contentType, + nullptr, + &shouldLoad); + if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { + mRequestFailed = true; + return NS_ERROR_CONTENT_BLOCKED; + } + + // Get the notification callbacks from the channel and save it as + // week ref we'll use it in nsPluginStreamInfo::RequestRead() when + // we'll create channel for byte range request. + nsCOMPtr<nsIInterfaceRequestor> callbacks; + channel->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) + mWeakPtrChannelCallbacks = do_GetWeakReference(callbacks); + + nsCOMPtr<nsILoadGroup> loadGroup; + channel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) + mWeakPtrChannelLoadGroup = do_GetWeakReference(loadGroup); + + int64_t length; + rv = channel->GetContentLength(&length); + + // it's possible for the server to not send a Content-Length. + // we should still work in this case. + if (NS_FAILED(rv) || length < 0 || length > UINT32_MAX) { + // check out if this is file channel + nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel); + if (fileChannel) { + // file does not exist + mRequestFailed = true; + return NS_ERROR_FAILURE; + } + mLength = 0; + } + else { + mLength = uint32_t(length); + } + + nsCOMPtr<nsIURI> aURL; + rv = channel->GetURI(getter_AddRefs(aURL)); + if (NS_FAILED(rv)) + return rv; + + aURL->GetSpec(mURLSpec); + + if (!contentType.IsEmpty()) + mContentType = contentType; + +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NOISY, + ("nsPluginStreamListenerPeer::OnStartRequest this=%p request=%p mime=%s, url=%s\n", + this, request, contentType.get(), mURLSpec.get())); + + PR_LogFlush(); +#endif + + // Set up the stream listener... + rv = SetUpStreamListener(request, aURL); + if (NS_FAILED(rv)) { + return rv; + } + + return rv; +} + +NS_IMETHODIMP nsPluginStreamListenerPeer::OnProgress(nsIRequest *request, + nsISupports* aContext, + int64_t aProgress, + int64_t aProgressMax) +{ + nsresult rv = NS_OK; + return rv; +} + +NS_IMETHODIMP nsPluginStreamListenerPeer::OnStatus(nsIRequest *request, + nsISupports* aContext, + nsresult aStatus, + const char16_t* aStatusArg) +{ + return NS_OK; +} + +nsresult +nsPluginStreamListenerPeer::GetContentType(char** result) +{ + *result = const_cast<char*>(mContentType.get()); + return NS_OK; +} + + +nsresult +nsPluginStreamListenerPeer::IsSeekable(bool* result) +{ + *result = mSeekable; + return NS_OK; +} + +nsresult +nsPluginStreamListenerPeer::GetLength(uint32_t* result) +{ + *result = mLength; + return NS_OK; +} + +nsresult +nsPluginStreamListenerPeer::GetLastModified(uint32_t* result) +{ + *result = mModified; + return NS_OK; +} + +nsresult +nsPluginStreamListenerPeer::GetURL(const char** result) +{ + *result = mURLSpec.get(); + return NS_OK; +} + +void +nsPluginStreamListenerPeer::MakeByteRangeString(NPByteRange* aRangeList, nsACString &rangeRequest, + int32_t *numRequests) +{ + rangeRequest.Truncate(); + *numRequests = 0; + //the string should look like this: bytes=500-700,601-999 + if (!aRangeList) + return; + + int32_t requestCnt = 0; + nsAutoCString string("bytes="); + + for (NPByteRange * range = aRangeList; range != nullptr; range = range->next) { + // XXX zero length? + if (!range->length) + continue; + + // XXX needs to be fixed for negative offsets + string.AppendInt(range->offset); + string.Append('-'); + string.AppendInt(range->offset + range->length - 1); + if (range->next) + string.Append(','); + + requestCnt++; + } + + // get rid of possible trailing comma + string.Trim(",", false); + + rangeRequest = string; + *numRequests = requestCnt; + return; +} + +// XXX: Converting the channel within nsPluginStreamListenerPeer +// to use asyncOpen2() and do not want to touch the fragile logic +// of byte range requests. Hence we just introduce this lightweight +// wrapper to proxy the context. +class PluginContextProxy final : public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS + + PluginContextProxy(nsIStreamListener *aListener, nsISupports* aContext) + : mListener(aListener) + , mContext(aContext) + { + MOZ_ASSERT(aListener); + MOZ_ASSERT(aContext); + } + + NS_IMETHOD + OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream *aIStream, + uint64_t aSourceOffset, + uint32_t aLength) override + { + // Proxy OnDataAvailable using the internal context + return mListener->OnDataAvailable(aRequest, + mContext, + aIStream, + aSourceOffset, + aLength); + } + + NS_IMETHOD + OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override + { + // Proxy OnStartRequest using the internal context + return mListener->OnStartRequest(aRequest, mContext); + } + + NS_IMETHOD + OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, + nsresult aStatusCode) override + { + // Proxy OnStopRequest using the inernal context + return mListener->OnStopRequest(aRequest, + mContext, + aStatusCode); + } + +private: + ~PluginContextProxy() {} + nsCOMPtr<nsIStreamListener> mListener; + nsCOMPtr<nsISupports> mContext; +}; + +NS_IMPL_ISUPPORTS(PluginContextProxy, nsIStreamListener) + +nsresult +nsPluginStreamListenerPeer::RequestRead(NPByteRange* rangeList) +{ + nsAutoCString rangeString; + int32_t numRequests; + + MakeByteRangeString(rangeList, rangeString, &numRequests); + + if (numRequests == 0) + return NS_ERROR_FAILURE; + + nsresult rv = NS_OK; + + RefPtr<nsPluginInstanceOwner> owner = mPluginInstance->GetOwner(); + nsCOMPtr<nsIDOMElement> element; + nsCOMPtr<nsIDocument> doc; + if (owner) { + rv = owner->GetDOMElement(getter_AddRefs(element)); + NS_ENSURE_SUCCESS(rv, rv); + rv = owner->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryReferent(mWeakPtrChannelCallbacks); + nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakPtrChannelLoadGroup); + + nsCOMPtr<nsIChannel> channel; + nsCOMPtr<nsINode> requestingNode(do_QueryInterface(element)); + if (requestingNode) { + rv = NS_NewChannel(getter_AddRefs(channel), + mURL, + requestingNode, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + loadGroup, + callbacks, + nsIChannel::LOAD_BYPASS_SERVICE_WORKER); + } + else { + // In this else branch we really don't know where the load is coming + // from. Let's fall back to using the SystemPrincipal for such Plugins. + rv = NS_NewChannel(getter_AddRefs(channel), + mURL, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + loadGroup, + callbacks, + nsIChannel::LOAD_BYPASS_SERVICE_WORKER); + } + + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + if (!httpChannel) + return NS_ERROR_FAILURE; + + httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false); + + mAbort = true; // instruct old stream listener to cancel + // the request on the next ODA. + + nsCOMPtr<nsIStreamListener> converter; + + if (numRequests == 1) { + converter = this; + // set current stream offset equal to the first offset in the range list + // it will work for single byte range request + // for multy range we'll reset it in ODA + SetStreamOffset(rangeList->offset); + } else { + nsWeakPtr weakpeer = + do_GetWeakReference(static_cast<nsISupportsWeakReference*>(this)); + converter = new nsPluginByteRangeStreamListener(weakpeer); + } + + mPendingRequests += numRequests; + + nsCOMPtr<nsISupportsPRUint32> container = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = container->SetData(BYTERANGE_REQUEST_CONTEXT); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<PluginContextProxy> pluginContextProxy = + new PluginContextProxy(converter, container); + rv = channel->AsyncOpen2(pluginContextProxy); + NS_ENSURE_SUCCESS(rv, rv); + TrackRequest(channel); + return NS_OK; +} + +nsresult +nsPluginStreamListenerPeer::GetStreamOffset(int32_t* result) +{ + *result = mStreamOffset; + return NS_OK; +} + +nsresult +nsPluginStreamListenerPeer::SetStreamOffset(int32_t value) +{ + mStreamOffset = value; + return NS_OK; +} + +nsresult nsPluginStreamListenerPeer::ServeStreamAsFile(nsIRequest *request, + nsISupports* aContext) +{ + if (!mPluginInstance) + return NS_ERROR_FAILURE; + + // mPluginInstance->Stop calls mPStreamListener->CleanUpStream(), so stream will be properly clean up + mPluginInstance->Stop(); + mPluginInstance->Start(); + RefPtr<nsPluginInstanceOwner> owner = mPluginInstance->GetOwner(); + if (owner) { + NPWindow* window = nullptr; + owner->GetWindow(window); +#if (MOZ_WIDGET_GTK == 2) + // Should call GetPluginPort() here. + // This part is copied from nsPluginInstanceOwner::GetPluginPort(). + nsCOMPtr<nsIWidget> widget; + ((nsPluginNativeWindow*)window)->GetPluginWidget(getter_AddRefs(widget)); + if (widget) { + window->window = widget->GetNativeData(NS_NATIVE_PLUGIN_PORT); + } +#endif + owner->CallSetWindow(); + } + + mSeekable = false; + mPStreamListener->OnStartBinding(this); + mStreamOffset = 0; + + // force the plugin to use stream as file + mStreamType = NP_ASFILE; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if (channel) { + SetupPluginCacheFile(channel); + } + + // unset mPendingRequests + mPendingRequests = 0; + + return NS_OK; +} + +bool +nsPluginStreamListenerPeer::UseExistingPluginCacheFile(nsPluginStreamListenerPeer* psi) +{ + NS_ENSURE_TRUE(psi, false); + + if (psi->mLength == mLength && + psi->mModified == mModified && + mStreamComplete && + mURLSpec.Equals(psi->mURLSpec)) + { + return true; + } + return false; +} + +NS_IMETHODIMP nsPluginStreamListenerPeer::OnDataAvailable(nsIRequest *request, + nsISupports* aContext, + nsIInputStream *aIStream, + uint64_t sourceOffset, + uint32_t aLength) +{ + nsCOMPtr<nsIRequest> baseRequest = GetBaseRequest(request); + if (mRequests.IndexOfObject(baseRequest) == -1) { + MOZ_ASSERT(false, "Received OnDataAvailable for untracked request."); + return NS_ERROR_UNEXPECTED; + } + + if (mRequestFailed) + return NS_ERROR_FAILURE; + + if (mAbort) { + uint32_t byteRangeRequest = 0; // set it to something that is not the byte range request. + nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(aContext); + if (container) + container->GetData(&byteRangeRequest); + + if (byteRangeRequest != BYTERANGE_REQUEST_CONTEXT) { + // this is not one of our range requests + mAbort = false; + return NS_BINDING_ABORTED; + } + } + + nsresult rv = NS_OK; + + if (!mPStreamListener) + return NS_ERROR_FAILURE; + + const char * url = nullptr; + GetURL(&url); + + PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("nsPluginStreamListenerPeer::OnDataAvailable this=%p request=%p, offset=%llu, length=%u, url=%s\n", + this, request, sourceOffset, aLength, url ? url : "no url set")); + + // if the plugin has requested an AsFileOnly stream, then don't + // call OnDataAvailable + if (mStreamType != NP_ASFILEONLY) { + // get the absolute offset of the request, if one exists. + nsCOMPtr<nsIByteRangeRequest> brr = do_QueryInterface(request); + if (brr) { + if (!mDataForwardToRequest) + return NS_ERROR_FAILURE; + + int64_t absoluteOffset64 = 0; + brr->GetStartRange(&absoluteOffset64); + + // XXX handle 64-bit for real + int32_t absoluteOffset = (int32_t)int64_t(absoluteOffset64); + + // we need to track how much data we have forwarded to the + // plugin. + + // FIXME: http://bugzilla.mozilla.org/show_bug.cgi?id=240130 + // + // Why couldn't this be tracked on the plugin info, and not in a + // *hash table*? + int32_t amtForwardToPlugin = mDataForwardToRequest->Get(absoluteOffset); + mDataForwardToRequest->Put(absoluteOffset, (amtForwardToPlugin + aLength)); + + SetStreamOffset(absoluteOffset + amtForwardToPlugin); + } + + nsCOMPtr<nsIInputStream> stream = aIStream; + + // if we are caching the file ourselves to disk, we want to 'tee' off + // the data as the plugin read from the stream. We do this by the magic + // of an input stream tee. + + if (mFileCacheOutputStream) { + rv = NS_NewInputStreamTee(getter_AddRefs(stream), aIStream, mFileCacheOutputStream); + if (NS_FAILED(rv)) + return rv; + } + + rv = mPStreamListener->OnDataAvailable(this, + stream, + aLength); + + // if a plugin returns an error, the peer must kill the stream + // else the stream and PluginStreamListener leak + if (NS_FAILED(rv)) + request->Cancel(rv); + } + else + { + // if we don't read from the stream, OnStopRequest will never be called + char* buffer = new char[aLength]; + uint32_t amountRead, amountWrote = 0; + rv = aIStream->Read(buffer, aLength, &amountRead); + + // if we are caching this to disk ourselves, lets write the bytes out. + if (mFileCacheOutputStream) { + while (amountWrote < amountRead && NS_SUCCEEDED(rv)) { + rv = mFileCacheOutputStream->Write(buffer, amountRead, &amountWrote); + } + } + delete [] buffer; + } + return rv; +} + +NS_IMETHODIMP nsPluginStreamListenerPeer::OnStopRequest(nsIRequest *request, + nsISupports* aContext, + nsresult aStatus) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIMultiPartChannel> mp = do_QueryInterface(request); + if (!mp) { + bool found = mRequests.RemoveObject(request); + if (!found) { + NS_ERROR("Received OnStopRequest for untracked request."); + } + } + + PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("nsPluginStreamListenerPeer::OnStopRequest this=%p aStatus=%d request=%p\n", + this, aStatus, request)); + + // for ByteRangeRequest we're just updating the mDataForwardToRequest hash and return. + nsCOMPtr<nsIByteRangeRequest> brr = do_QueryInterface(request); + if (brr) { + int64_t absoluteOffset64 = 0; + brr->GetStartRange(&absoluteOffset64); + // XXX support 64-bit offsets + int32_t absoluteOffset = (int32_t)int64_t(absoluteOffset64); + + // remove the request from our data forwarding count hash. + mDataForwardToRequest->Remove(absoluteOffset); + + + PLUGIN_LOG(PLUGIN_LOG_NOISY, + (" ::OnStopRequest for ByteRangeRequest Started=%d\n", + absoluteOffset)); + } else { + // if this is not byte range request and + // if we are writting the stream to disk ourselves, + // close & tear it down here + mFileCacheOutputStream = nullptr; + } + + // if we still have pending stuff to do, lets not close the plugin socket. + if (--mPendingRequests > 0) + return NS_OK; + + // we keep our connections around... + nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(aContext); + if (container) { + uint32_t byteRangeRequest = 0; // something other than the byte range request. + container->GetData(&byteRangeRequest); + if (byteRangeRequest == BYTERANGE_REQUEST_CONTEXT) { + // this is one of our range requests + return NS_OK; + } + } + + if (!mPStreamListener) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if (!channel) + return NS_ERROR_FAILURE; + // Set the content type to ensure we don't pass null to the plugin + nsAutoCString aContentType; + rv = channel->GetContentType(aContentType); + if (NS_FAILED(rv) && !mRequestFailed) + return rv; + + if (!aContentType.IsEmpty()) + mContentType = aContentType; + + // set error status if stream failed so we notify the plugin + if (mRequestFailed) + aStatus = NS_ERROR_FAILURE; + + if (NS_FAILED(aStatus)) { + // on error status cleanup the stream + // and return w/o OnFileAvailable() + mPStreamListener->OnStopBinding(this, aStatus); + return NS_OK; + } + + // call OnFileAvailable if plugin requests stream type StreamType_AsFile or StreamType_AsFileOnly + if (mStreamType >= NP_ASFILE) { + nsCOMPtr<nsIFile> localFile; + if (mLocalCachedFileHolder) + localFile = mLocalCachedFileHolder->file(); + else { + // see if it is a file channel. + nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(request); + if (fileChannel) { + fileChannel->GetFile(getter_AddRefs(localFile)); + } + } + + if (localFile) { + OnFileAvailable(localFile); + } + } + + if (mStartBinding) { + // On start binding has been called + mPStreamListener->OnStopBinding(this, aStatus); + } else { + // OnStartBinding hasn't been called, so complete the action. + mPStreamListener->OnStartBinding(this); + mPStreamListener->OnStopBinding(this, aStatus); + } + + if (NS_SUCCEEDED(aStatus)) { + mStreamComplete = true; + } + + return NS_OK; +} + +nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest *request, + nsIURI* aURL) +{ + nsresult rv = NS_OK; + + // If we don't yet have a stream listener, we need to get + // one from the plugin. + // NOTE: this should only happen when a stream was NOT created + // with GetURL or PostURL (i.e. it's the initial stream we + // send to the plugin as determined by the SRC or DATA attribute) + if (!mPStreamListener) { + if (!mPluginInstance) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsNPAPIPluginStreamListener> streamListener; + rv = mPluginInstance->NewStreamListener(nullptr, nullptr, + getter_AddRefs(streamListener)); + if (NS_FAILED(rv) || !streamListener) { + return NS_ERROR_FAILURE; + } + + mPStreamListener = static_cast<nsNPAPIPluginStreamListener*>(streamListener.get()); + } + + mPStreamListener->SetStreamListenerPeer(this); + + // get httpChannel to retrieve some info we need for nsIPluginStreamInfo setup + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel); + + /* + * Assumption + * By the time nsPluginStreamListenerPeer::OnDataAvailable() gets + * called, all the headers have been read. + */ + if (httpChannel) { + // Reassemble the HTTP response status line and provide it to our + // listener. Would be nice if we could get the raw status line, + // but nsIHttpChannel doesn't currently provide that. + // Status code: required; the status line isn't useful without it. + uint32_t statusNum; + if (NS_SUCCEEDED(httpChannel->GetResponseStatus(&statusNum)) && + statusNum < 1000) { + // HTTP version: provide if available. Defaults to empty string. + nsCString ver; + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(channel); + if (httpChannelInternal) { + uint32_t major, minor; + if (NS_SUCCEEDED(httpChannelInternal->GetResponseVersion(&major, + &minor))) { + ver = nsPrintfCString("/%lu.%lu", major, minor); + } + } + + // Status text: provide if available. Defaults to "OK". + nsCString statusText; + if (NS_FAILED(httpChannel->GetResponseStatusText(statusText))) { + statusText = "OK"; + } + + // Assemble everything and pass to listener. + nsPrintfCString status("HTTP%s %lu %s", ver.get(), statusNum, + statusText.get()); + static_cast<nsIHTTPHeaderListener*>(mPStreamListener)->StatusLine(status.get()); + } + + // Also provide all HTTP response headers to our listener. + httpChannel->VisitResponseHeaders(this); + + mSeekable = false; + // first we look for a content-encoding header. If we find one, we tell the + // plugin that stream is not seekable, because the plugin always sees + // uncompressed data, so it can't make meaningful range requests on a + // compressed entity. Also, we force the plugin to use + // nsPluginStreamType_AsFile stream type and we have to save decompressed + // file into local plugin cache, because necko cache contains original + // compressed file. + nsAutoCString contentEncoding; + if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"), + contentEncoding))) { + mUseLocalCache = true; + } else { + // set seekability (seekable if the stream has a known length and if the + // http server accepts byte ranges). + uint32_t length; + GetLength(&length); + if (length) { + nsAutoCString range; + if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("accept-ranges"), range)) && + range.Equals(NS_LITERAL_CSTRING("bytes"), nsCaseInsensitiveCStringComparator())) { + mSeekable = true; + } + } + } + + // we require a content len + // get Last-Modified header for plugin info + nsAutoCString lastModified; + if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("last-modified"), lastModified)) && + !lastModified.IsEmpty()) { + PRTime time64; + PR_ParseTimeString(lastModified.get(), true, &time64); //convert string time to integer time + + // Convert PRTime to unix-style time_t, i.e. seconds since the epoch + double fpTime = double(time64); + mModified = (uint32_t)(fpTime * 1e-6 + 0.5); + } + } + + MOZ_ASSERT(!mRequest); + mRequest = request; + + rv = mPStreamListener->OnStartBinding(this); + + mStartBinding = true; + + if (NS_FAILED(rv)) + return rv; + + int32_t streamType = NP_NORMAL; + mPStreamListener->GetStreamType(&streamType); + + if (streamType != STREAM_TYPE_UNKNOWN) { + OnStreamTypeSet(streamType); + } + + return NS_OK; +} + +void +nsPluginStreamListenerPeer::OnStreamTypeSet(const int32_t aStreamType) +{ + MOZ_ASSERT(aStreamType != STREAM_TYPE_UNKNOWN); + MOZ_ASSERT(mRequest); + mStreamType = aStreamType; + if (!mUseLocalCache && mStreamType >= NP_ASFILE) { + // check it out if this is not a file channel. + nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(mRequest); + if (!fileChannel) { + mUseLocalCache = true; + } + } + + if (mUseLocalCache) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest); + SetupPluginCacheFile(channel); + } +} + +nsresult +nsPluginStreamListenerPeer::OnFileAvailable(nsIFile* aFile) +{ + nsresult rv; + if (!mPStreamListener) + return NS_ERROR_FAILURE; + + nsAutoCString path; + rv = aFile->GetNativePath(path); + if (NS_FAILED(rv)) return rv; + + if (path.IsEmpty()) { + NS_WARNING("empty path"); + return NS_OK; + } + + rv = mPStreamListener->OnFileAvailable(this, path.get()); + return rv; +} + +NS_IMETHODIMP +nsPluginStreamListenerPeer::VisitHeader(const nsACString &header, const nsACString &value) +{ + return mPStreamListener->NewResponseHeader(PromiseFlatCString(header).get(), + PromiseFlatCString(value).get()); +} + +nsresult +nsPluginStreamListenerPeer::GetInterfaceGlobal(const nsIID& aIID, void** result) +{ + if (!mPluginInstance) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsPluginInstanceOwner> owner = mPluginInstance->GetOwner(); + if (!owner) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIDocument> doc; + nsresult rv = owner->GetDocument(getter_AddRefs(doc)); + if (NS_FAILED(rv) || !doc) { + return NS_ERROR_FAILURE; + } + + nsPIDOMWindowOuter *window = doc->GetWindow(); + if (!window) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window); + nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(webNav); + if (!ir) { + return NS_ERROR_FAILURE; + } + + return ir->GetInterface(aIID, result); +} + +NS_IMETHODIMP +nsPluginStreamListenerPeer::GetInterface(const nsIID& aIID, void** result) +{ + // Provide nsIChannelEventSink ourselves, otherwise let our document's + // script global object owner provide the interface. + if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { + return QueryInterface(aIID, result); + } + + return GetInterfaceGlobal(aIID, result); +} + +/** + * Proxy class which forwards async redirect notifications back to the necko + * callback, keeping nsPluginStreamListenerPeer::mRequests in sync with + * which channel is active. + */ +class ChannelRedirectProxyCallback : public nsIAsyncVerifyRedirectCallback +{ +public: + ChannelRedirectProxyCallback(nsPluginStreamListenerPeer* listener, + nsIAsyncVerifyRedirectCallback* parent, + nsIChannel* oldChannel, + nsIChannel* newChannel) + : mWeakListener(do_GetWeakReference(static_cast<nsIStreamListener*>(listener))) + , mParent(parent) + , mOldChannel(oldChannel) + , mNewChannel(newChannel) + { + } + + ChannelRedirectProxyCallback() {} + + NS_DECL_ISUPPORTS + + NS_IMETHOD OnRedirectVerifyCallback(nsresult aResult) override + { + if (NS_SUCCEEDED(aResult)) { + nsCOMPtr<nsIStreamListener> listener = do_QueryReferent(mWeakListener); + if (listener) + static_cast<nsPluginStreamListenerPeer*>(listener.get())->ReplaceRequest(mOldChannel, mNewChannel); + } + return mParent->OnRedirectVerifyCallback(aResult); + } + +private: + virtual ~ChannelRedirectProxyCallback() {} + + nsWeakPtr mWeakListener; + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mParent; + nsCOMPtr<nsIChannel> mOldChannel; + nsCOMPtr<nsIChannel> mNewChannel; +}; + +NS_IMPL_ISUPPORTS(ChannelRedirectProxyCallback, nsIAsyncVerifyRedirectCallback) + + +NS_IMETHODIMP +nsPluginStreamListenerPeer::AsyncOnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel, + uint32_t flags, nsIAsyncVerifyRedirectCallback* callback) +{ + // Disallow redirects if we don't have a stream listener. + if (!mPStreamListener) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIAsyncVerifyRedirectCallback> proxyCallback = + new ChannelRedirectProxyCallback(this, callback, oldChannel, newChannel); + + // Give NPAPI a chance to control redirects. + bool notificationHandled = mPStreamListener->HandleRedirectNotification(oldChannel, newChannel, proxyCallback); + if (notificationHandled) { + return NS_OK; + } + + // Don't allow cross-origin 307 POST redirects. + nsCOMPtr<nsIHttpChannel> oldHttpChannel(do_QueryInterface(oldChannel)); + if (oldHttpChannel) { + uint32_t responseStatus; + nsresult rv = oldHttpChannel->GetResponseStatus(&responseStatus); + if (NS_FAILED(rv)) { + return rv; + } + if (responseStatus == 307) { + nsAutoCString method; + rv = oldHttpChannel->GetRequestMethod(method); + if (NS_FAILED(rv)) { + return rv; + } + if (method.EqualsLiteral("POST")) { + rv = nsContentUtils::CheckSameOrigin(oldChannel, newChannel); + if (NS_FAILED(rv)) { + return rv; + } + } + } + } + + // Fall back to channel event sink for window. + nsCOMPtr<nsIChannelEventSink> channelEventSink; + nsresult rv = GetInterfaceGlobal(NS_GET_IID(nsIChannelEventSink), getter_AddRefs(channelEventSink)); + if (NS_FAILED(rv)) { + return rv; + } + + return channelEventSink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, proxyCallback); +} diff --git a/dom/plugins/base/nsPluginStreamListenerPeer.h b/dom/plugins/base/nsPluginStreamListenerPeer.h new file mode 100644 index 000000000..32f629b40 --- /dev/null +++ b/dom/plugins/base/nsPluginStreamListenerPeer.h @@ -0,0 +1,185 @@ +/* -*- 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/. */ + +#ifndef nsPluginStreamListenerPeer_h_ +#define nsPluginStreamListenerPeer_h_ + +#include "nscore.h" +#include "nsIFile.h" +#include "nsIStreamListener.h" +#include "nsIProgressEventSink.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsWeakReference.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsNPAPIPluginInstance.h" +#include "nsIInterfaceRequestor.h" +#include "nsIChannelEventSink.h" + +class nsIChannel; + +/** + * When a plugin requests opens multiple requests to the same URL and + * the request must be satified by saving a file to disk, each stream + * listener holds a reference to the backing file: the file is only removed + * when all the listeners are done. + */ +class CachedFileHolder +{ +public: + explicit CachedFileHolder(nsIFile* cacheFile); + ~CachedFileHolder(); + + void AddRef(); + void Release(); + + nsIFile* file() const { return mFile; } + +private: + nsAutoRefCnt mRefCnt; + nsCOMPtr<nsIFile> mFile; +}; + +class nsPluginStreamListenerPeer : public nsIStreamListener, +public nsIProgressEventSink, +public nsIHttpHeaderVisitor, +public nsSupportsWeakReference, +public nsIInterfaceRequestor, +public nsIChannelEventSink +{ + virtual ~nsPluginStreamListenerPeer(); + +public: + nsPluginStreamListenerPeer(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROGRESSEVENTSINK + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIHTTPHEADERVISITOR + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + + // Called by RequestRead + void + MakeByteRangeString(NPByteRange* aRangeList, nsACString &string, int32_t *numRequests); + + bool UseExistingPluginCacheFile(nsPluginStreamListenerPeer* psi); + + // Called by GetURL and PostURL (via NewStream) or by the host in the case of + // the initial plugin stream. + nsresult Initialize(nsIURI *aURL, + nsNPAPIPluginInstance *aInstance, + nsNPAPIPluginStreamListener *aListener); + + nsresult OnFileAvailable(nsIFile* aFile); + + nsresult ServeStreamAsFile(nsIRequest *request, nsISupports *ctxt); + + nsNPAPIPluginInstance *GetPluginInstance() { return mPluginInstance; } + + nsresult RequestRead(NPByteRange* rangeList); + nsresult GetLength(uint32_t* result); + nsresult GetURL(const char** result); + nsresult GetLastModified(uint32_t* result); + nsresult IsSeekable(bool* result); + nsresult GetContentType(char** result); + nsresult GetStreamOffset(int32_t* result); + nsresult SetStreamOffset(int32_t value); + + void TrackRequest(nsIRequest* request) + { + mRequests.AppendObject(request); + } + + void ReplaceRequest(nsIRequest* oldRequest, nsIRequest* newRequest) + { + int32_t i = mRequests.IndexOfObject(oldRequest); + if (i == -1) { + NS_ASSERTION(mRequests.Count() == 0, + "Only our initial stream should be unknown!"); + mRequests.AppendObject(oldRequest); + } + else { + mRequests.ReplaceObjectAt(newRequest, i); + } + } + + void CancelRequests(nsresult status) + { + // Copy the array to avoid modification during the loop. + nsCOMArray<nsIRequest> requestsCopy(mRequests); + for (int32_t i = 0; i < requestsCopy.Count(); ++i) + requestsCopy[i]->Cancel(status); + } + + void SuspendRequests() { + nsCOMArray<nsIRequest> requestsCopy(mRequests); + for (int32_t i = 0; i < requestsCopy.Count(); ++i) + requestsCopy[i]->Suspend(); + } + + void ResumeRequests() { + nsCOMArray<nsIRequest> requestsCopy(mRequests); + for (int32_t i = 0; i < requestsCopy.Count(); ++i) + requestsCopy[i]->Resume(); + } + + // Called by nsNPAPIPluginStreamListener + void OnStreamTypeSet(const int32_t aStreamType); + + enum { + STREAM_TYPE_UNKNOWN = UINT16_MAX + }; + +private: + nsresult SetUpStreamListener(nsIRequest* request, nsIURI* aURL); + nsresult SetupPluginCacheFile(nsIChannel* channel); + nsresult GetInterfaceGlobal(const nsIID& aIID, void** result); + + nsCOMPtr<nsIURI> mURL; + nsCString mURLSpec; // Have to keep this member because GetURL hands out char* + RefPtr<nsNPAPIPluginStreamListener> mPStreamListener; + + // Set to true if we request failed (like with a HTTP response of 404) + bool mRequestFailed; + + /* + * Set to true after nsNPAPIPluginStreamListener::OnStartBinding() has + * been called. Checked in ::OnStopRequest so we can call the + * plugin's OnStartBinding if, for some reason, it has not already + * been called. + */ + bool mStartBinding; + bool mHaveFiredOnStartRequest; + // these get passed to the plugin stream listener + uint32_t mLength; + int32_t mStreamType; + + // local cached file, we save the content into local cache if browser cache is not available, + // or plugin asks stream as file and it expects file extension until bug 90558 got fixed + RefPtr<CachedFileHolder> mLocalCachedFileHolder; + nsCOMPtr<nsIOutputStream> mFileCacheOutputStream; + nsDataHashtable<nsUint32HashKey, uint32_t>* mDataForwardToRequest; + + nsCString mContentType; + bool mUseLocalCache; + nsCOMPtr<nsIRequest> mRequest; + bool mSeekable; + uint32_t mModified; + RefPtr<nsNPAPIPluginInstance> mPluginInstance; + int32_t mStreamOffset; + bool mStreamComplete; + +public: + bool mAbort; + int32_t mPendingRequests; + nsWeakPtr mWeakPtrChannelCallbacks; + nsWeakPtr mWeakPtrChannelLoadGroup; + nsCOMArray<nsIRequest> mRequests; +}; + +#endif // nsPluginStreamListenerPeer_h_ diff --git a/dom/plugins/base/nsPluginTags.cpp b/dom/plugins/base/nsPluginTags.cpp new file mode 100644 index 000000000..ddc3968fd --- /dev/null +++ b/dom/plugins/base/nsPluginTags.cpp @@ -0,0 +1,1045 @@ +/* -*- 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 "nsPluginTags.h" + +#include "prlink.h" +#include "plstr.h" +#include "nsIPluginInstanceOwner.h" +#include "nsPluginsDir.h" +#include "nsPluginHost.h" +#include "nsIBlocklistService.h" +#include "nsIUnicodeDecoder.h" +#include "nsIPlatformCharset.h" +#include "nsPluginLogging.h" +#include "nsNPAPIPlugin.h" +#include "nsCharSeparatedTokenizer.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "nsNetUtil.h" +#include <cctype> +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/FakePluginTagInitBinding.h" + +using mozilla::dom::EncodingUtils; +using mozilla::dom::FakePluginTagInit; +using namespace mozilla; + +// These legacy flags are used in the plugin registry. The states are now +// stored in prefs, but we still need to be able to import them. +#define NS_PLUGIN_FLAG_ENABLED 0x0001 // is this plugin enabled? +// no longer used 0x0002 // reuse only if regenerating pluginreg.dat +#define NS_PLUGIN_FLAG_FROMCACHE 0x0004 // this plugintag info was loaded from cache +// no longer used 0x0008 // reuse only if regenerating pluginreg.dat +#define NS_PLUGIN_FLAG_CLICKTOPLAY 0x0020 // this is a click-to-play plugin + +static const char kPrefDefaultEnabledState[] = "plugin.default.state"; +static const char kPrefDefaultEnabledStateXpi[] = "plugin.defaultXpi.state"; + +// check comma delimited extensions +static bool ExtensionInList(const nsCString& aExtensionList, + const nsACString& aExtension) +{ + nsCCharSeparatedTokenizer extensions(aExtensionList, ','); + while (extensions.hasMoreTokens()) { + const nsCSubstring& extension = extensions.nextToken(); + if (extension.Equals(aExtension, nsCaseInsensitiveCStringComparator())) { + return true; + } + } + return false; +} + +// Search for an extension in an extensions array, and return its +// matching mime type +static bool SearchExtensions(const nsTArray<nsCString> & aExtensions, + const nsTArray<nsCString> & aMimeTypes, + const nsACString & aFindExtension, + nsACString & aMatchingType) +{ + uint32_t mimes = aMimeTypes.Length(); + MOZ_ASSERT(mimes == aExtensions.Length(), + "These arrays should have matching elements"); + + aMatchingType.Truncate(); + + for (uint32_t i = 0; i < mimes; i++) { + if (ExtensionInList(aExtensions[i], aFindExtension)) { + aMatchingType = aMimeTypes[i]; + return true; + } + } + + return false; +} + +static nsCString +MakeNiceFileName(const nsCString & aFileName) +{ + nsCString niceName = aFileName; + int32_t niceNameLength = aFileName.RFind("."); + NS_ASSERTION(niceNameLength != kNotFound, "aFileName doesn't have a '.'?"); + while (niceNameLength > 0) { + char chr = aFileName[niceNameLength - 1]; + if (!std::isalpha(chr)) + niceNameLength--; + else + break; + } + + // If it turns out that niceNameLength <= 0, we'll fall back and use the + // entire aFileName (which we've already taken care of, a few lines back). + if (niceNameLength > 0) { + niceName.Truncate(niceNameLength); + } + + ToLowerCase(niceName); + return niceName; +} + +static nsCString +MakePrefNameForPlugin(const char* const subname, nsIInternalPluginTag* aTag) +{ + nsCString pref; + nsAutoCString pluginName(aTag->GetNiceFileName()); + + if (pluginName.IsEmpty()) { + // Use filename if nice name fails + pluginName = aTag->FileName(); + if (pluginName.IsEmpty()) { + MOZ_ASSERT_UNREACHABLE("Plugin with no filename or nice name in list"); + pluginName.AssignLiteral("unknown-plugin-name"); + } + } + + pref.AssignLiteral("plugin."); + pref.Append(subname); + pref.Append('.'); + pref.Append(pluginName); + + return pref; +} + +static nsresult +CStringArrayToXPCArray(nsTArray<nsCString> & aArray, + uint32_t* aCount, + char16_t*** aResults) +{ + uint32_t count = aArray.Length(); + if (!count) { + *aResults = nullptr; + *aCount = 0; + return NS_OK; + } + + *aResults = + static_cast<char16_t**>(moz_xmalloc(count * sizeof(**aResults))); + *aCount = count; + + for (uint32_t i = 0; i < count; i++) { + (*aResults)[i] = ToNewUnicode(NS_ConvertUTF8toUTF16(aArray[i])); + } + + return NS_OK; +} + +static nsCString +GetStatePrefNameForPlugin(nsIInternalPluginTag* aTag) +{ + return MakePrefNameForPlugin("state", aTag); +} + +static nsresult +IsEnabledStateLockedForPlugin(nsIInternalPluginTag* aTag, + bool* aIsEnabledStateLocked) +{ + *aIsEnabledStateLocked = false; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + if (NS_WARN_IF(!prefs)) { + return NS_ERROR_FAILURE; + } + + Unused << prefs->PrefIsLocked(GetStatePrefNameForPlugin(aTag).get(), + aIsEnabledStateLocked); + + return NS_OK; +} + +/* nsIInternalPluginTag */ +nsIInternalPluginTag::nsIInternalPluginTag() +{ +} + +nsIInternalPluginTag::nsIInternalPluginTag(const char* aName, + const char* aDescription, + const char* aFileName, + const char* aVersion) + : mName(aName) + , mDescription(aDescription) + , mFileName(aFileName) + , mVersion(aVersion) +{ +} + +nsIInternalPluginTag::nsIInternalPluginTag(const char* aName, + const char* aDescription, + const char* aFileName, + const char* aVersion, + const nsTArray<nsCString>& aMimeTypes, + const nsTArray<nsCString>& aMimeDescriptions, + const nsTArray<nsCString>& aExtensions) + : mName(aName) + , mDescription(aDescription) + , mFileName(aFileName) + , mVersion(aVersion) + , mMimeTypes(aMimeTypes) + , mMimeDescriptions(aMimeDescriptions) + , mExtensions(aExtensions) +{ +} + +nsIInternalPluginTag::~nsIInternalPluginTag() +{ +} + +bool +nsIInternalPluginTag::HasExtension(const nsACString& aExtension, + nsACString& aMatchingType) const +{ + return SearchExtensions(mExtensions, mMimeTypes, aExtension, aMatchingType); +} + +bool +nsIInternalPluginTag::HasMimeType(const nsACString& aMimeType) const +{ + return mMimeTypes.Contains(aMimeType, + nsCaseInsensitiveCStringArrayComparator()); +} + +/* nsPluginTag */ + +uint32_t nsPluginTag::sNextId; + +nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo, + int64_t aLastModifiedTime, + bool fromExtension) + : nsIInternalPluginTag(aPluginInfo->fName, aPluginInfo->fDescription, + aPluginInfo->fFileName, aPluginInfo->fVersion), + mId(sNextId++), + mContentProcessRunningCount(0), + mHadLocalInstance(false), + mLibrary(nullptr), + mIsJavaPlugin(false), + mIsFlashPlugin(false), + mSupportsAsyncInit(false), + mSupportsAsyncRender(false), + mFullPath(aPluginInfo->fFullPath), + mLastModifiedTime(aLastModifiedTime), + mSandboxLevel(0), + mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED), + mCachedBlocklistStateValid(false), + mIsFromExtension(fromExtension) +{ + InitMime(aPluginInfo->fMimeTypeArray, + aPluginInfo->fMimeDescriptionArray, + aPluginInfo->fExtensionArray, + aPluginInfo->fVariantCount); + InitSandboxLevel(); + EnsureMembersAreUTF8(); + FixupVersion(); +} + +nsPluginTag::nsPluginTag(const char* aName, + const char* aDescription, + const char* aFileName, + const char* aFullPath, + const char* aVersion, + const char* const* aMimeTypes, + const char* const* aMimeDescriptions, + const char* const* aExtensions, + int32_t aVariants, + int64_t aLastModifiedTime, + bool fromExtension, + bool aArgsAreUTF8) + : nsIInternalPluginTag(aName, aDescription, aFileName, aVersion), + mId(sNextId++), + mContentProcessRunningCount(0), + mHadLocalInstance(false), + mLibrary(nullptr), + mIsJavaPlugin(false), + mIsFlashPlugin(false), + mSupportsAsyncInit(false), + mSupportsAsyncRender(false), + mFullPath(aFullPath), + mLastModifiedTime(aLastModifiedTime), + mSandboxLevel(0), + mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED), + mCachedBlocklistStateValid(false), + mIsFromExtension(fromExtension) +{ + InitMime(aMimeTypes, aMimeDescriptions, aExtensions, + static_cast<uint32_t>(aVariants)); + InitSandboxLevel(); + if (!aArgsAreUTF8) + EnsureMembersAreUTF8(); + FixupVersion(); +} + +nsPluginTag::nsPluginTag(uint32_t aId, + const char* aName, + const char* aDescription, + const char* aFileName, + const char* aFullPath, + const char* aVersion, + nsTArray<nsCString> aMimeTypes, + nsTArray<nsCString> aMimeDescriptions, + nsTArray<nsCString> aExtensions, + bool aIsJavaPlugin, + bool aIsFlashPlugin, + bool aSupportsAsyncInit, + bool aSupportsAsyncRender, + int64_t aLastModifiedTime, + bool aFromExtension, + int32_t aSandboxLevel) + : nsIInternalPluginTag(aName, aDescription, aFileName, aVersion, aMimeTypes, + aMimeDescriptions, aExtensions), + mId(aId), + mContentProcessRunningCount(0), + mLibrary(nullptr), + mIsJavaPlugin(aIsJavaPlugin), + mIsFlashPlugin(aIsFlashPlugin), + mSupportsAsyncInit(aSupportsAsyncInit), + mSupportsAsyncRender(aSupportsAsyncRender), + mLastModifiedTime(aLastModifiedTime), + mSandboxLevel(aSandboxLevel), + mNiceFileName(), + mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED), + mCachedBlocklistStateValid(false), + mIsFromExtension(aFromExtension) +{ +} + +nsPluginTag::~nsPluginTag() +{ + NS_ASSERTION(!mNext, "Risk of exhausting the stack space, bug 486349"); +} + +NS_IMPL_ISUPPORTS(nsPluginTag, nsPluginTag, nsIInternalPluginTag, nsIPluginTag) + +void nsPluginTag::InitMime(const char* const* aMimeTypes, + const char* const* aMimeDescriptions, + const char* const* aExtensions, + uint32_t aVariantCount) +{ + if (!aMimeTypes) { + return; + } + + for (uint32_t i = 0; i < aVariantCount; i++) { + if (!aMimeTypes[i]) { + continue; + } + + nsAutoCString mimeType(aMimeTypes[i]); + + // Convert the MIME type, which is case insensitive, to lowercase in order + // to properly handle a mixed-case type. + ToLowerCase(mimeType); + + if (!nsPluginHost::IsTypeWhitelisted(mimeType.get())) { + continue; + } + + // Look for certain special plugins. + switch (nsPluginHost::GetSpecialType(mimeType)) { + case nsPluginHost::eSpecialType_Java: + mIsJavaPlugin = true; + mSupportsAsyncInit = true; + break; + case nsPluginHost::eSpecialType_Flash: + // VLC sometimes claims to implement the Flash MIME type, and we want + // to allow users to control that separately from Adobe Flash. + if (Name().EqualsLiteral("Shockwave Flash")) { + mIsFlashPlugin = true; + mSupportsAsyncInit = true; + } + break; + case nsPluginHost::eSpecialType_Silverlight: + case nsPluginHost::eSpecialType_Unity: + case nsPluginHost::eSpecialType_Test: + mSupportsAsyncInit = true; + break; + case nsPluginHost::eSpecialType_None: + default: +#ifndef RELEASE_OR_BETA + // Allow async init for all plugins on Nightly and Aurora + mSupportsAsyncInit = true; +#endif + break; + } + + // Fill in our MIME type array. + mMimeTypes.AppendElement(mimeType); + + // Now fill in the MIME descriptions. + if (aMimeDescriptions && aMimeDescriptions[i]) { + // we should cut off the list of suffixes which the mime + // description string may have, see bug 53895 + // it is usually in form "some description (*.sf1, *.sf2)" + // so we can search for the opening round bracket + char cur = '\0'; + char pre = '\0'; + char * p = PL_strrchr(aMimeDescriptions[i], '('); + if (p && (p != aMimeDescriptions[i])) { + if ((p - 1) && *(p - 1) == ' ') { + pre = *(p - 1); + *(p - 1) = '\0'; + } else { + cur = *p; + *p = '\0'; + } + } + mMimeDescriptions.AppendElement(nsCString(aMimeDescriptions[i])); + // restore the original string + if (cur != '\0') { + *p = cur; + } + if (pre != '\0') { + *(p - 1) = pre; + } + } else { + mMimeDescriptions.AppendElement(nsCString()); + } + + // Now fill in the extensions. + if (aExtensions && aExtensions[i]) { + mExtensions.AppendElement(nsCString(aExtensions[i])); + } else { + mExtensions.AppendElement(nsCString()); + } + } +} + +void +nsPluginTag::InitSandboxLevel() +{ +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + nsAutoCString sandboxPref("dom.ipc.plugins.sandbox-level."); + sandboxPref.Append(GetNiceFileName()); + if (NS_FAILED(Preferences::GetInt(sandboxPref.get(), &mSandboxLevel))) { + mSandboxLevel = Preferences::GetInt("dom.ipc.plugins.sandbox-level.default" +); + } + +#if defined(_AMD64_) + // As level 2 is now the default NPAPI sandbox level for 64-bit flash, we + // don't want to allow a lower setting unless this environment variable is + // set. This should be changed if the firefox.js pref file is changed. + if (mIsFlashPlugin && + !PR_GetEnv("MOZ_ALLOW_WEAKER_SANDBOX") && mSandboxLevel < 2) { + mSandboxLevel = 2; + } +#endif +#endif +} + +#if !defined(XP_WIN) && !defined(XP_MACOSX) +static nsresult ConvertToUTF8(nsIUnicodeDecoder *aUnicodeDecoder, + nsAFlatCString& aString) +{ + int32_t numberOfBytes = aString.Length(); + int32_t outUnicodeLen; + nsAutoString buffer; + nsresult rv = aUnicodeDecoder->GetMaxLength(aString.get(), numberOfBytes, + &outUnicodeLen); + NS_ENSURE_SUCCESS(rv, rv); + if (!buffer.SetLength(outUnicodeLen, fallible)) + return NS_ERROR_OUT_OF_MEMORY; + rv = aUnicodeDecoder->Convert(aString.get(), &numberOfBytes, + buffer.BeginWriting(), &outUnicodeLen); + NS_ENSURE_SUCCESS(rv, rv); + buffer.SetLength(outUnicodeLen); + CopyUTF16toUTF8(buffer, aString); + + return NS_OK; +} +#endif + +nsresult nsPluginTag::EnsureMembersAreUTF8() +{ +#if defined(XP_WIN) || defined(XP_MACOSX) + return NS_OK; +#else + nsresult rv; + + nsCOMPtr<nsIPlatformCharset> pcs = + do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIUnicodeDecoder> decoder; + + nsAutoCString charset; + rv = pcs->GetCharset(kPlatformCharsetSel_FileName, charset); + NS_ENSURE_SUCCESS(rv, rv); + if (!charset.LowerCaseEqualsLiteral("utf-8")) { + decoder = EncodingUtils::DecoderForEncoding(charset); + ConvertToUTF8(decoder, mFileName); + ConvertToUTF8(decoder, mFullPath); + } + + // The description of the plug-in and the various MIME type descriptions + // should be encoded in the standard plain text file encoding for this system. + // XXX should we add kPlatformCharsetSel_PluginResource? + rv = pcs->GetCharset(kPlatformCharsetSel_PlainTextInFile, charset); + NS_ENSURE_SUCCESS(rv, rv); + if (!charset.LowerCaseEqualsLiteral("utf-8")) { + decoder = EncodingUtils::DecoderForEncoding(charset); + ConvertToUTF8(decoder, mName); + ConvertToUTF8(decoder, mDescription); + for (uint32_t i = 0; i < mMimeDescriptions.Length(); ++i) { + ConvertToUTF8(decoder, mMimeDescriptions[i]); + } + } + return NS_OK; +#endif +} + +void nsPluginTag::FixupVersion() +{ +#if defined(XP_LINUX) + if (mIsFlashPlugin) { + mVersion.ReplaceChar(',', '.'); + } +#endif +} + +NS_IMETHODIMP +nsPluginTag::GetDescription(nsACString& aDescription) +{ + aDescription = mDescription; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetFilename(nsACString& aFileName) +{ + aFileName = mFileName; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetFullpath(nsACString& aFullPath) +{ + aFullPath = mFullPath; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetVersion(nsACString& aVersion) +{ + aVersion = mVersion; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetName(nsACString& aName) +{ + aName = mName; + return NS_OK; +} + +bool +nsPluginTag::IsActive() +{ + return IsEnabled() && !IsBlocklisted(); +} + +NS_IMETHODIMP +nsPluginTag::GetActive(bool *aResult) +{ + *aResult = IsActive(); + return NS_OK; +} + +bool +nsPluginTag::IsEnabled() +{ + const PluginState state = GetPluginState(); + return (state == ePluginState_Enabled) || (state == ePluginState_Clicktoplay); +} + +NS_IMETHODIMP +nsPluginTag::GetDisabled(bool* aDisabled) +{ + *aDisabled = !IsEnabled(); + return NS_OK; +} + +bool +nsPluginTag::IsBlocklisted() +{ + uint32_t blocklistState; + nsresult rv = GetBlocklistState(&blocklistState); + return NS_FAILED(rv) || blocklistState == nsIBlocklistService::STATE_BLOCKED; +} + +NS_IMETHODIMP +nsPluginTag::GetBlocklisted(bool* aBlocklisted) +{ + *aBlocklisted = IsBlocklisted(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetIsEnabledStateLocked(bool* aIsEnabledStateLocked) +{ + return IsEnabledStateLockedForPlugin(this, aIsEnabledStateLocked); +} + +bool +nsPluginTag::IsClicktoplay() +{ + const PluginState state = GetPluginState(); + return (state == ePluginState_Clicktoplay); +} + +NS_IMETHODIMP +nsPluginTag::GetClicktoplay(bool *aClicktoplay) +{ + *aClicktoplay = IsClicktoplay(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetEnabledState(uint32_t *aEnabledState) +{ + int32_t enabledState; + nsresult rv = Preferences::GetInt(GetStatePrefNameForPlugin(this).get(), + &enabledState); + if (NS_SUCCEEDED(rv) && + enabledState >= nsIPluginTag::STATE_DISABLED && + enabledState <= nsIPluginTag::STATE_ENABLED) { + *aEnabledState = (uint32_t)enabledState; + return rv; + } + + const char* const pref = mIsFromExtension ? kPrefDefaultEnabledStateXpi + : kPrefDefaultEnabledState; + + enabledState = Preferences::GetInt(pref, nsIPluginTag::STATE_ENABLED); + if (enabledState >= nsIPluginTag::STATE_DISABLED && + enabledState <= nsIPluginTag::STATE_ENABLED) { + *aEnabledState = (uint32_t)enabledState; + return NS_OK; + } + + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsPluginTag::SetEnabledState(uint32_t aEnabledState) +{ + if (aEnabledState >= ePluginState_MaxValue) + return NS_ERROR_ILLEGAL_VALUE; + uint32_t oldState = nsIPluginTag::STATE_DISABLED; + GetEnabledState(&oldState); + if (oldState != aEnabledState) { + Preferences::SetInt(GetStatePrefNameForPlugin(this).get(), aEnabledState); + if (RefPtr<nsPluginHost> host = nsPluginHost::GetInst()) { + host->UpdatePluginInfo(this); + } + } + return NS_OK; +} + +nsPluginTag::PluginState +nsPluginTag::GetPluginState() +{ + uint32_t enabledState = nsIPluginTag::STATE_DISABLED; + GetEnabledState(&enabledState); + return (PluginState)enabledState; +} + +void +nsPluginTag::SetPluginState(PluginState state) +{ + static_assert((uint32_t)nsPluginTag::ePluginState_Disabled == nsIPluginTag::STATE_DISABLED, "nsPluginTag::ePluginState_Disabled must match nsIPluginTag::STATE_DISABLED"); + static_assert((uint32_t)nsPluginTag::ePluginState_Clicktoplay == nsIPluginTag::STATE_CLICKTOPLAY, "nsPluginTag::ePluginState_Clicktoplay must match nsIPluginTag::STATE_CLICKTOPLAY"); + static_assert((uint32_t)nsPluginTag::ePluginState_Enabled == nsIPluginTag::STATE_ENABLED, "nsPluginTag::ePluginState_Enabled must match nsIPluginTag::STATE_ENABLED"); + SetEnabledState((uint32_t)state); +} + +NS_IMETHODIMP +nsPluginTag::GetMimeTypes(uint32_t* aCount, char16_t*** aResults) +{ + return CStringArrayToXPCArray(mMimeTypes, aCount, aResults); +} + +NS_IMETHODIMP +nsPluginTag::GetMimeDescriptions(uint32_t* aCount, char16_t*** aResults) +{ + return CStringArrayToXPCArray(mMimeDescriptions, aCount, aResults); +} + +NS_IMETHODIMP +nsPluginTag::GetExtensions(uint32_t* aCount, char16_t*** aResults) +{ + return CStringArrayToXPCArray(mExtensions, aCount, aResults); +} + +bool +nsPluginTag::HasSameNameAndMimes(const nsPluginTag *aPluginTag) const +{ + NS_ENSURE_TRUE(aPluginTag, false); + + if ((!mName.Equals(aPluginTag->mName)) || + (mMimeTypes.Length() != aPluginTag->mMimeTypes.Length())) { + return false; + } + + for (uint32_t i = 0; i < mMimeTypes.Length(); i++) { + if (!mMimeTypes[i].Equals(aPluginTag->mMimeTypes[i])) { + return false; + } + } + + return true; +} + +NS_IMETHODIMP +nsPluginTag::GetLoaded(bool* aIsLoaded) +{ + *aIsLoaded = !!mPlugin; + return NS_OK; +} + +void nsPluginTag::TryUnloadPlugin(bool inShutdown) +{ + // We never want to send NPP_Shutdown to an in-process plugin unless + // this process is shutting down. + if (!mPlugin) { + return; + } + if (inShutdown || mPlugin->GetLibrary()->IsOOP()) { + mPlugin->Shutdown(); + mPlugin = nullptr; + } +} + +const nsCString& +nsPluginTag::GetNiceFileName() +{ + if (!mNiceFileName.IsEmpty()) { + return mNiceFileName; + } + + if (mIsFlashPlugin) { + mNiceFileName.AssignLiteral("flash"); + return mNiceFileName; + } + + if (mIsJavaPlugin) { + mNiceFileName.AssignLiteral("java"); + return mNiceFileName; + } + + mNiceFileName = MakeNiceFileName(mFileName); + return mNiceFileName; +} + +NS_IMETHODIMP +nsPluginTag::GetNiceName(nsACString & aResult) +{ + aResult = GetNiceFileName(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetBlocklistState(uint32_t *aResult) +{ +#if defined(MOZ_WIDGET_ANDROID) + *aResult = nsIBlocklistService::STATE_NOT_BLOCKED; + return NS_OK; +#else + if (mCachedBlocklistStateValid) { + *aResult = mCachedBlocklistState; + return NS_OK; + } + + if (!XRE_IsParentProcess()) { + *aResult = nsIBlocklistService::STATE_BLOCKED; + dom::ContentChild* cp = dom::ContentChild::GetSingleton(); + if (!cp->SendGetBlocklistState(mId, aResult)) { + return NS_OK; + } + } else { + nsCOMPtr<nsIBlocklistService> blocklist = + do_GetService("@mozilla.org/extensions/blocklist;1"); + + if (!blocklist) { + *aResult = nsIBlocklistService::STATE_NOT_BLOCKED; + return NS_OK; + } + + // The EmptyString()s are so we use the currently running application + // and toolkit versions + if (NS_FAILED(blocklist->GetPluginBlocklistState(this, EmptyString(), + EmptyString(), aResult))) { + *aResult = nsIBlocklistService::STATE_NOT_BLOCKED; + return NS_OK; + } + } + + MOZ_ASSERT(*aResult <= UINT16_MAX); + mCachedBlocklistState = (uint16_t) *aResult; + mCachedBlocklistStateValid = true; + return NS_OK; +#endif // defined(MOZ_WIDGET_ANDROID) +} + +void +nsPluginTag::InvalidateBlocklistState() +{ + mCachedBlocklistStateValid = false; +} + +NS_IMETHODIMP +nsPluginTag::GetLastModifiedTime(PRTime* aLastModifiedTime) +{ + MOZ_ASSERT(aLastModifiedTime); + *aLastModifiedTime = mLastModifiedTime; + return NS_OK; +} + +bool nsPluginTag::IsFromExtension() const +{ + return mIsFromExtension; +} + +/* nsFakePluginTag */ + +nsFakePluginTag::nsFakePluginTag() + : mState(nsPluginTag::ePluginState_Disabled) +{ +} + +nsFakePluginTag::~nsFakePluginTag() +{ +} + +NS_IMPL_ADDREF(nsFakePluginTag) +NS_IMPL_RELEASE(nsFakePluginTag) +NS_INTERFACE_TABLE_HEAD(nsFakePluginTag) + NS_INTERFACE_TABLE_BEGIN + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsFakePluginTag, nsIPluginTag, + nsIInternalPluginTag) + NS_INTERFACE_TABLE_ENTRY(nsFakePluginTag, nsIInternalPluginTag) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsFakePluginTag, nsISupports, + nsIInternalPluginTag) + NS_INTERFACE_TABLE_ENTRY(nsFakePluginTag, nsIFakePluginTag) + NS_INTERFACE_TABLE_END +NS_INTERFACE_TABLE_TAIL + +/* static */ +nsresult +nsFakePluginTag::Create(const FakePluginTagInit& aInitDictionary, + nsFakePluginTag** aPluginTag) +{ + NS_ENSURE_TRUE(!aInitDictionary.mMimeEntries.IsEmpty(), NS_ERROR_INVALID_ARG); + + RefPtr<nsFakePluginTag> tag = new nsFakePluginTag(); + nsresult rv = NS_NewURI(getter_AddRefs(tag->mHandlerURI), + aInitDictionary.mHandlerURI); + NS_ENSURE_SUCCESS(rv, rv); + + CopyUTF16toUTF8(aInitDictionary.mNiceName, tag->mNiceName); + CopyUTF16toUTF8(aInitDictionary.mFullPath, tag->mFullPath); + CopyUTF16toUTF8(aInitDictionary.mName, tag->mName); + CopyUTF16toUTF8(aInitDictionary.mDescription, tag->mDescription); + CopyUTF16toUTF8(aInitDictionary.mFileName, tag->mFileName); + CopyUTF16toUTF8(aInitDictionary.mVersion, tag->mVersion); + + for (const FakePluginMimeEntry& mimeEntry : aInitDictionary.mMimeEntries) { + CopyUTF16toUTF8(mimeEntry.mType, *tag->mMimeTypes.AppendElement()); + CopyUTF16toUTF8(mimeEntry.mDescription, + *tag->mMimeDescriptions.AppendElement()); + CopyUTF16toUTF8(mimeEntry.mExtension, *tag->mExtensions.AppendElement()); + } + + tag.forget(aPluginTag); + return NS_OK; +} + +bool +nsFakePluginTag::HandlerURIMatches(nsIURI* aURI) +{ + bool equals = false; + return NS_SUCCEEDED(mHandlerURI->Equals(aURI, &equals)) && equals; +} + +NS_IMETHODIMP +nsFakePluginTag::GetHandlerURI(nsIURI **aResult) +{ + NS_IF_ADDREF(*aResult = mHandlerURI); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetDescription(/* utf-8 */ nsACString& aResult) +{ + aResult = mDescription; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetFilename(/* utf-8 */ nsACString& aResult) +{ + aResult = mFileName; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetFullpath(/* utf-8 */ nsACString& aResult) +{ + aResult = mFullPath; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetVersion(/* utf-8 */ nsACString& aResult) +{ + aResult = mVersion; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetName(/* utf-8 */ nsACString& aResult) +{ + aResult = mName; + return NS_OK; +} + +const nsCString& +nsFakePluginTag::GetNiceFileName() +{ + // We don't try to mimic the special-cased flash/java names if the fake plugin + // claims one of their MIME types, but do allow directly setting niceName if + // emulating those is desired. + if (mNiceName.IsEmpty() && !mFileName.IsEmpty()) { + mNiceName = MakeNiceFileName(mFileName); + } + + return mNiceName; +} + +NS_IMETHODIMP +nsFakePluginTag::GetNiceName(/* utf-8 */ nsACString& aResult) +{ + aResult = GetNiceFileName(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetBlocklistState(uint32_t* aResult) +{ + // Fake tags don't currently support blocklisting + *aResult = nsIBlocklistService::STATE_NOT_BLOCKED; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetBlocklisted(bool* aBlocklisted) +{ + // Fake tags can't be blocklisted + *aBlocklisted = false; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetIsEnabledStateLocked(bool* aIsEnabledStateLocked) +{ + return IsEnabledStateLockedForPlugin(this, aIsEnabledStateLocked); +} + +bool +nsFakePluginTag::IsEnabled() +{ + return mState == nsPluginTag::ePluginState_Enabled || + mState == nsPluginTag::ePluginState_Clicktoplay; +} + +NS_IMETHODIMP +nsFakePluginTag::GetDisabled(bool* aDisabled) +{ + *aDisabled = !IsEnabled(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetClicktoplay(bool* aClicktoplay) +{ + *aClicktoplay = (mState == nsPluginTag::ePluginState_Clicktoplay); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetEnabledState(uint32_t* aEnabledState) +{ + *aEnabledState = (uint32_t)mState; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::SetEnabledState(uint32_t aEnabledState) +{ + // There are static asserts above enforcing that this enum matches + mState = (nsPluginTag::PluginState)aEnabledState; + // FIXME-jsplugins update + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetMimeTypes(uint32_t* aCount, char16_t*** aResults) +{ + return CStringArrayToXPCArray(mMimeTypes, aCount, aResults); +} + +NS_IMETHODIMP +nsFakePluginTag::GetMimeDescriptions(uint32_t* aCount, char16_t*** aResults) +{ + return CStringArrayToXPCArray(mMimeDescriptions, aCount, aResults); +} + +NS_IMETHODIMP +nsFakePluginTag::GetExtensions(uint32_t* aCount, char16_t*** aResults) +{ + return CStringArrayToXPCArray(mExtensions, aCount, aResults); +} + +NS_IMETHODIMP +nsFakePluginTag::GetActive(bool *aResult) +{ + // Fake plugins can't be blocklisted, so this is just !Disabled + *aResult = IsEnabled(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetLastModifiedTime(PRTime* aLastModifiedTime) +{ + // FIXME-jsplugins What should this return, if anything? + MOZ_ASSERT(aLastModifiedTime); + *aLastModifiedTime = 0; + return NS_OK; +} + +// We don't load fake plugins out of a library, so they should always be there. +NS_IMETHODIMP +nsFakePluginTag::GetLoaded(bool* ret) +{ + *ret = true; + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginTags.h b/dom/plugins/base/nsPluginTags.h new file mode 100644 index 000000000..f1f03169b --- /dev/null +++ b/dom/plugins/base/nsPluginTags.h @@ -0,0 +1,236 @@ +/* -*- 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/. */ + +#ifndef nsPluginTags_h_ +#define nsPluginTags_h_ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIPluginTag.h" +#include "nsITimer.h" +#include "nsString.h" + +class nsIURI; +struct PRLibrary; +struct nsPluginInfo; +class nsNPAPIPlugin; + +namespace mozilla { +namespace dom { +struct FakePluginTagInit; +} // namespace dom +} // namespace mozilla + +// An interface representing plugin tags internally. +#define NS_IINTERNALPLUGINTAG_IID \ +{ 0xe8fdd227, 0x27da, 0x46ee, \ + { 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 } } + +#define NS_PLUGINTAG_IID \ + { 0xcce2e8b9, 0x9702, 0x4d4b, \ + { 0xbe, 0xa4, 0x7c, 0x1e, 0x13, 0x1f, 0xaf, 0x78 } } +class nsIInternalPluginTag : public nsIPluginTag +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINTERNALPLUGINTAG_IID) + + nsIInternalPluginTag(); + nsIInternalPluginTag(const char* aName, const char* aDescription, + const char* aFileName, const char* aVersion); + nsIInternalPluginTag(const char* aName, const char* aDescription, + const char* aFileName, const char* aVersion, + const nsTArray<nsCString>& aMimeTypes, + const nsTArray<nsCString>& aMimeDescriptions, + const nsTArray<nsCString>& aExtensions); + + virtual bool IsEnabled() = 0; + virtual const nsCString& GetNiceFileName() = 0; + + const nsCString& Name() const { return mName; } + const nsCString& Description() const { return mDescription; } + + const nsTArray<nsCString>& MimeTypes() const { return mMimeTypes; } + + const nsTArray<nsCString>& MimeDescriptions() const { + return mMimeDescriptions; + } + + const nsTArray<nsCString>& Extensions() const { return mExtensions; } + + const nsCString& FileName() const { return mFileName; } + + const nsCString& Version() const { return mVersion; } + + // Returns true if this plugin claims it supports this MIME type. The + // comparison is done ASCII-case-insensitively. + bool HasMimeType(const nsACString & aMimeType) const; + + // Returns true if this plugin claims it supports the given extension. In + // that case, aMatchingType is set to the MIME type the plugin claims + // corresponds to this extension. The match on aExtension is done + // ASCII-case-insensitively. + bool HasExtension(const nsACString & aExtension, + /* out */ nsACString & aMatchingType) const; +protected: + ~nsIInternalPluginTag(); + + nsCString mName; // UTF-8 + nsCString mDescription; // UTF-8 + nsCString mFileName; // UTF-8 + nsCString mVersion; // UTF-8 + nsTArray<nsCString> mMimeTypes; // UTF-8 + nsTArray<nsCString> mMimeDescriptions; // UTF-8 + nsTArray<nsCString> mExtensions; // UTF-8 +}; +NS_DEFINE_STATIC_IID_ACCESSOR(nsIInternalPluginTag, NS_IINTERNALPLUGINTAG_IID) + +// A linked-list of plugin information that is used for instantiating plugins +// and reflecting plugin information into JavaScript. +class nsPluginTag final : public nsIInternalPluginTag +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_PLUGINTAG_IID) + + NS_DECL_ISUPPORTS + NS_DECL_NSIPLUGINTAG + + // These must match the STATE_* values in nsIPluginTag.idl + enum PluginState { + ePluginState_Disabled = 0, + ePluginState_Clicktoplay = 1, + ePluginState_Enabled = 2, + ePluginState_MaxValue = 3, + }; + + nsPluginTag(nsPluginInfo* aPluginInfo, + int64_t aLastModifiedTime, + bool fromExtension); + nsPluginTag(const char* aName, + const char* aDescription, + const char* aFileName, + const char* aFullPath, + const char* aVersion, + const char* const* aMimeTypes, + const char* const* aMimeDescriptions, + const char* const* aExtensions, + int32_t aVariants, + int64_t aLastModifiedTime, + bool fromExtension, + bool aArgsAreUTF8 = false); + nsPluginTag(uint32_t aId, + const char* aName, + const char* aDescription, + const char* aFileName, + const char* aFullPath, + const char* aVersion, + nsTArray<nsCString> aMimeTypes, + nsTArray<nsCString> aMimeDescriptions, + nsTArray<nsCString> aExtensions, + bool aIsJavaPlugin, + bool aIsFlashPlugin, + bool aSupportsAsyncInit, + bool aSupportsAsyncRender, + int64_t aLastModifiedTime, + bool aFromExtension, + int32_t aSandboxLevel); + + void TryUnloadPlugin(bool inShutdown); + + // plugin is enabled and not blocklisted + bool IsActive(); + + bool IsEnabled() override; + void SetEnabled(bool enabled); + bool IsClicktoplay(); + bool IsBlocklisted(); + + PluginState GetPluginState(); + void SetPluginState(PluginState state); + + bool HasSameNameAndMimes(const nsPluginTag *aPluginTag) const; + const nsCString& GetNiceFileName() override; + + bool IsFromExtension() const; + + RefPtr<nsPluginTag> mNext; + uint32_t mId; + + // Number of PluginModuleParents living in all content processes. + size_t mContentProcessRunningCount; + + // True if we've ever created an instance of this plugin in the current process. + bool mHadLocalInstance; + + PRLibrary *mLibrary; + RefPtr<nsNPAPIPlugin> mPlugin; + bool mIsJavaPlugin; + bool mIsFlashPlugin; + bool mSupportsAsyncInit; + bool mSupportsAsyncRender; + nsCString mFullPath; // UTF-8 + int64_t mLastModifiedTime; + nsCOMPtr<nsITimer> mUnloadTimer; + int32_t mSandboxLevel; + + void InvalidateBlocklistState(); + +private: + virtual ~nsPluginTag(); + + nsCString mNiceFileName; // UTF-8 + uint16_t mCachedBlocklistState; + bool mCachedBlocklistStateValid; + bool mIsFromExtension; + + void InitMime(const char* const* aMimeTypes, + const char* const* aMimeDescriptions, + const char* const* aExtensions, + uint32_t aVariantCount); + void InitSandboxLevel(); + nsresult EnsureMembersAreUTF8(); + void FixupVersion(); + + static uint32_t sNextId; +}; +NS_DEFINE_STATIC_IID_ACCESSOR(nsPluginTag, NS_PLUGINTAG_IID) + +// A class representing "fake" plugin tags; that is plugin tags not +// corresponding to actual NPAPI plugins. In practice these are all +// JS-implemented plugins; maybe we want a better name for this class? +class nsFakePluginTag : public nsIInternalPluginTag, + public nsIFakePluginTag +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPLUGINTAG + NS_DECL_NSIFAKEPLUGINTAG + + static nsresult Create(const mozilla::dom::FakePluginTagInit& aInitDictionary, + nsFakePluginTag** aPluginTag); + + bool IsEnabled() override; + const nsCString& GetNiceFileName() override; + + bool HandlerURIMatches(nsIURI* aURI); + + nsIURI* HandlerURI() const { return mHandlerURI; } + +private: + nsFakePluginTag(); + virtual ~nsFakePluginTag(); + + // The URI of the handler for our fake plugin. + // FIXME-jsplugins do we need to sanity check these? + nsCOMPtr<nsIURI> mHandlerURI; + + nsCString mFullPath; + nsCString mNiceName; + + nsPluginTag::PluginState mState; +}; + +#endif // nsPluginTags_h_ diff --git a/dom/plugins/base/nsPluginsCID.h b/dom/plugins/base/nsPluginsCID.h new file mode 100644 index 000000000..eba2d250d --- /dev/null +++ b/dom/plugins/base/nsPluginsCID.h @@ -0,0 +1,13 @@ +/* -*- 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/. */ + +#ifndef nsPluginsCID_h_ +#define nsPluginsCID_h_ + +#define NS_PLUGIN_HOST_CID \ +{ 0x23E8FD98, 0xA625, 0x4B08, \ +{ 0xBE, 0x1A, 0xF7, 0xCC, 0x18, 0xA5, 0xB1, 0x06 } } + +#endif // nsPluginsCID_h_ diff --git a/dom/plugins/base/nsPluginsDir.h b/dom/plugins/base/nsPluginsDir.h new file mode 100644 index 000000000..925bbb48b --- /dev/null +++ b/dom/plugins/base/nsPluginsDir.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsPluginsDir_h_ +#define nsPluginsDir_h_ + +#include "nsError.h" +#include "nsIFile.h" + +/** + * nsPluginsDir is nearly obsolete. Directory Service should be used instead. + * It exists for the sake of one static function. + */ + +class nsPluginsDir { +public: + /** + * Determines whether or not the given file is actually a plugin file. + */ + static bool IsPluginFile(nsIFile* file); +}; + +struct PRLibrary; + +struct nsPluginInfo { + char* fName; // name of the plugin + char* fDescription; // etc. + uint32_t fVariantCount; + char** fMimeTypeArray; + char** fMimeDescriptionArray; + char** fExtensionArray; + char* fFileName; + char* fFullPath; + char* fVersion; + bool fSupportsAsyncRender; +}; + +/** + * Provides cross-platform access to a plugin file. Deals with reading + * properties from the plugin file, and loading the plugin's shared + * library. Insulates core nsIPluginHost implementations from these + * details. + */ +class nsPluginFile { + PRLibrary* pLibrary; + nsCOMPtr<nsIFile> mPlugin; +public: + /** + * If spec corresponds to a valid plugin file, constructs a reference + * to a plugin file on disk. Plugins are typically located using the + * nsPluginsDir class. + */ + explicit nsPluginFile(nsIFile* spec); + virtual ~nsPluginFile(); + + /** + * Loads the plugin into memory using NSPR's shared-library loading + * mechanism. Handles platform differences in loading shared libraries. + */ + nsresult LoadPlugin(PRLibrary **outLibrary); + + /** + * Obtains all of the information currently available for this plugin. + * Has a library outparam which will be non-null if a library load was required. + */ + nsresult GetPluginInfo(nsPluginInfo &outPluginInfo, PRLibrary **outLibrary); + + /** + * Should be called after GetPluginInfo to free all allocated stuff + */ + nsresult FreePluginInfo(nsPluginInfo &PluginInfo); +}; + +#endif /* nsPluginsDir_h_ */ diff --git a/dom/plugins/base/nsPluginsDirDarwin.cpp b/dom/plugins/base/nsPluginsDirDarwin.cpp new file mode 100644 index 000000000..6edc4fd6a --- /dev/null +++ b/dom/plugins/base/nsPluginsDirDarwin.cpp @@ -0,0 +1,591 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* + nsPluginsDirDarwin.cpp + + Mac OS X implementation of the nsPluginsDir/nsPluginsFile classes. + + by Patrick C. Beard. + */ + +#include "GeckoChildProcessHost.h" +#include "base/process_util.h" + +#include "prlink.h" +#include "prnetdb.h" +#include "nsXPCOM.h" + +#include "nsPluginsDir.h" +#include "nsNPAPIPlugin.h" +#include "nsPluginsDirUtils.h" + +#include "nsILocalFileMac.h" +#include "mozilla/UniquePtr.h" + +#include "nsCocoaFeatures.h" +#if defined(MOZ_CRASHREPORTER) +#include "nsExceptionHandler.h" +#endif + +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +#include <Carbon/Carbon.h> +#include <CoreServices/CoreServices.h> +#include <mach-o/loader.h> +#include <mach-o/fat.h> + +typedef NS_NPAPIPLUGIN_CALLBACK(const char *, NP_GETMIMEDESCRIPTION) (); +typedef NS_NPAPIPLUGIN_CALLBACK(OSErr, BP_GETSUPPORTEDMIMETYPES) (BPSupportedMIMETypes *mimeInfo, UInt32 flags); + +/* +** Returns a CFBundleRef if the path refers to a Mac OS X bundle directory. +** The caller is responsible for calling CFRelease() to deallocate. +*/ +static CFBundleRef getPluginBundle(const char* path) +{ + CFBundleRef bundle = nullptr; + CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, path, + kCFStringEncodingUTF8); + if (pathRef) { + CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef, + kCFURLPOSIXPathStyle, + true); + if (bundleURL) { + bundle = ::CFBundleCreate(nullptr, bundleURL); + ::CFRelease(bundleURL); + } + ::CFRelease(pathRef); + } + return bundle; +} + +static nsresult toCFURLRef(nsIFile* file, CFURLRef& outURL) +{ + nsCOMPtr<nsILocalFileMac> lfm = do_QueryInterface(file); + if (!lfm) + return NS_ERROR_FAILURE; + CFURLRef url; + nsresult rv = lfm->GetCFURL(&url); + if (NS_SUCCEEDED(rv)) + outURL = url; + + return rv; +} + +bool nsPluginsDir::IsPluginFile(nsIFile* file) +{ + nsCString fileName; + file->GetNativeLeafName(fileName); + /* + * Don't load the VDP fake plugin, to avoid tripping a bad bug in OS X + * 10.5.3 (see bug 436575). + */ + if (!strcmp(fileName.get(), "VerifiedDownloadPlugin.plugin")) { + NS_WARNING("Preventing load of VerifiedDownloadPlugin.plugin (see bug 436575)"); + return false; + } + return true; +} + +// Caller is responsible for freeing returned buffer. +static char* CFStringRefToUTF8Buffer(CFStringRef cfString) +{ + const char* buffer = ::CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8); + if (buffer) { + return PL_strdup(buffer); + } + + int bufferLength = + ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(cfString), + kCFStringEncodingUTF8) + 1; + char* newBuffer = static_cast<char*>(moz_xmalloc(bufferLength)); + if (!newBuffer) { + return nullptr; + } + + if (!::CFStringGetCString(cfString, newBuffer, bufferLength, + kCFStringEncodingUTF8)) { + free(newBuffer); + return nullptr; + } + + newBuffer = static_cast<char*>(moz_xrealloc(newBuffer, + strlen(newBuffer) + 1)); + return newBuffer; +} + +class AutoCFTypeObject { +public: + explicit AutoCFTypeObject(CFTypeRef aObject) + { + mObject = aObject; + } + ~AutoCFTypeObject() + { + ::CFRelease(mObject); + } +private: + CFTypeRef mObject; +}; + +static Boolean MimeTypeEnabled(CFDictionaryRef mimeDict) { + if (!mimeDict) { + return true; + } + + CFTypeRef value; + if (::CFDictionaryGetValueIfPresent(mimeDict, CFSTR("WebPluginTypeEnabled"), &value)) { + if (value && ::CFGetTypeID(value) == ::CFBooleanGetTypeID()) { + return ::CFBooleanGetValue(static_cast<CFBooleanRef>(value)); + } + } + return true; +} + +static CFDictionaryRef ParsePlistForMIMETypesFilename(CFBundleRef bundle) +{ + CFTypeRef mimeFileName = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename")); + if (!mimeFileName || ::CFGetTypeID(mimeFileName) != ::CFStringGetTypeID()) { + return nullptr; + } + + FSRef homeDir; + if (::FSFindFolder(kUserDomain, kCurrentUserFolderType, kDontCreateFolder, &homeDir) != noErr) { + return nullptr; + } + + CFURLRef userDirURL = ::CFURLCreateFromFSRef(kCFAllocatorDefault, &homeDir); + if (!userDirURL) { + return nullptr; + } + + AutoCFTypeObject userDirURLAutorelease(userDirURL); + CFStringRef mimeFilePath = ::CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("Library/Preferences/%@"), static_cast<CFStringRef>(mimeFileName)); + if (!mimeFilePath) { + return nullptr; + } + + AutoCFTypeObject mimeFilePathAutorelease(mimeFilePath); + CFURLRef mimeFileURL = ::CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorDefault, mimeFilePath, kCFURLPOSIXPathStyle, false, userDirURL); + if (!mimeFileURL) { + return nullptr; + } + + AutoCFTypeObject mimeFileURLAutorelease(mimeFileURL); + SInt32 errorCode = 0; + CFDataRef mimeFileData = nullptr; + Boolean result = ::CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, mimeFileURL, &mimeFileData, nullptr, nullptr, &errorCode); + if (!result) { + return nullptr; + } + + AutoCFTypeObject mimeFileDataAutorelease(mimeFileData); + if (errorCode != 0) { + return nullptr; + } + + CFPropertyListRef propertyList = ::CFPropertyListCreateFromXMLData(kCFAllocatorDefault, mimeFileData, kCFPropertyListImmutable, nullptr); + if (!propertyList) { + return nullptr; + } + + AutoCFTypeObject propertyListAutorelease(propertyList); + if (::CFGetTypeID(propertyList) != ::CFDictionaryGetTypeID()) { + return nullptr; + } + + CFTypeRef mimeTypes = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyList), CFSTR("WebPluginMIMETypes")); + if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) { + return nullptr; + } + + return static_cast<CFDictionaryRef>(::CFRetain(mimeTypes)); +} + +static void ParsePlistPluginInfo(nsPluginInfo& info, CFBundleRef bundle) +{ + CFDictionaryRef mimeDict = ParsePlistForMIMETypesFilename(bundle); + + if (!mimeDict) { + CFTypeRef mimeTypes = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes")); + if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) + return; + mimeDict = static_cast<CFDictionaryRef>(::CFRetain(mimeTypes)); + } + + AutoCFTypeObject mimeDictAutorelease(mimeDict); + int mimeDictKeyCount = ::CFDictionaryGetCount(mimeDict); + + // Allocate memory for mime data + int mimeDataArraySize = mimeDictKeyCount * sizeof(char*); + info.fMimeTypeArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + if (!info.fMimeTypeArray) + return; + memset(info.fMimeTypeArray, 0, mimeDataArraySize); + info.fExtensionArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + if (!info.fExtensionArray) + return; + memset(info.fExtensionArray, 0, mimeDataArraySize); + info.fMimeDescriptionArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + if (!info.fMimeDescriptionArray) + return; + memset(info.fMimeDescriptionArray, 0, mimeDataArraySize); + + // Allocate memory for mime dictionary keys and values + mozilla::UniquePtr<CFTypeRef[]> keys(new CFTypeRef[mimeDictKeyCount]); + if (!keys) + return; + mozilla::UniquePtr<CFTypeRef[]> values(new CFTypeRef[mimeDictKeyCount]); + if (!values) + return; + + info.fVariantCount = 0; + + ::CFDictionaryGetKeysAndValues(mimeDict, keys.get(), values.get()); + for (int i = 0; i < mimeDictKeyCount; i++) { + CFTypeRef mimeString = keys[i]; + if (!mimeString || ::CFGetTypeID(mimeString) != ::CFStringGetTypeID()) { + continue; + } + CFTypeRef mimeDict = values[i]; + if (mimeDict && ::CFGetTypeID(mimeDict) == ::CFDictionaryGetTypeID()) { + if (!MimeTypeEnabled(static_cast<CFDictionaryRef>(mimeDict))) { + continue; + } + info.fMimeTypeArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(mimeString)); + if (!info.fMimeTypeArray[info.fVariantCount]) { + continue; + } + CFTypeRef extensions = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginExtensions")); + if (extensions && ::CFGetTypeID(extensions) == ::CFArrayGetTypeID()) { + int extensionCount = ::CFArrayGetCount(static_cast<CFArrayRef>(extensions)); + CFMutableStringRef extensionList = ::CFStringCreateMutable(kCFAllocatorDefault, 0); + for (int j = 0; j < extensionCount; j++) { + CFTypeRef extension = ::CFArrayGetValueAtIndex(static_cast<CFArrayRef>(extensions), j); + if (extension && ::CFGetTypeID(extension) == ::CFStringGetTypeID()) { + if (j > 0) + ::CFStringAppend(extensionList, CFSTR(",")); + ::CFStringAppend(static_cast<CFMutableStringRef>(extensionList), static_cast<CFStringRef>(extension)); + } + } + info.fExtensionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(extensionList)); + ::CFRelease(extensionList); + } + CFTypeRef description = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginTypeDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fMimeDescriptionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description)); + } + info.fVariantCount++; + } +} + +nsPluginFile::nsPluginFile(nsIFile *spec) + : mPlugin(spec) +{ +} + +nsPluginFile::~nsPluginFile() {} + +nsresult nsPluginFile::LoadPlugin(PRLibrary **outLibrary) +{ + if (!mPlugin) + return NS_ERROR_NULL_POINTER; + + // 64-bit NSPR does not (yet) support bundles. So in 64-bit builds we need + // (for now) to load the bundle's executable. However this can cause + // problems: CFBundleCreate() doesn't run the bundle's executable's + // initialization code, while NSAddImage() and dlopen() do run it. So using + // NSPR's dyld loading mechanisms here (NSAddImage() or dlopen()) can cause + // a bundle's initialization code to run earlier than expected, and lead to + // crashes. See bug 577967. +#ifdef __LP64__ + char executablePath[PATH_MAX]; + executablePath[0] = '\0'; + nsAutoCString bundlePath; + mPlugin->GetNativePath(bundlePath); + CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, bundlePath.get(), + kCFStringEncodingUTF8); + if (pathRef) { + CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef, + kCFURLPOSIXPathStyle, + true); + if (bundleURL) { + CFBundleRef bundle = ::CFBundleCreate(nullptr, bundleURL); + if (bundle) { + CFURLRef executableURL = ::CFBundleCopyExecutableURL(bundle); + if (executableURL) { + if (!::CFURLGetFileSystemRepresentation(executableURL, true, (UInt8*)&executablePath, PATH_MAX)) + executablePath[0] = '\0'; + ::CFRelease(executableURL); + } + ::CFRelease(bundle); + } + ::CFRelease(bundleURL); + } + ::CFRelease(pathRef); + } +#else + nsAutoCString bundlePath; + mPlugin->GetNativePath(bundlePath); + const char *executablePath = bundlePath.get(); +#endif + + *outLibrary = PR_LoadLibrary(executablePath); + pLibrary = *outLibrary; + if (!pLibrary) { + return NS_ERROR_FAILURE; + } +#ifdef DEBUG + printf("[loaded plugin %s]\n", bundlePath.get()); +#endif + return NS_OK; +} + +static char* p2cstrdup(StringPtr pstr) +{ + int len = pstr[0]; + char* cstr = static_cast<char*>(moz_xmalloc(len + 1)); + if (cstr) { + memmove(cstr, pstr + 1, len); + cstr[len] = '\0'; + } + return cstr; +} + +static char* GetNextPluginStringFromHandle(Handle h, short *index) +{ + char *ret = p2cstrdup((unsigned char*)(*h + *index)); + *index += (ret ? strlen(ret) : 0) + 1; + return ret; +} + +static bool IsCompatibleArch(nsIFile *file) +{ + CFURLRef pluginURL = nullptr; + if (NS_FAILED(toCFURLRef(file, pluginURL))) + return false; + + bool isPluginFile = false; + + CFBundleRef pluginBundle = ::CFBundleCreate(kCFAllocatorDefault, pluginURL); + if (pluginBundle) { + UInt32 packageType, packageCreator; + ::CFBundleGetPackageInfo(pluginBundle, &packageType, &packageCreator); + if (packageType == 'BRPL' || packageType == 'IEPL' || packageType == 'NSPL') { + // Get path to plugin as a C string. + char executablePath[PATH_MAX]; + executablePath[0] = '\0'; + if (!::CFURLGetFileSystemRepresentation(pluginURL, true, (UInt8*)&executablePath, PATH_MAX)) { + executablePath[0] = '\0'; + } + + uint32_t pluginLibArchitectures; + nsresult rv = mozilla::ipc::GeckoChildProcessHost::GetArchitecturesForBinary(executablePath, &pluginLibArchitectures); + if (NS_FAILED(rv)) { + return false; + } + + uint32_t supportedArchitectures = +#ifdef __LP64__ + mozilla::ipc::GeckoChildProcessHost::GetSupportedArchitecturesForProcessType(GeckoProcessType_Plugin); +#else + base::GetCurrentProcessArchitecture(); +#endif + + // Consider the plugin architecture valid if there is any overlap in the masks. + isPluginFile = !!(supportedArchitectures & pluginLibArchitectures); + } + ::CFRelease(pluginBundle); + } + + ::CFRelease(pluginURL); + return isPluginFile; +} + +/** + * Obtains all of the information currently available for this plugin. + */ +nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, PRLibrary **outLibrary) +{ + *outLibrary = nullptr; + + nsresult rv = NS_OK; + + if (!IsCompatibleArch(mPlugin)) { + return NS_ERROR_FAILURE; + } + + // clear out the info, except for the first field. + memset(&info, 0, sizeof(info)); + + // Try to get a bundle reference. + nsAutoCString path; + if (NS_FAILED(rv = mPlugin->GetNativePath(path))) + return rv; + CFBundleRef bundle = getPluginBundle(path.get()); + + // fill in full path + info.fFullPath = PL_strdup(path.get()); + + // fill in file name + nsAutoCString fileName; + if (NS_FAILED(rv = mPlugin->GetNativeLeafName(fileName))) + return rv; + info.fFileName = PL_strdup(fileName.get()); + + // Get fName + if (bundle) { + CFTypeRef name = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName")); + if (name && ::CFGetTypeID(name) == ::CFStringGetTypeID()) + info.fName = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(name)); + } + + // Get fDescription + if (bundle) { + CFTypeRef description = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fDescription = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description)); + } + + // Get fVersion + if (bundle) { + // Look for the release version first + CFTypeRef version = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString")); + if (!version) // try the build version + version = ::CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey); + if (version && ::CFGetTypeID(version) == ::CFStringGetTypeID()) + info.fVersion = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(version)); + } + + // The last thing we need to do is get MIME data + // fVariantCount, fMimeTypeArray, fExtensionArray, fMimeDescriptionArray + + // First look for data in a bundle plist + if (bundle) { + ParsePlistPluginInfo(info, bundle); + ::CFRelease(bundle); + if (info.fVariantCount > 0) + return NS_OK; + } + + // Don't load "fbplugin" or any plugins whose name starts with "fbplugin_" + // (Facebook plugins) if we're running on OS X 10.10 (Yosemite) or later. + // A "fbplugin" file crashes on load, in the call to LoadPlugin() below. + // See bug 1086977. + if (nsCocoaFeatures::OnYosemiteOrLater()) { + if (fileName.EqualsLiteral("fbplugin") || + StringBeginsWith(fileName, NS_LITERAL_CSTRING("fbplugin_"))) { + nsAutoCString msg; + msg.AppendPrintf("Preventing load of %s (see bug 1086977)", + fileName.get()); + NS_WARNING(msg.get()); + return NS_ERROR_FAILURE; + } +#if defined(MOZ_CRASHREPORTER) + // The block above assumes that "fbplugin" is the filename of the plugin + // to be blocked, or that the filename starts with "fbplugin_". But we + // don't yet know for sure if this is always true. So for the time being + // record extra information in our crash logs. + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Bug_1086977"), + fileName); +#endif + } + + // It's possible that our plugin has 2 entry points that'll give us mime type + // info. Quicktime does this to get around the need of having admin rights to + // change mime info in the resource fork. We need to use this info instead of + // the resource. See bug 113464. + + // Sadly we have to load the library for this to work. + rv = LoadPlugin(outLibrary); +#if defined(MOZ_CRASHREPORTER) + if (nsCocoaFeatures::OnYosemiteOrLater()) { + // If we didn't crash in LoadPlugin(), change the previous annotation so we + // don't sow confusion. + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Bug_1086977"), + NS_LITERAL_CSTRING("Didn't crash, please ignore")); + } +#endif + if (NS_FAILED(rv)) + return rv; + + // Try to get data from NP_GetMIMEDescription + if (pLibrary) { + NP_GETMIMEDESCRIPTION pfnGetMimeDesc = (NP_GETMIMEDESCRIPTION)PR_FindFunctionSymbol(pLibrary, NP_GETMIMEDESCRIPTION_NAME); + if (pfnGetMimeDesc) + ParsePluginMimeDescription(pfnGetMimeDesc(), info); + if (info.fVariantCount) + return NS_OK; + } + + // We'll fill this in using BP_GetSupportedMIMETypes and/or resource fork data + BPSupportedMIMETypes mi = {kBPSupportedMIMETypesStructVers_1, nullptr, nullptr}; + + // Try to get data from BP_GetSupportedMIMETypes + if (pLibrary) { + BP_GETSUPPORTEDMIMETYPES pfnMime = (BP_GETSUPPORTEDMIMETYPES)PR_FindFunctionSymbol(pLibrary, "BP_GetSupportedMIMETypes"); + if (pfnMime && noErr == pfnMime(&mi, 0) && mi.typeStrings) { + info.fVariantCount = (**(short**)mi.typeStrings) / 2; + ::HLock(mi.typeStrings); + if (mi.infoStrings) // it's possible some plugins have infoStrings missing + ::HLock(mi.infoStrings); + } + } + + // Fill in the info struct based on the data in the BPSupportedMIMETypes struct + int variantCount = info.fVariantCount; + info.fMimeTypeArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (!info.fMimeTypeArray) + return NS_ERROR_OUT_OF_MEMORY; + info.fExtensionArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (!info.fExtensionArray) + return NS_ERROR_OUT_OF_MEMORY; + if (mi.infoStrings) { + info.fMimeDescriptionArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (!info.fMimeDescriptionArray) + return NS_ERROR_OUT_OF_MEMORY; + } + short mimeIndex = 2; + short descriptionIndex = 2; + for (int i = 0; i < variantCount; i++) { + info.fMimeTypeArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex); + info.fExtensionArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex); + if (mi.infoStrings) + info.fMimeDescriptionArray[i] = GetNextPluginStringFromHandle(mi.infoStrings, &descriptionIndex); + } + + ::HUnlock(mi.typeStrings); + ::DisposeHandle(mi.typeStrings); + if (mi.infoStrings) { + ::HUnlock(mi.infoStrings); + ::DisposeHandle(mi.infoStrings); + } + + return NS_OK; +} + +nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info) +{ + free(info.fName); + free(info.fDescription); + int variantCount = info.fVariantCount; + for (int i = 0; i < variantCount; i++) { + free(info.fMimeTypeArray[i]); + free(info.fExtensionArray[i]); + free(info.fMimeDescriptionArray[i]); + } + free(info.fMimeTypeArray); + free(info.fMimeDescriptionArray); + free(info.fExtensionArray); + free(info.fFileName); + free(info.fFullPath); + free(info.fVersion); + + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginsDirUnix.cpp b/dom/plugins/base/nsPluginsDirUnix.cpp new file mode 100644 index 000000000..6d112b4fe --- /dev/null +++ b/dom/plugins/base/nsPluginsDirUnix.cpp @@ -0,0 +1,421 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsIMemory.h" +#include "nsPluginsDir.h" +#include "nsPluginsDirUtils.h" +#include "prmem.h" +#include "prenv.h" +#include "prerror.h" +#include "prio.h" +#include <sys/stat.h> +#include "nsString.h" +#include "nsIFile.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" + +#define LOCAL_PLUGIN_DLL_SUFFIX ".so" +#if defined(__hpux) +#define DEFAULT_X11_PATH "/usr/lib/X11R6/" +#undef LOCAL_PLUGIN_DLL_SUFFIX +#define LOCAL_PLUGIN_DLL_SUFFIX ".sl" +#define LOCAL_PLUGIN_DLL_ALT_SUFFIX ".so" +#elif defined(_AIX) +#define DEFAULT_X11_PATH "/usr/lib" +#define LOCAL_PLUGIN_DLL_ALT_SUFFIX ".a" +#elif defined(SOLARIS) +#define DEFAULT_X11_PATH "/usr/openwin/lib/" +#elif defined(LINUX) +#define DEFAULT_X11_PATH "/usr/X11R6/lib/" +#elif defined(__APPLE__) +#define DEFAULT_X11_PATH "/usr/X11R6/lib" +#undef LOCAL_PLUGIN_DLL_SUFFIX +#define LOCAL_PLUGIN_DLL_SUFFIX ".dylib" +#define LOCAL_PLUGIN_DLL_ALT_SUFFIX ".so" +#else +#define DEFAULT_X11_PATH "" +#endif + +#if (MOZ_WIDGET_GTK == 2) + +#define PLUGIN_MAX_LEN_OF_TMP_ARR 512 + +static void DisplayPR_LoadLibraryErrorMessage(const char *libName) +{ + char errorMsg[PLUGIN_MAX_LEN_OF_TMP_ARR] = "Cannot get error from NSPR."; + if (PR_GetErrorTextLength() < (int) sizeof(errorMsg)) + PR_GetErrorText(errorMsg); + + fprintf(stderr, "LoadPlugin: failed to initialize shared library %s [%s]\n", + libName, errorMsg); +} + +static void SearchForSoname(const char* name, char** soname) +{ + if (!(name && soname)) + return; + PRDir *fdDir = PR_OpenDir(DEFAULT_X11_PATH); + if (!fdDir) + return; + + int n = strlen(name); + PRDirEntry *dirEntry; + while ((dirEntry = PR_ReadDir(fdDir, PR_SKIP_BOTH))) { + if (!PL_strncmp(dirEntry->name, name, n)) { + if (dirEntry->name[n] == '.' && dirEntry->name[n+1] && !dirEntry->name[n+2]) { + // name.N, wild guess this is what we need + char out[PLUGIN_MAX_LEN_OF_TMP_ARR] = DEFAULT_X11_PATH; + PL_strcat(out, dirEntry->name); + *soname = PL_strdup(out); + break; + } + } + } + + PR_CloseDir(fdDir); +} + +static bool LoadExtraSharedLib(const char *name, char **soname, bool tryToGetSoname) +{ + bool ret = true; + PRLibSpec tempSpec; + PRLibrary *handle; + tempSpec.type = PR_LibSpec_Pathname; + tempSpec.value.pathname = name; + handle = PR_LoadLibraryWithFlags(tempSpec, PR_LD_NOW|PR_LD_GLOBAL); + if (!handle) { + ret = false; + DisplayPR_LoadLibraryErrorMessage(name); + if (tryToGetSoname) { + SearchForSoname(name, soname); + if (*soname) { + ret = LoadExtraSharedLib((const char *) *soname, nullptr, false); + } + } + } + return ret; +} + +#define PLUGIN_MAX_NUMBER_OF_EXTRA_LIBS 32 +#define PREF_PLUGINS_SONAME "plugin.soname.list" +#if defined(SOLARIS) || defined(HPUX) +#define DEFAULT_EXTRA_LIBS_LIST "libXt" LOCAL_PLUGIN_DLL_SUFFIX ":libXext" LOCAL_PLUGIN_DLL_SUFFIX ":libXm" LOCAL_PLUGIN_DLL_SUFFIX +#else +#define DEFAULT_EXTRA_LIBS_LIST "libXt" LOCAL_PLUGIN_DLL_SUFFIX ":libXext" LOCAL_PLUGIN_DLL_SUFFIX +#endif +/* + this function looks for + user_pref("plugin.soname.list", "/usr/X11R6/lib/libXt.so.6:libXext.so"); + in user's pref.js + and loads all libs in specified order +*/ + +static void LoadExtraSharedLibs() +{ + // check out if user's prefs.js has libs name + nsresult res; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &res)); + if (NS_SUCCEEDED(res) && (prefs != nullptr)) { + char *sonameList = nullptr; + bool prefSonameListIsSet = true; + res = prefs->GetCharPref(PREF_PLUGINS_SONAME, &sonameList); + if (!sonameList) { + // pref is not set, lets use hardcoded list + prefSonameListIsSet = false; + sonameList = PL_strdup(DEFAULT_EXTRA_LIBS_LIST); + } + if (sonameList) { + char *arrayOfLibs[PLUGIN_MAX_NUMBER_OF_EXTRA_LIBS] = {0}; + int numOfLibs = 0; + char *nextToken; + char *p = nsCRT::strtok(sonameList,":",&nextToken); + if (p) { + while (p && numOfLibs < PLUGIN_MAX_NUMBER_OF_EXTRA_LIBS) { + arrayOfLibs[numOfLibs++] = p; + p = nsCRT::strtok(nextToken,":",&nextToken); + } + } else // there is just one lib + arrayOfLibs[numOfLibs++] = sonameList; + + char sonameListToSave[PLUGIN_MAX_LEN_OF_TMP_ARR] = ""; + for (int i=0; i<numOfLibs; i++) { + // trim out head/tail white spaces (just in case) + bool head = true; + p = arrayOfLibs[i]; + while (*p) { + if (*p == ' ' || *p == '\t') { + if (head) { + arrayOfLibs[i] = ++p; + } else { + *p = 0; + } + } else { + head = false; + p++; + } + } + if (!arrayOfLibs[i][0]) { + continue; // null string + } + bool tryToGetSoname = true; + if (PL_strchr(arrayOfLibs[i], '/')) { + //assuming it's real name, try to stat it + struct stat st; + if (stat((const char*) arrayOfLibs[i], &st)) { + //get just a file name + arrayOfLibs[i] = PL_strrchr(arrayOfLibs[i], '/') + 1; + } else + tryToGetSoname = false; + } + char *soname = nullptr; + if (LoadExtraSharedLib(arrayOfLibs[i], &soname, tryToGetSoname)) { + //construct soname's list to save in prefs + p = soname ? soname : arrayOfLibs[i]; + int n = PLUGIN_MAX_LEN_OF_TMP_ARR - + (strlen(sonameListToSave) + strlen(p)); + if (n > 0) { + PL_strcat(sonameListToSave, p); + PL_strcat(sonameListToSave,":"); + } + if (soname) { + PL_strfree(soname); // it's from strdup + } + if (numOfLibs > 1) + arrayOfLibs[i][strlen(arrayOfLibs[i])] = ':'; //restore ":" in sonameList + } + } + + // Check whether sonameListToSave is a empty String, Bug: 329205 + if (sonameListToSave[0]) + for (p = &sonameListToSave[strlen(sonameListToSave) - 1]; *p == ':'; p--) + *p = 0; //delete tail ":" delimiters + + if (!prefSonameListIsSet || PL_strcmp(sonameList, sonameListToSave)) { + // if user specified some bogus soname I overwrite it here, + // otherwise it'll decrease performance by calling popen() in SearchForSoname + // every time for each bogus name + prefs->SetCharPref(PREF_PLUGINS_SONAME, (const char *)sonameListToSave); + } + PL_strfree(sonameList); + } + } +} +#endif //MOZ_WIDGET_GTK == 2 + +/* nsPluginsDir implementation */ + +bool nsPluginsDir::IsPluginFile(nsIFile* file) +{ + nsAutoCString filename; + if (NS_FAILED(file->GetNativeLeafName(filename))) + return false; + +#ifdef ANDROID + // It appears that if you load + // 'libstagefright_honeycomb.so' on froyo, or + // 'libstagefright_froyo.so' on honeycomb, we will abort. + // Since these are just helper libs, we can ignore. + const char *cFile = filename.get(); + if (strstr(cFile, "libstagefright") != nullptr) + return false; +#endif + + NS_NAMED_LITERAL_CSTRING(dllSuffix, LOCAL_PLUGIN_DLL_SUFFIX); + if (filename.Length() > dllSuffix.Length() && + StringEndsWith(filename, dllSuffix)) + return true; + +#ifdef LOCAL_PLUGIN_DLL_ALT_SUFFIX + NS_NAMED_LITERAL_CSTRING(dllAltSuffix, LOCAL_PLUGIN_DLL_ALT_SUFFIX); + if (filename.Length() > dllAltSuffix.Length() && + StringEndsWith(filename, dllAltSuffix)) + return true; +#endif + return false; +} + +/* nsPluginFile implementation */ + +nsPluginFile::nsPluginFile(nsIFile* file) +: mPlugin(file) +{ +} + +nsPluginFile::~nsPluginFile() +{ +} + +nsresult nsPluginFile::LoadPlugin(PRLibrary **outLibrary) +{ + PRLibSpec libSpec; + libSpec.type = PR_LibSpec_Pathname; + bool exists = false; + mPlugin->Exists(&exists); + if (!exists) + return NS_ERROR_FILE_NOT_FOUND; + + nsresult rv; + nsAutoCString path; + rv = mPlugin->GetNativePath(path); + if (NS_FAILED(rv)) + return rv; + + libSpec.value.pathname = path.get(); + +#if (MOZ_WIDGET_GTK == 2) + + // Normally, Mozilla isn't linked against libXt and libXext + // since it's a Gtk/Gdk application. On the other hand, + // legacy plug-ins expect the libXt and libXext symbols + // to already exist in the global name space. This plug-in + // wrapper is linked against libXt and libXext, but since + // we never call on any of these libraries, plug-ins still + // fail to resolve Xt symbols when trying to do a dlopen + // at runtime. Explicitly opening Xt/Xext into the global + // namespace before attempting to load the plug-in seems to + // work fine. + + +#if defined(SOLARIS) || defined(HPUX) + // Acrobat/libXm: Lazy resolving might cause crash later (bug 211587) + *outLibrary = PR_LoadLibraryWithFlags(libSpec, PR_LD_NOW); + pLibrary = *outLibrary; +#else + // Some dlopen() doesn't recover from a failed PR_LD_NOW (bug 223744) + *outLibrary = PR_LoadLibraryWithFlags(libSpec, 0); + pLibrary = *outLibrary; +#endif + if (!pLibrary) { + LoadExtraSharedLibs(); + // try reload plugin once more + *outLibrary = PR_LoadLibraryWithFlags(libSpec, 0); + pLibrary = *outLibrary; + if (!pLibrary) { + DisplayPR_LoadLibraryErrorMessage(libSpec.value.pathname); + return NS_ERROR_FAILURE; + } + } +#else + *outLibrary = PR_LoadLibraryWithFlags(libSpec, 0); + pLibrary = *outLibrary; +#endif // MOZ_WIDGET_GTK == 2 + +#ifdef DEBUG + printf("LoadPlugin() %s returned %lx\n", + libSpec.value.pathname, (unsigned long)pLibrary); +#endif + + if (!pLibrary) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, PRLibrary **outLibrary) +{ + *outLibrary = nullptr; + + info.fVersion = nullptr; + + // Sadly we have to load the library for this to work. + nsresult rv = LoadPlugin(outLibrary); + if (NS_FAILED(rv)) + return rv; + + const char* (*npGetPluginVersion)() = + (const char* (*)()) PR_FindFunctionSymbol(pLibrary, "NP_GetPluginVersion"); + if (npGetPluginVersion) { + info.fVersion = PL_strdup(npGetPluginVersion()); + } + + const char* (*npGetMIMEDescription)() = + (const char* (*)()) PR_FindFunctionSymbol(pLibrary, "NP_GetMIMEDescription"); + if (!npGetMIMEDescription) { + return NS_ERROR_FAILURE; + } + + const char* mimedescr = npGetMIMEDescription(); + if (!mimedescr) { + return NS_ERROR_FAILURE; + } + + rv = ParsePluginMimeDescription(mimedescr, info); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoCString path; + if (NS_FAILED(rv = mPlugin->GetNativePath(path))) + return rv; + info.fFullPath = PL_strdup(path.get()); + + nsAutoCString fileName; + if (NS_FAILED(rv = mPlugin->GetNativeLeafName(fileName))) + return rv; + info.fFileName = PL_strdup(fileName.get()); + + NP_GetValueFunc npGetValue = (NP_GetValueFunc)PR_FindFunctionSymbol(pLibrary, "NP_GetValue"); + if (!npGetValue) { + return NS_ERROR_FAILURE; + } + + const char *name = nullptr; + npGetValue(nullptr, NPPVpluginNameString, &name); + if (name) { + info.fName = PL_strdup(name); + } + else { + info.fName = PL_strdup(fileName.get()); + } + + const char *description = nullptr; + npGetValue(nullptr, NPPVpluginDescriptionString, &description); + if (description) { + info.fDescription = PL_strdup(description); + } + else { + info.fDescription = PL_strdup(""); + } + + return NS_OK; +} + +nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info) +{ + if (info.fName != nullptr) + PL_strfree(info.fName); + + if (info.fDescription != nullptr) + PL_strfree(info.fDescription); + + for (uint32_t i = 0; i < info.fVariantCount; i++) { + if (info.fMimeTypeArray[i] != nullptr) + PL_strfree(info.fMimeTypeArray[i]); + + if (info.fMimeDescriptionArray[i] != nullptr) + PL_strfree(info.fMimeDescriptionArray[i]); + + if (info.fExtensionArray[i] != nullptr) + PL_strfree(info.fExtensionArray[i]); + } + + PR_FREEIF(info.fMimeTypeArray); + PR_FREEIF(info.fMimeDescriptionArray); + PR_FREEIF(info.fExtensionArray); + + if (info.fFullPath != nullptr) + PL_strfree(info.fFullPath); + + if (info.fFileName != nullptr) + PL_strfree(info.fFileName); + + if (info.fVersion != nullptr) + PL_strfree(info.fVersion); + + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginsDirUtils.h b/dom/plugins/base/nsPluginsDirUtils.h new file mode 100644 index 000000000..178dc6968 --- /dev/null +++ b/dom/plugins/base/nsPluginsDirUtils.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsPluginsDirUtils_h___ +#define nsPluginsDirUtils_h___ + +#include "nsPluginsDir.h" +#include "nsTArray.h" +#include "prmem.h" + +/////////////////////////////////////////////////////////////////////////////// +// Output format from NPP_GetMIMEDescription: "...mime type[;version]:[extension]:[desecription];..." +// The ambiguity of mime description could cause the browser fail to parse the MIME types +// correctly. +// E.g. "mime type::desecription;" // correct w/o ext +// "mime type:desecription;" // wrong w/o ext +// +static nsresult +ParsePluginMimeDescription(const char *mdesc, nsPluginInfo &info) +{ + nsresult rv = NS_ERROR_FAILURE; + if (!mdesc || !*mdesc) + return rv; + + char *mdescDup = PL_strdup(mdesc); // make a dup of intput string we'll change it content + char anEmptyString[] = ""; + AutoTArray<char*, 8> tmpMimeTypeArr; + char delimiters[] = {':',':',';'}; + int mimeTypeVariantCount = 0; + char *p = mdescDup; // make a dup of intput string we'll change it content + while(p) { + char *ptrMimeArray[] = {anEmptyString, anEmptyString, anEmptyString}; + + // It's easy to point out ptrMimeArray[0] to the string sounds like + // "Mime type is not specified, plugin will not function properly." + // and show this on "about:plugins" page, but we have to mark this particular + // mime type of given plugin as disable on "about:plugins" page, + // which feature is not implemented yet. + // So we'll ignore, without any warnings, an empty description strings, + // in other words, if after parsing ptrMimeArray[0] == anEmptyString is true. + // It is possible do not to registry a plugin at all if it returns + // an empty string on GetMIMEDescription() call, + // e.g. plugger returns "" if pluggerrc file is not found. + + char *s = p; + int i; + for (i = 0; i < (int) sizeof(delimiters) && (p = PL_strchr(s, delimiters[i])); i++) { + ptrMimeArray[i] = s; // save start ptr + *p++ = 0; // overwrite delimiter + s = p; // move forward + } + if (i == 2) + ptrMimeArray[i] = s; + // fill out the temp array + // the order is important, it should be the same in for loop below + if (ptrMimeArray[0] != anEmptyString) { + tmpMimeTypeArr.AppendElement(ptrMimeArray[0]); + tmpMimeTypeArr.AppendElement(ptrMimeArray[1]); + tmpMimeTypeArr.AppendElement(ptrMimeArray[2]); + mimeTypeVariantCount++; + } + } + + // fill out info structure + if (mimeTypeVariantCount) { + info.fVariantCount = mimeTypeVariantCount; + // we can do these 3 mallocs at once, later on code cleanup + info.fMimeTypeArray = (char **)PR_Malloc(mimeTypeVariantCount * sizeof(char *)); + info.fMimeDescriptionArray = (char **)PR_Malloc(mimeTypeVariantCount * sizeof(char *)); + info.fExtensionArray = (char **)PR_Malloc(mimeTypeVariantCount * sizeof(char *)); + + int j,i; + for (j = i = 0; i < mimeTypeVariantCount; i++) { + // the order is important, do not change it + // we can get rid of PL_strdup here, later on code cleanup + info.fMimeTypeArray[i] = PL_strdup(tmpMimeTypeArr.ElementAt(j++)); + info.fExtensionArray[i] = PL_strdup(tmpMimeTypeArr.ElementAt(j++)); + info.fMimeDescriptionArray[i] = PL_strdup(tmpMimeTypeArr.ElementAt(j++)); + } + rv = NS_OK; + } + if (mdescDup) + PR_Free(mdescDup); + return rv; +} + +#endif /* nsPluginsDirUtils_h___ */ diff --git a/dom/plugins/base/nsPluginsDirWin.cpp b/dom/plugins/base/nsPluginsDirWin.cpp new file mode 100644 index 000000000..8c2d26ca2 --- /dev/null +++ b/dom/plugins/base/nsPluginsDirWin.cpp @@ -0,0 +1,433 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +/* + nsPluginsDirWin.cpp + + Windows implementation of the nsPluginsDir/nsPluginsFile classes. + + by Alex Musil + */ + +#include "mozilla/ArrayUtils.h" // ArrayLength +#include "mozilla/DebugOnly.h" + +#include "nsPluginsDir.h" +#include "prlink.h" +#include "plstr.h" +#include "prmem.h" +#include "prprf.h" + +#include "windows.h" +#include "winbase.h" + +#include "nsString.h" +#include "nsIFile.h" +#include "nsUnicharUtils.h" + +#include <shlwapi.h> +#define SHOCKWAVE_BASE_FILENAME L"np32dsw" +/** + * Determines whether or not SetDllDirectory should be called for this plugin. + * + * @param pluginFilePath The full path of the plugin file + * @return true if SetDllDirectory can be called for the plugin + */ +bool +ShouldProtectPluginCurrentDirectory(char16ptr_t pluginFilePath) +{ + LPCWSTR passedInFilename = PathFindFileName(pluginFilePath); + if (!passedInFilename) { + return true; + } + + // Somewhere in the middle of 11.6 version of Shockwave, naming of the DLL + // after its version number is introduced. + if (!wcsicmp(passedInFilename, SHOCKWAVE_BASE_FILENAME L".dll")) { + return false; + } + + // Shockwave versions before 1202122 will break if you call SetDllDirectory + const uint64_t kFixedShockwaveVersion = 1202122; + uint64_t version; + int found = swscanf(passedInFilename, SHOCKWAVE_BASE_FILENAME L"_%llu.dll", + &version); + if (found && version < kFixedShockwaveVersion) { + return false; + } + + // We always want to call SetDllDirectory otherwise + return true; +} + +using namespace mozilla; + +/* Local helper functions */ + +static char* GetKeyValue(void* verbuf, const WCHAR* key, + UINT language, UINT codepage) +{ + WCHAR keybuf[64]; // plenty for the template below, with the longest key + // we use (currently "FileDescription") + const WCHAR keyFormat[] = L"\\StringFileInfo\\%04X%04X\\%ls"; + WCHAR *buf = nullptr; + UINT blen; + + if (_snwprintf_s(keybuf, ArrayLength(keybuf), _TRUNCATE, + keyFormat, language, codepage, key) < 0) + { + NS_NOTREACHED("plugin info key too long for buffer!"); + return nullptr; + } + + if (::VerQueryValueW(verbuf, keybuf, (void **)&buf, &blen) == 0 || + buf == nullptr || blen == 0) + { + return nullptr; + } + + return PL_strdup(NS_ConvertUTF16toUTF8(buf, blen).get()); +} + +static char* GetVersion(void* verbuf) +{ + VS_FIXEDFILEINFO *fileInfo; + UINT fileInfoLen; + + ::VerQueryValueW(verbuf, L"\\", (void **)&fileInfo, &fileInfoLen); + + if (fileInfo) { + return PR_smprintf("%ld.%ld.%ld.%ld", + HIWORD(fileInfo->dwFileVersionMS), + LOWORD(fileInfo->dwFileVersionMS), + HIWORD(fileInfo->dwFileVersionLS), + LOWORD(fileInfo->dwFileVersionLS)); + } + + return nullptr; +} + +// Returns a boolean indicating if the key's value contains a string +// entry equal to "1" or "0". No entry for the key returns false. +static bool GetBooleanFlag(void* verbuf, const WCHAR* key, + UINT language, UINT codepage) +{ + char* flagStr = GetKeyValue(verbuf, key, language, codepage); + if (!flagStr) { + return false; + } + bool result = (PL_strncmp("1", flagStr, 1) == 0); + PL_strfree(flagStr); + return result; +} + +static uint32_t CalculateVariantCount(char* mimeTypes) +{ + uint32_t variants = 1; + + if (!mimeTypes) + return 0; + + char* index = mimeTypes; + while (*index) { + if (*index == '|') + variants++; + + ++index; + } + return variants; +} + +static char** MakeStringArray(uint32_t variants, char* data) +{ + // The number of variants has been calculated based on the mime + // type array. Plugins are not explicitely required to match + // this number in two other arrays: file extention array and mime + // description array, and some of them actually don't. + // We should handle such situations gracefully + + if ((variants <= 0) || !data) + return nullptr; + + char ** array = (char **)PR_Calloc(variants, sizeof(char *)); + if (!array) + return nullptr; + + char * start = data; + + for (uint32_t i = 0; i < variants; i++) { + char * p = PL_strchr(start, '|'); + if (p) + *p = 0; + + array[i] = PL_strdup(start); + + if (!p) { + // nothing more to look for, fill everything left + // with empty strings and break + while(++i < variants) + array[i] = PL_strdup(""); + + break; + } + + start = ++p; + } + return array; +} + +static void FreeStringArray(uint32_t variants, char ** array) +{ + if ((variants == 0) || !array) + return; + + for (uint32_t i = 0; i < variants; i++) { + if (array[i]) { + PL_strfree(array[i]); + array[i] = nullptr; + } + } + PR_Free(array); +} + +static bool CanLoadPlugin(char16ptr_t aBinaryPath) +{ +#if defined(_M_IX86) || defined(_M_X64) || defined(_M_IA64) + bool canLoad = false; + + HANDLE file = CreateFileW(aBinaryPath, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file != INVALID_HANDLE_VALUE) { + HANDLE map = CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, + GetFileSize(file, nullptr), nullptr); + if (map != nullptr) { + LPVOID mapView = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0); + if (mapView != nullptr) { + if (((IMAGE_DOS_HEADER*)mapView)->e_magic == IMAGE_DOS_SIGNATURE) { + long peImageHeaderStart = (((IMAGE_DOS_HEADER*)mapView)->e_lfanew); + if (peImageHeaderStart != 0L) { + DWORD arch = (((IMAGE_NT_HEADERS*)((LPBYTE)mapView + peImageHeaderStart))->FileHeader.Machine); +#ifdef _M_IX86 + canLoad = (arch == IMAGE_FILE_MACHINE_I386); +#elif defined(_M_X64) + canLoad = (arch == IMAGE_FILE_MACHINE_AMD64); +#elif defined(_M_IA64) + canLoad = (arch == IMAGE_FILE_MACHINE_IA64); +#endif + } + } + UnmapViewOfFile(mapView); + } + CloseHandle(map); + } + CloseHandle(file); + } + + return canLoad; +#else + // Assume correct binaries for unhandled cases. + return true; +#endif +} + +/* nsPluginsDir implementation */ + +// The file name must be in the form "np*.dll" +bool nsPluginsDir::IsPluginFile(nsIFile* file) +{ + nsAutoCString path; + if (NS_FAILED(file->GetNativePath(path))) + return false; + + const char *cPath = path.get(); + + // this is most likely a path, so skip to the filename + const char* filename = PL_strrchr(cPath, '\\'); + if (filename) + ++filename; + else + filename = cPath; + + char* extension = PL_strrchr(filename, '.'); + if (extension) + ++extension; + + uint32_t fullLength = strlen(filename); + uint32_t extLength = extension ? strlen(extension) : 0; + if (fullLength >= 7 && extLength == 3) { + if (!PL_strncasecmp(filename, "np", 2) && !PL_strncasecmp(extension, "dll", 3)) { + // don't load OJI-based Java plugins + if (!PL_strncasecmp(filename, "npoji", 5) || + !PL_strncasecmp(filename, "npjava", 6)) + return false; + return true; + } + } + + return false; +} + +/* nsPluginFile implementation */ + +nsPluginFile::nsPluginFile(nsIFile* file) +: mPlugin(file) +{ + // nada +} + +nsPluginFile::~nsPluginFile() +{ + // nada +} + +/** + * Loads the plugin into memory using NSPR's shared-library loading + * mechanism. Handles platform differences in loading shared libraries. + */ +nsresult nsPluginFile::LoadPlugin(PRLibrary **outLibrary) +{ + if (!mPlugin) + return NS_ERROR_NULL_POINTER; + + bool protectCurrentDirectory = true; + + nsAutoString pluginFilePath; + mPlugin->GetPath(pluginFilePath); + protectCurrentDirectory = + ShouldProtectPluginCurrentDirectory(pluginFilePath.BeginReading()); + + nsAutoString pluginFolderPath = pluginFilePath; + int32_t idx = pluginFilePath.RFindChar('\\'); + pluginFolderPath.SetLength(idx); + + BOOL restoreOrigDir = FALSE; + WCHAR aOrigDir[MAX_PATH + 1]; + DWORD dwCheck = GetCurrentDirectoryW(MAX_PATH, aOrigDir); + NS_ASSERTION(dwCheck <= MAX_PATH + 1, "Error in Loading plugin"); + + if (dwCheck <= MAX_PATH + 1) { + restoreOrigDir = SetCurrentDirectoryW(pluginFolderPath.get()); + NS_ASSERTION(restoreOrigDir, "Error in Loading plugin"); + } + + if (protectCurrentDirectory) { + SetDllDirectory(nullptr); + } + + nsresult rv = mPlugin->Load(outLibrary); + if (NS_FAILED(rv)) + *outLibrary = nullptr; + + if (protectCurrentDirectory) { + SetDllDirectory(L""); + } + + if (restoreOrigDir) { + DebugOnly<BOOL> bCheck = SetCurrentDirectoryW(aOrigDir); + NS_ASSERTION(bCheck, "Error in Loading plugin"); + } + + return rv; +} + +/** + * Obtains all of the information currently available for this plugin. + */ +nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, PRLibrary **outLibrary) +{ + *outLibrary = nullptr; + + nsresult rv = NS_OK; + DWORD zerome, versionsize; + void* verbuf = nullptr; + + if (!mPlugin) + return NS_ERROR_NULL_POINTER; + + nsAutoString fullPath; + if (NS_FAILED(rv = mPlugin->GetPath(fullPath))) + return rv; + + if (!CanLoadPlugin(fullPath.get())) + return NS_ERROR_FAILURE; + + nsAutoString fileName; + if (NS_FAILED(rv = mPlugin->GetLeafName(fileName))) + return rv; + + LPCWSTR lpFilepath = fullPath.get(); + + versionsize = ::GetFileVersionInfoSizeW(lpFilepath, &zerome); + + if (versionsize > 0) + verbuf = PR_Malloc(versionsize); + if (!verbuf) + return NS_ERROR_OUT_OF_MEMORY; + + if (::GetFileVersionInfoW(lpFilepath, 0, versionsize, verbuf)) + { + // TODO: get appropriately-localized info from plugin file + UINT lang = 1033; // language = English, 0x409 + UINT cp = 1252; // codepage = Western, 0x4E4 + info.fName = GetKeyValue(verbuf, L"ProductName", lang, cp); + info.fDescription = GetKeyValue(verbuf, L"FileDescription", lang, cp); + info.fSupportsAsyncRender = GetBooleanFlag(verbuf, L"AsyncDrawingSupport", lang, cp); + + char *mimeType = GetKeyValue(verbuf, L"MIMEType", lang, cp); + char *mimeDescription = GetKeyValue(verbuf, L"FileOpenName", lang, cp); + char *extensions = GetKeyValue(verbuf, L"FileExtents", lang, cp); + + info.fVariantCount = CalculateVariantCount(mimeType); + info.fMimeTypeArray = MakeStringArray(info.fVariantCount, mimeType); + info.fMimeDescriptionArray = MakeStringArray(info.fVariantCount, mimeDescription); + info.fExtensionArray = MakeStringArray(info.fVariantCount, extensions); + info.fFullPath = PL_strdup(NS_ConvertUTF16toUTF8(fullPath).get()); + info.fFileName = PL_strdup(NS_ConvertUTF16toUTF8(fileName).get()); + info.fVersion = GetVersion(verbuf); + + PL_strfree(mimeType); + PL_strfree(mimeDescription); + PL_strfree(extensions); + } + else { + rv = NS_ERROR_FAILURE; + } + + PR_Free(verbuf); + + return rv; +} + +nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info) +{ + if (info.fName) + PL_strfree(info.fName); + + if (info.fDescription) + PL_strfree(info.fDescription); + + if (info.fMimeTypeArray) + FreeStringArray(info.fVariantCount, info.fMimeTypeArray); + + if (info.fMimeDescriptionArray) + FreeStringArray(info.fVariantCount, info.fMimeDescriptionArray); + + if (info.fExtensionArray) + FreeStringArray(info.fVariantCount, info.fExtensionArray); + + if (info.fFullPath) + PL_strfree(info.fFullPath); + + if (info.fFileName) + PL_strfree(info.fFileName); + + if (info.fVersion) + PR_smprintf_free(info.fVersion); + + ZeroMemory((void *)&info, sizeof(info)); + + return NS_OK; +} diff --git a/dom/plugins/base/nspluginroot.idl b/dom/plugins/base/nspluginroot.idl new file mode 100644 index 000000000..6c931d371 --- /dev/null +++ b/dom/plugins/base/nspluginroot.idl @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +native REFNSIID(REFNSIID); +native nativeVoid(void *); +native nativeChar(const char * *); +[ptr] native constVoidPtr(const void); +[ref] native PRUint32Ref(uint32_t); +[ref] native PRUint16Ref(uint16_t); +[ref] native constCharStarConstStar(const char* const*); +[ptr] native constCharPtr(const char); +[ref] native constCharStarRef(const char *); + +native NPWindowType(NPWindowType); +native NPWindow(NPWindow); +[ptr] native NPWindowPtr(NPWindow); +[ref] native NPWindowStarRef(NPWindow *); +[ptr] native NPPrintPtr(NPPrint); +native NPByteRange(NPByteRange); +[ptr] native NPByteRangePtr(NPByteRange); +native NPPVariable(NPPVariable); +native NPNVariable(NPNVariable); +[ptr] native NPRectPtr(NPRect); +native NPRegion(NPRegion); +native NPDrawingModel(NPDrawingModel); +native NPEventModel(NPEventModel); + +[ptr] native JRIEnvPtr(JRIEnv); +native jref(jref); |