diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/plugins | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/plugins')
409 files changed, 79458 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); diff --git a/dom/plugins/ipc/AStream.h b/dom/plugins/ipc/AStream.h new file mode 100644 index 000000000..dd48e844b --- /dev/null +++ b/dom/plugins/ipc/AStream.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 mozilla_plugins_AStream_h +#define mozilla_plugins_AStream_h + +namespace mozilla { +namespace plugins { + +/** + * When we are passed NPStream->{ndata,pdata} in {NPN,NPP}_DestroyStream, we + * don't know whether it's a plugin stream or a browser stream. This abstract + * class lets us cast to the right type of object and send the appropriate + * message. + */ +class AStream +{ +public: + virtual bool IsBrowserStream() = 0; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/BrowserStreamChild.cpp b/dom/plugins/ipc/BrowserStreamChild.cpp new file mode 100644 index 000000000..4fdfc1d47 --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamChild.cpp @@ -0,0 +1,304 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/plugins/BrowserStreamChild.h" + +#include "mozilla/Attributes.h" +#include "mozilla/plugins/PluginInstanceChild.h" +#include "mozilla/plugins/StreamNotifyChild.h" + +namespace mozilla { +namespace plugins { + +BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance, + const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + StreamNotifyChild* notifyData, + const nsCString& headers) + : mInstance(instance) + , mStreamStatus(kStreamOpen) + , mDestroyPending(NOT_DESTROYED) + , mNotifyPending(false) + , mStreamAsFilePending(false) + , mInstanceDying(false) + , mState(CONSTRUCTING) + , mURL(url) + , mHeaders(headers) + , mStreamNotify(notifyData) + , mDeliveryTracker(this) +{ + PLUGIN_LOG_DEBUG(("%s (%s, %i, %i, %p, %s)", FULLFUNCTION, + url.get(), length, lastmodified, (void*) notifyData, + headers.get())); + + AssertPluginThread(); + + memset(&mStream, 0, sizeof(mStream)); + mStream.ndata = static_cast<AStream*>(this); + mStream.url = NullableStringGet(mURL); + mStream.end = length; + mStream.lastmodified = lastmodified; + mStream.headers = NullableStringGet(mHeaders); + if (notifyData) { + mStream.notifyData = notifyData->mClosure; + notifyData->SetAssociatedStream(this); + } +} + +NPError +BrowserStreamChild::StreamConstructed( + const nsCString& mimeType, + const bool& seekable, + uint16_t* stype) +{ + NPError rv = NPERR_NO_ERROR; + + *stype = NP_NORMAL; + rv = mInstance->mPluginIface->newstream( + &mInstance->mData, const_cast<char*>(NullableStringGet(mimeType)), + &mStream, seekable, stype); + if (rv != NPERR_NO_ERROR) { + mState = DELETING; + if (mStreamNotify) { + mStreamNotify->SetAssociatedStream(nullptr); + mStreamNotify = nullptr; + } + } + else { + mState = ALIVE; + } + + return rv; +} + +BrowserStreamChild::~BrowserStreamChild() +{ + NS_ASSERTION(!mStreamNotify, "Should have nulled it by now!"); +} + +bool +BrowserStreamChild::RecvWrite(const int32_t& offset, + const uint32_t& newlength, + const Buffer& data) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + AssertPluginThread(); + + if (ALIVE != mState) + NS_RUNTIMEABORT("Unexpected state: received data after NPP_DestroyStream?"); + + if (kStreamOpen != mStreamStatus) + return true; + + mStream.end = newlength; + + NS_ASSERTION(data.Length() > 0, "Empty data"); + + PendingData* newdata = mPendingData.AppendElement(); + newdata->offset = offset; + newdata->data = data; + newdata->curpos = 0; + + EnsureDeliveryPending(); + + return true; +} + +bool +BrowserStreamChild::RecvNPP_StreamAsFile(const nsCString& fname) +{ + PLUGIN_LOG_DEBUG(("%s (fname=%s)", FULLFUNCTION, fname.get())); + + AssertPluginThread(); + + if (ALIVE != mState) + NS_RUNTIMEABORT("Unexpected state: received file after NPP_DestroyStream?"); + + if (kStreamOpen != mStreamStatus) + return true; + + mStreamAsFilePending = true; + mStreamAsFileName = fname; + EnsureDeliveryPending(); + + return true; +} + +bool +BrowserStreamChild::RecvNPP_DestroyStream(const NPReason& reason) +{ + PLUGIN_LOG_DEBUG_METHOD; + + if (ALIVE != mState) + NS_RUNTIMEABORT("Unexpected state: recevied NPP_DestroyStream twice?"); + + mState = DYING; + mDestroyPending = DESTROY_PENDING; + if (NPRES_DONE != reason) + mStreamStatus = reason; + + EnsureDeliveryPending(); + return true; +} + +bool +BrowserStreamChild::Recv__delete__() +{ + AssertPluginThread(); + + if (DELETING != mState) + NS_RUNTIMEABORT("Bad state, not DELETING"); + + return true; +} + +NPError +BrowserStreamChild::NPN_RequestRead(NPByteRange* aRangeList) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + AssertPluginThread(); + + if (ALIVE != mState || kStreamOpen != mStreamStatus) + return NPERR_GENERIC_ERROR; + + IPCByteRanges ranges; + for (; aRangeList; aRangeList = aRangeList->next) { + IPCByteRange br = {aRangeList->offset, aRangeList->length}; + ranges.AppendElement(br); + } + + NPError result; + CallNPN_RequestRead(ranges, &result); + return result; +} + +void +BrowserStreamChild::NPN_DestroyStream(NPReason reason) +{ + mStreamStatus = reason; + if (ALIVE == mState) + SendNPN_DestroyStream(reason); + + EnsureDeliveryPending(); +} + +void +BrowserStreamChild::EnsureDeliveryPending() +{ + MessageLoop::current()->PostTask( + mDeliveryTracker.NewRunnableMethod(&BrowserStreamChild::Deliver)); +} + +void +BrowserStreamChild::Deliver() +{ + while (kStreamOpen == mStreamStatus && mPendingData.Length()) { + if (DeliverPendingData() && kStreamOpen == mStreamStatus) { + SetSuspendedTimer(); + return; + } + } + ClearSuspendedTimer(); + + NS_ASSERTION(kStreamOpen != mStreamStatus || 0 == mPendingData.Length(), + "Exit out of the data-delivery loop with pending data"); + mPendingData.Clear(); + + // NPP_StreamAsFile() is documented (at MDN) to be called "when the stream + // is complete" -- i.e. after all calls to NPP_WriteReady() and NPP_Write() + // have finished. We make these calls asynchronously (from + // DeliverPendingData()). So we need to make sure all the "pending data" + // has been "delivered" before calling NPP_StreamAsFile() (also + // asynchronously). Doing this resolves bug 687610, bug 670036 and possibly + // also other bugs. + if (mStreamAsFilePending) { + if (mStreamStatus == kStreamOpen) + mInstance->mPluginIface->asfile(&mInstance->mData, &mStream, + mStreamAsFileName.get()); + mStreamAsFilePending = false; + } + + if (DESTROY_PENDING == mDestroyPending) { + mDestroyPending = DESTROYED; + if (mState != DYING) + NS_RUNTIMEABORT("mDestroyPending but state not DYING"); + + NS_ASSERTION(NPRES_DONE != mStreamStatus, "Success status set too early!"); + if (kStreamOpen == mStreamStatus) + mStreamStatus = NPRES_DONE; + + (void) mInstance->mPluginIface + ->destroystream(&mInstance->mData, &mStream, mStreamStatus); + } + if (DESTROYED == mDestroyPending && mNotifyPending) { + NS_ASSERTION(mStreamNotify, "mDestroyPending but no mStreamNotify?"); + + mNotifyPending = false; + mStreamNotify->NPP_URLNotify(mStreamStatus); + delete mStreamNotify; + mStreamNotify = nullptr; + } + if (DYING == mState && DESTROYED == mDestroyPending + && !mStreamNotify && !mInstanceDying) { + SendStreamDestroyed(); + mState = DELETING; + } +} + +bool +BrowserStreamChild::DeliverPendingData() +{ + if (mState != ALIVE && mState != DYING) + NS_RUNTIMEABORT("Unexpected state"); + + NS_ASSERTION(mPendingData.Length(), "Called from Deliver with empty pending"); + + while (mPendingData[0].curpos < static_cast<int32_t>(mPendingData[0].data.Length())) { + int32_t r = mInstance->mPluginIface->writeready(&mInstance->mData, &mStream); + if (kStreamOpen != mStreamStatus) + return false; + if (0 == r) // plugin wants to suspend delivery + return true; + + r = mInstance->mPluginIface->write( + &mInstance->mData, &mStream, + mPendingData[0].offset + mPendingData[0].curpos, // offset + mPendingData[0].data.Length() - mPendingData[0].curpos, // length + const_cast<char*>(mPendingData[0].data.BeginReading() + mPendingData[0].curpos)); + if (kStreamOpen != mStreamStatus) + return false; + if (0 == r) + return true; + if (r < 0) { // error condition + NPN_DestroyStream(NPRES_NETWORK_ERR); + return false; + } + mPendingData[0].curpos += r; + } + mPendingData.RemoveElementAt(0); + return false; +} + +void +BrowserStreamChild::SetSuspendedTimer() +{ + if (mSuspendedTimer.IsRunning()) + return; + mSuspendedTimer.Start( + base::TimeDelta::FromMilliseconds(100), // 100ms copied from Mozilla plugin host + this, &BrowserStreamChild::Deliver); +} + +void +BrowserStreamChild::ClearSuspendedTimer() +{ + mSuspendedTimer.Stop(); +} + +} /* namespace plugins */ +} /* namespace mozilla */ diff --git a/dom/plugins/ipc/BrowserStreamChild.h b/dom/plugins/ipc/BrowserStreamChild.h new file mode 100644 index 000000000..ad334e4a3 --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamChild.h @@ -0,0 +1,173 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 mozilla_plugins_BrowserStreamChild_h +#define mozilla_plugins_BrowserStreamChild_h 1 + +#include "mozilla/plugins/PBrowserStreamChild.h" +#include "mozilla/plugins/AStream.h" +#include "base/task.h" +#include "base/timer.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceChild; +class StreamNotifyChild; + +class BrowserStreamChild : public PBrowserStreamChild, public AStream +{ +public: + BrowserStreamChild(PluginInstanceChild* instance, + const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + StreamNotifyChild* notifyData, + const nsCString& headers); + virtual ~BrowserStreamChild(); + + virtual bool IsBrowserStream() override { return true; } + + NPError StreamConstructed( + const nsCString& mimeType, + const bool& seekable, + uint16_t* stype); + + virtual bool RecvWrite(const int32_t& offset, + const uint32_t& newsize, + const Buffer& data) override; + virtual bool RecvNPP_StreamAsFile(const nsCString& fname) override; + virtual bool RecvNPP_DestroyStream(const NPReason& reason) override; + virtual bool Recv__delete__() override; + + void EnsureCorrectInstance(PluginInstanceChild* i) + { + if (i != mInstance) + NS_RUNTIMEABORT("Incorrect stream instance"); + } + void EnsureCorrectStream(NPStream* s) + { + if (s != &mStream) + NS_RUNTIMEABORT("Incorrect stream data"); + } + + NPError NPN_RequestRead(NPByteRange* aRangeList); + void NPN_DestroyStream(NPReason reason); + + void NotifyPending() { + NS_ASSERTION(!mNotifyPending, "Pending twice?"); + mNotifyPending = true; + EnsureDeliveryPending(); + } + + /** + * During instance destruction, artificially cancel all outstanding streams. + * + * @return false if we are already in the DELETING state. + */ + bool InstanceDying() { + if (DELETING == mState) + return false; + + mInstanceDying = true; + return true; + } + + void FinishDelivery() { + NS_ASSERTION(mInstanceDying, "Should only be called after InstanceDying"); + NS_ASSERTION(DELETING != mState, "InstanceDying didn't work?"); + mStreamStatus = NPRES_USER_BREAK; + Deliver(); + NS_ASSERTION(!mStreamNotify, "Didn't deliver NPN_URLNotify?"); + } + +private: + friend class StreamNotifyChild; + using PBrowserStreamChild::SendNPN_DestroyStream; + + /** + * Post an event to ensure delivery of pending data/destroy/urlnotify events + * outside of the current RPC stack. + */ + void EnsureDeliveryPending(); + + /** + * Deliver data, destruction, notify scheduling + * or cancelling the suspended timer as needed. + */ + void Deliver(); + + /** + * Deliver one chunk of pending data. + * @return true if the plugin indicated a pause was necessary + */ + bool DeliverPendingData(); + + void SetSuspendedTimer(); + void ClearSuspendedTimer(); + + PluginInstanceChild* mInstance; + NPStream mStream; + + static const NPReason kStreamOpen = -1; + + /** + * The plugin's notion of whether a stream has been "closed" (no more + * data delivery) differs from the plugin host due to asynchronous delivery + * of data and NPN_DestroyStream. While the plugin-visible stream is open, + * mStreamStatus should be kStreamOpen (-1). mStreamStatus will be a + * failure code if either the parent or child indicates stream failure. + */ + NPReason mStreamStatus; + + /** + * Delivery of NPP_DestroyStream and NPP_URLNotify must be postponed until + * all data has been delivered. + */ + enum { + NOT_DESTROYED, // NPP_DestroyStream not yet received + DESTROY_PENDING, // NPP_DestroyStream received, not yet delivered + DESTROYED // NPP_DestroyStream delivered, NPP_URLNotify may still be pending + } mDestroyPending; + bool mNotifyPending; + bool mStreamAsFilePending; + nsCString mStreamAsFileName; + + // When NPP_Destroy is called for our instance (manager), this flag is set + // cancels the stream and avoids sending StreamDestroyed. + bool mInstanceDying; + + enum { + CONSTRUCTING, + ALIVE, + DYING, + DELETING + } mState; + nsCString mURL; + nsCString mHeaders; + StreamNotifyChild* mStreamNotify; + + struct PendingData + { + int32_t offset; + Buffer data; + int32_t curpos; + }; + nsTArray<PendingData> mPendingData; + + /** + * Asynchronous RecvWrite messages are never delivered to the plugin + * immediately, because that may be in the midst of an unexpected RPC + * stack frame. It instead posts a runnable using this tracker to cancel + * in case we are destroyed. + */ + ScopedRunnableMethodFactory<BrowserStreamChild> mDeliveryTracker; + base::RepeatingTimer<BrowserStreamChild> mSuspendedTimer; +}; + +} // namespace plugins +} // namespace mozilla + +#endif /* mozilla_plugins_BrowserStreamChild_h */ diff --git a/dom/plugins/ipc/BrowserStreamParent.cpp b/dom/plugins/ipc/BrowserStreamParent.cpp new file mode 100644 index 000000000..26dd3c943 --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamParent.cpp @@ -0,0 +1,222 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "BrowserStreamParent.h" +#include "PluginAsyncSurrogate.h" +#include "PluginInstanceParent.h" +#include "nsNPAPIPlugin.h" + +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" + +// How much data are we willing to send across the wire +// in one chunk? +static const int32_t kSendDataChunk = 0xffff; + +namespace mozilla { +namespace plugins { + +BrowserStreamParent::BrowserStreamParent(PluginInstanceParent* npp, + NPStream* stream) + : mNPP(npp) + , mStream(stream) + , mDeferredDestroyReason(NPRES_DONE) + , mState(INITIALIZING) +{ + mStream->pdata = static_cast<AStream*>(this); + nsNPAPIStreamWrapper* wrapper = + reinterpret_cast<nsNPAPIStreamWrapper*>(mStream->ndata); + if (wrapper) { + mStreamListener = wrapper->GetStreamListener(); + } +} + +BrowserStreamParent::~BrowserStreamParent() +{ + mStream->pdata = nullptr; +} + +void +BrowserStreamParent::ActorDestroy(ActorDestroyReason aWhy) +{ + // Implement me! Bug 1005159 +} + +bool +BrowserStreamParent::RecvAsyncNPP_NewStreamResult(const NPError& rv, + const uint16_t& stype) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + PluginAsyncSurrogate* surrogate = mNPP->GetAsyncSurrogate(); + MOZ_ASSERT(surrogate); + surrogate->AsyncCallArriving(); + if (mState == DEFERRING_DESTROY) { + // We've been asked to destroy ourselves before init was complete. + mState = DYING; + Unused << SendNPP_DestroyStream(mDeferredDestroyReason); + return true; + } + + NPError error = rv; + if (error == NPERR_NO_ERROR) { + if (!mStreamListener) { + return false; + } + if (mStreamListener->SetStreamType(stype)) { + mState = ALIVE; + } else { + error = NPERR_GENERIC_ERROR; + } + } + + if (error != NPERR_NO_ERROR) { + surrogate->DestroyAsyncStream(mStream); + Unused << PBrowserStreamParent::Send__delete__(this); + } + + return true; +} + +bool +BrowserStreamParent::AnswerNPN_RequestRead(const IPCByteRanges& ranges, + NPError* result) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + switch (mState) { + case INITIALIZING: + NS_ERROR("Requesting a read before initialization has completed"); + *result = NPERR_GENERIC_ERROR; + return false; + + case ALIVE: + break; + + case DYING: + *result = NPERR_GENERIC_ERROR; + return true; + + default: + NS_ERROR("Unexpected state"); + return false; + } + + if (!mStream) + return false; + + if (ranges.Length() > INT32_MAX) + return false; + + UniquePtr<NPByteRange[]> rp(new NPByteRange[ranges.Length()]); + for (uint32_t i = 0; i < ranges.Length(); ++i) { + rp[i].offset = ranges[i].offset; + rp[i].length = ranges[i].length; + rp[i].next = &rp[i + 1]; + } + rp[ranges.Length() - 1].next = nullptr; + + *result = mNPP->mNPNIface->requestread(mStream, rp.get()); + return true; +} + +bool +BrowserStreamParent::RecvNPN_DestroyStream(const NPReason& reason) +{ + switch (mState) { + case ALIVE: + break; + + case DYING: + return true; + + default: + NS_ERROR("Unexpected state"); + return false; + } + + mNPP->mNPNIface->destroystream(mNPP->mNPP, mStream, reason); + return true; +} + +void +BrowserStreamParent::NPP_DestroyStream(NPReason reason) +{ + NS_ASSERTION(ALIVE == mState || INITIALIZING == mState, + "NPP_DestroyStream called twice?"); + bool stillInitializing = INITIALIZING == mState; + if (stillInitializing) { + mState = DEFERRING_DESTROY; + mDeferredDestroyReason = reason; + } else { + mState = DYING; + Unused << SendNPP_DestroyStream(reason); + } +} + +bool +BrowserStreamParent::RecvStreamDestroyed() +{ + if (DYING != mState) { + NS_ERROR("Unexpected state"); + return false; + } + + mStreamPeer = nullptr; + + mState = DELETING; + return Send__delete__(this); +} + +int32_t +BrowserStreamParent::WriteReady() +{ + if (mState == INITIALIZING) { + return 0; + } + return kSendDataChunk; +} + +int32_t +BrowserStreamParent::Write(int32_t offset, + int32_t len, + void* buffer) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + NS_ASSERTION(ALIVE == mState, "Sending data after NPP_DestroyStream?"); + NS_ASSERTION(len > 0, "Non-positive length to NPP_Write"); + + if (len > kSendDataChunk) + len = kSendDataChunk; + + return SendWrite(offset, + mStream->end, + nsCString(static_cast<char*>(buffer), len)) ? + len : -1; +} + +void +BrowserStreamParent::StreamAsFile(const char* fname) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + NS_ASSERTION(ALIVE == mState, + "Calling streamasfile after NPP_DestroyStream?"); + + // Make sure our stream survives until the plugin process tells us we've + // been destroyed (until RecvStreamDestroyed() is called). Since we retain + // mStreamPeer at most once, we won't get in trouble if StreamAsFile() is + // called more than once. + if (!mStreamPeer) { + nsNPAPIPlugin::RetainStream(mStream, getter_AddRefs(mStreamPeer)); + } + + Unused << SendNPP_StreamAsFile(nsCString(fname)); + return; +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/BrowserStreamParent.h b/dom/plugins/ipc/BrowserStreamParent.h new file mode 100644 index 000000000..410f2313f --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamParent.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 mozilla_plugins_BrowserStreamParent_h +#define mozilla_plugins_BrowserStreamParent_h + +#include "mozilla/plugins/PBrowserStreamParent.h" +#include "mozilla/plugins/AStream.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsPluginStreamListenerPeer.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceParent; + +class BrowserStreamParent : public PBrowserStreamParent, public AStream +{ + friend class PluginModuleParent; + friend class PluginInstanceParent; + +public: + BrowserStreamParent(PluginInstanceParent* npp, + NPStream* stream); + virtual ~BrowserStreamParent(); + + virtual bool IsBrowserStream() override { return true; } + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool RecvAsyncNPP_NewStreamResult( + const NPError& rv, + const uint16_t& stype) override; + + virtual bool AnswerNPN_RequestRead(const IPCByteRanges& ranges, + NPError* result) override; + + virtual bool RecvNPN_DestroyStream(const NPReason& reason) override; + + virtual bool RecvStreamDestroyed() override; + + int32_t WriteReady(); + int32_t Write(int32_t offset, int32_t len, void* buffer); + void StreamAsFile(const char* fname); + + void NPP_DestroyStream(NPReason reason); + + void SetAlive() + { + if (mState == INITIALIZING) { + mState = ALIVE; + } + } + +private: + using PBrowserStreamParent::SendNPP_DestroyStream; + + PluginInstanceParent* mNPP; + NPStream* mStream; + nsCOMPtr<nsISupports> mStreamPeer; + RefPtr<nsNPAPIPluginStreamListener> mStreamListener; + NPReason mDeferredDestroyReason; + + enum { + INITIALIZING, + DEFERRING_DESTROY, + ALIVE, + DYING, + DELETING + } mState; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/ChildAsyncCall.cpp b/dom/plugins/ipc/ChildAsyncCall.cpp new file mode 100644 index 000000000..6adc05d55 --- /dev/null +++ b/dom/plugins/ipc/ChildAsyncCall.cpp @@ -0,0 +1,52 @@ +/* -*- 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 "ChildAsyncCall.h" +#include "PluginInstanceChild.h" + +namespace mozilla { +namespace plugins { + +ChildAsyncCall::ChildAsyncCall(PluginInstanceChild* instance, + PluginThreadCallback aFunc, void* aUserData) + : mInstance(instance) + , mFunc(aFunc) + , mData(aUserData) +{ +} + +nsresult +ChildAsyncCall::Cancel() +{ + mInstance = nullptr; + mFunc = nullptr; + mData = nullptr; + return NS_OK; +} + +void +ChildAsyncCall::RemoveFromAsyncList() +{ + if (mInstance) { + MutexAutoLock lock(mInstance->mAsyncCallMutex); + mInstance->mPendingAsyncCalls.RemoveElement(this); + } +} + +NS_IMETHODIMP +ChildAsyncCall::Run() +{ + RemoveFromAsyncList(); + + if (mFunc) + mFunc(mData); + + return NS_OK; +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/ChildAsyncCall.h b/dom/plugins/ipc/ChildAsyncCall.h new file mode 100644 index 000000000..ae5c45a93 --- /dev/null +++ b/dom/plugins/ipc/ChildAsyncCall.h @@ -0,0 +1,41 @@ +/* -*- 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/. */ + +#ifndef mozilla_plugins_ChildAsyncCall_h +#define mozilla_plugins_ChildAsyncCall_h + +#include "PluginMessageUtils.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace plugins { + +typedef void (*PluginThreadCallback)(void*); + +class PluginInstanceChild; + +class ChildAsyncCall : public CancelableRunnable +{ +public: + ChildAsyncCall(PluginInstanceChild* instance, + PluginThreadCallback aFunc, void* aUserData); + + NS_IMETHOD Run() override; + nsresult Cancel() override; + +protected: + PluginInstanceChild* mInstance; + PluginThreadCallback mFunc; + void* mData; + + void RemoveFromAsyncList(); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_ChildAsyncCall_h diff --git a/dom/plugins/ipc/ChildTimer.cpp b/dom/plugins/ipc/ChildTimer.cpp new file mode 100644 index 000000000..5bcf5498b --- /dev/null +++ b/dom/plugins/ipc/ChildTimer.cpp @@ -0,0 +1,40 @@ +/* -*- 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 "ChildTimer.h" +#include "PluginInstanceChild.h" +#include "nsComponentManagerUtils.h" + +namespace mozilla { +namespace plugins { + +ChildTimer::ChildTimer(PluginInstanceChild* instance, + uint32_t interval, + bool repeat, + TimerFunc func) + : mInstance(instance) + , mFunc(func) + , mRepeating(repeat) + , mID(gNextTimerID++) +{ + mTimer.Start(base::TimeDelta::FromMilliseconds(interval), + this, &ChildTimer::Run); +} + +uint32_t +ChildTimer::gNextTimerID = 1; + +void +ChildTimer::Run() +{ + if (!mRepeating) + mTimer.Stop(); + mFunc(mInstance->GetNPP(), mID); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/ChildTimer.h b/dom/plugins/ipc/ChildTimer.h new file mode 100644 index 000000000..194fa77ab --- /dev/null +++ b/dom/plugins/ipc/ChildTimer.h @@ -0,0 +1,58 @@ +/* -*- 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/. */ + +#ifndef mozilla_plugins_ChildTimer_h +#define mozilla_plugins_ChildTimer_h + +#include "PluginMessageUtils.h" +#include "npapi.h" +#include "base/timer.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceChild; +typedef void (*TimerFunc)(NPP npp, uint32_t timerID); + +class ChildTimer +{ +public: + /** + * If initialization failed, ID() will return 0. + */ + ChildTimer(PluginInstanceChild* instance, + uint32_t interval, + bool repeat, + TimerFunc func); + ~ChildTimer() { } + + uint32_t ID() const { return mID; } + + class IDComparator + { + public: + bool Equals(ChildTimer* t, uint32_t id) const { + return t->ID() == id; + } + }; + +private: + PluginInstanceChild* mInstance; + TimerFunc mFunc; + bool mRepeating; + uint32_t mID; + base::RepeatingTimer<ChildTimer> mTimer; + + void Run(); + + static uint32_t gNextTimerID; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_ChildTimer_h diff --git a/dom/plugins/ipc/D3D11SurfaceHolder.cpp b/dom/plugins/ipc/D3D11SurfaceHolder.cpp new file mode 100644 index 000000000..f02146c20 --- /dev/null +++ b/dom/plugins/ipc/D3D11SurfaceHolder.cpp @@ -0,0 +1,87 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "nsDebug.h" +#include "D3D11SurfaceHolder.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/layers/TextureD3D11.h" +#include <d3d11.h> + +namespace mozilla { +namespace plugins { + +using namespace mozilla::gfx; +using namespace mozilla::layers; + +D3D11SurfaceHolder::D3D11SurfaceHolder(ID3D11Texture2D* back, + SurfaceFormat format, + const IntSize& size) + : mDevice11(DeviceManagerDx::Get()->GetContentDevice()), + mBack(back), + mFormat(format), + mSize(size) +{ +} + +D3D11SurfaceHolder::~D3D11SurfaceHolder() +{ +} + +bool +D3D11SurfaceHolder::IsValid() +{ + // If a TDR occurred, platform devices will be recreated. + if (DeviceManagerDx::Get()->GetContentDevice() != mDevice11) { + return false; + } + return true; +} + +bool +D3D11SurfaceHolder::CopyToTextureClient(TextureClient* aClient) +{ + MOZ_ASSERT(NS_IsMainThread()); + + D3D11TextureData* data = aClient->GetInternalData()->AsD3D11TextureData(); + if (!data) { + // We don't support this yet. We expect to have a D3D11 compositor, and + // therefore D3D11 surfaces. + NS_WARNING("Plugin DXGI surface has unsupported TextureClient"); + return false; + } + + RefPtr<ID3D11DeviceContext> context; + mDevice11->GetImmediateContext(getter_AddRefs(context)); + if (!context) { + NS_WARNING("Could not get an immediate D3D11 context"); + return false; + } + + TextureClientAutoLock autoLock(aClient, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return false; + } + + RefPtr<IDXGIKeyedMutex> mutex; + HRESULT hr = mBack->QueryInterface(__uuidof(IDXGIKeyedMutex), (void **)getter_AddRefs(mutex)); + if (FAILED(hr) || !mutex) { + NS_WARNING("Could not acquire an IDXGIKeyedMutex"); + return false; + } + + { + AutoTextureLock lock(mutex, hr); + if (hr == WAIT_ABANDONED || hr == WAIT_TIMEOUT || FAILED(hr)) { + NS_WARNING("Could not acquire DXGI surface lock - plugin forgot to release?"); + return false; + } + + context->CopyResource(data->GetD3D11Texture(), mBack); + } + return true; +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/D3D11SurfaceHolder.h b/dom/plugins/ipc/D3D11SurfaceHolder.h new file mode 100644 index 000000000..16cd2d182 --- /dev/null +++ b/dom/plugins/ipc/D3D11SurfaceHolder.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 _include_dom_plugins_ipc_D3D11SurfaceHolder_h__ +#define _include_dom_plugins_ipc_D3D11SurfaceHolder_h__ + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace layers { +class D3D11ShareHandleImage; +class TextureClient; +} // namespace layers + +namespace plugins { + +class D3D11SurfaceHolder +{ +public: + D3D11SurfaceHolder(ID3D11Texture2D* back, gfx::SurfaceFormat format, const gfx::IntSize& size); + + NS_INLINE_DECL_REFCOUNTING(D3D11SurfaceHolder); + + bool IsValid(); + bool CopyToTextureClient(layers::TextureClient* aClient); + + gfx::SurfaceFormat GetFormat() const { + return mFormat; + } + const gfx::IntSize& GetSize() const { + return mSize; + } + +private: + ~D3D11SurfaceHolder(); + +private: + RefPtr<ID3D11Device> mDevice11; + RefPtr<ID3D11Texture2D> mBack; + gfx::SurfaceFormat mFormat; + gfx::IntSize mSize; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // _include_dom_plugins_ipc_D3D11nSurfaceHolder_h__ diff --git a/dom/plugins/ipc/MiniShmParent.cpp b/dom/plugins/ipc/MiniShmParent.cpp new file mode 100644 index 000000000..eb0b16c8a --- /dev/null +++ b/dom/plugins/ipc/MiniShmParent.cpp @@ -0,0 +1,218 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "MiniShmParent.h" + +#include "base/scoped_handle.h" + +#include <sstream> + +namespace mozilla { +namespace plugins { + +// static +const unsigned int MiniShmParent::kDefaultMiniShmSectionSize = 0x1000; + +MiniShmParent::MiniShmParent() + : mSectionSize(0), + mParentEvent(nullptr), + mParentGuard(nullptr), + mChildEvent(nullptr), + mChildGuard(nullptr), + mRegWait(nullptr), + mFileMapping(nullptr), + mView(nullptr), + mIsConnected(false), + mTimeout(INFINITE) +{ +} + +MiniShmParent::~MiniShmParent() +{ + CleanUp(); +} + +void +MiniShmParent::CleanUp() +{ + if (mRegWait) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = nullptr; + } + if (mParentEvent) { + ::CloseHandle(mParentEvent); + mParentEvent = nullptr; + } + if (mParentGuard) { + ::CloseHandle(mParentGuard); + mParentGuard = nullptr; + } + if (mChildEvent) { + ::CloseHandle(mChildEvent); + mChildEvent = nullptr; + } + if (mChildGuard) { + ::CloseHandle(mChildGuard); + mChildGuard = nullptr; + } + if (mView) { + ::UnmapViewOfFile(mView); + mView = nullptr; + } + if (mFileMapping) { + ::CloseHandle(mFileMapping); + mFileMapping = nullptr; + } +} + +nsresult +MiniShmParent::Init(MiniShmObserver* aObserver, const DWORD aTimeout, + const unsigned int aSectionSize) +{ + if (!aObserver || !aSectionSize || (aSectionSize % 0x1000) || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (mFileMapping) { + return NS_ERROR_ALREADY_INITIALIZED; + } + SECURITY_ATTRIBUTES securityAttributes = {sizeof(securityAttributes), + nullptr, + TRUE}; + ScopedHandle parentEvent(::CreateEvent(&securityAttributes, + FALSE, + FALSE, + nullptr)); + if (!parentEvent.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle parentGuard(::CreateEvent(&securityAttributes, + FALSE, + TRUE, + nullptr)); + if (!parentGuard.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle childEvent(::CreateEvent(&securityAttributes, + FALSE, + FALSE, + nullptr)); + if (!childEvent.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle childGuard(::CreateEvent(&securityAttributes, + FALSE, + TRUE, + nullptr)); + if (!childGuard.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle mapping(::CreateFileMapping(INVALID_HANDLE_VALUE, + &securityAttributes, + PAGE_READWRITE, + 0, + aSectionSize, + nullptr)); + if (!mapping.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedMappedFileView view(::MapViewOfFile(mapping, + FILE_MAP_WRITE, + 0, 0, 0)); + if (!view.IsValid()) { + return NS_ERROR_FAILURE; + } + nsresult rv = SetView(view, aSectionSize, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetGuard(childGuard, aTimeout); + NS_ENSURE_SUCCESS(rv, rv); + + MiniShmInit* initStruct = nullptr; + rv = GetWritePtrInternal(initStruct); + NS_ENSURE_SUCCESS(rv, rv); + initStruct->mParentEvent = parentEvent; + initStruct->mParentGuard = parentGuard; + initStruct->mChildEvent = childEvent; + initStruct->mChildGuard = childGuard; + + if (!::RegisterWaitForSingleObject(&mRegWait, + parentEvent, + &SOnEvent, + this, + INFINITE, + WT_EXECUTEDEFAULT)) { + return NS_ERROR_FAILURE; + } + + mParentEvent = parentEvent.Take(); + mParentGuard = parentGuard.Take(); + mChildEvent = childEvent.Take(); + mChildGuard = childGuard.Take(); + mFileMapping = mapping.Take(); + mView = view.Take(); + mSectionSize = aSectionSize; + SetObserver(aObserver); + mTimeout = aTimeout; + return NS_OK; +} + +nsresult +MiniShmParent::GetCookie(std::wstring& cookie) +{ + if (!mFileMapping) { + return NS_ERROR_NOT_INITIALIZED; + } + std::wostringstream oss; + oss << mFileMapping; + if (!oss) { + return NS_ERROR_FAILURE; + } + cookie = oss.str(); + return NS_OK; +} + +nsresult +MiniShmParent::Send() +{ + if (!mChildEvent) { + return NS_ERROR_NOT_INITIALIZED; + } + if (!::SetEvent(mChildEvent)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +bool +MiniShmParent::IsConnected() const +{ + return mIsConnected; +} + +void +MiniShmParent::OnEvent() +{ + if (mIsConnected) { + MiniShmBase::OnEvent(); + } else { + FinalizeConnection(); + } + ::SetEvent(mParentGuard); +} + +void +MiniShmParent::FinalizeConnection() +{ + const MiniShmInitComplete* initCompleteStruct = nullptr; + nsresult rv = GetReadPtr(initCompleteStruct); + mIsConnected = NS_SUCCEEDED(rv) && initCompleteStruct->mSucceeded; + if (mIsConnected) { + OnConnect(); + } +} + +} // namespace plugins +} // namespace mozilla + diff --git a/dom/plugins/ipc/MiniShmParent.h b/dom/plugins/ipc/MiniShmParent.h new file mode 100644 index 000000000..dc6cd8b18 --- /dev/null +++ b/dom/plugins/ipc/MiniShmParent.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 mozilla_plugins_MiniShmParent_h +#define mozilla_plugins_MiniShmParent_h + +#include "MiniShmBase.h" + +#include <string> + +namespace mozilla { +namespace plugins { + +/** + * This class provides a lightweight shared memory interface for a parent + * process in Win32. + * This code assumes that there is a parent-child relationship between + * processes, as it creates inheritable handles. + * Note that this class is *not* an IPDL actor. + * + * @see MiniShmChild + */ +class MiniShmParent : public MiniShmBase +{ +public: + MiniShmParent(); + virtual ~MiniShmParent(); + + static const unsigned int kDefaultMiniShmSectionSize; + + /** + * Initialize shared memory on the parent side. + * + * @param aObserver A MiniShmObserver object to receive event notifications. + * @param aTimeout Timeout in milliseconds. + * @param aSectionSize Desired size of the shared memory section. This is + * expected to be a multiple of 0x1000 (4KiB). + * @return nsresult error code + */ + nsresult + Init(MiniShmObserver* aObserver, const DWORD aTimeout, + const unsigned int aSectionSize = kDefaultMiniShmSectionSize); + + /** + * Destroys the shared memory section. Useful to explicitly release + * resources if it is known that they won't be needed again. + */ + void + CleanUp(); + + /** + * Provides a cookie string that should be passed to MiniShmChild + * during its initialization. + * + * @param aCookie A std::wstring variable to receive the cookie. + * @return nsresult error code + */ + nsresult + GetCookie(std::wstring& aCookie); + + virtual nsresult + Send() override; + + bool + IsConnected() const; + +protected: + void + OnEvent() override; + +private: + void + FinalizeConnection(); + + unsigned int mSectionSize; + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + HANDLE mRegWait; + HANDLE mFileMapping; + LPVOID mView; + bool mIsConnected; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmParent); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmParent_h + diff --git a/dom/plugins/ipc/NPEventAndroid.h b/dom/plugins/ipc/NPEventAndroid.h new file mode 100644 index 000000000..f664af857 --- /dev/null +++ b/dom/plugins/ipc/NPEventAndroid.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */ +/* 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 is a NPEventX11.h derived stub for Android +// Plugins aren't actually supported yet + +#ifndef mozilla_dom_plugins_NPEventAndroid_h +#define mozilla_dom_plugins_NPEventAndroid_h + +#include "npapi.h" + +namespace mozilla { + +namespace plugins { + +struct NPRemoteEvent { + NPEvent event; +}; + +} + +} + + +namespace IPC { + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteEvent> +{ + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + aMsg->WriteBytes(&aParam, sizeof(paramType)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + return aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType)); + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + // TODO + aLog->append(L"(AndroidEvent)"); + } +}; + +} // namespace IPC + + +#endif // mozilla_dom_plugins_NPEventAndroid_h diff --git a/dom/plugins/ipc/NPEventOSX.h b/dom/plugins/ipc/NPEventOSX.h new file mode 100644 index 000000000..ca1736a8a --- /dev/null +++ b/dom/plugins/ipc/NPEventOSX.h @@ -0,0 +1,194 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */ +/* 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 mozilla_dom_plugins_NPEventOSX_h +#define mozilla_dom_plugins_NPEventOSX_h 1 + + +#include "npapi.h" + +namespace mozilla { + +namespace plugins { + +struct NPRemoteEvent { + NPCocoaEvent event; + double contentsScaleFactor; +}; + +} // namespace plugins + +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteEvent> +{ + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + aMsg->WriteInt(aParam.event.type); + aMsg->WriteUInt32(aParam.event.version); + switch (aParam.event.type) { + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseEntered: + case NPCocoaEventMouseExited: + case NPCocoaEventMouseDragged: + case NPCocoaEventScrollWheel: + aMsg->WriteUInt32(aParam.event.data.mouse.modifierFlags); + aMsg->WriteDouble(aParam.event.data.mouse.pluginX); + aMsg->WriteDouble(aParam.event.data.mouse.pluginY); + aMsg->WriteInt32(aParam.event.data.mouse.buttonNumber); + aMsg->WriteInt32(aParam.event.data.mouse.clickCount); + aMsg->WriteDouble(aParam.event.data.mouse.deltaX); + aMsg->WriteDouble(aParam.event.data.mouse.deltaY); + aMsg->WriteDouble(aParam.event.data.mouse.deltaZ); + break; + case NPCocoaEventKeyDown: + case NPCocoaEventKeyUp: + case NPCocoaEventFlagsChanged: + aMsg->WriteUInt32(aParam.event.data.key.modifierFlags); + WriteParam(aMsg, aParam.event.data.key.characters); + WriteParam(aMsg, aParam.event.data.key.charactersIgnoringModifiers); + aMsg->WriteUnsignedChar(aParam.event.data.key.isARepeat); + aMsg->WriteUInt16(aParam.event.data.key.keyCode); + break; + case NPCocoaEventFocusChanged: + case NPCocoaEventWindowFocusChanged: + aMsg->WriteUnsignedChar(aParam.event.data.focus.hasFocus); + break; + case NPCocoaEventDrawRect: + // We don't write out the context pointer, it would always be + // nullptr and is just filled in as such on the read. + aMsg->WriteDouble(aParam.event.data.draw.x); + aMsg->WriteDouble(aParam.event.data.draw.y); + aMsg->WriteDouble(aParam.event.data.draw.width); + aMsg->WriteDouble(aParam.event.data.draw.height); + break; + case NPCocoaEventTextInput: + WriteParam(aMsg, aParam.event.data.text.text); + break; + default: + NS_NOTREACHED("Attempted to serialize unknown event type."); + return; + } + aMsg->WriteDouble(aParam.contentsScaleFactor); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + int type = 0; + if (!aMsg->ReadInt(aIter, &type)) { + return false; + } + aResult->event.type = static_cast<NPCocoaEventType>(type); + + if (!aMsg->ReadUInt32(aIter, &aResult->event.version)) { + return false; + } + + switch (aResult->event.type) { + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseEntered: + case NPCocoaEventMouseExited: + case NPCocoaEventMouseDragged: + case NPCocoaEventScrollWheel: + if (!aMsg->ReadUInt32(aIter, &aResult->event.data.mouse.modifierFlags)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.pluginX)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.pluginY)) { + return false; + } + if (!aMsg->ReadInt32(aIter, &aResult->event.data.mouse.buttonNumber)) { + return false; + } + if (!aMsg->ReadInt32(aIter, &aResult->event.data.mouse.clickCount)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaX)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaY)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaZ)) { + return false; + } + break; + case NPCocoaEventKeyDown: + case NPCocoaEventKeyUp: + case NPCocoaEventFlagsChanged: + if (!aMsg->ReadUInt32(aIter, &aResult->event.data.key.modifierFlags)) { + return false; + } + if (!ReadParam(aMsg, aIter, &aResult->event.data.key.characters)) { + return false; + } + if (!ReadParam(aMsg, aIter, &aResult->event.data.key.charactersIgnoringModifiers)) { + return false; + } + if (!aMsg->ReadUnsignedChar(aIter, &aResult->event.data.key.isARepeat)) { + return false; + } + if (!aMsg->ReadUInt16(aIter, &aResult->event.data.key.keyCode)) { + return false; + } + break; + case NPCocoaEventFocusChanged: + case NPCocoaEventWindowFocusChanged: + if (!aMsg->ReadUnsignedChar(aIter, &aResult->event.data.focus.hasFocus)) { + return false; + } + break; + case NPCocoaEventDrawRect: + aResult->event.data.draw.context = nullptr; + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.x)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.y)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.width)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.height)) { + return false; + } + break; + case NPCocoaEventTextInput: + if (!ReadParam(aMsg, aIter, &aResult->event.data.text.text)) { + return false; + } + break; + default: + NS_NOTREACHED("Attempted to de-serialize unknown event type."); + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->contentsScaleFactor)) { + return false; + } + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(L"(NPCocoaEvent)"); + } +}; + +} // namespace IPC + +#endif // ifndef mozilla_dom_plugins_NPEventOSX_h diff --git a/dom/plugins/ipc/NPEventUnix.h b/dom/plugins/ipc/NPEventUnix.h new file mode 100644 index 000000000..4cc9a5456 --- /dev/null +++ b/dom/plugins/ipc/NPEventUnix.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */ +/* 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 mozilla_dom_plugins_NPEventUnix_h +#define mozilla_dom_plugins_NPEventUnix_h 1 + +#include "npapi.h" + +#ifdef MOZ_X11 +#include "mozilla/X11Util.h" +#endif + +namespace mozilla { + +namespace plugins { + +struct NPRemoteEvent { + NPEvent event; +}; + +} + +} + + +// +// XEvent is defined as a union of all more specific X*Events. +// Luckily, as of xorg 1.6.0 / X protocol 11 rev 0, the only pointer +// field contained in any of these specific X*Event structs is a +// |Display*|. So to simplify serializing these XEvents, we make the +// +// ********** XXX ASSUMPTION XXX ********** +// +// that the process to which the event is forwarded shares the same +// display as the process on which the event originated. +// +// With this simplification, serialization becomes a simple memcpy to +// the output stream. Deserialization starts as just a memcpy from +// the input stream, BUT we then have to write the correct |Display*| +// into the right field of each X*Event that contains one. +// + +namespace IPC { + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteEvent> // synonym for XEvent +{ + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + aMsg->WriteBytes(&aParam, sizeof(paramType)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + if (!aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType))) { + return false; + } + +#ifdef MOZ_X11 + SetXDisplay(aResult->event); +#endif + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + // TODO + aLog->append(L"(XEvent)"); + } + +#ifdef MOZ_X11 +private: + static void SetXDisplay(XEvent& ev) + { + Display* display = mozilla::DefaultXDisplay(); + if (ev.type >= KeyPress) { + ev.xany.display = display; + } + else { + // XXX assuming that this is an error event + // (type == 0? not clear from Xlib.h) + ev.xerror.display = display; + } + } +#endif +}; + +} // namespace IPC + + +#endif // ifndef mozilla_dom_plugins_NPEventX11_h diff --git a/dom/plugins/ipc/NPEventWindows.h b/dom/plugins/ipc/NPEventWindows.h new file mode 100644 index 000000000..faf93a601 --- /dev/null +++ b/dom/plugins/ipc/NPEventWindows.h @@ -0,0 +1,160 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */ +/* 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 mozilla_dom_plugins_NPEventWindows_h +#define mozilla_dom_plugins_NPEventWindows_h 1 + +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL (0x020E) +#endif + +#include "npapi.h" +namespace mozilla { + +namespace plugins { + +// We use an NPRemoteEvent struct so that we can store the extra data on +// the stack so that we don't need to worry about managing the memory. +struct NPRemoteEvent +{ + NPEvent event; + union { + RECT rect; + WINDOWPOS windowpos; + } lParamData; + double contentsScaleFactor; +}; + +} + +} + +namespace IPC { + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteEvent> +{ + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + // Make a non-const copy of aParam so that we can muck with + // its insides for tranport + paramType paramCopy; + + paramCopy.event = aParam.event; + + // We can't blindly ipc events because they may sometimes contain + // pointers to memory in the sending process. For example, the + // WM_IME_CONTROL with the IMC_GETCOMPOSITIONFONT message has lParam + // set to a pointer to a LOGFONT structure. + switch (paramCopy.event.event) { + case WM_WINDOWPOSCHANGED: + // The lParam parameter of WM_WINDOWPOSCHANGED holds a pointer to + // a WINDOWPOS structure that contains information about the + // window's new size and position + paramCopy.lParamData.windowpos = *(reinterpret_cast<WINDOWPOS*>(paramCopy.event.lParam)); + break; + case WM_PAINT: + // The lParam parameter of WM_PAINT holds a pointer to an RECT + // structure specifying the bounding box of the update area. + paramCopy.lParamData.rect = *(reinterpret_cast<RECT*>(paramCopy.event.lParam)); + break; + + // the white list of events that we will ipc to the client + case WM_CHAR: + case WM_SYSCHAR: + + case WM_KEYUP: + case WM_SYSKEYUP: + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + case WM_CONTEXTMENU: + + case WM_CUT: + case WM_COPY: + case WM_PASTE: + case WM_CLEAR: + case WM_UNDO: + + case WM_MOUSELEAVE: + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + + case WM_SETFOCUS: + case WM_KILLFOCUS: + + case WM_IME_STARTCOMPOSITION: + case WM_IME_COMPOSITION: + case WM_IME_ENDCOMPOSITION: + case WM_IME_CHAR: + case WM_IME_SETCONTEXT: + case WM_IME_COMPOSITIONFULL: + case WM_IME_KEYDOWN: + case WM_IME_KEYUP: + case WM_IME_SELECT: + case WM_INPUTLANGCHANGEREQUEST: + case WM_INPUTLANGCHANGE: + break; + + default: + // RegisterWindowMessage events should be passed. + if (paramCopy.event.event >= 0xC000) + break; + + // FIXME/bug 567465: temporarily work around unhandled + // events by forwarding a "dummy event". The eventual + // fix will be to stop trying to send these events + // entirely. + paramCopy.event.event = WM_NULL; + break; + } + + aMsg->WriteBytes(¶mCopy, sizeof(paramType)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + if (!aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType))) { + return false; + } + + if (aResult->event.event == WM_PAINT) { + // restore the lParam to point at the RECT + aResult->event.lParam = reinterpret_cast<LPARAM>(&aResult->lParamData.rect); + } else if (aResult->event.event == WM_WINDOWPOSCHANGED) { + // restore the lParam to point at the WINDOWPOS + aResult->event.lParam = reinterpret_cast<LPARAM>(&aResult->lParamData.windowpos); + } + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(L"(WINEvent)"); + } + +}; + +} // namespace IPC + +#endif // ifndef mozilla_dom_plugins_NPEventWindows_h diff --git a/dom/plugins/ipc/PBrowserStream.ipdl b/dom/plugins/ipc/PBrowserStream.ipdl new file mode 100644 index 000000000..dbd238750 --- /dev/null +++ b/dom/plugins/ipc/PBrowserStream.ipdl @@ -0,0 +1,69 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 protocol PPluginInstance; + + +using mozilla::plugins::Buffer from "mozilla/plugins/PluginMessageUtils.h"; +using mozilla::plugins::IPCByteRanges from "mozilla/plugins/PluginMessageUtils.h"; + +using NPError from "npapi.h"; +using NPReason from "npapi.h"; + +namespace mozilla { +namespace plugins { + +/** + * NPBrowserStream represents a NPStream sent from the browser to the plugin. + */ + +intr protocol PBrowserStream +{ + manager PPluginInstance; + +child: + async Write(int32_t offset, uint32_t newlength, + Buffer data); + async NPP_StreamAsFile(nsCString fname); + + /** + * NPP_DestroyStream may race with other messages: the child acknowledges + * the message with StreamDestroyed before this actor is deleted. + */ + async NPP_DestroyStream(NPReason reason); + async __delete__(); + +parent: + async AsyncNPP_NewStreamResult(NPError rv, uint16_t stype); + intr NPN_RequestRead(IPCByteRanges ranges) + returns (NPError result); + async NPN_DestroyStream(NPReason reason); + async StreamDestroyed(); + +/* + TODO: turn on state machine. + + // need configurable start state: if the constructor + // returns an error in result, start state should + // be DELETING. +start state ALIVE: + send Write goto ALIVE; + call NPP_StreamAsFile goto ALIVE; + send NPP_DestroyStream goto ALIVE; + answer NPN_RequestRead goto ALIVE; + recv NPN_DestroyStream goto DYING; + +state DYING: + answer NPN_RequestRead goto DYING; + recv NPN_DestroyStream goto DYING; + recv StreamDestroyed goto DELETING; + +state DELETING: + send __delete__; +*/ +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl b/dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl new file mode 100644 index 000000000..cc8ff558e --- /dev/null +++ b/dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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 protocol PPluginInstance; + +namespace mozilla { +namespace plugins { + +/** + * This protocol exists to allow us to correctly destroy background + * surfaces. The browser owns the surfaces, but shares a "reference" + * with the plugin. The browser needs to notify the plugin when the + * background is going to be destroyed, but it can't rely on the + * plugin to destroy it because the plugin may crash at any time. So + * the plugin instance relinquishes destruction of the its old + * background to actors of this protocol, which can deal with crashy + * corner cases more easily than the instance. + */ +protocol PPluginBackgroundDestroyer { + manager PPluginInstance; + + // The ctor message for this protocol serves double-duty as + // notification that that the background is stale. + +parent: + async __delete__(); + +state DESTROYING: + recv __delete__; +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginInstance.ipdl b/dom/plugins/ipc/PPluginInstance.ipdl new file mode 100644 index 000000000..525e0a145 --- /dev/null +++ b/dom/plugins/ipc/PPluginInstance.ipdl @@ -0,0 +1,329 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 protocol PPluginBackgroundDestroyer; +include protocol PPluginModule; +include protocol PPluginScriptableObject; +include protocol PBrowserStream; +include protocol PPluginStream; +include protocol PStreamNotify; +include protocol PPluginSurface; + +include "mozilla/GfxMessageUtils.h"; + +using NPError from "npapi.h"; +using struct mozilla::plugins::NPRemoteWindow from "mozilla/plugins/PluginMessageUtils.h"; +using struct mozilla::plugins::NPRemoteEvent from "mozilla/plugins/PluginMessageUtils.h"; +using NPRect from "npapi.h"; +using NPNURLVariable from "npapi.h"; +using NPCoordinateSpace from "npapi.h"; +using NPNVariable from "npapi.h"; +using mozilla::plugins::NativeWindowHandle from "mozilla/plugins/PluginMessageUtils.h"; +using gfxSurfaceType from "gfxTypes.h"; +using mozilla::gfx::IntSize from "mozilla/gfx/2D.h"; +using mozilla::gfx::IntRect from "mozilla/gfx/2D.h"; +using struct mozilla::null_t from "ipc/IPCMessageUtils.h"; +using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h"; +using mozilla::plugins::WindowsSharedMemoryHandle from "mozilla/plugins/PluginMessageUtils.h"; +using mozilla::layers::SurfaceDescriptorX11 from "gfxipc/ShadowLayerUtils.h"; +using nsIntRect from "nsRect.h"; +using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h"; +using struct DxgiAdapterDesc from "mozilla/D3DMessageUtils.h"; +using struct mozilla::widget::CandidateWindowPosition from "ipc/nsGUIEventIPC.h"; +using class mozilla::NativeEventData from "ipc/nsGUIEventIPC.h"; + +namespace mozilla { +namespace plugins { + +struct IOSurfaceDescriptor { + uint32_t surfaceId; + double contentsScaleFactor; +}; + +union SurfaceDescriptor { + Shmem; + SurfaceDescriptorX11; + PPluginSurface; // used on Windows + IOSurfaceDescriptor; // used on OSX 10.5+ + // Descriptor can be null here in case + // 1) of first Show call (prevSurface is null) + // 2) when child is going to destroy + // and it just want to grab prevSurface + // back without giving new surface + null_t; +}; + +union OptionalShmem { + Shmem; + null_t; +}; + +intr protocol PPluginInstance +{ + manager PPluginModule; + + manages PPluginBackgroundDestroyer; + manages PPluginScriptableObject; + manages PBrowserStream; + manages PPluginStream; + manages PStreamNotify; + manages PPluginSurface; + +child: + intr __delete__(); + + // This is only used on Windows and, for windowed plugins, must be called + // before the first call to NPP_SetWindow. + intr CreateChildPluginWindow() + returns (NativeWindowHandle childPluginWindow); + + // This is only used on Windows and, for windowless plugins. + async CreateChildPopupSurrogate(NativeWindowHandle netscapeWindow); + + intr NPP_SetWindow(NPRemoteWindow window); + + intr NPP_GetValue_NPPVpluginWantsAllNetworkStreams() + returns (bool value, NPError result); + + // this message is not used on non-X platforms + intr NPP_GetValue_NPPVpluginNeedsXEmbed() + returns (bool value, NPError result); + + intr NPP_GetValue_NPPVpluginScriptableNPObject() + returns (nullable PPluginScriptableObject value, NPError result); + + intr NPP_SetValue_NPNVprivateModeBool(bool value) returns (NPError result); + intr NPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId() + returns (nsCString plug_id, NPError result); + + intr NPP_SetValue_NPNVCSSZoomFactor(double value) returns (NPError result); + + intr NPP_SetValue_NPNVmuteAudioBool(bool muted) returns (NPError result); + + intr NPP_HandleEvent(NPRemoteEvent event) + returns (int16_t handled); + // special cases where we need to a shared memory buffer + intr NPP_HandleEvent_Shmem(NPRemoteEvent event, Shmem buffer) + returns (int16_t handled, Shmem rtnbuffer); + // special cases where we need an iosurface + intr NPP_HandleEvent_IOSurface(NPRemoteEvent event, uint32_t surfaceid) + returns (int16_t handled); + // special cases of HandleEvent to make mediating races simpler + intr Paint(NPRemoteEvent event) + returns (int16_t handled); + // this is only used on windows to forward WM_WINDOWPOSCHANGE + async WindowPosChanged(NPRemoteEvent event); + // used on OS X to tell the child the contents scale factor + // of its parent has changed + async ContentsScaleFactorChanged(double aContentsScaleFactor); + + // ********************** Async plugins rendering + // see https://wiki.mozilla.org/Gecko:AsyncPluginPainting + // ********************** + + // Async version of SetWindow call + // @param surfaceType - gfxASurface::gfxSurfaceType + // plugin child must create offscreen buffer + // with type equals to surfaceType + async AsyncSetWindow(gfxSurfaceType surfaceType, NPRemoteWindow window); + + // There is now an opaque background behind this instance (or the + // background was updated). The changed area is |rect|. The + // browser owns the background surface, and it's read-only from + // within the plugin process. |background| is either null_t to + // refer to the existing background or a fresh descriptor. + async UpdateBackground(SurfaceDescriptor background, nsIntRect rect); + + async NPP_DidComposite(); + + intr NPP_Destroy() + returns (NPError rv); + + // HandledWindowedPluginKeyEvent() is always called after posting a native + // key event with OnWindowedPluginKeyEvent(). + // + // @param aKeyEventData The key event which was posted to the parent + // process. + // @param aIsConsumed true if aKeyEventData is consumed in the + // parent process. Otherwise, false. + async HandledWindowedPluginKeyEvent(NativeEventData aKeyEventData, + bool aIsConsumed); + +parent: + intr NPN_GetValue_NPNVWindowNPObject() + returns (nullable PPluginScriptableObject value, NPError result); + intr NPN_GetValue_NPNVPluginElementNPObject() + returns (nullable PPluginScriptableObject value, NPError result); + intr NPN_GetValue_NPNVprivateModeBool() + returns (bool value, NPError result); + intr NPN_GetValue_NPNVnetscapeWindow() + returns (NativeWindowHandle value, NPError result); + intr NPN_GetValue_NPNVdocumentOrigin() + returns (nsCString value, NPError result); + intr NPN_GetValue_DrawingModelSupport(NPNVariable model) + returns (bool value); + intr NPN_GetValue_SupportsAsyncBitmapSurface() + returns (bool value); + intr NPN_GetValue_SupportsAsyncDXGISurface() + returns (bool value); + intr NPN_GetValue_PreferredDXGIAdapter() + returns (DxgiAdapterDesc desc); + + intr NPN_SetValue_NPPVpluginWindow(bool windowed) + returns (NPError result); + intr NPN_SetValue_NPPVpluginTransparent(bool transparent) + returns (NPError result); + intr NPN_SetValue_NPPVpluginUsesDOMForCursor(bool useDOMForCursor) + returns (NPError result); + intr NPN_SetValue_NPPVpluginDrawingModel(int drawingModel) + returns (NPError result); + intr NPN_SetValue_NPPVpluginEventModel(int eventModel) + returns (NPError result); + intr NPN_SetValue_NPPVpluginIsPlayingAudio(bool isAudioPlaying) + returns (NPError result); + + intr NPN_GetURL(nsCString url, nsCString target) + returns (NPError result); + intr NPN_PostURL(nsCString url, nsCString target, nsCString buffer, bool file) + returns (NPError result); + + /** + * Covers both NPN_GetURLNotify and NPN_PostURLNotify. + * @TODO This would be more readable as an overloaded method, + * but IPDL doesn't allow that for constructors. + */ + intr PStreamNotify(nsCString url, nsCString target, bool post, + nsCString buffer, bool file) + returns (NPError result); + + async NPN_InvalidateRect(NPRect rect); + + // Clear the current plugin image. + sync RevokeCurrentDirectSurface(); + + // Create a new DXGI shared surface with the given format and size. The + // returned handle, on success, can be opened as an ID3D10Texture2D or + // ID3D11Texture2D on a corresponding device. + sync InitDXGISurface(SurfaceFormat format, IntSize size) + returns (WindowsHandle handle, NPError result); + + // Destroy a surface previously allocated with InitDXGISurface(). + sync FinalizeDXGISurface(WindowsHandle handle); + + // Set the current plugin image to the bitmap in the given shmem buffer. The + // format must be B8G8R8A8 or B8G8R8X8. + sync ShowDirectBitmap(Shmem buffer, + SurfaceFormat format, + uint32_t stride, + IntSize size, + IntRect dirty); + + // Set the current plugin image to the DXGI surface in |handle|. + sync ShowDirectDXGISurface(WindowsHandle handle, + IntRect dirty); + + // Give |newSurface|, containing this instance's updated pixels, to + // the browser for compositing. When this method returns, any surface + // previously passed to Show may be destroyed. + // + // @param rect - actually updated rectangle, comparing to prevSurface content + // could be used for partial render of layer to topLevel context + // @param newSurface - remotable surface + // @param prevSurface - if the previous surface was shared-memory, returns + // the shmem for reuse + sync Show(NPRect updatedRect, SurfaceDescriptor newSurface) + returns (SurfaceDescriptor prevSurface); + + async PPluginSurface(WindowsSharedMemoryHandle handle, + IntSize size, + bool transparent); + + intr NPN_PushPopupsEnabledState(bool aState); + + intr NPN_PopPopupsEnabledState(); + + intr NPN_GetValueForURL(NPNURLVariable variable, nsCString url) + returns (nsCString value, NPError result); + + intr NPN_SetValueForURL(NPNURLVariable variable, nsCString url, + nsCString value) + returns (NPError result); + + intr NPN_GetAuthenticationInfo(nsCString protocol_, nsCString host, + int32_t port, nsCString scheme, + nsCString realm) + returns (nsCString username, nsCString password, NPError result); + + intr NPN_ConvertPoint(double sourceX, bool ignoreDestX, double sourceY, bool ignoreDestY, NPCoordinateSpace sourceSpace, + NPCoordinateSpace destSpace) + returns (double destX, double destY, bool result); + + async RedrawPlugin(); + + // Send notification that a plugin tried to negotiate Carbon NPAPI so that + // users can be notified that restarting the browser in i386 mode may allow + // them to use the plugin. + sync NegotiatedCarbon(); + + // Notifies the parent of its NPP_New result code. + async AsyncNPP_NewResult(NPError aResult); + + // Sends a native window to be adopted by the native window that would be + // returned by NPN_GetValue_NPNVnetscapeWindow. Only used on Windows. + async SetNetscapeWindowAsParent(NativeWindowHandle childWindow); + + sync GetCompositionString(uint32_t aType) + returns (uint8_t[] aDist, int32_t aLength); + // Set candidate window position. + // + // @param aPosition position information of candidate window + async SetCandidateWindow(CandidateWindowPosition aPosition); + async RequestCommitOrCancel(bool aCommitted); + + // Notifies the parent process of a plugin instance receiving key event + // directly. + // + // @param aKeyEventData The native key event which will be sent to + // plugin from native event handler. + async OnWindowedPluginKeyEvent(NativeEventData aKeyEventData); + +both: + async PPluginScriptableObject(); + +child: + /* NPP_NewStream */ + async PBrowserStream(nsCString url, + uint32_t length, + uint32_t lastmodified, + nullable PStreamNotify notifyData, + nsCString headers); + + // Implements the legacy (synchronous) version of NPP_NewStream for when + // async plugin init is preffed off. + intr NPP_NewStream(PBrowserStream actor, nsCString mimeType, bool seekable) + returns (NPError rv, + uint16_t stype); + + // Implements the async plugin init version of NPP_NewStream. + async AsyncNPP_NewStream(PBrowserStream actor, nsCString mimeType, bool seekable); + +parent: + /* NPN_NewStream */ + intr PPluginStream(nsCString mimeType, + nsCString target) + returns (NPError result); + +parent: + intr PluginFocusChange(bool gotFocus); + +child: + intr SetPluginFocus(); + intr UpdateWindow(); + + async PPluginBackgroundDestroyer(); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginModule.ipdl b/dom/plugins/ipc/PPluginModule.ipdl new file mode 100644 index 000000000..ecde41b63 --- /dev/null +++ b/dom/plugins/ipc/PPluginModule.ipdl @@ -0,0 +1,171 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 protocol PPluginInstance; +include protocol PPluginScriptableObject; +include protocol PCrashReporter; +include protocol PContent; +include ProfilerTypes; + +using NPError from "npapi.h"; +using NPNVariable from "npapi.h"; +using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h"; +using class mac_plugin_interposing::NSCursorInfo from "mozilla/plugins/PluginMessageUtils.h"; +using struct nsID from "nsID.h"; +using struct mozilla::plugins::NPAudioDeviceChangeDetailsIPC from "mozilla/plugins/PluginMessageUtils.h"; + +namespace mozilla { +namespace plugins { + +struct PluginSettings +{ + // These settings correspond to NPNVariable. They are fetched from + // mozilla::plugins::parent::_getvalue. + bool javascriptEnabled; + bool asdEnabled; + bool isOffline; + bool supportsXembed; + bool supportsWindowless; + + // These settings come from elsewhere. + nsCString userAgent; + bool nativeCursorsSupported; +}; + +intr protocol PPluginModule +{ + bridges PContent, PPluginModule; + + manages PPluginInstance; + manages PCrashReporter; + +both: + // Window-specific message which instructs the interrupt mechanism to enter + // a nested event loop for the current interrupt call. + async ProcessNativeEventsInInterruptCall(); + +child: + async DisableFlashProtectedMode(); + + // Sync query to check if a Flash library indicates it + // supports async rendering mode. + intr ModuleSupportsAsyncRender() + returns (bool result); + + // Forces the child process to update its plugin function table. + intr NP_GetEntryPoints() + returns (NPError rv); + + intr NP_Initialize(PluginSettings settings) + returns (NPError rv); + + async AsyncNP_Initialize(PluginSettings settings); + + async PPluginInstance(nsCString aMimeType, + uint16_t aMode, + nsCString[] aNames, + nsCString[] aValues); + + // Implements the synchronous version of NPP_New for when async plugin init + // is preffed off. + intr SyncNPP_New(PPluginInstance aActor) + returns (NPError rv); + + // Implements the async plugin init version of NPP_New. + async AsyncNPP_New(PPluginInstance aActor); + + intr NP_Shutdown() + returns (NPError rv); + + intr OptionalFunctionsSupported() + returns (bool aURLRedirectNotify, bool aClearSiteData, + bool aGetSitesWithData); + + async NPP_ClearSiteData(nsCString site, uint64_t flags, uint64_t maxAge, uint64_t aCallbackId); + + async NPP_GetSitesWithData(uint64_t aCallbackId); + + // Windows specific message to set up an audio session in the plugin process + async SetAudioSessionData(nsID aID, + nsString aDisplayName, + nsString aIconPath); + + async SetParentHangTimeout(uint32_t seconds); + + intr PCrashReporter() + returns (NativeThreadId tid, uint32_t processType); + + /** + * Control the Gecko Profiler in the plugin process. + */ + async StartProfiler(ProfilerInitParams params); + async StopProfiler(); + + async GatherProfile(); + + async SettingChanged(PluginSettings settings); + + async NPP_SetValue_NPNVaudioDeviceChangeDetails(NPAudioDeviceChangeDetailsIPC changeDetails); + +parent: + async NP_InitializeResult(NPError aError); + + /** + * This message is only used on X11 platforms. + * + * Send a dup of the plugin process's X socket to the parent + * process. In theory, this scheme keeps the plugin's X resources + * around until after both the plugin process shuts down *and* the + * parent process closes the dup fd. This is used to prevent the + * parent process from crashing on X errors if, e.g., the plugin + * crashes *just before* a repaint and the parent process tries to + * use the newly-invalid surface. + */ + async BackUpXResources(FileDescriptor aXSocketFd); + + // Wake up and process a few native events. Periodically called by + // Gtk-specific code upon detecting that the plugin process has + // entered a nested event loop. If the browser doesn't process + // native events, then "livelock" and some other glitches can occur. + intr ProcessSomeEvents(); + + // OS X Specific calls to manage the plugin's window + // when interposing system calls. + async PluginShowWindow(uint32_t aWindowId, bool aModal, + int32_t aX, int32_t aY, + size_t aWidth, size_t aHeight); + async PluginHideWindow(uint32_t aWindowId); + + // OS X Specific calls to allow the plugin to manage the cursor. + async SetCursor(NSCursorInfo cursorInfo); + async ShowCursor(bool show); + async PushCursor(NSCursorInfo cursorInfo); + async PopCursor(); + + sync NPN_SetException(nsCString message); + + async NPN_ReloadPlugins(bool aReloadPages); + + // Notifies the chrome process that a PluginModuleChild linked to a content + // process was destroyed. The chrome process may choose to asynchronously shut + // down the plugin process in response. + async NotifyContentModuleDestroyed(); + + async Profile(nsCString aProfile); + + // Answers to request about site data + async ReturnClearSiteData(NPError aRv, uint64_t aCallbackId); + + async ReturnSitesWithData(nsCString[] aSites, uint64_t aCallbackId); + + intr GetKeyState(int32_t aVirtKey) + returns (int16_t aState); + + intr NPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(bool shouldRegister) + returns (NPError result); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginScriptableObject.ipdl b/dom/plugins/ipc/PPluginScriptableObject.ipdl new file mode 100644 index 000000000..15d6cc148 --- /dev/null +++ b/dom/plugins/ipc/PPluginScriptableObject.ipdl @@ -0,0 +1,102 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 protocol PPluginInstance; +include PluginTypes; + +using struct mozilla::void_t from "ipc/IPCMessageUtils.h"; +using struct mozilla::null_t from "ipc/IPCMessageUtils.h"; + +namespace mozilla { +namespace plugins { + +union Variant { + void_t; + null_t; + bool; + int; + double; + nsCString; + nullable PPluginScriptableObject; +}; + +intr protocol PPluginScriptableObject +{ + manager PPluginInstance; + +both: + async __delete__(); + +parent: + intr NPN_Evaluate(nsCString aScript) + returns (Variant aResult, + bool aSuccess); + +child: + intr Invalidate(); + +both: + // NPClass methods + intr HasMethod(PluginIdentifier aId) + returns (bool aHasMethod); + + intr Invoke(PluginIdentifier aId, + Variant[] aArgs) + returns (Variant aResult, + bool aSuccess); + + intr InvokeDefault(Variant[] aArgs) + returns (Variant aResult, + bool aSuccess); + + intr HasProperty(PluginIdentifier aId) + returns (bool aHasProperty); + + intr SetProperty(PluginIdentifier aId, + Variant aValue) + returns (bool aSuccess); + + intr RemoveProperty(PluginIdentifier aId) + returns (bool aSuccess); + + intr Enumerate() + returns (PluginIdentifier[] aProperties, + bool aSuccess); + + intr Construct(Variant[] aArgs) + returns (Variant aResult, + bool aSuccess); + + // Objects are initially unprotected, and the Protect and Unprotect functions + // only affect protocol objects that represent NPObjects created in the same + // process (rather than protocol objects that are a proxy for an NPObject + // created in another process). Protocol objects representing local NPObjects + // are protected after an NPObject has been associated with the protocol + // object. Sending the protocol object as an argument to the other process + // temporarily protects the protocol object again for the duration of the call. + async Protect(); + async Unprotect(); + + /** + * GetProperty is slightly wonky due to the way we support NPObjects that have + * methods and properties with the same name. When child calls parent we + * simply return a property. When parent calls child, however, we need to do + * several checks at once and return all the results simultaneously. + */ +parent: + intr GetParentProperty(PluginIdentifier aId) + returns (Variant aResult, + bool aSuccess); + +child: + intr GetChildProperty(PluginIdentifier aId) + returns (bool aHasProperty, + bool aHasMethod, + Variant aResult, + bool aSuccess); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginStream.ipdl b/dom/plugins/ipc/PPluginStream.ipdl new file mode 100644 index 000000000..49ba00e79 --- /dev/null +++ b/dom/plugins/ipc/PPluginStream.ipdl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 protocol PPluginInstance; + + +using mozilla::plugins::Buffer from "mozilla/plugins/PluginMessageUtils.h"; +using NPError from "npapi.h"; +using NPReason from "npapi.h"; + +namespace mozilla { +namespace plugins { + +/** + * PPluginStream represents an NPStream sent from the plugin to the browser. + */ + +intr protocol PPluginStream +{ + manager PPluginInstance; + +parent: + intr NPN_Write(Buffer data) returns (int32_t written); + +both: + /** + * ~PPluginStream is for both NPN_DestroyStream and NPP_DestroyStream. + * @param artificial True when the stream is closed as a by-product of + * some other call (such as a failure in NPN_Write). + */ + intr __delete__(NPReason reason, bool artificial); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginSurface.ipdl b/dom/plugins/ipc/PPluginSurface.ipdl new file mode 100644 index 000000000..7be038c60 --- /dev/null +++ b/dom/plugins/ipc/PPluginSurface.ipdl @@ -0,0 +1,18 @@ +/* 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 protocol PPluginInstance; + +namespace mozilla { +namespace plugins { + +async protocol PPluginSurface { + manager PPluginInstance; + +parent: + async __delete__(); +}; + +} +} diff --git a/dom/plugins/ipc/PStreamNotify.ipdl b/dom/plugins/ipc/PStreamNotify.ipdl new file mode 100644 index 000000000..3e196acab --- /dev/null +++ b/dom/plugins/ipc/PStreamNotify.ipdl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 protocol PPluginInstance; + + +using NPReason from "npapi.h"; + +namespace mozilla { +namespace plugins { + +intr protocol PStreamNotify +{ + manager PPluginInstance; + +parent: + + /** + * Represents NPN_URLRedirectResponse + */ + async RedirectNotifyResponse(bool allow); + +child: + /** + * Represents NPP_URLRedirectNotify + */ + async RedirectNotify(nsCString url, int32_t status); + + /** + * Represents NPP_URLNotify + */ + async __delete__(NPReason reason); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginAsyncSurrogate.cpp b/dom/plugins/ipc/PluginAsyncSurrogate.cpp new file mode 100644 index 000000000..da07116cc --- /dev/null +++ b/dom/plugins/ipc/PluginAsyncSurrogate.cpp @@ -0,0 +1,998 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "PluginAsyncSurrogate.h" + +#include "base/message_loop.h" +#include "base/message_pump_default.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/plugins/PluginInstanceParent.h" +#include "mozilla/plugins/PluginModuleParent.h" +#include "mozilla/plugins/PluginScriptableObjectParent.h" +#include "mozilla/Telemetry.h" +#include "nsJSNPRuntime.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsPluginInstanceOwner.h" +#include "nsPluginStreamListenerPeer.h" +#include "npruntime.h" +#include "nsThreadUtils.h" +#include "PluginMessageUtils.h" + +namespace mozilla { +namespace plugins { + +AsyncNPObject::AsyncNPObject(PluginAsyncSurrogate* aSurrogate) + : NPObject() + , mSurrogate(aSurrogate) + , mRealObject(nullptr) +{ +} + +AsyncNPObject::~AsyncNPObject() +{ + if (mRealObject) { + --mRealObject->asyncWrapperCount; + parent::_releaseobject(mRealObject); + mRealObject = nullptr; + } +} + +NPObject* +AsyncNPObject::GetRealObject() +{ + if (mRealObject) { + return mRealObject; + } + PluginInstanceParent* instance = PluginInstanceParent::Cast(mSurrogate->GetNPP()); + if (!instance) { + return nullptr; + } + NPObject* realObject = nullptr; + NPError err = instance->NPP_GetValue(NPPVpluginScriptableNPObject, + &realObject); + if (err != NPERR_NO_ERROR) { + return nullptr; + } + if (realObject->_class != PluginScriptableObjectParent::GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + parent::_releaseobject(realObject); + return nullptr; + } + mRealObject = static_cast<ParentNPObject*>(realObject); + ++mRealObject->asyncWrapperCount; + return mRealObject; +} + +class MOZ_STACK_CLASS RecursionGuard +{ +public: + RecursionGuard() + : mIsRecursive(sHasEntered) + { + if (!mIsRecursive) { + sHasEntered = true; + } + } + + ~RecursionGuard() + { + if (!mIsRecursive) { + sHasEntered = false; + } + } + + inline bool + IsRecursive() + { + return mIsRecursive; + } + +private: + bool mIsRecursive; + static bool sHasEntered; +}; + +bool RecursionGuard::sHasEntered = false; + +PluginAsyncSurrogate::PluginAsyncSurrogate(PluginModuleParent* aParent) + : mParent(aParent) + , mMode(0) + , mWindow(nullptr) + , mAcceptCalls(false) + , mInstantiated(false) + , mAsyncSetWindow(false) + , mInitCancelled(false) + , mDestroyPending(false) + , mAsyncCallsInFlight(0) +{ + MOZ_ASSERT(aParent); +} + +PluginAsyncSurrogate::~PluginAsyncSurrogate() +{ +} + +bool +PluginAsyncSurrogate::Init(NPMIMEType aPluginType, NPP aInstance, uint16_t aMode, + int16_t aArgc, char* aArgn[], char* aArgv[]) +{ + mMimeType = aPluginType; + nsNPAPIPluginInstance* instance = + static_cast<nsNPAPIPluginInstance*>(aInstance->ndata); + MOZ_ASSERT(instance); + mInstance = instance; + mMode = aMode; + for (int i = 0; i < aArgc; ++i) { + mNames.AppendElement(NullableString(aArgn[i])); + mValues.AppendElement(NullableString(aArgv[i])); + } + return true; +} + +/* static */ bool +PluginAsyncSurrogate::Create(PluginModuleParent* aParent, NPMIMEType aPluginType, + NPP aInstance, uint16_t aMode, int16_t aArgc, + char* aArgn[], char* aArgv[]) +{ + RefPtr<PluginAsyncSurrogate> surrogate(new PluginAsyncSurrogate(aParent)); + if (!surrogate->Init(aPluginType, aInstance, aMode, aArgc, aArgn, aArgv)) { + return false; + } + PluginAsyncSurrogate* rawSurrogate = nullptr; + surrogate.forget(&rawSurrogate); + aInstance->pdata = static_cast<PluginDataResolver*>(rawSurrogate); + return true; +} + +/* static */ PluginAsyncSurrogate* +PluginAsyncSurrogate::Cast(NPP aInstance) +{ + MOZ_ASSERT(aInstance); + PluginDataResolver* resolver = + reinterpret_cast<PluginDataResolver*>(aInstance->pdata); + if (!resolver) { + return nullptr; + } + return resolver->GetAsyncSurrogate(); +} + +nsresult +PluginAsyncSurrogate::NPP_New(NPError* aError) +{ + if (!mInstance) { + return NS_ERROR_NULL_POINTER; + } + + nsresult rv = mParent->NPP_NewInternal(mMimeType.BeginWriting(), GetNPP(), + mMode, mNames, mValues, nullptr, + aError); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +void +PluginAsyncSurrogate::NP_GetEntryPoints(NPPluginFuncs* aFuncs) +{ + aFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + aFuncs->destroy = &NPP_Destroy; + aFuncs->getvalue = &NPP_GetValue; + aFuncs->setvalue = &NPP_SetValue; + aFuncs->newstream = &NPP_NewStream; + aFuncs->setwindow = &NPP_SetWindow; + aFuncs->writeready = &NPP_WriteReady; + aFuncs->event = &NPP_HandleEvent; + aFuncs->destroystream = &NPP_DestroyStream; + // We need to set these so that content code doesn't make assumptions + // about these operations not being supported + aFuncs->write = &PluginModuleParent::NPP_Write; + aFuncs->asfile = &PluginModuleParent::NPP_StreamAsFile; +} + +/* static */ void +PluginAsyncSurrogate::NotifyDestroyPending(NPP aInstance) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + if (!surrogate) { + return; + } + surrogate->NotifyDestroyPending(); +} + +NPP +PluginAsyncSurrogate::GetNPP() +{ + MOZ_ASSERT(mInstance); + NPP npp; + DebugOnly<nsresult> rv = mInstance->GetNPP(&npp); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return npp; +} + +void +PluginAsyncSurrogate::NotifyDestroyPending() +{ + mDestroyPending = true; + nsJSNPRuntime::OnPluginDestroyPending(GetNPP()); +} + +NPError +PluginAsyncSurrogate::NPP_Destroy(NPSavedData** aSave) +{ + NotifyDestroyPending(); + if (!WaitForInit()) { + return NPERR_GENERIC_ERROR; + } + return PluginModuleParent::NPP_Destroy(GetNPP(), aSave); +} + +NPError +PluginAsyncSurrogate::NPP_GetValue(NPPVariable aVariable, void* aRetval) +{ + if (aVariable != NPPVpluginScriptableNPObject) { + if (!WaitForInit()) { + return NPERR_GENERIC_ERROR; + } + + PluginInstanceParent* instance = PluginInstanceParent::Cast(GetNPP()); + MOZ_ASSERT(instance); + return instance->NPP_GetValue(aVariable, aRetval); + } + + NPObject* npobject = parent::_createobject(GetNPP(), + const_cast<NPClass*>(GetClass())); + MOZ_ASSERT(npobject); + MOZ_ASSERT(npobject->_class == GetClass()); + MOZ_ASSERT(npobject->referenceCount == 1); + *(NPObject**)aRetval = npobject; + return npobject ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR; +} + +NPError +PluginAsyncSurrogate::NPP_SetValue(NPNVariable aVariable, void* aValue) +{ + if (!WaitForInit()) { + return NPERR_GENERIC_ERROR; + } + return PluginModuleParent::NPP_SetValue(GetNPP(), aVariable, aValue); +} + +NPError +PluginAsyncSurrogate::NPP_NewStream(NPMIMEType aType, NPStream* aStream, + NPBool aSeekable, uint16_t* aStype) +{ + mPendingNewStreamCalls.AppendElement(PendingNewStreamCall(aType, aStream, + aSeekable)); + if (aStype) { + *aStype = nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN; + } + return NPERR_NO_ERROR; +} + +NPError +PluginAsyncSurrogate::NPP_SetWindow(NPWindow* aWindow) +{ + mWindow = aWindow; + mAsyncSetWindow = false; + return NPERR_NO_ERROR; +} + +nsresult +PluginAsyncSurrogate::AsyncSetWindow(NPWindow* aWindow) +{ + mWindow = aWindow; + mAsyncSetWindow = true; + return NS_OK; +} + +void +PluginAsyncSurrogate::NPP_Print(NPPrint* aPrintInfo) +{ + // Do nothing, we've got nothing to print right now +} + +int16_t +PluginAsyncSurrogate::NPP_HandleEvent(void* event) +{ + // Drop the event -- the plugin isn't around to handle it + return false; +} + +int32_t +PluginAsyncSurrogate::NPP_WriteReady(NPStream* aStream) +{ + // We'll tell the browser to retry in a bit. Eventually NPP_WriteReady + // will resolve to the plugin's NPP_WriteReady and this should all just work. + return 0; +} + +NPError +PluginAsyncSurrogate::NPP_DestroyStream(NPStream* aStream, NPReason aReason) +{ + for (uint32_t idx = 0, len = mPendingNewStreamCalls.Length(); idx < len; ++idx) { + PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[idx]; + if (curPendingCall.mStream == aStream) { + mPendingNewStreamCalls.RemoveElementAt(idx); + break; + } + } + return NPERR_NO_ERROR; +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_Destroy(NPP aInstance, NPSavedData** aSave) +{ + PluginAsyncSurrogate* rawSurrogate = Cast(aInstance); + MOZ_ASSERT(rawSurrogate); + PluginModuleParent* module = rawSurrogate->GetParent(); + if (module && !module->IsInitialized()) { + // Take ownership of pdata's surrogate since we're going to release it + RefPtr<PluginAsyncSurrogate> surrogate(dont_AddRef(rawSurrogate)); + aInstance->pdata = nullptr; + // We haven't actually called NPP_New yet, so we should remove the + // surrogate for this instance. + bool removeOk = module->RemovePendingSurrogate(surrogate); + MOZ_ASSERT(removeOk); + if (!removeOk) { + return NPERR_GENERIC_ERROR; + } + surrogate->mInitCancelled = true; + return NPERR_NO_ERROR; + } + return rawSurrogate->NPP_Destroy(aSave); +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_GetValue(NPP aInstance, NPPVariable aVariable, + void* aRetval) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_GetValue(aVariable, aRetval); +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_SetValue(NPP aInstance, NPNVariable aVariable, + void* aValue) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_SetValue(aVariable, aValue); +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_NewStream(NPP aInstance, NPMIMEType aType, + NPStream* aStream, NPBool aSeekable, + uint16_t* aStype) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_NewStream(aType, aStream, aSeekable, aStype); +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_SetWindow(NPP aInstance, NPWindow* aWindow) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_SetWindow(aWindow); +} + +/* static */ void +PluginAsyncSurrogate::NPP_Print(NPP aInstance, NPPrint* aPrintInfo) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + surrogate->NPP_Print(aPrintInfo); +} + +/* static */ int16_t +PluginAsyncSurrogate::NPP_HandleEvent(NPP aInstance, void* aEvent) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_HandleEvent(aEvent); +} + +/* static */ int32_t +PluginAsyncSurrogate::NPP_WriteReady(NPP aInstance, NPStream* aStream) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_WriteReady(aStream); +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_DestroyStream(NPP aInstance, + NPStream* aStream, + NPReason aReason) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_DestroyStream(aStream, aReason); +} + +PluginAsyncSurrogate::PendingNewStreamCall::PendingNewStreamCall( + NPMIMEType aType, NPStream* aStream, NPBool aSeekable) + : mType(NullableString(aType)) + , mStream(aStream) + , mSeekable(aSeekable) +{ +} + +/* static */ nsNPAPIPluginStreamListener* +PluginAsyncSurrogate::GetStreamListener(NPStream* aStream) +{ + nsNPAPIStreamWrapper* wrapper = + reinterpret_cast<nsNPAPIStreamWrapper*>(aStream->ndata); + if (!wrapper) { + return nullptr; + } + return wrapper->GetStreamListener(); +} + +void +PluginAsyncSurrogate::DestroyAsyncStream(NPStream* aStream) +{ + MOZ_ASSERT(aStream); + nsNPAPIPluginStreamListener* streamListener = GetStreamListener(aStream); + MOZ_ASSERT(streamListener); + // streamListener was suspended during async init. We must resume the stream + // request prior to calling _destroystream for cleanup to work correctly. + streamListener->ResumeRequest(); + if (!mInstance) { + return; + } + parent::_destroystream(GetNPP(), aStream, NPRES_DONE); +} + +/* static */ bool +PluginAsyncSurrogate::SetStreamType(NPStream* aStream, uint16_t aStreamType) +{ + nsNPAPIPluginStreamListener* streamListener = GetStreamListener(aStream); + if (!streamListener) { + return false; + } + return streamListener->SetStreamType(aStreamType); +} + +void +PluginAsyncSurrogate::OnInstanceCreated(PluginInstanceParent* aInstance) +{ + if (!mDestroyPending) { + // If NPP_Destroy has already been called then these streams have already + // been cleaned up on the browser side and are no longer valid. + for (uint32_t i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) { + PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i]; + uint16_t streamType = NP_NORMAL; + NPError curError = aInstance->NPP_NewStream( + const_cast<char*>(NullableStringGet(curPendingCall.mType)), + curPendingCall.mStream, curPendingCall.mSeekable, + &streamType); + if (curError != NPERR_NO_ERROR) { + // If we failed here then the send failed and we need to clean up + DestroyAsyncStream(curPendingCall.mStream); + } + } + } + mPendingNewStreamCalls.Clear(); + mInstantiated = true; +} + +/** + * During asynchronous initialization it might be necessary to wait for the + * plugin to complete its initialization. This typically occurs when the result + * of a plugin call depends on the plugin being fully instantiated. For example, + * if some JS calls into the plugin, the call must be executed synchronously to + * preserve correctness. + * + * This function works by pumping the plugin's IPC channel for events until + * initialization has completed. + */ +bool +PluginAsyncSurrogate::WaitForInit() +{ + if (mInitCancelled) { + return false; + } + if (mAcceptCalls) { + return true; + } + Telemetry::AutoTimer<Telemetry::BLOCKED_ON_PLUGINASYNCSURROGATE_WAITFORINIT_MS> + timer(mParent->GetHistogramKey()); + bool result = false; + MOZ_ASSERT(mParent); + if (mParent->IsChrome()) { + PluginProcessParent* process = static_cast<PluginModuleChromeParent*>(mParent)->Process(); + MOZ_ASSERT(process); + process->SetCallRunnableImmediately(true); + if (!process->WaitUntilConnected()) { + return false; + } + } + if (!mParent->WaitForIPCConnection()) { + return false; + } + if (!mParent->IsChrome()) { + // For e10s content processes, we need to spin the content channel until the + // protocol bridging has occurred. + dom::ContentChild* cp = dom::ContentChild::GetSingleton(); + mozilla::ipc::MessageChannel* contentChannel = cp->GetIPCChannel(); + MOZ_ASSERT(contentChannel); + while (!mParent->mNPInitialized) { + if (mParent->mShutdown) { + // Since we are pumping the message channel for events, it may be + // possible for module initialization to fail during this loop. We must + // return false if this happens or else we'll be permanently stuck. + return false; + } + result = contentChannel->WaitForIncomingMessage(); + if (!result) { + return result; + } + } + } + mozilla::ipc::MessageChannel* channel = mParent->GetIPCChannel(); + MOZ_ASSERT(channel); + while (!mAcceptCalls) { + if (mInitCancelled) { + // Since we are pumping the message channel for events, it may be + // possible for plugin instantiation to fail during this loop. We must + // return false if this happens or else we'll be permanently stuck. + return false; + } + result = channel->WaitForIncomingMessage(); + if (!result) { + break; + } + } + return result; +} + +void +PluginAsyncSurrogate::AsyncCallDeparting() +{ + ++mAsyncCallsInFlight; + if (!mPluginDestructionGuard) { + mPluginDestructionGuard = MakeUnique<PluginDestructionGuard>(this); + } +} + +void +PluginAsyncSurrogate::AsyncCallArriving() +{ + MOZ_ASSERT(mAsyncCallsInFlight > 0); + if (--mAsyncCallsInFlight == 0) { + mPluginDestructionGuard.reset(nullptr); + } +} + +void +PluginAsyncSurrogate::NotifyAsyncInitFailed() +{ + if (!mDestroyPending) { + // Clean up any pending NewStream requests + for (uint32_t i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) { + PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i]; + DestroyAsyncStream(curPendingCall.mStream); + } + } + mPendingNewStreamCalls.Clear(); + + // Make sure that any WaitForInit calls on this surrogate will fail, or else + // we'll be perma-blocked + mInitCancelled = true; + + if (!mInstance) { + return; + } + nsPluginInstanceOwner* owner = mInstance->GetOwner(); + if (owner) { + owner->NotifyHostAsyncInitFailed(); + } +} + +// static +NPObject* +PluginAsyncSurrogate::ScriptableAllocate(NPP aInstance, NPClass* aClass) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aClass != GetClass()) { + NS_ERROR("Huh?! Wrong class!"); + return nullptr; + } + + return new AsyncNPObject(Cast(aInstance)); +} + +// static +void +PluginAsyncSurrogate::ScriptableInvalidate(NPObject* aObject) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + if (!object->mSurrogate->WaitForInit()) { + return; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return; + } + realObject->_class->invalidate(realObject); +} + +// static +void +PluginAsyncSurrogate::ScriptableDeallocate(NPObject* aObject) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + delete object; +} + +// static +bool +PluginAsyncSurrogate::ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + RecursionGuard guard; + if (guard.IsRecursive()) { + return false; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + MOZ_ASSERT(object); + bool checkPluginObject = !object->mSurrogate->mInstantiated && + !object->mSurrogate->mAcceptCalls; + + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + bool result = realObject->_class->hasMethod(realObject, aName); + if (!result && checkPluginObject) { + // We may be calling into this object because properties in the WebIDL + // object hadn't been set yet. Now that we're further along in + // initialization, we should try again. + const NPNetscapeFuncs* npn = object->mSurrogate->mParent->GetNetscapeFuncs(); + NPObject* pluginObject = nullptr; + NPError nperror = npn->getvalue(object->mSurrogate->GetNPP(), + NPNVPluginElementNPObject, + (void*)&pluginObject); + if (nperror == NPERR_NO_ERROR) { + NPPAutoPusher nppPusher(object->mSurrogate->GetNPP()); + result = pluginObject->_class->hasMethod(pluginObject, aName); + npn->releaseobject(pluginObject); + NPUTF8* idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + } + } + return result; +} + +bool +PluginAsyncSurrogate::GetPropertyHelper(NPObject* aObject, NPIdentifier aName, + bool* aHasProperty, bool* aHasMethod, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + if (!aObject) { + return false; + } + + RecursionGuard guard; + if (guard.IsRecursive()) { + return false; + } + + if (!WaitForInit()) { + return false; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + if (realObject->_class != PluginScriptableObjectParent::GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + PluginScriptableObjectParent* actor = + static_cast<ParentNPObject*>(realObject)->parent; + if (!actor) { + return false; + } + bool success = actor->GetPropertyHelper(aName, aHasProperty, aHasMethod, aResult); + if (!success) { + const NPNetscapeFuncs* npn = mParent->GetNetscapeFuncs(); + NPObject* pluginObject = nullptr; + NPError nperror = npn->getvalue(GetNPP(), NPNVPluginElementNPObject, + (void*)&pluginObject); + if (nperror == NPERR_NO_ERROR) { + NPPAutoPusher nppPusher(GetNPP()); + bool hasProperty = nsJSObjWrapper::HasOwnProperty(pluginObject, aName); + NPUTF8* idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + bool hasMethod = false; + if (hasProperty) { + hasMethod = pluginObject->_class->hasMethod(pluginObject, aName); + success = pluginObject->_class->getProperty(pluginObject, aName, aResult); + idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + } + *aHasProperty = hasProperty; + *aHasMethod = hasMethod; + npn->releaseobject(pluginObject); + } + } + return success; +} + +// static +bool +PluginAsyncSurrogate::ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->invoke(realObject, aName, aArgs, aArgCount, aResult); +} + +// static +bool +PluginAsyncSurrogate::ScriptableInvokeDefault(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->invokeDefault(realObject, aArgs, aArgCount, aResult); +} + +// static +bool +PluginAsyncSurrogate::ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + RecursionGuard guard; + if (guard.IsRecursive()) { + return false; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + MOZ_ASSERT(object); + bool checkPluginObject = !object->mSurrogate->mInstantiated && + !object->mSurrogate->mAcceptCalls; + + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + bool result = realObject->_class->hasProperty(realObject, aName); + const NPNetscapeFuncs* npn = object->mSurrogate->mParent->GetNetscapeFuncs(); + NPUTF8* idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + if (!result && checkPluginObject) { + // We may be calling into this object because properties in the WebIDL + // object hadn't been set yet. Now that we're further along in + // initialization, we should try again. + NPObject* pluginObject = nullptr; + NPError nperror = npn->getvalue(object->mSurrogate->GetNPP(), + NPNVPluginElementNPObject, + (void*)&pluginObject); + if (nperror == NPERR_NO_ERROR) { + NPPAutoPusher nppPusher(object->mSurrogate->GetNPP()); + result = nsJSObjWrapper::HasOwnProperty(pluginObject, aName); + npn->releaseobject(pluginObject); + idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + } + } + return result; +} + +// static +bool +PluginAsyncSurrogate::ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + // See GetPropertyHelper below. + NS_NOTREACHED("Shouldn't ever call this directly!"); + return false; +} + +// static +bool +PluginAsyncSurrogate::ScriptableSetProperty(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aValue) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->setProperty(realObject, aName, aValue); +} + +// static +bool +PluginAsyncSurrogate::ScriptableRemoveProperty(NPObject* aObject, + NPIdentifier aName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->removeProperty(realObject, aName); +} + +// static +bool +PluginAsyncSurrogate::ScriptableEnumerate(NPObject* aObject, + NPIdentifier** aIdentifiers, + uint32_t* aCount) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->enumerate(realObject, aIdentifiers, aCount); +} + +// static +bool +PluginAsyncSurrogate::ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast<AsyncNPObject*>(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->construct(realObject, aArgs, aArgCount, aResult); +} + +const NPClass PluginAsyncSurrogate::sNPClass = { + NP_CLASS_STRUCT_VERSION, + PluginAsyncSurrogate::ScriptableAllocate, + PluginAsyncSurrogate::ScriptableDeallocate, + PluginAsyncSurrogate::ScriptableInvalidate, + PluginAsyncSurrogate::ScriptableHasMethod, + PluginAsyncSurrogate::ScriptableInvoke, + PluginAsyncSurrogate::ScriptableInvokeDefault, + PluginAsyncSurrogate::ScriptableHasProperty, + PluginAsyncSurrogate::ScriptableGetProperty, + PluginAsyncSurrogate::ScriptableSetProperty, + PluginAsyncSurrogate::ScriptableRemoveProperty, + PluginAsyncSurrogate::ScriptableEnumerate, + PluginAsyncSurrogate::ScriptableConstruct +}; + +PushSurrogateAcceptCalls::PushSurrogateAcceptCalls(PluginInstanceParent* aInstance) + : mSurrogate(nullptr) + , mPrevAcceptCallsState(false) +{ + MOZ_ASSERT(aInstance); + mSurrogate = aInstance->GetAsyncSurrogate(); + if (mSurrogate) { + mPrevAcceptCallsState = mSurrogate->SetAcceptingCalls(true); + } +} + +PushSurrogateAcceptCalls::~PushSurrogateAcceptCalls() +{ + if (mSurrogate) { + mSurrogate->SetAcceptingCalls(mPrevAcceptCallsState); + } +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginAsyncSurrogate.h b/dom/plugins/ipc/PluginAsyncSurrogate.h new file mode 100644 index 000000000..5b6315715 --- /dev/null +++ b/dom/plugins/ipc/PluginAsyncSurrogate.h @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 dom_plugins_ipc_PluginAsyncSurrogate_h +#define dom_plugins_ipc_PluginAsyncSurrogate_h + +#include "mozilla/UniquePtr.h" +#include "npapi.h" +#include "npfunctions.h" +#include "npruntime.h" +#include "nsISupportsImpl.h" +#include "nsPluginHost.h" +#include "nsString.h" +#include "nsTArray.h" +#include "PluginDataResolver.h" + +namespace mozilla { +namespace plugins { + +struct ParentNPObject; +class PluginInstanceParent; +class PluginModuleParent; + +class PluginAsyncSurrogate : public PluginDataResolver +{ +public: + NS_INLINE_DECL_REFCOUNTING(PluginAsyncSurrogate) + + bool Init(NPMIMEType aPluginType, NPP aInstance, uint16_t aMode, + int16_t aArgc, char* aArgn[], char* aArgv[]); + nsresult NPP_New(NPError* aError); + NPError NPP_Destroy(NPSavedData** aSave); + NPError NPP_GetValue(NPPVariable aVariable, void* aRetval); + NPError NPP_SetValue(NPNVariable aVariable, void* aValue); + NPError NPP_NewStream(NPMIMEType aType, NPStream* aStream, NPBool aSeekable, + uint16_t* aStype); + NPError NPP_SetWindow(NPWindow* aWindow); + nsresult AsyncSetWindow(NPWindow* aWindow); + void NPP_Print(NPPrint* aPrintInfo); + int16_t NPP_HandleEvent(void* aEvent); + int32_t NPP_WriteReady(NPStream* aStream); + NPError NPP_DestroyStream(NPStream* aStream, NPReason aReason); + void OnInstanceCreated(PluginInstanceParent* aInstance); + static bool Create(PluginModuleParent* aParent, NPMIMEType aPluginType, + NPP aInstance, uint16_t aMode, int16_t aArgc, + char* aArgn[], char* aArgv[]); + static const NPClass* GetClass() { return &sNPClass; } + static void NP_GetEntryPoints(NPPluginFuncs* aFuncs); + static PluginAsyncSurrogate* Cast(NPP aInstance); + static void NotifyDestroyPending(NPP aInstance); + void NotifyDestroyPending(); + + virtual PluginAsyncSurrogate* + GetAsyncSurrogate() { return this; } + + virtual PluginInstanceParent* + GetInstance() { return nullptr; } + + NPP GetNPP(); + + bool GetPropertyHelper(NPObject* aObject, NPIdentifier aName, + bool* aHasProperty, bool* aHasMethod, + NPVariant* aResult); + + PluginModuleParent* GetParent() { return mParent; } + + bool IsDestroyPending() const { return mDestroyPending; } + + bool SetAcceptingCalls(bool aAccept) + { + bool prevState = mAcceptCalls; + if (mInstantiated) { + aAccept = true; + } + mAcceptCalls = aAccept; + return prevState; + } + + void AsyncCallDeparting(); + void AsyncCallArriving(); + + void NotifyAsyncInitFailed(); + void DestroyAsyncStream(NPStream* aStream); + +private: + explicit PluginAsyncSurrogate(PluginModuleParent* aParent); + virtual ~PluginAsyncSurrogate(); + + bool WaitForInit(); + + static bool SetStreamType(NPStream* aStream, uint16_t aStreamType); + + static NPError NPP_Destroy(NPP aInstance, NPSavedData** aSave); + static NPError NPP_GetValue(NPP aInstance, NPPVariable aVariable, void* aRetval); + static NPError NPP_SetValue(NPP aInstance, NPNVariable aVariable, void* aValue); + static NPError NPP_NewStream(NPP aInstance, NPMIMEType aType, NPStream* aStream, + NPBool aSeekable, uint16_t* aStype); + static NPError NPP_SetWindow(NPP aInstance, NPWindow* aWindow); + static void NPP_Print(NPP aInstance, NPPrint* aPrintInfo); + static int16_t NPP_HandleEvent(NPP aInstance, void* aEvent); + static int32_t NPP_WriteReady(NPP aInstance, NPStream* aStream); + static NPError NPP_DestroyStream(NPP aInstance, NPStream* aStream, + NPReason aReason); + + static NPObject* ScriptableAllocate(NPP aInstance, NPClass* aClass); + static void ScriptableInvalidate(NPObject* aObject); + static void ScriptableDeallocate(NPObject* aObject); + static bool ScriptableHasMethod(NPObject* aObject, NPIdentifier aName); + static bool ScriptableInvoke(NPObject* aObject, NPIdentifier aName, + const NPVariant* aArgs, uint32_t aArgCount, + NPVariant* aResult); + static bool ScriptableInvokeDefault(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + static bool ScriptableHasProperty(NPObject* aObject, NPIdentifier aName); + static bool ScriptableGetProperty(NPObject* aObject, NPIdentifier aName, + NPVariant* aResult); + static bool ScriptableSetProperty(NPObject* aObject, NPIdentifier aName, + const NPVariant* aValue); + static bool ScriptableRemoveProperty(NPObject* aObject, NPIdentifier aName); + static bool ScriptableEnumerate(NPObject* aObject, NPIdentifier** aIdentifiers, + uint32_t* aCount); + static bool ScriptableConstruct(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + static nsNPAPIPluginStreamListener* GetStreamListener(NPStream* aStream); + +private: + struct PendingNewStreamCall + { + PendingNewStreamCall(NPMIMEType aType, NPStream* aStream, NPBool aSeekable); + ~PendingNewStreamCall() {} + nsCString mType; + NPStream* mStream; + NPBool mSeekable; + }; + +private: + PluginModuleParent* mParent; + // These values are used to construct the plugin instance + nsCString mMimeType; + mozilla::WeakPtr<nsNPAPIPluginInstance> mInstance; + uint16_t mMode; + InfallibleTArray<nsCString> mNames; + InfallibleTArray<nsCString> mValues; + // This is safe to store as a pointer because the spec says it will remain + // valid until destruction or a subsequent NPP_SetWindow call. + NPWindow* mWindow; + nsTArray<PendingNewStreamCall> mPendingNewStreamCalls; + UniquePtr<PluginDestructionGuard> mPluginDestructionGuard; + + bool mAcceptCalls; + bool mInstantiated; + bool mAsyncSetWindow; + bool mInitCancelled; + bool mDestroyPending; + int32_t mAsyncCallsInFlight; + + static const NPClass sNPClass; +}; + +struct AsyncNPObject : NPObject +{ + explicit AsyncNPObject(PluginAsyncSurrogate* aSurrogate); + ~AsyncNPObject(); + + NPObject* GetRealObject(); + + RefPtr<PluginAsyncSurrogate> mSurrogate; + ParentNPObject* mRealObject; +}; + +class MOZ_STACK_CLASS PushSurrogateAcceptCalls +{ +public: + explicit PushSurrogateAcceptCalls(PluginInstanceParent* aInstance); + ~PushSurrogateAcceptCalls(); + +private: + PluginAsyncSurrogate* mSurrogate; + bool mPrevAcceptCallsState; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_ipc_PluginAsyncSurrogate_h diff --git a/dom/plugins/ipc/PluginBackgroundDestroyer.cpp b/dom/plugins/ipc/PluginBackgroundDestroyer.cpp new file mode 100644 index 000000000..3f822d1a3 --- /dev/null +++ b/dom/plugins/ipc/PluginBackgroundDestroyer.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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 "PluginBackgroundDestroyer.h" +#include "gfxSharedImageSurface.h" + +using namespace mozilla; +using namespace plugins; + +PluginBackgroundDestroyerParent::PluginBackgroundDestroyerParent(gfxASurface* aDyingBackground) + : mDyingBackground(aDyingBackground) +{ +} + +PluginBackgroundDestroyerParent::~PluginBackgroundDestroyerParent() +{ +} + +void +PluginBackgroundDestroyerParent::ActorDestroy(ActorDestroyReason why) +{ + switch(why) { + case Deletion: + case AncestorDeletion: + if (gfxSharedImageSurface::IsSharedImage(mDyingBackground)) { + gfxSharedImageSurface* s = + static_cast<gfxSharedImageSurface*>(mDyingBackground.get()); + DeallocShmem(s->GetShmem()); + } + break; + default: + // We're shutting down or crashed, let automatic cleanup + // take care of our shmem, if we have one. + break; + } +} diff --git a/dom/plugins/ipc/PluginBackgroundDestroyer.h b/dom/plugins/ipc/PluginBackgroundDestroyer.h new file mode 100644 index 000000000..226fdff8a --- /dev/null +++ b/dom/plugins/ipc/PluginBackgroundDestroyer.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 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/. */ + +#ifndef dom_plugins_PluginBackgroundDestroyer +#define dom_plugins_PluginBackgroundDestroyer + +#include "mozilla/plugins/PPluginBackgroundDestroyerChild.h" +#include "mozilla/plugins/PPluginBackgroundDestroyerParent.h" + +#include "gfxSharedImageSurface.h" + +class gfxASurface; + +namespace mozilla { +namespace plugins { + +/** + * When instances of this class are destroyed, the old background goes + * along with them, completing the destruction process (whether or not + * the plugin stayed alive long enough to ack). + */ +class PluginBackgroundDestroyerParent : public PPluginBackgroundDestroyerParent { +public: + explicit PluginBackgroundDestroyerParent(gfxASurface* aDyingBackground); + + virtual ~PluginBackgroundDestroyerParent(); + +private: + virtual void ActorDestroy(ActorDestroyReason why) override; + + RefPtr<gfxASurface> mDyingBackground; +}; + +/** + * This class exists solely to instruct its instance to release its + * current background, a new one may be coming. + */ +class PluginBackgroundDestroyerChild : public PPluginBackgroundDestroyerChild { +public: + PluginBackgroundDestroyerChild() { } + virtual ~PluginBackgroundDestroyerChild() { } + +private: + // Implementing this for good hygiene. + virtual void ActorDestroy(ActorDestroyReason why) override + { } +}; + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_PluginBackgroundDestroyer diff --git a/dom/plugins/ipc/PluginBridge.h b/dom/plugins/ipc/PluginBridge.h new file mode 100644 index 000000000..f3cb56edc --- /dev/null +++ b/dom/plugins/ipc/PluginBridge.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 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 mozilla_plugins_PluginBridge_h +#define mozilla_plugins_PluginBridge_h + +#include "base/process.h" + +namespace mozilla { + +namespace dom { +class ContentParent; +} // namespace dom + +namespace plugins { + +bool +SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent, + bool aForceBridgeNow, nsresult* aResult, uint32_t* aRunID); + +nsresult +FindPluginsForContent(uint32_t aPluginEpoch, + nsTArray<PluginTag>* aPlugins, + uint32_t* aNewPluginEpoch); + +void +TakeFullMinidump(uint32_t aPluginId, + base::ProcessId aContentProcessId, + const nsAString& aBrowserDumpId, + nsString& aDumpId); + +void +TerminatePlugin(uint32_t aPluginId, + base::ProcessId aContentProcessId, + const nsCString& aMonitorDescription, + const nsAString& aDumpId); + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginBridge_h diff --git a/dom/plugins/ipc/PluginDataResolver.h b/dom/plugins/ipc/PluginDataResolver.h new file mode 100644 index 000000000..8371c4df7 --- /dev/null +++ b/dom/plugins/ipc/PluginDataResolver.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 dom_plugins_ipc_PluginDataResolver_h +#define dom_plugins_ipc_PluginDataResolver_h + +namespace mozilla { +namespace plugins { + +class PluginAsyncSurrogate; +class PluginInstanceParent; + +class PluginDataResolver +{ +public: + virtual PluginAsyncSurrogate* GetAsyncSurrogate() = 0; + virtual PluginInstanceParent* GetInstance() = 0; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_ipc_PluginDataResolver_h diff --git a/dom/plugins/ipc/PluginHangUIParent.cpp b/dom/plugins/ipc/PluginHangUIParent.cpp new file mode 100644 index 000000000..5114f2e9a --- /dev/null +++ b/dom/plugins/ipc/PluginHangUIParent.cpp @@ -0,0 +1,445 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "PluginHangUI.h" + +#include "PluginHangUIParent.h" + +#include "mozilla/Telemetry.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/plugins/PluginModuleParent.h" + +#include "nsContentUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsIWindowMediator.h" +#include "nsIWinTaskbar.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +#include "WidgetUtils.h" + +#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1" + +using base::ProcessHandle; + +using mozilla::widget::WidgetUtils; + +using std::string; +using std::vector; + +namespace { +class nsPluginHangUITelemetry : public mozilla::Runnable +{ +public: + nsPluginHangUITelemetry(int aResponseCode, int aDontAskCode, + uint32_t aResponseTimeMs, uint32_t aTimeoutMs) + : mResponseCode(aResponseCode), + mDontAskCode(aDontAskCode), + mResponseTimeMs(aResponseTimeMs), + mTimeoutMs(aTimeoutMs) + { + } + + NS_IMETHOD + Run() override + { + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::PLUGIN_HANG_UI_USER_RESPONSE, mResponseCode); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::PLUGIN_HANG_UI_DONT_ASK, mDontAskCode); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::PLUGIN_HANG_UI_RESPONSE_TIME, mResponseTimeMs); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::PLUGIN_HANG_TIME, mTimeoutMs + mResponseTimeMs); + return NS_OK; + } + +private: + int mResponseCode; + int mDontAskCode; + uint32_t mResponseTimeMs; + uint32_t mTimeoutMs; +}; +} // namespace + +namespace mozilla { +namespace plugins { + +PluginHangUIParent::PluginHangUIParent(PluginModuleChromeParent* aModule, + const int32_t aHangUITimeoutPref, + const int32_t aChildTimeoutPref) + : mMutex("mozilla::plugins::PluginHangUIParent::mMutex"), + mModule(aModule), + mTimeoutPrefMs(static_cast<uint32_t>(aHangUITimeoutPref) * 1000U), + mIPCTimeoutMs(static_cast<uint32_t>(aChildTimeoutPref) * 1000U), + mMainThreadMessageLoop(MessageLoop::current()), + mIsShowing(false), + mLastUserResponse(0), + mHangUIProcessHandle(nullptr), + mMainWindowHandle(nullptr), + mRegWait(nullptr), + mShowEvent(nullptr), + mShowTicks(0), + mResponseTicks(0) +{ +} + +PluginHangUIParent::~PluginHangUIParent() +{ + { // Scope for lock + MutexAutoLock lock(mMutex); + UnwatchHangUIChildProcess(true); + } + if (mShowEvent) { + ::CloseHandle(mShowEvent); + } + if (mHangUIProcessHandle) { + ::CloseHandle(mHangUIProcessHandle); + } +} + +bool +PluginHangUIParent::DontShowAgain() const +{ + return (mLastUserResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); +} + +bool +PluginHangUIParent::WasLastHangStopped() const +{ + return (mLastUserResponse & HANGUI_USER_RESPONSE_STOP); +} + +unsigned int +PluginHangUIParent::LastShowDurationMs() const +{ + // We only return something if there was a user response + if (!mLastUserResponse) { + return 0; + } + return static_cast<unsigned int>(mResponseTicks - mShowTicks); +} + +bool +PluginHangUIParent::Init(const nsString& aPluginName) +{ + if (mHangUIProcessHandle) { + return false; + } + + nsresult rv; + rv = mMiniShm.Init(this, ::IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIProperties> + directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + if (!directoryService) { + return false; + } + nsCOMPtr<nsIFile> greDir; + rv = directoryService->Get(NS_GRE_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + return false; + } + nsAutoString path; + greDir->GetPath(path); + + FilePath exePath(path.get()); + exePath = exePath.AppendASCII(MOZ_HANGUI_PROCESS_NAME); + CommandLine commandLine(exePath.value()); + + nsXPIDLString localizedStr; + const char16_t* formatParams[] = { aPluginName.get() }; + rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "PluginHangUIMessage", + formatParams, + localizedStr); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(localizedStr.get()); + + const char* keys[] = { "PluginHangUITitle", + "PluginHangUIWaitButton", + "PluginHangUIStopButton", + "DontAskAgain" }; + for (unsigned int i = 0; i < ArrayLength(keys); ++i) { + rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + keys[i], + localizedStr); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(localizedStr.get()); + } + + rv = GetHangUIOwnerWindowHandle(mMainWindowHandle); + if (NS_FAILED(rv)) { + return false; + } + nsAutoString hwndStr; + hwndStr.AppendPrintf("%p", mMainWindowHandle); + commandLine.AppendLooseValue(hwndStr.get()); + + ScopedHandle procHandle(::OpenProcess(SYNCHRONIZE, + TRUE, + GetCurrentProcessId())); + if (!procHandle.IsValid()) { + return false; + } + nsAutoString procHandleStr; + procHandleStr.AppendPrintf("%p", procHandle.Get()); + commandLine.AppendLooseValue(procHandleStr.get()); + + // On Win7+, pass the application user model to the child, so it can + // register with it. This insures windows created by the Hang UI + // properly group with the parent app on the Win7 taskbar. + nsCOMPtr<nsIWinTaskbar> taskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID); + if (taskbarInfo) { + bool isSupported = false; + taskbarInfo->GetAvailable(&isSupported); + nsAutoString appId; + if (isSupported && NS_SUCCEEDED(taskbarInfo->GetDefaultGroupId(appId))) { + commandLine.AppendLooseValue(appId.get()); + } else { + commandLine.AppendLooseValue(L"-"); + } + } else { + commandLine.AppendLooseValue(L"-"); + } + + nsAutoString ipcTimeoutStr; + ipcTimeoutStr.AppendInt(mIPCTimeoutMs); + commandLine.AppendLooseValue(ipcTimeoutStr.get()); + + std::wstring ipcCookie; + rv = mMiniShm.GetCookie(ipcCookie); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(ipcCookie); + + ScopedHandle showEvent(::CreateEventW(nullptr, FALSE, FALSE, nullptr)); + if (!showEvent.IsValid()) { + return false; + } + mShowEvent = showEvent.Get(); + + MutexAutoLock lock(mMutex); + STARTUPINFO startupInfo = { sizeof(STARTUPINFO) }; + PROCESS_INFORMATION processInfo = { nullptr }; + BOOL isProcessCreated = ::CreateProcess(exePath.value().c_str(), + const_cast<wchar_t*>(commandLine.command_line_string().c_str()), + nullptr, + nullptr, + TRUE, + DETACHED_PROCESS, + nullptr, + nullptr, + &startupInfo, + &processInfo); + if (isProcessCreated) { + ::CloseHandle(processInfo.hThread); + mHangUIProcessHandle = processInfo.hProcess; + ::RegisterWaitForSingleObject(&mRegWait, + processInfo.hProcess, + &SOnHangUIProcessExit, + this, + INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + ::WaitForSingleObject(mShowEvent, ::IsDebuggerPresent() ? INFINITE + : mIPCTimeoutMs); + // Setting this to true even if we time out on mShowEvent. This timeout + // typically occurs when the machine is thrashing so badly that + // plugin-hang-ui.exe is taking a while to start. If we didn't set + // this to true, Firefox would keep spawning additional plugin-hang-ui + // processes, which is not what we want. + mIsShowing = true; + } + mShowEvent = nullptr; + return !(!isProcessCreated); +} + +// static +VOID CALLBACK PluginHangUIParent::SOnHangUIProcessExit(PVOID aContext, + BOOLEAN aIsTimer) +{ + PluginHangUIParent* object = static_cast<PluginHangUIParent*>(aContext); + MutexAutoLock lock(object->mMutex); + // If the Hang UI child process died unexpectedly, act as if the UI cancelled + if (object->IsShowing()) { + object->RecvUserResponse(HANGUI_USER_RESPONSE_CANCEL); + // Firefox window was disabled automatically when the Hang UI was shown. + // If plugin-hang-ui.exe was unexpectedly terminated, we need to re-enable. + ::EnableWindow(object->mMainWindowHandle, TRUE); + } +} + +// A precondition for this function is that the caller has locked mMutex +bool +PluginHangUIParent::UnwatchHangUIChildProcess(bool aWait) +{ + mMutex.AssertCurrentThreadOwns(); + if (mRegWait) { + // If aWait is false then we want to pass a nullptr (i.e. default + // constructor) completionEvent + ScopedHandle completionEvent; + if (aWait) { + completionEvent.Set(::CreateEventW(nullptr, FALSE, FALSE, nullptr)); + if (!completionEvent.IsValid()) { + return false; + } + } + + // if aWait == false and UnregisterWaitEx fails with ERROR_IO_PENDING, + // it is okay to clear mRegWait; Windows is telling us that the wait's + // callback is running but will be cleaned up once the callback returns. + if (::UnregisterWaitEx(mRegWait, completionEvent) || + (!aWait && ::GetLastError() == ERROR_IO_PENDING)) { + mRegWait = nullptr; + if (aWait) { + // We must temporarily unlock mMutex while waiting for the registered + // wait callback to complete, or else we could deadlock. + MutexAutoUnlock unlock(mMutex); + ::WaitForSingleObject(completionEvent, INFINITE); + } + return true; + } + } + return false; +} + +bool +PluginHangUIParent::Cancel() +{ + MutexAutoLock lock(mMutex); + bool result = mIsShowing && SendCancel(); + if (result) { + mIsShowing = false; + } + return result; +} + +bool +PluginHangUIParent::SendCancel() +{ + PluginHangUICommand* cmd = nullptr; + nsresult rv = mMiniShm.GetWritePtr(cmd); + if (NS_FAILED(rv)) { + return false; + } + cmd->mCode = PluginHangUICommand::HANGUI_CMD_CANCEL; + return NS_SUCCEEDED(mMiniShm.Send()); +} + +// A precondition for this function is that the caller has locked mMutex +bool +PluginHangUIParent::RecvUserResponse(const unsigned int& aResponse) +{ + mMutex.AssertCurrentThreadOwns(); + if (!mIsShowing && !(aResponse & HANGUI_USER_RESPONSE_CANCEL)) { + // Don't process a user response if a cancellation is already pending + return true; + } + mLastUserResponse = aResponse; + mResponseTicks = ::GetTickCount(); + mIsShowing = false; + // responseCode: 1 = Stop, 2 = Continue, 3 = Cancel + int responseCode; + if (aResponse & HANGUI_USER_RESPONSE_STOP) { + // User clicked Stop + mModule->TerminateChildProcess(mMainThreadMessageLoop, + mozilla::ipc::kInvalidProcessId, + NS_LITERAL_CSTRING("ModalHangUI"), + EmptyString()); + responseCode = 1; + } else if(aResponse & HANGUI_USER_RESPONSE_CONTINUE) { + mModule->OnHangUIContinue(); + // User clicked Continue + responseCode = 2; + } else { + // Dialog was cancelled + responseCode = 3; + } + int dontAskCode = (aResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN) ? 1 : 0; + nsCOMPtr<nsIRunnable> workItem = new nsPluginHangUITelemetry(responseCode, + dontAskCode, + LastShowDurationMs(), + mTimeoutPrefMs); + NS_DispatchToMainThread(workItem); + return true; +} + +nsresult +PluginHangUIParent::GetHangUIOwnerWindowHandle(NativeWindowHandle& windowHandle) +{ + windowHandle = nullptr; + + nsresult rv; + nsCOMPtr<nsIWindowMediator> winMediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIDOMWindowProxy> navWin; + rv = winMediator->GetMostRecentWindow(u"navigator:browser", + getter_AddRefs(navWin)); + NS_ENSURE_SUCCESS(rv, rv); + if (!navWin) { + return NS_ERROR_FAILURE; + } + + nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin); + nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(win); + if (!widget) { + return NS_ERROR_FAILURE; + } + + windowHandle = reinterpret_cast<NativeWindowHandle>(widget->GetNativeData(NS_NATIVE_WINDOW)); + if (!windowHandle) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +PluginHangUIParent::OnMiniShmEvent(MiniShmBase *aMiniShmObj) +{ + const PluginHangUIResponse* response = nullptr; + nsresult rv = aMiniShmObj->GetReadPtr(response); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Couldn't obtain read pointer OnMiniShmEvent"); + if (NS_SUCCEEDED(rv)) { + // The child process has returned a response so we shouldn't worry about + // its state anymore. + MutexAutoLock lock(mMutex); + UnwatchHangUIChildProcess(false); + RecvUserResponse(response->mResponseBits); + } +} + +void +PluginHangUIParent::OnMiniShmConnect(MiniShmBase* aMiniShmObj) +{ + PluginHangUICommand* cmd = nullptr; + nsresult rv = aMiniShmObj->GetWritePtr(cmd); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Couldn't obtain write pointer OnMiniShmConnect"); + if (NS_FAILED(rv)) { + return; + } + cmd->mCode = PluginHangUICommand::HANGUI_CMD_SHOW; + if (NS_SUCCEEDED(aMiniShmObj->Send())) { + mShowTicks = ::GetTickCount(); + } + ::SetEvent(mShowEvent); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginHangUIParent.h b/dom/plugins/ipc/PluginHangUIParent.h new file mode 100644 index 000000000..8a6b2e6cb --- /dev/null +++ b/dom/plugins/ipc/PluginHangUIParent.h @@ -0,0 +1,159 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 mozilla_plugins_PluginHangUIParent_h +#define mozilla_plugins_PluginHangUIParent_h + +#include "nsString.h" + +#include "base/process.h" +#include "base/process_util.h" + +#include "mozilla/Mutex.h" +#include "mozilla/plugins/PluginMessageUtils.h" + +#include "MiniShmParent.h" + +namespace mozilla { +namespace plugins { + +class PluginModuleChromeParent; + +/** + * This class is responsible for launching and communicating with the + * plugin-hang-ui process. + * + * NOTE: PluginHangUIParent is *not* an IPDL actor! In this case, "Parent" + * is describing the fact that firefox is the parent process to the + * plugin-hang-ui process, which is the PluginHangUIChild. + * PluginHangUIParent and PluginHangUIChild are a matched pair. + * @see PluginHangUIChild + */ +class PluginHangUIParent : public MiniShmObserver +{ +public: + PluginHangUIParent(PluginModuleChromeParent* aModule, + const int32_t aHangUITimeoutPref, + const int32_t aChildTimeoutPref); + virtual ~PluginHangUIParent(); + + /** + * Spawn the plugin-hang-ui.exe child process and terminate the given + * plugin container process if the user elects to stop the hung plugin. + * + * @param aPluginName Human-readable name of the affected plugin. + * @return true if the plugin hang ui process was successfully launched, + * otherwise false. + */ + bool + Init(const nsString& aPluginName); + + /** + * If the Plugin Hang UI is being shown, send a cancel notification to the + * Plugin Hang UI child process. + * + * @return true if the UI was shown and the cancel command was successfully + * sent to the child process, otherwise false. + */ + bool + Cancel(); + + /** + * Returns whether the Plugin Hang UI is currently being displayed. + * + * @return true if the Plugin Hang UI is showing, otherwise false. + */ + bool + IsShowing() const { return mIsShowing; } + + /** + * Returns whether this Plugin Hang UI instance has been shown. Note + * that this does not necessarily mean that the UI is showing right now. + * + * @return true if the Plugin Hang UI has shown, otherwise false. + */ + bool + WasShown() const { return mIsShowing || mLastUserResponse != 0; } + + /** + * Returns whether the user checked the "Don't ask me again" checkbox. + * + * @return true if the user does not want to see the Hang UI again. + */ + bool + DontShowAgain() const; + + /** + * Returns whether the user clicked stop during the last time that the + * Plugin Hang UI was displayed, if applicable. + * + * @return true if the UI was shown and the user chose to stop the + * plugin, otherwise false + */ + bool + WasLastHangStopped() const; + + /** + * @return unsigned int containing the response bits from the last + * time the Plugin Hang UI ran. + */ + unsigned int + LastUserResponse() const { return mLastUserResponse; } + + /** + * @return unsigned int containing the number of milliseconds that + * the Plugin Hang UI was displayed before the user responded. + * Returns 0 if the Plugin Hang UI has not been shown or was cancelled. + */ + unsigned int + LastShowDurationMs() const; + + virtual void + OnMiniShmEvent(MiniShmBase* aMiniShmObj) override; + + virtual void + OnMiniShmConnect(MiniShmBase* aMiniShmObj) override; + +private: + nsresult + GetHangUIOwnerWindowHandle(NativeWindowHandle& windowHandle); + + bool + SendCancel(); + + bool + RecvUserResponse(const unsigned int& aResponse); + + bool + UnwatchHangUIChildProcess(bool aWait); + + static + VOID CALLBACK SOnHangUIProcessExit(PVOID aContext, BOOLEAN aIsTimer); + +private: + Mutex mMutex; + PluginModuleChromeParent* mModule; + const uint32_t mTimeoutPrefMs; + const uint32_t mIPCTimeoutMs; + MessageLoop* mMainThreadMessageLoop; + bool mIsShowing; + unsigned int mLastUserResponse; + base::ProcessHandle mHangUIProcessHandle; + NativeWindowHandle mMainWindowHandle; + HANDLE mRegWait; + HANDLE mShowEvent; + DWORD mShowTicks; + DWORD mResponseTicks; + MiniShmParent mMiniShm; + + DISALLOW_COPY_AND_ASSIGN(PluginHangUIParent); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUIParent_h + diff --git a/dom/plugins/ipc/PluginInstanceChild.cpp b/dom/plugins/ipc/PluginInstanceChild.cpp new file mode 100644 index 000000000..af9db9103 --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceChild.cpp @@ -0,0 +1,4684 @@ +/* -*- 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/. */ + +#include "PluginBackgroundDestroyer.h" +#include "PluginInstanceChild.h" +#include "PluginModuleChild.h" +#include "BrowserStreamChild.h" +#include "PluginStreamChild.h" +#include "StreamNotifyChild.h" +#include "PluginProcessChild.h" +#include "gfxASurface.h" +#include "gfxPlatform.h" +#include "gfx2DGlue.h" +#include "nsNPAPIPluginInstance.h" +#include "mozilla/gfx/2D.h" +#ifdef MOZ_X11 +#include "gfxXlibSurface.h" +#endif +#ifdef XP_WIN +#include "mozilla/D3DMessageUtils.h" +#include "mozilla/gfx/SharedDIBSurface.h" +#include "nsCrashOnException.h" +#include "gfxWindowsPlatform.h" +extern const wchar_t* kFlashFullscreenClass; +using mozilla::gfx::SharedDIBSurface; +#endif +#include "gfxSharedImageSurface.h" +#include "gfxUtils.h" +#include "gfxAlphaRecovery.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "ImageContainer.h" + +using namespace mozilla; +using mozilla::ipc::ProcessChild; +using namespace mozilla::plugins; +using namespace mozilla::layers; +using namespace mozilla::gfx; +using namespace mozilla::widget; +using namespace std; + +#ifdef MOZ_WIDGET_GTK + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include <gdk/gdk.h> +#include "gtk2xtbin.h" + +#elif defined(OS_WIN) + +#include <windows.h> +#include <windowsx.h> + +#include "mozilla/widget/WinMessages.h" +#include "mozilla/widget/WinModifierKeyState.h" +#include "mozilla/widget/WinNativeEventData.h" +#include "nsWindowsDllInterceptor.h" +#include "X11UndefineNone.h" + +typedef BOOL (WINAPI *User32TrackPopupMenu)(HMENU hMenu, + UINT uFlags, + int x, + int y, + int nReserved, + HWND hWnd, + CONST RECT *prcRect); +static WindowsDllInterceptor sUser32Intercept; +static HWND sWinlessPopupSurrogateHWND = nullptr; +static User32TrackPopupMenu sUser32TrackPopupMenuStub = nullptr; + +typedef HIMC (WINAPI *Imm32ImmGetContext)(HWND hWND); +typedef BOOL (WINAPI *Imm32ImmReleaseContext)(HWND hWND, HIMC hIMC); +typedef LONG (WINAPI *Imm32ImmGetCompositionString)(HIMC hIMC, + DWORD dwIndex, + LPVOID lpBuf, + DWORD dwBufLen); +typedef BOOL (WINAPI *Imm32ImmSetCandidateWindow)(HIMC hIMC, + LPCANDIDATEFORM lpCandidate); +typedef BOOL (WINAPI *Imm32ImmNotifyIME)(HIMC hIMC, DWORD dwAction, + DWORD dwIndex, DWORD dwValue); +static WindowsDllInterceptor sImm32Intercept; +static Imm32ImmGetContext sImm32ImmGetContextStub = nullptr; +static Imm32ImmReleaseContext sImm32ImmReleaseContextStub = nullptr; +static Imm32ImmGetCompositionString sImm32ImmGetCompositionStringStub = nullptr; +static Imm32ImmSetCandidateWindow sImm32ImmSetCandidateWindowStub = nullptr; +static Imm32ImmNotifyIME sImm32ImmNotifyIME = nullptr; +static PluginInstanceChild* sCurrentPluginInstance = nullptr; +static const HIMC sHookIMC = (const HIMC)0xefefefef; + +using mozilla::gfx::SharedDIB; + +// Flash WM_USER message delay time for PostDelayedTask. Borrowed +// from Chromium's web plugin delegate src. See 'flash msg throttling +// helpers' section for details. +const int kFlashWMUSERMessageThrottleDelayMs = 5; + +static const TCHAR kPluginIgnoreSubclassProperty[] = TEXT("PluginIgnoreSubclassProperty"); + +#elif defined(XP_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#include "PluginUtilsOSX.h" +#endif // defined(XP_MACOSX) + +/** + * We can't use gfxPlatform::CreateDrawTargetForSurface() because calling + * gfxPlatform::GetPlatform() instantiates the prefs service, and that's not + * allowed from processes other than the main process. So we have our own + * version here. + */ +static RefPtr<DrawTarget> +CreateDrawTargetForSurface(gfxASurface *aSurface) +{ + SurfaceFormat format = aSurface->GetSurfaceFormat(); + RefPtr<DrawTarget> drawTarget = + Factory::CreateDrawTargetForCairoSurface(aSurface->CairoSurface(), + aSurface->GetSize(), + &format); + if (!drawTarget) { + NS_RUNTIMEABORT("CreateDrawTargetForSurface failed in plugin"); + } + return drawTarget; +} + +bool PluginInstanceChild::sIsIMEComposing = false; + +PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface, + const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray<nsCString>& aNames, + const InfallibleTArray<nsCString>& aValues) + : mPluginIface(aPluginIface) + , mMimeType(aMimeType) + , mMode(aMode) + , mNames(aNames) + , mValues(aValues) +#if defined(XP_DARWIN) || defined (XP_WIN) + , mContentsScaleFactor(1.0) +#endif + , mPostingKeyEvents(0) + , mPostingKeyEventsOutdated(0) + , mDrawingModel(kDefaultDrawingModel) + , mCurrentDirectSurface(nullptr) + , mAsyncInvalidateMutex("PluginInstanceChild::mAsyncInvalidateMutex") + , mAsyncInvalidateTask(0) + , mCachedWindowActor(nullptr) + , mCachedElementActor(nullptr) +#ifdef MOZ_WIDGET_GTK + , mXEmbed(false) +#endif // MOZ_WIDGET_GTK +#if defined(OS_WIN) + , mPluginWindowHWND(0) + , mPluginWndProc(0) + , mPluginParentHWND(0) + , mCachedWinlessPluginHWND(0) + , mWinlessPopupSurrogateHWND(0) + , mWinlessThrottleOldWndProc(0) + , mWinlessHiddenMsgHWND(0) + , mUnityGetMessageHook(NULL) + , mUnitySendMessageHook(NULL) +#endif // OS_WIN + , mAsyncCallMutex("PluginInstanceChild::mAsyncCallMutex") +#if defined(MOZ_WIDGET_COCOA) +#if defined(__i386__) + , mEventModel(NPEventModelCarbon) +#endif + , mShColorSpace(nullptr) + , mShContext(nullptr) + , mCGLayer(nullptr) + , mCurrentEvent(nullptr) +#endif + , mLayersRendering(false) +#ifdef XP_WIN + , mCurrentSurfaceActor(nullptr) + , mBackSurfaceActor(nullptr) +#endif + , mAccumulatedInvalidRect(0,0,0,0) + , mIsTransparent(false) + , mSurfaceType(gfxSurfaceType::Max) + , mPendingPluginCall(false) + , mDoAlphaExtraction(false) + , mHasPainted(false) + , mSurfaceDifferenceRect(0,0,0,0) + , mDestroyed(false) +#ifdef XP_WIN + , mLastKeyEventConsumed(false) +#endif // #ifdef XP_WIN + , mStackDepth(0) +{ + memset(&mWindow, 0, sizeof(mWindow)); + mWindow.type = NPWindowTypeWindow; + mData.ndata = (void*) this; + mData.pdata = nullptr; +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + mWindow.ws_info = &mWsInfo; + memset(&mWsInfo, 0, sizeof(mWsInfo)); +#ifdef MOZ_WIDGET_GTK + mWsInfo.display = nullptr; + mXtClient.top_widget = nullptr; +#else + mWsInfo.display = DefaultXDisplay(); +#endif +#endif // MOZ_X11 && XP_UNIX && !XP_MACOSX +#if defined(OS_WIN) + InitPopupMenuHook(); + if (GetQuirks() & QUIRK_UNITY_FIXUP_MOUSE_CAPTURE) { + SetUnityHooks(); + } + InitImm32Hook(); +#endif // OS_WIN +} + +PluginInstanceChild::~PluginInstanceChild() +{ +#if defined(OS_WIN) + NS_ASSERTION(!mPluginWindowHWND, "Destroying PluginInstanceChild without NPP_Destroy?"); + if (GetQuirks() & QUIRK_UNITY_FIXUP_MOUSE_CAPTURE) { + ClearUnityHooks(); + } + // In the event that we registered for audio device changes, stop. + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
+ if (chromeInstance) {
+ NPError rv = chromeInstance->PluginRequiresAudioDeviceChanges(this, false); + } +#endif +#if defined(MOZ_WIDGET_COCOA) + if (mShColorSpace) { + ::CGColorSpaceRelease(mShColorSpace); + } + if (mShContext) { + ::CGContextRelease(mShContext); + } + if (mCGLayer) { + PluginUtilsOSX::ReleaseCGLayer(mCGLayer); + } + if (mDrawingModel == NPDrawingModelCoreAnimation) { + UnscheduleTimer(mCARefreshTimer); + } +#endif +} + +NPError +PluginInstanceChild::DoNPP_New() +{ + // unpack the arguments into a C format + int argc = mNames.Length(); + NS_ASSERTION(argc == (int) mValues.Length(), + "argn.length != argv.length"); + + UniquePtr<char*[]> argn(new char*[1 + argc]); + UniquePtr<char*[]> argv(new char*[1 + argc]); + argn[argc] = 0; + argv[argc] = 0; + + for (int i = 0; i < argc; ++i) { + argn[i] = const_cast<char*>(NullableStringGet(mNames[i])); + argv[i] = const_cast<char*>(NullableStringGet(mValues[i])); + } + + NPP npp = GetNPP(); + + NPError rv = mPluginIface->newp((char*)NullableStringGet(mMimeType), npp, + mMode, argc, argn.get(), argv.get(), 0); + if (NPERR_NO_ERROR != rv) { + return rv; + } + + Initialize(); + +#if defined(XP_MACOSX) && defined(__i386__) + // If an i386 Mac OS X plugin has selected the Carbon event model then + // we have to fail. We do not support putting Carbon event model plugins + // out of process. Note that Carbon is the default model so out of process + // plugins need to actively negotiate something else in order to work + // out of process. + if (EventModel() == NPEventModelCarbon) { + // Send notification that a plugin tried to negotiate Carbon NPAPI so that + // users can be notified that restarting the browser in i386 mode may allow + // them to use the plugin. + SendNegotiatedCarbon(); + + // Fail to instantiate. + rv = NPERR_MODULE_LOAD_FAILED_ERROR; + } +#endif + + return rv; +} + +int +PluginInstanceChild::GetQuirks() +{ + return PluginModuleChild::GetChrome()->GetQuirks(); +} + +NPError +PluginInstanceChild::InternalGetNPObjectForValue(NPNVariable aValue, + NPObject** aObject) +{ + PluginScriptableObjectChild* actor = nullptr; + NPError result = NPERR_NO_ERROR; + + switch (aValue) { + case NPNVWindowNPObject: + if (!(actor = mCachedWindowActor)) { + PPluginScriptableObjectChild* actorProtocol; + CallNPN_GetValue_NPNVWindowNPObject(&actorProtocol, &result); + if (result == NPERR_NO_ERROR) { + actor = mCachedWindowActor = + static_cast<PluginScriptableObjectChild*>(actorProtocol); + NS_ASSERTION(actor, "Null actor!"); + PluginModuleChild::sBrowserFuncs.retainobject( + actor->GetObject(false)); + } + } + break; + + case NPNVPluginElementNPObject: + if (!(actor = mCachedElementActor)) { + PPluginScriptableObjectChild* actorProtocol; + CallNPN_GetValue_NPNVPluginElementNPObject(&actorProtocol, + &result); + if (result == NPERR_NO_ERROR) { + actor = mCachedElementActor = + static_cast<PluginScriptableObjectChild*>(actorProtocol); + NS_ASSERTION(actor, "Null actor!"); + PluginModuleChild::sBrowserFuncs.retainobject( + actor->GetObject(false)); + } + } + break; + + default: + NS_NOTREACHED("Don't know what to do with this value type!"); + } + +#ifdef DEBUG + { + NPError currentResult; + PPluginScriptableObjectChild* currentActor = nullptr; + + switch (aValue) { + case NPNVWindowNPObject: + CallNPN_GetValue_NPNVWindowNPObject(¤tActor, + ¤tResult); + break; + case NPNVPluginElementNPObject: + CallNPN_GetValue_NPNVPluginElementNPObject(¤tActor, + ¤tResult); + break; + default: + MOZ_ASSERT(false); + } + + // Make sure that the current actor returned by the parent matches our + // cached actor! + NS_ASSERTION(!currentActor || + static_cast<PluginScriptableObjectChild*>(currentActor) == + actor, "Cached actor is out of date!"); + } +#endif + + if (result != NPERR_NO_ERROR) { + return result; + } + + NPObject* object = actor->GetObject(false); + NS_ASSERTION(object, "Null object?!"); + + *aObject = PluginModuleChild::sBrowserFuncs.retainobject(object); + return NPERR_NO_ERROR; + +} + +NPError +PluginInstanceChild::NPN_GetValue(NPNVariable aVar, + void* aValue) +{ + PLUGIN_LOG_DEBUG(("%s (aVar=%i)", FULLFUNCTION, (int) aVar)); + AssertPluginThread(); + AutoStackHelper guard(this); + + switch(aVar) { + +#if defined(MOZ_X11) + case NPNVToolkit: + *((NPNToolkitType*)aValue) = NPNVGtk2; + return NPERR_NO_ERROR; + + case NPNVxDisplay: + if (!mWsInfo.display) { + // We are called before Initialize() so we have to call it now. + Initialize(); + NS_ASSERTION(mWsInfo.display, "We should have a valid display!"); + } + *(void **)aValue = mWsInfo.display; + return NPERR_NO_ERROR; + +#elif defined(OS_WIN) + case NPNVToolkit: + return NPERR_GENERIC_ERROR; +#endif + case NPNVprivateModeBool: { + bool v = false; + NPError result; + if (!CallNPN_GetValue_NPNVprivateModeBool(&v, &result)) { + return NPERR_GENERIC_ERROR; + } + *static_cast<NPBool*>(aValue) = v; + return result; + } + + case NPNVdocumentOrigin: { + nsCString v; + NPError result; + if (!CallNPN_GetValue_NPNVdocumentOrigin(&v, &result)) { + return NPERR_GENERIC_ERROR; + } + if (result == NPERR_NO_ERROR || + (GetQuirks() & + QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN)) { + *static_cast<char**>(aValue) = ToNewCString(v); + } + return result; + } + + case NPNVWindowNPObject: // Intentional fall-through + case NPNVPluginElementNPObject: { + NPObject* object; + NPError result = InternalGetNPObjectForValue(aVar, &object); + if (result == NPERR_NO_ERROR) { + *((NPObject**)aValue) = object; + } + return result; + } + + case NPNVnetscapeWindow: { +#ifdef XP_WIN + if (mWindow.type == NPWindowTypeDrawable) { + if (mCachedWinlessPluginHWND) { + *static_cast<HWND*>(aValue) = mCachedWinlessPluginHWND; + return NPERR_NO_ERROR; + } + NPError result; + if (!CallNPN_GetValue_NPNVnetscapeWindow(&mCachedWinlessPluginHWND, &result)) { + return NPERR_GENERIC_ERROR; + } + *static_cast<HWND*>(aValue) = mCachedWinlessPluginHWND; + return result; + } + else { + *static_cast<HWND*>(aValue) = mPluginWindowHWND; + return NPERR_NO_ERROR; + } +#elif defined(MOZ_X11) + NPError result; + CallNPN_GetValue_NPNVnetscapeWindow(static_cast<XID*>(aValue), &result); + return result; +#else + return NPERR_GENERIC_ERROR; +#endif + } + + case NPNVsupportsAsyncBitmapSurfaceBool: { + bool value = false; + CallNPN_GetValue_SupportsAsyncBitmapSurface(&value); + *((NPBool*)aValue) = value; + return NPERR_NO_ERROR; + } + +#ifdef XP_WIN + case NPNVsupportsAsyncWindowsDXGISurfaceBool: { + bool value = false; + CallNPN_GetValue_SupportsAsyncDXGISurface(&value); + *((NPBool*)aValue) = value; + return NPERR_NO_ERROR; + } +#endif + +#ifdef XP_WIN + case NPNVpreferredDXGIAdapter: { + DxgiAdapterDesc desc; + if (!CallNPN_GetValue_PreferredDXGIAdapter(&desc)) { + return NPERR_GENERIC_ERROR; + } + *reinterpret_cast<DXGI_ADAPTER_DESC*>(aValue) = desc.ToDesc(); + return NPERR_NO_ERROR; + } +#endif + +#ifdef XP_MACOSX + case NPNVsupportsCoreGraphicsBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsCoreAnimationBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsInvalidatingCoreAnimationBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsCompositingCoreAnimationPluginsBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsCocoaBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + +#ifndef NP_NO_CARBON + case NPNVsupportsCarbonBool: { + *((NPBool*)aValue) = false; + return NPERR_NO_ERROR; + } +#endif + + case NPNVsupportsUpdatedCocoaTextInputBool: { + *static_cast<NPBool*>(aValue) = true; + return NPERR_NO_ERROR; + } + +#ifndef NP_NO_QUICKDRAW + case NPNVsupportsQuickDrawBool: { + *((NPBool*)aValue) = false; + return NPERR_NO_ERROR; + } +#endif /* NP_NO_QUICKDRAW */ +#endif /* XP_MACOSX */ + +#if defined(XP_MACOSX) || defined(XP_WIN) + case NPNVcontentsScaleFactor: { + *static_cast<double*>(aValue) = mContentsScaleFactor; + return NPERR_NO_ERROR; + } +#endif /* defined(XP_MACOSX) || defined(XP_WIN) */ + + case NPNVCSSZoomFactor: { + *static_cast<double*>(aValue) = mCSSZoomFactor; + return NPERR_NO_ERROR; + } +#ifdef DEBUG + case NPNVjavascriptEnabledBool: + case NPNVasdEnabledBool: + case NPNVisOfflineBool: + case NPNVSupportsXEmbedBool: + case NPNVSupportsWindowless: + NS_NOTREACHED("NPNVariable should be handled in PluginModuleChild."); + MOZ_FALLTHROUGH; +#endif + + default: + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceChild::NPN_GetValue: Unhandled NPNVariable %i (%s)", + (int) aVar, NPNVariableToString(aVar))); + return NPERR_GENERIC_ERROR; + } +} + +#ifdef MOZ_WIDGET_COCOA +#define DEFAULT_REFRESH_MS 20 // CoreAnimation: 50 FPS + +void +CAUpdate(NPP npp, uint32_t timerID) { + static_cast<PluginInstanceChild*>(npp->ndata)->Invalidate(); +} + +void +PluginInstanceChild::Invalidate() +{ + NPRect windowRect = {0, 0, uint16_t(mWindow.height), + uint16_t(mWindow.width)}; + + InvalidateRect(&windowRect); +} +#endif + +NPError +PluginInstanceChild::NPN_SetValue(NPPVariable aVar, void* aValue) +{ + MOZ_LOG(GetPluginLog(), LogLevel::Debug, ("%s (aVar=%i, aValue=%p)", + FULLFUNCTION, (int) aVar, aValue)); + + AssertPluginThread(); + + AutoStackHelper guard(this); + + switch (aVar) { + case NPPVpluginWindowBool: { + NPError rv; + bool windowed = (NPBool) (intptr_t) aValue; + + if (!CallNPN_SetValue_NPPVpluginWindow(windowed, &rv)) + return NPERR_GENERIC_ERROR; + + NPWindowType newWindowType = windowed ? NPWindowTypeWindow : NPWindowTypeDrawable; +#ifdef MOZ_WIDGET_GTK + if (mWindow.type != newWindowType && mWsInfo.display) { + // plugin type has been changed but we already have a valid display + // so update it for the recent plugin mode + if (mXEmbed || !windowed) { + // Use default GTK display for XEmbed and windowless plugins + mWsInfo.display = DefaultXDisplay(); + } + else { + mWsInfo.display = xt_client_get_display(); + } + } +#endif + mWindow.type = newWindowType; + return rv; + } + + case NPPVpluginTransparentBool: { + NPError rv; + mIsTransparent = (!!aValue); + + if (!CallNPN_SetValue_NPPVpluginTransparent(mIsTransparent, &rv)) + return NPERR_GENERIC_ERROR; + + return rv; + } + + case NPPVpluginUsesDOMForCursorBool: { + NPError rv = NPERR_GENERIC_ERROR; + if (!CallNPN_SetValue_NPPVpluginUsesDOMForCursor((NPBool)(intptr_t)aValue, &rv)) { + return NPERR_GENERIC_ERROR; + } + return rv; + } + + case NPPVpluginDrawingModel: { + NPError rv; + int drawingModel = (int16_t) (intptr_t) aValue; + + if (!CallNPN_SetValue_NPPVpluginDrawingModel(drawingModel, &rv)) + return NPERR_GENERIC_ERROR; + + mDrawingModel = drawingModel; + +#ifdef XP_MACOSX + if (drawingModel == NPDrawingModelCoreAnimation) { + mCARefreshTimer = ScheduleTimer(DEFAULT_REFRESH_MS, true, CAUpdate); + } +#endif + + PLUGIN_LOG_DEBUG((" Plugin requested drawing model id #%i\n", + mDrawingModel)); + + return rv; + } + +#ifdef XP_MACOSX + case NPPVpluginEventModel: { + NPError rv; + int eventModel = (int16_t) (intptr_t) aValue; + + if (!CallNPN_SetValue_NPPVpluginEventModel(eventModel, &rv)) + return NPERR_GENERIC_ERROR; +#if defined(__i386__) + mEventModel = static_cast<NPEventModel>(eventModel); +#endif + + PLUGIN_LOG_DEBUG((" Plugin requested event model id # %i\n", + eventModel)); + + return rv; + } +#endif + + case NPPVpluginIsPlayingAudio: { + NPError rv = NPERR_GENERIC_ERROR; + if (!CallNPN_SetValue_NPPVpluginIsPlayingAudio((NPBool)(intptr_t)aValue, &rv)) { + return NPERR_GENERIC_ERROR; + } + return rv; + } + +#ifdef XP_WIN + case NPPVpluginRequiresAudioDeviceChanges: { + // Many other NPN_SetValue variables are forwarded to our + // PluginInstanceParent, which runs on a content process. We + // instead forward this message to the PluginModuleParent, which runs + // on the chrome process. This is because our audio + // API calls should run the chrome proc, not content. + NPError rv = NPERR_GENERIC_ERROR; + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome();
+ if (chromeInstance) {
+ rv = chromeInstance->PluginRequiresAudioDeviceChanges(this, + (NPBool)(intptr_t)aValue); + } + return rv; + } +#endif + + default: + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceChild::NPN_SetValue: Unhandled NPPVariable %i (%s)", + (int) aVar, NPPVariableToString(aVar))); + return NPERR_GENERIC_ERROR; + } +} + +bool +PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginWantsAllNetworkStreams( + bool* wantsAllStreams, NPError* rv) +{ + AssertPluginThread(); + AutoStackHelper guard(this); + + uint32_t value = 0; + if (!mPluginIface->getvalue) { + *rv = NPERR_GENERIC_ERROR; + } + else { + *rv = mPluginIface->getvalue(GetNPP(), NPPVpluginWantsAllNetworkStreams, + &value); + } + *wantsAllStreams = value; + return true; +} + +bool +PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginNeedsXEmbed( + bool* needs, NPError* rv) +{ + AssertPluginThread(); + AutoStackHelper guard(this); + +#ifdef MOZ_X11 + // 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 (!mPluginIface->getvalue) { + *rv = NPERR_GENERIC_ERROR; + } + else { + *rv = mPluginIface->getvalue(GetNPP(), NPPVpluginNeedsXEmbed, + &needsXEmbed); + } + *needs = needsXEmbed; + return true; + +#else + + NS_RUNTIMEABORT("shouldn't be called on non-X11 platforms"); + return false; // not reached + +#endif +} + +bool +PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginScriptableNPObject( + PPluginScriptableObjectChild** aValue, + NPError* aResult) +{ + AssertPluginThread(); + AutoStackHelper guard(this); + + NPObject* object = nullptr; + NPError result = NPERR_GENERIC_ERROR; + if (mPluginIface->getvalue) { + result = mPluginIface->getvalue(GetNPP(), NPPVpluginScriptableNPObject, + &object); + } + if (result == NPERR_NO_ERROR && object) { + PluginScriptableObjectChild* actor = GetActorForNPObject(object); + + // If we get an actor then it has retained. Otherwise we don't need it + // any longer. + PluginModuleChild::sBrowserFuncs.releaseobject(object); + if (actor) { + *aValue = actor; + *aResult = NPERR_NO_ERROR; + return true; + } + + NS_ERROR("Failed to get actor!"); + result = NPERR_GENERIC_ERROR; + } + else { + result = NPERR_GENERIC_ERROR; + } + + *aValue = nullptr; + *aResult = result; + return true; +} + +bool +PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId( + nsCString* aPlugId, + NPError* aResult) +{ + AssertPluginThread(); + AutoStackHelper guard(this); + +#if MOZ_ACCESSIBILITY_ATK + + char* plugId = nullptr; + NPError result = NPERR_GENERIC_ERROR; + if (mPluginIface->getvalue) { + result = mPluginIface->getvalue(GetNPP(), + NPPVpluginNativeAccessibleAtkPlugId, + &plugId); + } + + *aPlugId = nsCString(plugId); + *aResult = result; + return true; + +#else + + NS_RUNTIMEABORT("shouldn't be called on non-ATK platforms"); + return false; + +#endif +} + +bool +PluginInstanceChild::AnswerNPP_SetValue_NPNVprivateModeBool(const bool& value, + NPError* result) +{ + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return true; + } + + NPBool v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVprivateModeBool, &v); + return true; +} + +bool +PluginInstanceChild::AnswerNPP_SetValue_NPNVCSSZoomFactor(const double& value, + NPError* result) +{ + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return true; + } + + mCSSZoomFactor = value; + double v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVCSSZoomFactor, &v); + return true; +} + +bool +PluginInstanceChild::AnswerNPP_SetValue_NPNVmuteAudioBool(const bool& value, + NPError* result) +{ + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return true; + } + + NPBool v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVmuteAudioBool, &v); + return true; +} + +#if defined(XP_WIN) +NPError +PluginInstanceChild::DefaultAudioDeviceChanged(NPAudioDeviceChangeDetails& details) +{ + if (!mPluginIface->setvalue) { + return NPERR_GENERIC_ERROR; + } + return mPluginIface->setvalue(GetNPP(), NPNVaudioDeviceChangeDetails, (void*)&details); +} +#endif + + +bool +PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event, + int16_t* handled) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + AutoStackHelper guard(this); + +#if defined(MOZ_X11) && defined(DEBUG) + if (GraphicsExpose == event.event.type) + PLUGIN_LOG_DEBUG((" received drawable 0x%lx\n", + event.event.xgraphicsexpose.drawable)); +#endif + +#ifdef XP_MACOSX + // Mac OS X does not define an NPEvent structure. It defines more specific types. + NPCocoaEvent evcopy = event.event; + + // Make sure we reset mCurrentEvent in case of an exception + AutoRestore<const NPCocoaEvent*> savePreviousEvent(mCurrentEvent); + + // Track the current event for NPN_PopUpContextMenu. + mCurrentEvent = &event.event; +#else + // Make a copy since we may modify values. + NPEvent evcopy = event.event; +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + // event.contentsScaleFactor <= 0 is a signal we shouldn't use it, + // for example when AnswerNPP_HandleEvent() is called from elsewhere + // in the child process (not via rpc code from the parent process). + if (event.contentsScaleFactor > 0) { + mContentsScaleFactor = event.contentsScaleFactor; + } +#endif + +#ifdef OS_WIN + // FIXME/bug 567645: temporarily drop the "dummy event" on the floor + if (WM_NULL == evcopy.event) + return true; + + *handled = WinlessHandleEvent(evcopy); + return true; +#endif + + // XXX A previous call to mPluginIface->event might block, e.g. right click + // for context menu. Still, we might get here again, calling into the plugin + // a second time while it's in the previous call. + if (!mPluginIface->event) + *handled = false; + else + *handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy)); + +#ifdef XP_MACOSX + // Release any reference counted objects created in the child process. + if (evcopy.type == NPCocoaEventKeyDown || + evcopy.type == NPCocoaEventKeyUp) { + ::CFRelease((CFStringRef)evcopy.data.key.characters); + ::CFRelease((CFStringRef)evcopy.data.key.charactersIgnoringModifiers); + } + else if (evcopy.type == NPCocoaEventTextInput) { + ::CFRelease((CFStringRef)evcopy.data.text.text); + } +#endif + +#ifdef MOZ_X11 + if (GraphicsExpose == event.event.type) { + // Make sure the X server completes the drawing before the parent + // draws on top and destroys the Drawable. + // + // XSync() waits for the X server to complete. Really this child + // process does not need to wait; the parent is the process that needs + // to wait. A possibly-slightly-better alternative would be to send + // an X event to the parent that the parent would wait for. + XSync(mWsInfo.display, False); + } +#endif + + return true; +} + +#ifdef XP_MACOSX + +bool +PluginInstanceChild::AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event, + Shmem&& mem, + int16_t* handled, + Shmem* rtnmem) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + AutoStackHelper guard(this); + + PaintTracker pt; + + NPCocoaEvent evcopy = event.event; + mContentsScaleFactor = event.contentsScaleFactor; + + if (evcopy.type == NPCocoaEventDrawRect) { + int scaleFactor = ceil(mContentsScaleFactor); + if (!mShColorSpace) { + mShColorSpace = CreateSystemColorSpace(); + if (!mShColorSpace) { + PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace.")); + *handled = false; + *rtnmem = mem; + return true; + } + } + if (!mShContext) { + void* cgContextByte = mem.get<char>(); + mShContext = ::CGBitmapContextCreate(cgContextByte, + mWindow.width * scaleFactor, + mWindow.height * scaleFactor, 8, + mWindow.width * 4 * scaleFactor, mShColorSpace, + kCGImageAlphaPremultipliedFirst | + kCGBitmapByteOrder32Host); + + if (!mShContext) { + PLUGIN_LOG_DEBUG(("Could not allocate CGBitmapContext.")); + *handled = false; + *rtnmem = mem; + return true; + } + } + CGRect clearRect = ::CGRectMake(0, 0, mWindow.width, mWindow.height); + ::CGContextClearRect(mShContext, clearRect); + evcopy.data.draw.context = mShContext; + } else { + PLUGIN_LOG_DEBUG(("Invalid event type for AnswerNNP_HandleEvent_Shmem.")); + *handled = false; + *rtnmem = mem; + return true; + } + + if (!mPluginIface->event) { + *handled = false; + } else { + ::CGContextSaveGState(evcopy.data.draw.context); + *handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy)); + ::CGContextRestoreGState(evcopy.data.draw.context); + } + + *rtnmem = mem; + return true; +} + +#else +bool +PluginInstanceChild::AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event, + Shmem&& mem, + int16_t* handled, + Shmem* rtnmem) +{ + NS_RUNTIMEABORT("not reached."); + *rtnmem = mem; + return true; +} +#endif + +#ifdef XP_MACOSX + +void CallCGDraw(CGContextRef ref, void* aPluginInstance, nsIntRect aUpdateRect) { + PluginInstanceChild* pluginInstance = (PluginInstanceChild*)aPluginInstance; + + pluginInstance->CGDraw(ref, aUpdateRect); +} + +bool +PluginInstanceChild::CGDraw(CGContextRef ref, nsIntRect aUpdateRect) { + + NPCocoaEvent drawEvent; + drawEvent.type = NPCocoaEventDrawRect; + drawEvent.version = 0; + drawEvent.data.draw.x = aUpdateRect.x; + drawEvent.data.draw.y = aUpdateRect.y; + drawEvent.data.draw.width = aUpdateRect.width; + drawEvent.data.draw.height = aUpdateRect.height; + drawEvent.data.draw.context = ref; + + NPRemoteEvent remoteDrawEvent = {drawEvent}; + // Signal to AnswerNPP_HandleEvent() not to use this value + remoteDrawEvent.contentsScaleFactor = -1.0; + + int16_t handled; + AnswerNPP_HandleEvent(remoteDrawEvent, &handled); + return handled == true; +} + +bool +PluginInstanceChild::AnswerNPP_HandleEvent_IOSurface(const NPRemoteEvent& event, + const uint32_t &surfaceid, + int16_t* handled) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + AutoStackHelper guard(this); + + PaintTracker pt; + + NPCocoaEvent evcopy = event.event; + mContentsScaleFactor = event.contentsScaleFactor; + RefPtr<MacIOSurface> surf = MacIOSurface::LookupSurface(surfaceid, + mContentsScaleFactor); + if (!surf) { + NS_ERROR("Invalid IOSurface."); + *handled = false; + return false; + } + + if (!mCARenderer) { + mCARenderer = new nsCARenderer(); + } + + if (evcopy.type == NPCocoaEventDrawRect) { + mCARenderer->AttachIOSurface(surf); + if (!mCARenderer->isInit()) { + void *caLayer = nullptr; + NPError result = mPluginIface->getvalue(GetNPP(), + NPPVpluginCoreAnimationLayer, + &caLayer); + + if (result != NPERR_NO_ERROR || !caLayer) { + PLUGIN_LOG_DEBUG(("Plugin requested CoreAnimation but did not " + "provide CALayer.")); + *handled = false; + return false; + } + + mCARenderer->SetupRenderer(caLayer, mWindow.width, mWindow.height, + mContentsScaleFactor, + GetQuirks() & QUIRK_ALLOW_OFFLINE_RENDERER ? + ALLOW_OFFLINE_RENDERER : DISALLOW_OFFLINE_RENDERER); + + // Flash needs to have the window set again after this step + if (mPluginIface->setwindow) + (void) mPluginIface->setwindow(&mData, &mWindow); + } + } else { + PLUGIN_LOG_DEBUG(("Invalid event type for " + "AnswerNNP_HandleEvent_IOSurface.")); + *handled = false; + return false; + } + + mCARenderer->Render(mWindow.width, mWindow.height, + mContentsScaleFactor, nullptr); + + return true; + +} + +#else +bool +PluginInstanceChild::AnswerNPP_HandleEvent_IOSurface(const NPRemoteEvent& event, + const uint32_t &surfaceid, + int16_t* handled) +{ + NS_RUNTIMEABORT("NPP_HandleEvent_IOSurface is a OSX-only message"); + return false; +} +#endif + +bool +PluginInstanceChild::RecvWindowPosChanged(const NPRemoteEvent& event) +{ + NS_ASSERTION(!mLayersRendering && !mPendingPluginCall, + "Shouldn't be receiving WindowPosChanged with layer rendering"); + +#ifdef OS_WIN + int16_t dontcare; + return AnswerNPP_HandleEvent(event, &dontcare); +#else + NS_RUNTIMEABORT("WindowPosChanged is a windows-only message"); + return false; +#endif +} + +bool +PluginInstanceChild::RecvContentsScaleFactorChanged(const double& aContentsScaleFactor) +{ +#if defined(XP_MACOSX) || defined(XP_WIN) + mContentsScaleFactor = aContentsScaleFactor; +#if defined(XP_MACOSX) + if (mShContext) { + // Release the shared context so that it is reallocated + // with the new size. + ::CGContextRelease(mShContext); + mShContext = nullptr; + } +#endif + return true; +#else + NS_RUNTIMEABORT("ContentsScaleFactorChanged is an Windows or OSX only message"); + return false; +#endif +} + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) +// Create a new window from NPWindow +bool PluginInstanceChild::CreateWindow(const NPRemoteWindow& aWindow) +{ + PLUGIN_LOG_DEBUG(("%s (aWindow=<window: 0x%lx, x: %d, y: %d, width: %d, height: %d>)", + FULLFUNCTION, + aWindow.window, + aWindow.x, aWindow.y, + aWindow.width, aWindow.height)); + +#ifdef MOZ_WIDGET_GTK + if (mXEmbed) { + mWindow.window = reinterpret_cast<void*>(aWindow.window); + } + else { + Window browserSocket = (Window)(aWindow.window); + xt_client_init(&mXtClient, mWsInfo.visual, mWsInfo.colormap, mWsInfo.depth); + xt_client_create(&mXtClient, browserSocket, mWindow.width, mWindow.height); + mWindow.window = (void *)XtWindow(mXtClient.child_widget); + } +#else + mWindow.window = reinterpret_cast<void*>(aWindow.window); +#endif + + return true; +} + +// Destroy window +void PluginInstanceChild::DeleteWindow() +{ + PLUGIN_LOG_DEBUG(("%s (aWindow=<window: 0x%lx, x: %d, y: %d, width: %d, height: %d>)", + FULLFUNCTION, + mWindow.window, + mWindow.x, mWindow.y, + mWindow.width, mWindow.height)); + + if (!mWindow.window) + return; + +#ifdef MOZ_WIDGET_GTK + if (mXtClient.top_widget) { + xt_client_unrealize(&mXtClient); + xt_client_destroy(&mXtClient); + mXtClient.top_widget = nullptr; + } +#endif + + // We don't have to keep the plug-in window ID any longer. + mWindow.window = nullptr; +} +#endif + +bool +PluginInstanceChild::AnswerCreateChildPluginWindow(NativeWindowHandle* aChildPluginWindow) +{ +#if defined(XP_WIN) + MOZ_ASSERT(!mPluginWindowHWND); + + if (!CreatePluginWindow()) { + return false; + } + + MOZ_ASSERT(mPluginWindowHWND); + + *aChildPluginWindow = mPluginWindowHWND; + return true; +#else + NS_NOTREACHED("PluginInstanceChild::CreateChildPluginWindow not implemented!"); + return false; +#endif +} + +bool +PluginInstanceChild::RecvCreateChildPopupSurrogate(const NativeWindowHandle& aNetscapeWindow) +{ +#if defined(XP_WIN) + mCachedWinlessPluginHWND = aNetscapeWindow; + CreateWinlessPopupSurrogate(); + return true; +#else + NS_NOTREACHED("PluginInstanceChild::CreateChildPluginWindow not implemented!"); + return false; +#endif +} + +bool +PluginInstanceChild::AnswerNPP_SetWindow(const NPRemoteWindow& aWindow) +{ + PLUGIN_LOG_DEBUG(("%s (aWindow=<window: 0x%lx, x: %d, y: %d, width: %d, height: %d>)", + FULLFUNCTION, + aWindow.window, + aWindow.x, aWindow.y, + aWindow.width, aWindow.height)); + NS_ASSERTION(!mLayersRendering && !mPendingPluginCall, + "Shouldn't be receiving NPP_SetWindow with layer rendering"); + AssertPluginThread(); + AutoStackHelper guard(this); + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + NS_ASSERTION(mWsInfo.display, "We should have a valid display!"); + + // The minimum info is sent over IPC to allow this + // code to determine the rest. + + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.clipRect = aWindow.clipRect; + mWindow.type = aWindow.type; + + mWsInfo.colormap = aWindow.colormap; + int depth; + FindVisualAndDepth(mWsInfo.display, aWindow.visualID, + &mWsInfo.visual, &depth); + mWsInfo.depth = depth; + + if (!mWindow.window && mWindow.type == NPWindowTypeWindow) { + CreateWindow(aWindow); + } + +#ifdef MOZ_WIDGET_GTK + if (mXEmbed && gtk_check_version(2,18,7) != nullptr) { // older + if (aWindow.type == NPWindowTypeWindow) { + GdkWindow* socket_window = gdk_window_lookup(static_cast<GdkNativeWindow>(aWindow.window)); + if (socket_window) { + // A GdkWindow for the socket already exists. Need to + // workaround https://bugzilla.gnome.org/show_bug.cgi?id=607061 + // See wrap_gtk_plug_embedded in PluginModuleChild.cpp. + g_object_set_data(G_OBJECT(socket_window), + "moz-existed-before-set-window", + GUINT_TO_POINTER(1)); + } + } + + if (aWindow.visualID != X11None + && gtk_check_version(2, 12, 10) != nullptr) { // older + // Workaround for a bug in Gtk+ (prior to 2.12.10) where deleting + // a foreign GdkColormap will also free the XColormap. + // http://git.gnome.org/browse/gtk+/log/gdk/x11/gdkcolor-x11.c?id=GTK_2_12_10 + GdkVisual *gdkvisual = gdkx_visual_get(aWindow.visualID); + GdkColormap *gdkcolor = + gdk_x11_colormap_foreign_new(gdkvisual, aWindow.colormap); + + if (g_object_get_data(G_OBJECT(gdkcolor), "moz-have-extra-ref")) { + // We already have a ref to keep the object alive. + g_object_unref(gdkcolor); + } else { + // leak and mark as already leaked + g_object_set_data(G_OBJECT(gdkcolor), + "moz-have-extra-ref", GUINT_TO_POINTER(1)); + } + } + } +#endif + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Answer_SetWindow w=<x=%d,y=%d, w=%d,h=%d>, clip=<l=%d,t=%d,r=%d,b=%d>", + this, mWindow.x, mWindow.y, mWindow.width, mWindow.height, + mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, mWindow.clipRect.bottom)); + + if (mPluginIface->setwindow) + (void) mPluginIface->setwindow(&mData, &mWindow); + +#elif defined(OS_WIN) + switch (aWindow.type) { + case NPWindowTypeWindow: + { + // This check is now done in PluginInstanceParent before this call, so + // we should never see it here. + MOZ_ASSERT(!(GetQuirks() & QUIRK_QUICKTIME_AVOID_SETWINDOW) || + aWindow.width != 0 || aWindow.height != 0); + + MOZ_ASSERT(mPluginWindowHWND, + "Child plugin window must exist before call to SetWindow"); + + HWND parentHWND = reinterpret_cast<HWND>(aWindow.window); + if (mPluginWindowHWND != parentHWND) { + mPluginParentHWND = parentHWND; + ShowWindow(mPluginWindowHWND, SW_SHOWNA); + } + + SizePluginWindow(aWindow.width, aWindow.height); + + mWindow.window = (void*)mPluginWindowHWND; + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.type = aWindow.type; + mContentsScaleFactor = aWindow.contentsScaleFactor; + + if (mPluginIface->setwindow) { + SetProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty, (HANDLE)1); + (void) mPluginIface->setwindow(&mData, &mWindow); + WNDPROC wndProc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC)); + if (wndProc != PluginWindowProc) { + mPluginWndProc = reinterpret_cast<WNDPROC>( + SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(PluginWindowProc))); + NS_ASSERTION(mPluginWndProc != PluginWindowProc, "WTF?"); + } + RemoveProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty); + HookSetWindowLongPtr(); + } + } + break; + + default: + NS_NOTREACHED("Bad plugin window type."); + return false; + break; + } + +#elif defined(XP_MACOSX) + + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.clipRect = aWindow.clipRect; + mWindow.type = aWindow.type; + mContentsScaleFactor = aWindow.contentsScaleFactor; + + if (mShContext) { + // Release the shared context so that it is reallocated + // with the new size. + ::CGContextRelease(mShContext); + mShContext = nullptr; + } + + if (mPluginIface->setwindow) + (void) mPluginIface->setwindow(&mData, &mWindow); + +#elif defined(ANDROID) + // TODO: Need Android impl +#elif defined(MOZ_WIDGET_UIKIT) + // Don't care +#else +# error Implement me for your OS +#endif + + return true; +} + +bool +PluginInstanceChild::Initialize() +{ +#ifdef MOZ_WIDGET_GTK + NPError rv; + + if (mWsInfo.display) { + // Already initialized + return false; + } + + // Request for windowless plugins is set in newp(), before this call. + if (mWindow.type == NPWindowTypeWindow) { + AnswerNPP_GetValue_NPPVpluginNeedsXEmbed(&mXEmbed, &rv); + + // Set up Xt loop for windowed plugins without XEmbed support + if (!mXEmbed) { + xt_client_xloop_create(); + } + } + + // Use default GTK display for XEmbed and windowless plugins + if (mXEmbed || mWindow.type != NPWindowTypeWindow) { + mWsInfo.display = DefaultXDisplay(); + } + else { + mWsInfo.display = xt_client_get_display(); + } +#endif + + return true; +} + +bool +PluginInstanceChild::RecvHandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, + const bool& aIsConsumed) +{ +#if defined(OS_WIN) + const WinNativeKeyEventData* eventData = + static_cast<const WinNativeKeyEventData*>(aKeyEventData); + switch (eventData->mMessage) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + mLastKeyEventConsumed = aIsConsumed; + break; + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + // If preceding keydown or keyup event is consumed by the chrome + // process, we should consume WM_*CHAR messages too. + if (mLastKeyEventConsumed) { + return true; + } + default: + MOZ_CRASH("Needs to handle all messages posted to the parent"); + } +#endif // #if defined(OS_WIN) + + // Unknown key input shouldn't be sent to plugin for security. + // XXX Is this possible if a plugin process which posted the message + // already crashed and this plugin process is recreated? + if (NS_WARN_IF(!mPostingKeyEvents && !mPostingKeyEventsOutdated)) { + return true; + } + + // If there is outdated posting key events, we should consume the key + // events. + if (mPostingKeyEventsOutdated) { + mPostingKeyEventsOutdated--; + return true; + } + + mPostingKeyEvents--; + + // If composition has been started after posting the key event, + // we should discard the event since if we send the event to plugin, + // the plugin may be confused and the result may be broken because + // the event order is shuffled. + if (aIsConsumed || sIsIMEComposing) { + return true; + } + +#if defined(OS_WIN) + UINT message = 0; + switch (eventData->mMessage) { + case WM_KEYDOWN: + message = MOZ_WM_KEYDOWN; + break; + case WM_SYSKEYDOWN: + message = MOZ_WM_SYSKEYDOWN; + break; + case WM_KEYUP: + message = MOZ_WM_KEYUP; + break; + case WM_SYSKEYUP: + message = MOZ_WM_SYSKEYUP; + break; + case WM_CHAR: + message = MOZ_WM_CHAR; + break; + case WM_SYSCHAR: + message = MOZ_WM_SYSCHAR; + break; + case WM_DEADCHAR: + message = MOZ_WM_DEADCHAR; + break; + case WM_SYSDEADCHAR: + message = MOZ_WM_SYSDEADCHAR; + break; + default: + MOZ_CRASH("Needs to handle all messages posted to the parent"); + } + PluginWindowProcInternal(mPluginWindowHWND, message, + eventData->mWParam, eventData->mLParam); +#endif + return true; +} + +#if defined(OS_WIN) + +static const TCHAR kWindowClassName[] = TEXT("GeckoPluginWindow"); +static const TCHAR kPluginInstanceChildProperty[] = TEXT("PluginInstanceChildProperty"); +static const TCHAR kFlashThrottleProperty[] = TEXT("MozillaFlashThrottleProperty"); + +// static +bool +PluginInstanceChild::RegisterWindowClass() +{ + static bool alreadyRegistered = false; + if (alreadyRegistered) + return true; + + alreadyRegistered = true; + + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = DummyWindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(nullptr); + wcex.hIcon = 0; + wcex.hCursor = 0; + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = kWindowClassName; + wcex.hIconSm = 0; + + return RegisterClassEx(&wcex) ? true : false; +} + +bool +PluginInstanceChild::CreatePluginWindow() +{ + // already initialized + if (mPluginWindowHWND) + return true; + + if (!RegisterWindowClass()) + return false; + + mPluginWindowHWND = + CreateWindowEx(WS_EX_LEFT | WS_EX_LTRREADING | + WS_EX_NOPARENTNOTIFY | // XXXbent Get rid of this! + WS_EX_RIGHTSCROLLBAR, + kWindowClassName, 0, + WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, + 0, 0, nullptr, 0, GetModuleHandle(nullptr), 0); + if (!mPluginWindowHWND) + return false; + if (!SetProp(mPluginWindowHWND, kPluginInstanceChildProperty, this)) + return false; + + // Apparently some plugins require an ASCII WndProc. + SetWindowLongPtrA(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(DefWindowProcA)); + + return true; +} + +void +PluginInstanceChild::DestroyPluginWindow() +{ + if (mPluginWindowHWND) { + // Unsubclass the window. + WNDPROC wndProc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC)); + // Removed prior to SetWindowLongPtr, see HookSetWindowLongPtr. + RemoveProp(mPluginWindowHWND, kPluginInstanceChildProperty); + if (wndProc == PluginWindowProc) { + NS_ASSERTION(mPluginWndProc, "Should have old proc here!"); + SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(mPluginWndProc)); + mPluginWndProc = 0; + } + DestroyWindow(mPluginWindowHWND); + mPluginWindowHWND = 0; + } +} + +void +PluginInstanceChild::SizePluginWindow(int width, + int height) +{ + if (mPluginWindowHWND) { + mPluginSize.x = width; + mPluginSize.y = height; + SetWindowPos(mPluginWindowHWND, nullptr, 0, 0, width, height, + SWP_NOZORDER | SWP_NOREPOSITION); + } +} + +// See chromium's webplugin_delegate_impl.cc for explanation of this function. +// static +LRESULT CALLBACK +PluginInstanceChild::DummyWindowProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + return CallWindowProc(DefWindowProc, hWnd, message, wParam, lParam); +} + +// static +LRESULT CALLBACK +PluginInstanceChild::PluginWindowProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + return mozilla::CallWindowProcCrashProtected(PluginWindowProcInternal, hWnd, message, wParam, lParam); +} + +// static +LRESULT CALLBACK +PluginInstanceChild::PluginWindowProcInternal(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + NS_ASSERTION(!mozilla::ipc::MessageChannel::IsPumpingMessages(), + "Failed to prevent a nonqueued message from running!"); + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>( + GetProp(hWnd, kPluginInstanceChildProperty)); + if (!self) { + NS_NOTREACHED("Badness!"); + return 0; + } + + NS_ASSERTION(self->mPluginWindowHWND == hWnd, "Wrong window!"); + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, "Self-referential windowproc. Infinite recursion will happen soon."); + + bool isIMECompositionMessage = false; + switch (message) { + // Adobe's shockwave positions the plugin window relative to the browser + // frame when it initializes. With oopp disabled, this wouldn't have an + // effect. With oopp, GeckoPluginWindow is a child of the parent plugin + // window, so the move offsets the child within the parent. Generally + // we don't want plugins moving or sizing our window, so we prevent + // these changes here. + case WM_WINDOWPOSCHANGING: { + WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam); + if (pos && + (!(pos->flags & SWP_NOMOVE) || !(pos->flags & SWP_NOSIZE))) { + pos->x = pos->y = 0; + pos->cx = self->mPluginSize.x; + pos->cy = self->mPluginSize.y; + LRESULT res = CallWindowProc(self->mPluginWndProc, + hWnd, message, wParam, lParam); + pos->x = pos->y = 0; + pos->cx = self->mPluginSize.x; + pos->cy = self->mPluginSize.y; + return res; + } + break; + } + + case WM_SETFOCUS: + // If this gets focus, ensure that there is no pending key events. + // Even if there were, we should ignore them for performance reason. + // Although, such case shouldn't occur. + NS_WARNING_ASSERTION(self->mPostingKeyEvents == 0, + "pending events"); + self->mPostingKeyEvents = 0; + self->mLastKeyEventConsumed = false; + break; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + if (self->MaybePostKeyMessage(message, wParam, lParam)) { + // If PreHandleKeyMessage() posts the message to the parent + // process, we need to wait RecvOnKeyEventHandledBeforePlugin() + // to be called. + return 0; // Consume current message temporarily. + } + break; + + case MOZ_WM_KEYDOWN: + message = WM_KEYDOWN; + break; + case MOZ_WM_SYSKEYDOWN: + message = WM_SYSKEYDOWN; + break; + case MOZ_WM_KEYUP: + message = WM_KEYUP; + break; + case MOZ_WM_SYSKEYUP: + message = WM_SYSKEYUP; + break; + case MOZ_WM_CHAR: + message = WM_CHAR; + break; + case MOZ_WM_SYSCHAR: + message = WM_SYSCHAR; + break; + case MOZ_WM_DEADCHAR: + message = WM_DEADCHAR; + break; + case MOZ_WM_SYSDEADCHAR: + message = WM_SYSDEADCHAR; + break; + + case WM_IME_STARTCOMPOSITION: + isIMECompositionMessage = true; + sIsIMEComposing = true; + break; + case WM_IME_ENDCOMPOSITION: + isIMECompositionMessage = true; + sIsIMEComposing = false; + break; + case WM_IME_COMPOSITION: + isIMECompositionMessage = true; + // XXX Some old IME may not send WM_IME_COMPOSITION_START or + // WM_IME_COMPSOITION_END properly. So, we need to check + // WM_IME_COMPSOITION and if it includes commit string. + sIsIMEComposing = !(lParam & GCS_RESULTSTR); + break; + + // The plugin received keyboard focus, let the parent know so the dom + // is up to date. + case WM_MOUSEACTIVATE: + self->CallPluginFocusChange(true); + break; + } + + // When a composition is committed, there may be pending key + // events which were posted to the parent process before starting + // the composition. Then, we shouldn't handle it since they are + // now outdated. + if (isIMECompositionMessage && !sIsIMEComposing) { + self->mPostingKeyEventsOutdated += self->mPostingKeyEvents; + self->mPostingKeyEvents = 0; + } + + // Prevent lockups due to plugins making rpc calls when the parent + // is making a synchronous SendMessage call to the child window. Add + // more messages as needed. + if ((InSendMessageEx(nullptr)&(ISMEX_REPLIED|ISMEX_SEND)) == ISMEX_SEND) { + switch(message) { + case WM_CHILDACTIVATE: + case WM_KILLFOCUS: + ReplyMessage(0); + break; + } + } + + if (message == WM_KILLFOCUS) { + self->CallPluginFocusChange(false); + } + + if (message == WM_USER+1 && + (self->GetQuirks() & QUIRK_FLASH_THROTTLE_WMUSER_EVENTS)) { + self->FlashThrottleMessage(hWnd, message, wParam, lParam, true); + return 0; + } + + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, + "Self-referential windowproc happened inside our hook proc. " + "Infinite recursion will happen soon."); + + LRESULT res = CallWindowProc(self->mPluginWndProc, hWnd, message, wParam, + lParam); + + // Make sure capture is released by the child on mouse events. Fixes a + // problem with flash full screen mode mouse input. Appears to be + // caused by a bug in flash, since we are not setting the capture + // on the window. + if (message == WM_LBUTTONDOWN && + self->GetQuirks() & QUIRK_FLASH_FIXUP_MOUSE_CAPTURE) { + wchar_t szClass[26]; + HWND hwnd = GetForegroundWindow(); + if (hwnd && GetClassNameW(hwnd, szClass, + sizeof(szClass)/sizeof(char16_t)) && + !wcscmp(szClass, kFlashFullscreenClass)) { + ReleaseCapture(); + SetFocus(hwnd); + } + } + + if (message == WM_CLOSE) { + self->DestroyPluginWindow(); + } + + if (message == WM_NCDESTROY) { + RemoveProp(hWnd, kPluginInstanceChildProperty); + } + + return res; +} + +bool +PluginInstanceChild::ShouldPostKeyMessage(UINT message, + WPARAM wParam, + LPARAM lParam) +{ + // If there is a composition, we shouldn't post the key message to the + // parent process because we cannot handle IME messages asynchronously. + // Therefore, if we posted key events to the parent process, the event + // order of the posted key events and IME events are shuffled. + if (sIsIMEComposing) { + return false; + } + + // If there are some pending keyboard events which are not handled in + // the parent process, we should post the message for avoiding to shuffle + // the key event order. + if (mPostingKeyEvents) { + return true; + } + + // If we are not waiting calls of RecvOnKeyEventHandledBeforePlugin(), + // we don't need to post WM_*CHAR messages. + switch (message) { + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + return false; + } + + // Otherwise, we should post key message which might match with a + // shortcut key. + ModifierKeyState modifierState; + if (!modifierState.MaybeMatchShortcutKey()) { + // For better UX, we shouldn't use IPC when user tries to + // input character(s). + return false; + } + + // Ignore modifier key events and keys already handled by IME. + switch (wParam) { + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + case VK_LWIN: + case VK_RWIN: + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + // Following virtual keycodes shouldn't come with WM_(SYS)KEY* message + // but check it for avoiding unnecessary cross process communication. + case VK_LSHIFT: + case VK_RSHIFT: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_LMENU: + case VK_RMENU: + case VK_PROCESSKEY: + case VK_PACKET: + case 0xFF: // 0xFF could be sent with unidentified key by the layout. + return false; + default: + break; + } + return true; +} + +bool +PluginInstanceChild::MaybePostKeyMessage(UINT message, + WPARAM wParam, + LPARAM lParam) +{ + if (!ShouldPostKeyMessage(message, wParam, lParam)) { + return false; + } + + ModifierKeyState modifierState; + WinNativeKeyEventData winNativeKeyData(message, wParam, lParam, + modifierState); + NativeEventData nativeKeyData; + nativeKeyData.Copy(winNativeKeyData); + if (NS_WARN_IF(!SendOnWindowedPluginKeyEvent(nativeKeyData))) { + return false; + } + + mPostingKeyEvents++; + return true; +} + +/* set window long ptr hook for flash */ + +/* + * 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. + */ + +#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 + +extern LRESULT CALLBACK +NeuteredWindowProc(HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam); + +const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc"; + +// static +bool +PluginInstanceChild::SetWindowLongHookCheck(HWND hWnd, + int nIndex, + LONG_PTR newLong) +{ + // Let this go through if it's not a subclass + if (nIndex != GWLP_WNDPROC || + // if it's not a subclassed plugin window + !GetProp(hWnd, kPluginInstanceChildProperty) || + // if we're not disabled + GetProp(hWnd, kPluginIgnoreSubclassProperty) || + // if the subclass is set to a known procedure + newLong == reinterpret_cast<LONG_PTR>(PluginWindowProc) || + newLong == reinterpret_cast<LONG_PTR>(NeuteredWindowProc) || + newLong == reinterpret_cast<LONG_PTR>(DefWindowProcA) || + newLong == reinterpret_cast<LONG_PTR>(DefWindowProcW) || + // if the subclass is a WindowsMessageLoop subclass restore + GetProp(hWnd, kOldWndProcProp)) + return true; + // prevent the subclass + return false; +} + +#ifdef _WIN64 +LONG_PTR WINAPI +PluginInstanceChild::SetWindowLongPtrAHook(HWND hWnd, + int nIndex, + LONG_PTR newLong) +#else +LONG WINAPI +PluginInstanceChild::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 + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>( + GetProp(hWnd, kPluginInstanceChildProperty)); + + // Hook our subclass back up, just like we do on setwindow. + WNDPROC currentProc = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (currentProc != PluginWindowProc) { + self->mPluginWndProc = + reinterpret_cast<WNDPROC>(sUser32SetWindowLongWHookStub(hWnd, nIndex, + reinterpret_cast<LONG_PTR>(PluginWindowProc))); + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, "Infinite recursion coming up!"); + } + return proc; +} + +#ifdef _WIN64 +LONG_PTR WINAPI +PluginInstanceChild::SetWindowLongPtrWHook(HWND hWnd, + int nIndex, + LONG_PTR newLong) +#else +LONG WINAPI +PluginInstanceChild::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 + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>( + GetProp(hWnd, kPluginInstanceChildProperty)); + + // Hook our subclass back up, just like we do on setwindow. + WNDPROC currentProc = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (currentProc != PluginWindowProc) { + self->mPluginWndProc = + reinterpret_cast<WNDPROC>(sUser32SetWindowLongWHookStub(hWnd, nIndex, + reinterpret_cast<LONG_PTR>(PluginWindowProc))); + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, "Infinite recursion coming up!"); + } + return proc; +} + +void +PluginInstanceChild::HookSetWindowLongPtr() +{ + if (!(GetQuirks() & QUIRK_FLASH_HOOK_SETLONGPTR)) + return; + + 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 +} + +class SetCaptureHookData +{ +public: + explicit SetCaptureHookData(HWND aHwnd) + : mHwnd(aHwnd) + , mHaveRect(false) + { + MOZ_ASSERT(aHwnd); + mHaveRect = !!GetClientRect(aHwnd, &mCaptureRect); + } + + /** + * @return true if capture was released + */ + bool HandleMouseMsg(const MSG& aMsg) + { + // If the window belongs to Unity, the mouse button is up, and the mouse + // has moved outside the client rect of the Unity window, release capture. + if (aMsg.hwnd != mHwnd || !mHaveRect) { + return false; + } + if (aMsg.message != WM_MOUSEMOVE && aMsg.message != WM_LBUTTONUP) { + return false; + } + if ((aMsg.message == WM_MOUSEMOVE && (aMsg.wParam & MK_LBUTTON))) { + return false; + } + POINT pt = { GET_X_LPARAM(aMsg.lParam), GET_Y_LPARAM(aMsg.lParam) }; + if (PtInRect(&mCaptureRect, pt)) { + return false; + } + return !!ReleaseCapture(); + } + + bool IsUnityLosingCapture(const CWPSTRUCT& aInfo) const + { + return aInfo.message == WM_CAPTURECHANGED && + aInfo.hwnd == mHwnd; + } + +private: + HWND mHwnd; + bool mHaveRect; + RECT mCaptureRect; +}; + +static StaticAutoPtr<SetCaptureHookData> sSetCaptureHookData; +typedef HWND (WINAPI* User32SetCapture)(HWND); +static User32SetCapture sUser32SetCaptureHookStub = nullptr; + +HWND WINAPI +PluginInstanceChild::SetCaptureHook(HWND aHwnd) +{ + // Don't do anything unless aHwnd belongs to Unity + wchar_t className[256] = {0}; + int numChars = GetClassNameW(aHwnd, className, ArrayLength(className)); + NS_NAMED_LITERAL_STRING(unityClassName, "Unity.WebPlayer"); + if (numChars == unityClassName.Length() && unityClassName == wwc(className)) { + sSetCaptureHookData = new SetCaptureHookData(aHwnd); + } + return sUser32SetCaptureHookStub(aHwnd); +} + +void +PluginInstanceChild::SetUnityHooks() +{ + if (!(GetQuirks() & QUIRK_UNITY_FIXUP_MOUSE_CAPTURE)) { + return; + } + + sUser32Intercept.Init("user32.dll"); + if (!sUser32SetCaptureHookStub) { + sUser32Intercept.AddHook("SetCapture", + reinterpret_cast<intptr_t>(SetCaptureHook), + (void**) &sUser32SetCaptureHookStub); + } + if (!mUnityGetMessageHook) { + mUnityGetMessageHook = SetWindowsHookEx(WH_GETMESSAGE, + &UnityGetMessageHookProc, NULL, + GetCurrentThreadId()); + } + if (!mUnitySendMessageHook) { + mUnitySendMessageHook = SetWindowsHookEx(WH_CALLWNDPROC, + &UnitySendMessageHookProc, + NULL, GetCurrentThreadId()); + } +} + +void +PluginInstanceChild::ClearUnityHooks() +{ + if (mUnityGetMessageHook) { + UnhookWindowsHookEx(mUnityGetMessageHook); + mUnityGetMessageHook = NULL; + } + if (mUnitySendMessageHook) { + UnhookWindowsHookEx(mUnitySendMessageHook); + mUnitySendMessageHook = NULL; + } + sSetCaptureHookData = nullptr; +} + +LRESULT CALLBACK +PluginInstanceChild::UnityGetMessageHookProc(int aCode, WPARAM aWparam, + LPARAM aLParam) +{ + if (aCode >= 0) { + MSG* info = reinterpret_cast<MSG*>(aLParam); + MOZ_ASSERT(info); + if (sSetCaptureHookData && sSetCaptureHookData->HandleMouseMsg(*info)) { + sSetCaptureHookData = nullptr; + } + } + + return CallNextHookEx(0, aCode, aWparam, aLParam); +} + +LRESULT CALLBACK +PluginInstanceChild::UnitySendMessageHookProc(int aCode, WPARAM aWparam, + LPARAM aLParam) +{ + if (aCode >= 0) { + CWPSTRUCT* info = reinterpret_cast<CWPSTRUCT*>(aLParam); + MOZ_ASSERT(info); + if (sSetCaptureHookData && + sSetCaptureHookData->IsUnityLosingCapture(*info)) { + sSetCaptureHookData = nullptr; + } + } + + return CallNextHookEx(0, aCode, aWparam, aLParam); +} + +/* windowless track popup menu helpers */ + +BOOL +WINAPI +PluginInstanceChild::TrackPopupHookProc(HMENU hMenu, + UINT uFlags, + int x, + int y, + int nReserved, + HWND hWnd, + CONST RECT *prcRect) +{ + if (!sUser32TrackPopupMenuStub) { + NS_ERROR("TrackPopupMenu stub isn't set! Badness!"); + return 0; + } + + // Only change the parent when we know this is a context on the plugin + // surface within the browser. Prevents resetting the parent on child ui + // displayed by plugins that have working parent-child relationships. + wchar_t szClass[21]; + bool haveClass = GetClassNameW(hWnd, szClass, ArrayLength(szClass)); + if (!haveClass || + (wcscmp(szClass, L"MozillaWindowClass") && + wcscmp(szClass, L"SWFlash_Placeholder"))) { + // Unrecognized parent + return sUser32TrackPopupMenuStub(hMenu, uFlags, x, y, nReserved, + hWnd, prcRect); + } + + // Called on an unexpected event, warn. + if (!sWinlessPopupSurrogateHWND) { + NS_WARNING( + "Untraced TrackPopupHookProc call! Menu might not work right!"); + return sUser32TrackPopupMenuStub(hMenu, uFlags, x, y, nReserved, + hWnd, prcRect); + } + + HWND surrogateHwnd = sWinlessPopupSurrogateHWND; + sWinlessPopupSurrogateHWND = nullptr; + + // Popups that don't use TPM_RETURNCMD expect a final command message + // when an item is selected and the context closes. Since we replace + // the parent, we need to forward this back to the real parent so it + // can act on the menu item selected. + bool isRetCmdCall = (uFlags & TPM_RETURNCMD); + + DWORD res = sUser32TrackPopupMenuStub(hMenu, uFlags|TPM_RETURNCMD, x, y, + nReserved, surrogateHwnd, prcRect); + + if (!isRetCmdCall && res) { + SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(res, 0), 0); + } + + return res; +} + +void +PluginInstanceChild::InitPopupMenuHook() +{ + if (!(GetQuirks() & QUIRK_WINLESS_TRACKPOPUP_HOOK) || + sUser32TrackPopupMenuStub) + return; + + // Note, once WindowsDllInterceptor is initialized for a module, + // it remains initialized for that particular module for it's + // lifetime. Additional instances are needed if other modules need + // to be hooked. + if (!sUser32TrackPopupMenuStub) { + sUser32Intercept.Init("user32.dll"); + sUser32Intercept.AddHook("TrackPopupMenu", reinterpret_cast<intptr_t>(TrackPopupHookProc), + (void**) &sUser32TrackPopupMenuStub); + } +} + +void +PluginInstanceChild::CreateWinlessPopupSurrogate() +{ + // already initialized + if (mWinlessPopupSurrogateHWND) + return; + + mWinlessPopupSurrogateHWND = + CreateWindowEx(WS_EX_NOPARENTNOTIFY, L"Static", nullptr, WS_POPUP, + 0, 0, 0, 0, nullptr, 0, GetModuleHandle(nullptr), 0); + if (!mWinlessPopupSurrogateHWND) { + NS_ERROR("CreateWindowEx failed for winless placeholder!"); + return; + } + + SendSetNetscapeWindowAsParent(mWinlessPopupSurrogateHWND); +} + +// static +HIMC +PluginInstanceChild::ImmGetContextProc(HWND aWND) +{ + if (!sCurrentPluginInstance) { + return sImm32ImmGetContextStub(aWND); + } + + wchar_t szClass[21]; + int haveClass = GetClassNameW(aWND, szClass, ArrayLength(szClass)); + if (!haveClass || wcscmp(szClass, L"SWFlash_PlaceholderX")) { + NS_WARNING("We cannot recongnize hooked window class"); + return sImm32ImmGetContextStub(aWND); + } + + return sHookIMC; +} + +// static +BOOL +PluginInstanceChild::ImmReleaseContextProc(HWND aWND, HIMC aIMC) +{ + if (aIMC == sHookIMC) { + return TRUE; + } + + return sImm32ImmReleaseContextStub(aWND, aIMC); +} + +// static +LONG +PluginInstanceChild::ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex, + LPVOID aBuf, DWORD aLen) +{ + if (aIMC != sHookIMC) { + return sImm32ImmGetCompositionStringStub(aIMC, aIndex, aBuf, aLen); + } + if (!sCurrentPluginInstance) { + return IMM_ERROR_GENERAL; + } + AutoTArray<uint8_t, 16> dist; + int32_t length = 0; // IMM_ERROR_NODATA + sCurrentPluginInstance->SendGetCompositionString(aIndex, &dist, &length); + if (length == IMM_ERROR_NODATA || length == IMM_ERROR_GENERAL) { + return length; + } + + if (aBuf && aLen >= static_cast<DWORD>(length)) { + memcpy(aBuf, dist.Elements(), length); + } + return length; +} + +// staitc +BOOL +PluginInstanceChild::ImmSetCandidateWindowProc(HIMC aIMC, LPCANDIDATEFORM aForm) +{ + if (aIMC != sHookIMC) { + return sImm32ImmSetCandidateWindowStub(aIMC, aForm); + } + + if (!sCurrentPluginInstance || + aForm->dwIndex != 0) { + return FALSE; + } + + CandidateWindowPosition position; + position.mPoint.x = aForm->ptCurrentPos.x; + position.mPoint.y = aForm->ptCurrentPos.y; + position.mExcludeRect = !!(aForm->dwStyle & CFS_EXCLUDE); + if (position.mExcludeRect) { + position.mRect.x = aForm->rcArea.left; + position.mRect.y = aForm->rcArea.top; + position.mRect.width = aForm->rcArea.right - aForm->rcArea.left; + position.mRect.height = aForm->rcArea.bottom - aForm->rcArea.top; + } + + sCurrentPluginInstance->SendSetCandidateWindow(position); + return TRUE; +} + +// static +BOOL +PluginInstanceChild::ImmNotifyIME(HIMC aIMC, DWORD aAction, DWORD aIndex, + DWORD aValue) +{ + if (aIMC != sHookIMC) { + return sImm32ImmNotifyIME(aIMC, aAction, aIndex, aValue); + } + + // We only supports NI_COMPOSITIONSTR because Flash uses it only + if (!sCurrentPluginInstance || + aAction != NI_COMPOSITIONSTR || + (aIndex != CPS_COMPLETE && aIndex != CPS_CANCEL)) { + return FALSE; + } + + sCurrentPluginInstance->SendRequestCommitOrCancel(aAction == CPS_COMPLETE); + return TRUE; +} + +void +PluginInstanceChild::InitImm32Hook() +{ + if (!(GetQuirks() & QUIRK_WINLESS_HOOK_IME)) { + return; + } + + if (sImm32ImmGetContextStub) { + return; + } + + // When using windowless plugin, IMM API won't work due ot OOP. + + sImm32Intercept.Init("imm32.dll"); + sImm32Intercept.AddHook( + "ImmGetContext", + reinterpret_cast<intptr_t>(ImmGetContextProc), + (void**)&sImm32ImmGetContextStub); + sImm32Intercept.AddHook( + "ImmReleaseContext", + reinterpret_cast<intptr_t>(ImmReleaseContextProc), + (void**)&sImm32ImmReleaseContextStub); + sImm32Intercept.AddHook( + "ImmGetCompositionStringW", + reinterpret_cast<intptr_t>(ImmGetCompositionStringProc), + (void**)&sImm32ImmGetCompositionStringStub); + sImm32Intercept.AddHook( + "ImmSetCandidateWindow", + reinterpret_cast<intptr_t>(ImmSetCandidateWindowProc), + (void**)&sImm32ImmSetCandidateWindowStub); + sImm32Intercept.AddHook( + "ImmNotifyIME", + reinterpret_cast<intptr_t>(ImmNotifyIME), + (void**)&sImm32ImmNotifyIME); +} + +void +PluginInstanceChild::DestroyWinlessPopupSurrogate() +{ + if (mWinlessPopupSurrogateHWND) + DestroyWindow(mWinlessPopupSurrogateHWND); + mWinlessPopupSurrogateHWND = nullptr; +} + +int16_t +PluginInstanceChild::WinlessHandleEvent(NPEvent& event) +{ + if (!mPluginIface->event) + return false; + + // Events that might generate nested event dispatch loops need + // special handling during delivery. + int16_t handled; + + HWND focusHwnd = nullptr; + + // TrackPopupMenu will fail if the parent window is not associated with + // our ui thread. So we hook TrackPopupMenu so we can hand in a surrogate + // parent created in the child process. + if ((GetQuirks() & QUIRK_WINLESS_TRACKPOPUP_HOOK) && // XXX turn on by default? + (event.event == WM_RBUTTONDOWN || // flash + event.event == WM_RBUTTONUP)) { // silverlight + sWinlessPopupSurrogateHWND = mWinlessPopupSurrogateHWND; + + // A little trick scrounged from chromium's code - set the focus + // to our surrogate parent so keyboard nav events go to the menu. + focusHwnd = SetFocus(mWinlessPopupSurrogateHWND); + } + + AutoRestore<PluginInstanceChild *> pluginInstance(sCurrentPluginInstance); + if (event.event == WM_IME_STARTCOMPOSITION || + event.event == WM_IME_COMPOSITION || + event.event == WM_KILLFOCUS) { + sCurrentPluginInstance = this; + } + + MessageLoop* loop = MessageLoop::current(); + AutoRestore<bool> modalLoop(loop->os_modal_loop()); + + handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&event)); + + sWinlessPopupSurrogateHWND = nullptr; + + if (IsWindow(focusHwnd)) { + SetFocus(focusHwnd); + } + + return handled; +} + +/* flash msg throttling helpers */ + +// Flash has the unfortunate habit of flooding dispatch loops with custom +// windowing events they use for timing. We throttle these by dropping the +// delivery priority below any other event, including pending ipc io +// notifications. We do this for both windowed and windowless controls. +// Note flash's windowless msg window can last longer than our instance, +// so we try to unhook when the window is destroyed and in NPP_Destroy. + +void +PluginInstanceChild::UnhookWinlessFlashThrottle() +{ + // We may have already unhooked + if (!mWinlessThrottleOldWndProc) + return; + + WNDPROC tmpProc = mWinlessThrottleOldWndProc; + mWinlessThrottleOldWndProc = nullptr; + + NS_ASSERTION(mWinlessHiddenMsgHWND, + "Missing mWinlessHiddenMsgHWND w/subclass set??"); + + // reset the subclass + SetWindowLongPtr(mWinlessHiddenMsgHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(tmpProc)); + + // Remove our instance prop + RemoveProp(mWinlessHiddenMsgHWND, kFlashThrottleProperty); + mWinlessHiddenMsgHWND = nullptr; +} + +// static +LRESULT CALLBACK +PluginInstanceChild::WinlessHiddenFlashWndProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>( + GetProp(hWnd, kFlashThrottleProperty)); + if (!self) { + NS_NOTREACHED("Badness!"); + return 0; + } + + NS_ASSERTION(self->mWinlessThrottleOldWndProc, + "Missing subclass procedure!!"); + + // Throttle + if (message == WM_USER+1) { + self->FlashThrottleMessage(hWnd, message, wParam, lParam, false); + return 0; + } + + // Unhook + if (message == WM_CLOSE || message == WM_NCDESTROY) { + WNDPROC tmpProc = self->mWinlessThrottleOldWndProc; + self->UnhookWinlessFlashThrottle(); + LRESULT res = CallWindowProc(tmpProc, hWnd, message, wParam, lParam); + return res; + } + + return CallWindowProc(self->mWinlessThrottleOldWndProc, + hWnd, message, wParam, lParam); +} + +// Enumerate all thread windows looking for flash's hidden message window. +// Once we find it, sub class it so we can throttle user msgs. +// static +BOOL CALLBACK +PluginInstanceChild::EnumThreadWindowsCallback(HWND hWnd, + LPARAM aParam) +{ + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>(aParam); + if (!self) { + NS_NOTREACHED("Enum befuddled!"); + return FALSE; + } + + wchar_t className[64]; + if (!GetClassNameW(hWnd, className, sizeof(className)/sizeof(char16_t))) + return TRUE; + + if (!wcscmp(className, L"SWFlash_PlaceholderX")) { + WNDPROC oldWndProc = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + // Only set this if we haven't already. + if (oldWndProc != WinlessHiddenFlashWndProc) { + if (self->mWinlessThrottleOldWndProc) { + NS_WARNING("mWinlessThrottleWndProc already set???"); + return FALSE; + } + // Subsclass and store self as a property + self->mWinlessHiddenMsgHWND = hWnd; + self->mWinlessThrottleOldWndProc = + reinterpret_cast<WNDPROC>(SetWindowLongPtr(hWnd, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(WinlessHiddenFlashWndProc))); + SetProp(hWnd, kFlashThrottleProperty, self); + NS_ASSERTION(self->mWinlessThrottleOldWndProc, + "SetWindowLongPtr failed?!"); + } + // Return no matter what once we find the right window. + return FALSE; + } + + return TRUE; +} + +void +PluginInstanceChild::SetupFlashMsgThrottle() +{ + if (mWindow.type == NPWindowTypeDrawable) { + // Search for the flash hidden message window and subclass it. Only + // search for flash windows belonging to our ui thread! + if (mWinlessThrottleOldWndProc) + return; + EnumThreadWindows(GetCurrentThreadId(), EnumThreadWindowsCallback, + reinterpret_cast<LPARAM>(this)); + } + else { + // Already setup through quirks and the subclass. + return; + } +} + +WNDPROC +PluginInstanceChild::FlashThrottleAsyncMsg::GetProc() +{ + if (mInstance) { + return mWindowed ? mInstance->mPluginWndProc : + mInstance->mWinlessThrottleOldWndProc; + } + return nullptr; +} + +NS_IMETHODIMP +PluginInstanceChild::FlashThrottleAsyncMsg::Run() +{ + RemoveFromAsyncList(); + + // GetProc() checks mInstance, and pulls the procedure from + // PluginInstanceChild. We don't transport sub-class procedure + // ptrs around in FlashThrottleAsyncMsg msgs. + if (!GetProc()) + return NS_OK; + + // deliver the event to flash + CallWindowProc(GetProc(), GetWnd(), GetMsg(), GetWParam(), GetLParam()); + return NS_OK; +} + +void +PluginInstanceChild::FlashThrottleMessage(HWND aWnd, + UINT aMsg, + WPARAM aWParam, + LPARAM aLParam, + bool isWindowed) +{ + // We reuse ChildAsyncCall so we get the cancelation work + // that's done in Destroy. + RefPtr<FlashThrottleAsyncMsg> task = + new FlashThrottleAsyncMsg(this, aWnd, aMsg, aWParam, + aLParam, isWindowed); + { + MutexAutoLock lock(mAsyncCallMutex); + mPendingAsyncCalls.AppendElement(task); + } + MessageLoop::current()->PostDelayedTask(task.forget(), + kFlashWMUSERMessageThrottleDelayMs); +} + +#endif // OS_WIN + +bool +PluginInstanceChild::AnswerSetPluginFocus() +{ + MOZ_LOG(GetPluginLog(), LogLevel::Debug, ("%s", FULLFUNCTION)); + +#if defined(OS_WIN) + // Parent is letting us know the dom set focus to the plugin. Note, + // focus can change during transit in certain edge cases, for example + // when a button click brings up a full screen window. Since we send + // this in response to a WM_SETFOCUS event on our parent, the parent + // should have focus when we receive this. If not, ignore the call. + if (::GetFocus() == mPluginWindowHWND || + ((GetQuirks() & QUIRK_SILVERLIGHT_FOCUS_CHECK_PARENT) && + (::GetFocus() != mPluginParentHWND))) + return true; + ::SetFocus(mPluginWindowHWND); + return true; +#else + NS_NOTREACHED("PluginInstanceChild::AnswerSetPluginFocus not implemented!"); + return false; +#endif +} + +bool +PluginInstanceChild::AnswerUpdateWindow() +{ + MOZ_LOG(GetPluginLog(), LogLevel::Debug, ("%s", FULLFUNCTION)); + +#if defined(OS_WIN) + if (mPluginWindowHWND) { + RECT rect; + if (GetUpdateRect(GetParent(mPluginWindowHWND), &rect, FALSE)) { + ::InvalidateRect(mPluginWindowHWND, &rect, FALSE); + } + UpdateWindow(mPluginWindowHWND); + } + return true; +#else + NS_NOTREACHED("PluginInstanceChild::AnswerUpdateWindow not implemented!"); + return false; +#endif +} + +bool +PluginInstanceChild::RecvNPP_DidComposite() +{ + if (mPluginIface->didComposite) { + mPluginIface->didComposite(GetNPP()); + } + return true; +} + +PPluginScriptableObjectChild* +PluginInstanceChild::AllocPPluginScriptableObjectChild() +{ + AssertPluginThread(); + return new PluginScriptableObjectChild(Proxy); +} + +bool +PluginInstanceChild::DeallocPPluginScriptableObjectChild( + PPluginScriptableObjectChild* aObject) +{ + AssertPluginThread(); + delete aObject; + return true; +} + +bool +PluginInstanceChild::RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectChild* aActor) +{ + AssertPluginThread(); + + // This is only called in response to the parent process requesting the + // creation of an actor. This actor will represent an NPObject that is + // created by the browser and returned to the plugin. + PluginScriptableObjectChild* actor = + static_cast<PluginScriptableObjectChild*>(aActor); + NS_ASSERTION(!actor->GetObject(false), "Actor already has an object?!"); + + actor->InitializeProxy(); + NS_ASSERTION(actor->GetObject(false), "Actor should have an object!"); + + return true; +} + +bool +PluginInstanceChild::RecvPBrowserStreamConstructor( + PBrowserStreamChild* aActor, + const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + PStreamNotifyChild* notifyData, + const nsCString& headers) +{ + return true; +} + +NPError +PluginInstanceChild::DoNPP_NewStream(BrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, + uint16_t* stype) +{ + AssertPluginThread(); + AutoStackHelper guard(this); + NPError rv = actor->StreamConstructed(mimeType, seekable, stype); + return rv; +} + +bool +PluginInstanceChild::AnswerNPP_NewStream(PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, + NPError* rv, + uint16_t* stype) +{ + *rv = DoNPP_NewStream(static_cast<BrowserStreamChild*>(actor), mimeType, + seekable, stype); + return true; +} + +class NewStreamAsyncCall : public ChildAsyncCall +{ +public: + NewStreamAsyncCall(PluginInstanceChild* aInstance, + BrowserStreamChild* aBrowserStreamChild, + const nsCString& aMimeType, + const bool aSeekable) + : ChildAsyncCall(aInstance, nullptr, nullptr) + , mBrowserStreamChild(aBrowserStreamChild) + , mMimeType(aMimeType) + , mSeekable(aSeekable) + { + } + + NS_IMETHOD Run() override + { + RemoveFromAsyncList(); + + uint16_t stype = NP_NORMAL; + NPError rv = mInstance->DoNPP_NewStream(mBrowserStreamChild, mMimeType, + mSeekable, &stype); + DebugOnly<bool> sendOk = + mBrowserStreamChild->SendAsyncNPP_NewStreamResult(rv, stype); + MOZ_ASSERT(sendOk); + return NS_OK; + } + +private: + BrowserStreamChild* mBrowserStreamChild; + const nsCString mMimeType; + const bool mSeekable; +}; + +bool +PluginInstanceChild::RecvAsyncNPP_NewStream(PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable) +{ + // Reusing ChildAsyncCall so that the task is cancelled properly on Destroy + BrowserStreamChild* child = static_cast<BrowserStreamChild*>(actor); + RefPtr<NewStreamAsyncCall> task = + new NewStreamAsyncCall(this, child, mimeType, seekable); + PostChildAsyncCall(task.forget()); + return true; +} + +PBrowserStreamChild* +PluginInstanceChild::AllocPBrowserStreamChild(const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + PStreamNotifyChild* notifyData, + const nsCString& headers) +{ + AssertPluginThread(); + return new BrowserStreamChild(this, url, length, lastmodified, + static_cast<StreamNotifyChild*>(notifyData), + headers); +} + +bool +PluginInstanceChild::DeallocPBrowserStreamChild(PBrowserStreamChild* stream) +{ + AssertPluginThread(); + delete stream; + return true; +} + +PPluginStreamChild* +PluginInstanceChild::AllocPPluginStreamChild(const nsCString& mimeType, + const nsCString& target, + NPError* result) +{ + NS_RUNTIMEABORT("not callable"); + return nullptr; +} + +bool +PluginInstanceChild::DeallocPPluginStreamChild(PPluginStreamChild* stream) +{ + AssertPluginThread(); + delete stream; + return true; +} + +PStreamNotifyChild* +PluginInstanceChild::AllocPStreamNotifyChild(const nsCString& url, + const nsCString& target, + const bool& post, + const nsCString& buffer, + const bool& file, + NPError* result) +{ + AssertPluginThread(); + NS_RUNTIMEABORT("not reached"); + return nullptr; +} + +void +StreamNotifyChild::ActorDestroy(ActorDestroyReason why) +{ + if (AncestorDeletion == why && mBrowserStream) { + NS_ERROR("Pending NPP_URLNotify not called when closing an instance."); + + // reclaim responsibility for deleting ourself + mBrowserStream->mStreamNotify = nullptr; + mBrowserStream = nullptr; + } +} + +void +StreamNotifyChild::SetAssociatedStream(BrowserStreamChild* bs) +{ + NS_ASSERTION(!mBrowserStream, "Two streams for one streamnotify?"); + + mBrowserStream = bs; +} + +bool +StreamNotifyChild::Recv__delete__(const NPReason& reason) +{ + AssertPluginThread(); + + if (mBrowserStream) + mBrowserStream->NotifyPending(); + else + NPP_URLNotify(reason); + + return true; +} + +bool +StreamNotifyChild::RecvRedirectNotify(const nsCString& url, const int32_t& status) +{ + // NPP_URLRedirectNotify requires a non-null closure. Since core logic + // assumes that all out-of-process notify streams have non-null closure + // data it will assume that the plugin was notified at this point and + // expect a response otherwise the redirect will hang indefinitely. + if (!mClosure) { + SendRedirectNotifyResponse(false); + } + + PluginInstanceChild* instance = static_cast<PluginInstanceChild*>(Manager()); + if (instance->mPluginIface->urlredirectnotify) + instance->mPluginIface->urlredirectnotify(instance->GetNPP(), url.get(), status, mClosure); + + return true; +} + +void +StreamNotifyChild::NPP_URLNotify(NPReason reason) +{ + PluginInstanceChild* instance = static_cast<PluginInstanceChild*>(Manager()); + + if (mClosure) + instance->mPluginIface->urlnotify(instance->GetNPP(), mURL.get(), + reason, mClosure); +} + +bool +PluginInstanceChild::DeallocPStreamNotifyChild(PStreamNotifyChild* notifyData) +{ + AssertPluginThread(); + + if (!static_cast<StreamNotifyChild*>(notifyData)->mBrowserStream) + delete notifyData; + return true; +} + +PluginScriptableObjectChild* +PluginInstanceChild::GetActorForNPObject(NPObject* aObject) +{ + AssertPluginThread(); + NS_ASSERTION(aObject, "Null pointer!"); + + if (aObject->_class == PluginScriptableObjectChild::GetClass()) { + // One of ours! It's a browser-provided object. + ChildNPObject* object = static_cast<ChildNPObject*>(aObject); + NS_ASSERTION(object->parent, "Null actor!"); + return object->parent; + } + + PluginScriptableObjectChild* actor = + PluginScriptableObjectChild::GetActorForNPObject(aObject); + if (actor) { + // Plugin-provided object that we've previously wrapped. + return actor; + } + + actor = new PluginScriptableObjectChild(LocalObject); + if (!SendPPluginScriptableObjectConstructor(actor)) { + NS_ERROR("Failed to send constructor message!"); + return nullptr; + } + + actor->InitializeLocal(aObject); + return actor; +} + +NPError +PluginInstanceChild::NPN_NewStream(NPMIMEType aMIMEType, const char* aWindow, + NPStream** aStream) +{ + AssertPluginThread(); + AutoStackHelper guard(this); + + PluginStreamChild* ps = new PluginStreamChild(); + + NPError result; + CallPPluginStreamConstructor(ps, nsDependentCString(aMIMEType), + NullableString(aWindow), &result); + if (NPERR_NO_ERROR != result) { + *aStream = nullptr; + PPluginStreamChild::Call__delete__(ps, NPERR_GENERIC_ERROR, true); + return result; + } + + *aStream = &ps->mStream; + return NPERR_NO_ERROR; +} + +void +PluginInstanceChild::NPN_URLRedirectResponse(void* notifyData, NPBool allow) +{ + if (!notifyData) { + return; + } + + InfallibleTArray<PStreamNotifyChild*> notifyStreams; + ManagedPStreamNotifyChild(notifyStreams); + uint32_t notifyStreamCount = notifyStreams.Length(); + for (uint32_t i = 0; i < notifyStreamCount; i++) { + StreamNotifyChild* sn = static_cast<StreamNotifyChild*>(notifyStreams[i]); + if (sn->mClosure == notifyData) { + sn->SendRedirectNotifyResponse(static_cast<bool>(allow)); + return; + } + } + NS_ASSERTION(false, "Couldn't find stream for redirect response!"); +} + +bool +PluginInstanceChild::IsUsingDirectDrawing() +{ + return IsDrawingModelDirect(mDrawingModel); +} + +PluginInstanceChild::DirectBitmap::DirectBitmap(PluginInstanceChild* aOwner, const Shmem& shmem, + const IntSize& size, uint32_t stride, SurfaceFormat format) + : mOwner(aOwner), + mShmem(shmem), + mFormat(format), + mSize(size), + mStride(stride) +{ +} + +PluginInstanceChild::DirectBitmap::~DirectBitmap() +{ + mOwner->DeallocShmem(mShmem); +} + +static inline SurfaceFormat +NPImageFormatToSurfaceFormat(NPImageFormat aFormat) +{ + switch (aFormat) { + case NPImageFormatBGRA32: + return SurfaceFormat::B8G8R8A8; + case NPImageFormatBGRX32: + return SurfaceFormat::B8G8R8X8; + default: + MOZ_ASSERT_UNREACHABLE("unknown NPImageFormat"); + return SurfaceFormat::UNKNOWN; + } +} + +static inline gfx::IntRect +NPRectToIntRect(const NPRect& in) +{ + return IntRect(in.left, in.top, in.right - in.left, in.bottom - in.top); +} + +NPError +PluginInstanceChild::NPN_InitAsyncSurface(NPSize *size, NPImageFormat format, + void *initData, NPAsyncSurface *surface) +{ + AssertPluginThread(); + AutoStackHelper guard(this); + + if (!IsUsingDirectDrawing()) { + return NPERR_INVALID_PARAM; + } + if (format != NPImageFormatBGRA32 && format != NPImageFormatBGRX32) { + return NPERR_INVALID_PARAM; + } + + PodZero(surface); + + // NPAPI guarantees that the SetCurrentAsyncSurface call will release the + // previous surface if it was different. However, no functionality exists + // within content to synchronize a non-shadow-layers transaction with the + // compositor. + // + // To get around this, we allocate two surfaces: a child copy, which we + // hand off to the plugin, and a parent copy, which we will hand off to + // the compositor. Each call to SetCurrentAsyncSurface will copy the + // invalid region from the child surface to its parent. + switch (mDrawingModel) { + case NPDrawingModelAsyncBitmapSurface: { + // Validate that the caller does not expect initial data to be set. + if (initData) { + return NPERR_INVALID_PARAM; + } + + // Validate that we're not double-allocating a surface. + RefPtr<DirectBitmap> holder; + if (mDirectBitmaps.Get(surface, getter_AddRefs(holder))) { + return NPERR_INVALID_PARAM; + } + + SurfaceFormat mozformat = NPImageFormatToSurfaceFormat(format); + int32_t bytesPerPixel = BytesPerPixel(mozformat); + + if (size->width <= 0 || size->height <= 0) { + return NPERR_INVALID_PARAM; + } + + CheckedInt<uint32_t> nbytes = SafeBytesForBitmap(size->width, size->height, bytesPerPixel); + if (!nbytes.isValid()) { + return NPERR_INVALID_PARAM; + } + + Shmem shmem; + if (!AllocUnsafeShmem(nbytes.value(), SharedMemory::TYPE_BASIC, &shmem)) { + return NPERR_OUT_OF_MEMORY_ERROR; + } + MOZ_ASSERT(shmem.Size<uint8_t>() == nbytes.value()); + + surface->version = 0; + surface->size = *size; + surface->format = format; + surface->bitmap.data = shmem.get<unsigned char>(); + surface->bitmap.stride = size->width * bytesPerPixel; + + // Hold the shmem alive until Finalize() is called or this actor dies. + holder = new DirectBitmap(this, shmem, + IntSize(size->width, size->height), + surface->bitmap.stride, mozformat); + mDirectBitmaps.Put(surface, holder); + return NPERR_NO_ERROR; + } +#if defined(XP_WIN) + case NPDrawingModelAsyncWindowsDXGISurface: { + // Validate that the caller does not expect initial data to be set. + if (initData) { + return NPERR_INVALID_PARAM; + } + + // Validate that we're not double-allocating a surface. + WindowsHandle handle = 0; + if (mDxgiSurfaces.Get(surface, &handle)) { + return NPERR_INVALID_PARAM; + } + + NPError error = NPERR_NO_ERROR; + SurfaceFormat mozformat = NPImageFormatToSurfaceFormat(format); + if (!SendInitDXGISurface(mozformat, + IntSize(size->width, size->height), + &handle, + &error)) + { + return NPERR_GENERIC_ERROR; + } + if (error != NPERR_NO_ERROR) { + return error; + } + + surface->version = 0; + surface->size = *size; + surface->format = format; + surface->sharedHandle = reinterpret_cast<HANDLE>(handle); + + mDxgiSurfaces.Put(surface, handle); + return NPERR_NO_ERROR; + } +#endif + default: + MOZ_ASSERT_UNREACHABLE("unknown drawing model"); + } + + return NPERR_INVALID_PARAM; +} + +NPError +PluginInstanceChild::NPN_FinalizeAsyncSurface(NPAsyncSurface *surface) +{ + AssertPluginThread(); + + if (!IsUsingDirectDrawing()) { + return NPERR_GENERIC_ERROR; + } + + // The API forbids this. If it becomes a problem we can revoke the current + // surface instead. + MOZ_ASSERT(!surface || mCurrentDirectSurface != surface); + + switch (mDrawingModel) { + case NPDrawingModelAsyncBitmapSurface: { + RefPtr<DirectBitmap> bitmap; + if (!mDirectBitmaps.Get(surface, getter_AddRefs(bitmap))) { + return NPERR_INVALID_PARAM; + } + + PodZero(surface); + mDirectBitmaps.Remove(surface); + return NPERR_NO_ERROR; + } +#if defined(XP_WIN) + case NPDrawingModelAsyncWindowsDXGISurface: { + WindowsHandle handle; + if (!mDxgiSurfaces.Get(surface, &handle)) { + return NPERR_INVALID_PARAM; + } + + SendFinalizeDXGISurface(handle); + mDxgiSurfaces.Remove(surface); + return NPERR_NO_ERROR; + } +#endif + default: + MOZ_ASSERT_UNREACHABLE("unknown drawing model"); + } + + return NPERR_INVALID_PARAM; +} + +void +PluginInstanceChild::NPN_SetCurrentAsyncSurface(NPAsyncSurface *surface, NPRect *changed) +{ + AssertPluginThread(); + + if (!IsUsingDirectDrawing()) { + return; + } + + mCurrentDirectSurface = surface; + + if (!surface) { + SendRevokeCurrentDirectSurface(); + return; + } + + switch (mDrawingModel) { + case NPDrawingModelAsyncBitmapSurface: { + RefPtr<DirectBitmap> bitmap; + if (!mDirectBitmaps.Get(surface, getter_AddRefs(bitmap))) { + return; + } + + IntRect dirty = changed + ? NPRectToIntRect(*changed) + : IntRect(IntPoint(0, 0), bitmap->mSize); + + // Need a holder since IPDL zaps the object for mysterious reasons. + Shmem shmemHolder = bitmap->mShmem; + SendShowDirectBitmap(shmemHolder, bitmap->mFormat, bitmap->mStride, bitmap->mSize, dirty); + break; + } +#if defined(XP_WIN) + case NPDrawingModelAsyncWindowsDXGISurface: { + WindowsHandle handle; + if (!mDxgiSurfaces.Get(surface, &handle)) { + return; + } + + IntRect dirty = changed + ? NPRectToIntRect(*changed) + : IntRect(IntPoint(0, 0), IntSize(surface->size.width, surface->size.height)); + + SendShowDirectDXGISurface(handle, dirty); + break; + } +#endif + default: + MOZ_ASSERT_UNREACHABLE("unknown drawing model"); + } +} + +void +PluginInstanceChild::DoAsyncRedraw() +{ + { + MutexAutoLock autoLock(mAsyncInvalidateMutex); + mAsyncInvalidateTask = nullptr; + } + + SendRedrawPlugin(); +} + +bool +PluginInstanceChild::RecvAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow) +{ + AssertPluginThread(); + + AutoStackHelper guard(this); + NS_ASSERTION(!aWindow.window, "Remote window should be null."); + + if (mCurrentAsyncSetWindowTask) { + mCurrentAsyncSetWindowTask->Cancel(); + mCurrentAsyncSetWindowTask = nullptr; + } + + // We shouldn't process this now because it may be received within a nested + // RPC call, and both Flash and Java don't expect to receive setwindow calls + // at arbitrary times. + mCurrentAsyncSetWindowTask = + NewNonOwningCancelableRunnableMethod<gfxSurfaceType, NPRemoteWindow, bool> + (this, &PluginInstanceChild::DoAsyncSetWindow, aSurfaceType, aWindow, true); + RefPtr<Runnable> addrefedTask = mCurrentAsyncSetWindowTask; + MessageLoop::current()->PostTask(addrefedTask.forget()); + + return true; +} + +void +PluginInstanceChild::DoAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow, + bool aIsAsync) +{ + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] AsyncSetWindow to <x=%d,y=%d, w=%d,h=%d>", + this, aWindow.x, aWindow.y, aWindow.width, aWindow.height)); + + AssertPluginThread(); + NS_ASSERTION(!aWindow.window, "Remote window should be null."); + NS_ASSERTION(!mPendingPluginCall, "Can't do SetWindow during plugin call!"); + + if (aIsAsync) { + if (!mCurrentAsyncSetWindowTask) { + return; + } + mCurrentAsyncSetWindowTask = nullptr; + } + + mWindow.window = nullptr; + if (mWindow.width != aWindow.width || mWindow.height != aWindow.height || + mWindow.clipRect.top != aWindow.clipRect.top || + mWindow.clipRect.left != aWindow.clipRect.left || + mWindow.clipRect.bottom != aWindow.clipRect.bottom || + mWindow.clipRect.right != aWindow.clipRect.right) + mAccumulatedInvalidRect = nsIntRect(0, 0, aWindow.width, aWindow.height); + + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.clipRect = aWindow.clipRect; + mWindow.type = aWindow.type; +#if defined(XP_MACOSX) || defined(XP_WIN) + mContentsScaleFactor = aWindow.contentsScaleFactor; +#endif + + if (GetQuirks() & QUIRK_SILVERLIGHT_DEFAULT_TRANSPARENT) + mIsTransparent = true; + + mLayersRendering = true; + mSurfaceType = aSurfaceType; + UpdateWindowAttributes(true); + +#ifdef XP_WIN + if (GetQuirks() & QUIRK_FLASH_THROTTLE_WMUSER_EVENTS) + SetupFlashMsgThrottle(); +#endif + + if (!mAccumulatedInvalidRect.IsEmpty()) { + AsyncShowPluginFrame(); + } +} + +bool +PluginInstanceChild::CreateOptSurface(void) +{ + MOZ_ASSERT(mSurfaceType != gfxSurfaceType::Max, + "Need a valid surface type here"); + NS_ASSERTION(!mCurrentSurface, "mCurrentSurfaceActor can get out of sync."); + + // Use an opaque surface unless we're transparent and *don't* have + // a background to source from. + gfxImageFormat format = + (mIsTransparent && !mBackground) ? SurfaceFormat::A8R8G8B8_UINT32 : + SurfaceFormat::X8R8G8B8_UINT32; + +#ifdef MOZ_X11 + Display* dpy = mWsInfo.display; + Screen* screen = DefaultScreenOfDisplay(dpy); + if (format == SurfaceFormat::X8R8G8B8_UINT32 && + DefaultDepth(dpy, DefaultScreen(dpy)) == 16) { + format = SurfaceFormat::R5G6B5_UINT16; + } + + if (mSurfaceType == gfxSurfaceType::Xlib) { + if (!mIsTransparent || mBackground) { + Visual* defaultVisual = DefaultVisualOfScreen(screen); + mCurrentSurface = + gfxXlibSurface::Create(screen, defaultVisual, + IntSize(mWindow.width, mWindow.height)); + return mCurrentSurface != nullptr; + } + + XRenderPictFormat* xfmt = XRenderFindStandardFormat(dpy, PictStandardARGB32); + if (!xfmt) { + NS_ERROR("Need X falback surface, but FindRenderFormat failed"); + return false; + } + mCurrentSurface = + gfxXlibSurface::Create(screen, xfmt, + IntSize(mWindow.width, mWindow.height)); + return mCurrentSurface != nullptr; + } +#endif + +#ifdef XP_WIN + if (mSurfaceType == gfxSurfaceType::Win32) { + bool willHaveTransparentPixels = mIsTransparent && !mBackground; + + SharedDIBSurface* s = new SharedDIBSurface(); + if (!s->Create(reinterpret_cast<HDC>(mWindow.window), + mWindow.width, mWindow.height, + willHaveTransparentPixels)) + return false; + + mCurrentSurface = s; + return true; + } + + NS_RUNTIMEABORT("Shared-memory drawing not expected on Windows."); +#endif + + // Make common shmem implementation working for any platform + mCurrentSurface = + gfxSharedImageSurface::CreateUnsafe(this, IntSize(mWindow.width, mWindow.height), format); + return !!mCurrentSurface; +} + +bool +PluginInstanceChild::MaybeCreatePlatformHelperSurface(void) +{ + if (!mCurrentSurface) { + NS_ERROR("Cannot create helper surface without mCurrentSurface"); + return false; + } + +#ifdef MOZ_X11 + bool supportNonDefaultVisual = false; + Screen* screen = DefaultScreenOfDisplay(mWsInfo.display); + Visual* defaultVisual = DefaultVisualOfScreen(screen); + Visual* visual = nullptr; + Colormap colormap = 0; + mDoAlphaExtraction = false; + bool createHelperSurface = false; + + if (mCurrentSurface->GetType() == gfxSurfaceType::Xlib) { + static_cast<gfxXlibSurface*>(mCurrentSurface.get())-> + GetColormapAndVisual(&colormap, &visual); + // Create helper surface if layer surface visual not same as default + // and we don't support non-default visual rendering + if (!visual || (defaultVisual != visual && !supportNonDefaultVisual)) { + createHelperSurface = true; + visual = defaultVisual; + mDoAlphaExtraction = mIsTransparent; + } + } else if (mCurrentSurface->GetType() == gfxSurfaceType::Image) { + // For image layer surface we should always create helper surface + createHelperSurface = true; + // Check if we can create helper surface with non-default visual + visual = gfxXlibSurface::FindVisual(screen, + static_cast<gfxImageSurface*>(mCurrentSurface.get())->Format()); + if (!visual || (defaultVisual != visual && !supportNonDefaultVisual)) { + visual = defaultVisual; + mDoAlphaExtraction = mIsTransparent; + } + } + + if (createHelperSurface) { + if (!visual) { + NS_ERROR("Need X falback surface, but visual failed"); + return false; + } + mHelperSurface = + gfxXlibSurface::Create(screen, visual, + mCurrentSurface->GetSize()); + if (!mHelperSurface) { + NS_WARNING("Fail to create create helper surface"); + return false; + } + } +#elif defined(XP_WIN) + mDoAlphaExtraction = mIsTransparent && !mBackground; +#endif + + return true; +} + +bool +PluginInstanceChild::EnsureCurrentBuffer(void) +{ +#ifndef XP_DARWIN + nsIntRect toInvalidate(0, 0, 0, 0); + IntSize winSize = IntSize(mWindow.width, mWindow.height); + + if (mBackground && mBackground->GetSize() != winSize) { + // It would be nice to keep the old background here, but doing + // so can lead to cases in which we permanently keep the old + // background size. + mBackground = nullptr; + toInvalidate.UnionRect(toInvalidate, + nsIntRect(0, 0, winSize.width, winSize.height)); + } + + if (mCurrentSurface) { + IntSize surfSize = mCurrentSurface->GetSize(); + if (winSize != surfSize || + (mBackground && !CanPaintOnBackground()) || + (mBackground && + gfxContentType::COLOR != mCurrentSurface->GetContentType()) || + (!mBackground && mIsTransparent && + gfxContentType::COLOR == mCurrentSurface->GetContentType())) { + // Don't try to use an old, invalid DC. + mWindow.window = nullptr; + ClearCurrentSurface(); + toInvalidate.UnionRect(toInvalidate, + nsIntRect(0, 0, winSize.width, winSize.height)); + } + } + + mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect, toInvalidate); + + if (mCurrentSurface) { + return true; + } + + if (!CreateOptSurface()) { + NS_ERROR("Cannot create optimized surface"); + return false; + } + + if (!MaybeCreatePlatformHelperSurface()) { + NS_ERROR("Cannot create helper surface"); + return false; + } + + return true; +#elif defined(XP_MACOSX) + + if (!mDoubleBufferCARenderer.HasCALayer()) { + void *caLayer = nullptr; + if (mDrawingModel == NPDrawingModelCoreGraphics) { + if (!mCGLayer) { + caLayer = mozilla::plugins::PluginUtilsOSX::GetCGLayer(CallCGDraw, + this, + mContentsScaleFactor); + + if (!caLayer) { + PLUGIN_LOG_DEBUG(("GetCGLayer failed.")); + return false; + } + } + mCGLayer = caLayer; + } else { + NPError result = mPluginIface->getvalue(GetNPP(), + NPPVpluginCoreAnimationLayer, + &caLayer); + if (result != NPERR_NO_ERROR || !caLayer) { + PLUGIN_LOG_DEBUG(("Plugin requested CoreAnimation but did not " + "provide CALayer.")); + return false; + } + } + mDoubleBufferCARenderer.SetCALayer(caLayer); + } + + if (mDoubleBufferCARenderer.HasFrontSurface() && + (mDoubleBufferCARenderer.GetFrontSurfaceWidth() != mWindow.width || + mDoubleBufferCARenderer.GetFrontSurfaceHeight() != mWindow.height || + mDoubleBufferCARenderer.GetContentsScaleFactor() != mContentsScaleFactor)) { + mDoubleBufferCARenderer.ClearFrontSurface(); + } + + if (!mDoubleBufferCARenderer.HasFrontSurface()) { + bool allocSurface = mDoubleBufferCARenderer.InitFrontSurface( + mWindow.width, mWindow.height, mContentsScaleFactor, + GetQuirks() & QUIRK_ALLOW_OFFLINE_RENDERER ? + ALLOW_OFFLINE_RENDERER : DISALLOW_OFFLINE_RENDERER); + if (!allocSurface) { + PLUGIN_LOG_DEBUG(("Fail to allocate front IOSurface")); + return false; + } + + if (mPluginIface->setwindow) + (void) mPluginIface->setwindow(&mData, &mWindow); + + nsIntRect toInvalidate(0, 0, mWindow.width, mWindow.height); + mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect, toInvalidate); + } +#endif + return true; +} + +void +PluginInstanceChild::UpdateWindowAttributes(bool aForceSetWindow) +{ +#if defined(MOZ_X11) || defined(XP_WIN) + RefPtr<gfxASurface> curSurface = mHelperSurface ? mHelperSurface : mCurrentSurface; +#endif // Only used within MOZ_X11 or XP_WIN blocks. Unused variable otherwise + bool needWindowUpdate = aForceSetWindow; +#ifdef MOZ_X11 + Visual* visual = nullptr; + Colormap colormap = 0; + if (curSurface && curSurface->GetType() == gfxSurfaceType::Xlib) { + static_cast<gfxXlibSurface*>(curSurface.get())-> + GetColormapAndVisual(&colormap, &visual); + if (visual != mWsInfo.visual || colormap != mWsInfo.colormap) { + mWsInfo.visual = visual; + mWsInfo.colormap = colormap; + needWindowUpdate = true; + } + } +#endif // MOZ_X11 +#ifdef XP_WIN + HDC dc = nullptr; + + if (curSurface) { + if (!SharedDIBSurface::IsSharedDIBSurface(curSurface)) + NS_RUNTIMEABORT("Expected SharedDIBSurface!"); + + SharedDIBSurface* dibsurf = static_cast<SharedDIBSurface*>(curSurface.get()); + dc = dibsurf->GetHDC(); + } + if (mWindow.window != dc) { + mWindow.window = dc; + needWindowUpdate = true; + } +#endif // XP_WIN + + if (!needWindowUpdate) { + return; + } + +#ifndef XP_MACOSX + // Adjusting the window isn't needed for OSX +#ifndef XP_WIN + // On Windows, we translate the device context, in order for the window + // origin to be correct. + mWindow.x = mWindow.y = 0; +#endif + + if (IsVisible()) { + // The clip rect is relative to drawable top-left. + nsIntRect clipRect; + + // Don't ask the plugin to draw outside the drawable. The clip rect + // is in plugin coordinates, not window coordinates. + // This also ensures that the unsigned clip rectangle offsets won't be -ve. + clipRect.SetRect(0, 0, mWindow.width, mWindow.height); + + mWindow.clipRect.left = 0; + mWindow.clipRect.top = 0; + mWindow.clipRect.right = clipRect.XMost(); + mWindow.clipRect.bottom = clipRect.YMost(); + } +#endif // XP_MACOSX + +#ifdef XP_WIN + // Windowless plugins on Windows need a WM_WINDOWPOSCHANGED event to update + // their location... or at least Flash does: Silverlight uses the + // window.x/y passed to NPP_SetWindow + + if (mPluginIface->event) { + WINDOWPOS winpos = { + 0, 0, + mWindow.x, mWindow.y, + mWindow.width, mWindow.height, + 0 + }; + NPEvent pluginEvent = { + WM_WINDOWPOSCHANGED, 0, + (LPARAM) &winpos + }; + mPluginIface->event(&mData, &pluginEvent); + } +#endif + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] UpdateWindow w=<x=%d,y=%d, w=%d,h=%d>, clip=<l=%d,t=%d,r=%d,b=%d>", + this, mWindow.x, mWindow.y, mWindow.width, mWindow.height, + mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, mWindow.clipRect.bottom)); + + if (mPluginIface->setwindow) { + mPluginIface->setwindow(&mData, &mWindow); + } +} + +void +PluginInstanceChild::PaintRectToPlatformSurface(const nsIntRect& aRect, + gfxASurface* aSurface) +{ + UpdateWindowAttributes(); + + // We should not send an async surface if we're using direct rendering. + MOZ_ASSERT(!IsUsingDirectDrawing()); + +#ifdef MOZ_X11 + { + NS_ASSERTION(aSurface->GetType() == gfxSurfaceType::Xlib, + "Non supported platform surface type"); + + NPEvent pluginEvent; + XGraphicsExposeEvent& exposeEvent = pluginEvent.xgraphicsexpose; + exposeEvent.type = GraphicsExpose; + exposeEvent.display = mWsInfo.display; + exposeEvent.drawable = static_cast<gfxXlibSurface*>(aSurface)->XDrawable(); + exposeEvent.x = aRect.x; + exposeEvent.y = aRect.y; + exposeEvent.width = aRect.width; + exposeEvent.height = aRect.height; + exposeEvent.count = 0; + // information not set: + exposeEvent.serial = 0; + exposeEvent.send_event = False; + exposeEvent.major_code = 0; + exposeEvent.minor_code = 0; + mPluginIface->event(&mData, reinterpret_cast<void*>(&exposeEvent)); + } +#elif defined(XP_WIN) + NS_ASSERTION(SharedDIBSurface::IsSharedDIBSurface(aSurface), + "Expected (SharedDIB) image surface."); + + // This rect is in the window coordinate space. aRect is in the plugin + // coordinate space. + RECT rect = { + mWindow.x + aRect.x, + mWindow.y + aRect.y, + mWindow.x + aRect.XMost(), + mWindow.y + aRect.YMost() + }; + NPEvent paintEvent = { + WM_PAINT, + uintptr_t(mWindow.window), + uintptr_t(&rect) + }; + + ::SetViewportOrgEx((HDC) mWindow.window, -mWindow.x, -mWindow.y, nullptr); + ::SelectClipRgn((HDC) mWindow.window, nullptr); + ::IntersectClipRect((HDC) mWindow.window, rect.left, rect.top, rect.right, rect.bottom); + mPluginIface->event(&mData, reinterpret_cast<void*>(&paintEvent)); +#else + NS_RUNTIMEABORT("Surface type not implemented."); +#endif +} + +void +PluginInstanceChild::PaintRectToSurface(const nsIntRect& aRect, + gfxASurface* aSurface, + const Color& aColor) +{ + // Render using temporary X surface, with copy to image surface + nsIntRect plPaintRect(aRect); + RefPtr<gfxASurface> renderSurface = aSurface; +#ifdef MOZ_X11 + if (mIsTransparent && (GetQuirks() & QUIRK_FLASH_EXPOSE_COORD_TRANSLATION)) { + // 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.), see bug 574583 + plPaintRect.SetRect(0, 0, aRect.XMost(), aRect.YMost()); + } + if (mHelperSurface) { + // On X11 we can paint to non Xlib surface only with HelperSurface + renderSurface = mHelperSurface; + } +#endif + + if (mIsTransparent && !CanPaintOnBackground()) { + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(renderSurface); + gfx::Rect rect(plPaintRect.x, plPaintRect.y, + plPaintRect.width, plPaintRect.height); + // Moz2D treats OP_SOURCE operations as unbounded, so we need to + // clip to the rect that we want to fill: + dt->PushClipRect(rect); + dt->FillRect(rect, ColorPattern(aColor), // aColor is already a device color + DrawOptions(1.f, CompositionOp::OP_SOURCE)); + dt->PopClip(); + dt->Flush(); + } + + PaintRectToPlatformSurface(plPaintRect, renderSurface); + + if (renderSurface != aSurface) { + RefPtr<DrawTarget> dt; + if (aSurface == mCurrentSurface && + aSurface->GetType() == gfxSurfaceType::Image && + aSurface->GetSurfaceFormat() == SurfaceFormat::B8G8R8X8) { + gfxImageSurface* imageSurface = static_cast<gfxImageSurface*>(aSurface); + // Bug 1196927 - Reinterpret target surface as BGRA to fill alpha with opaque. + // Certain backends (i.e. Skia) may not truly support BGRX formats, so they must + // be emulated by filling the alpha channel opaque as if it was BGRA data. Cairo + // leaves the alpha zeroed out for BGRX, so we cause Cairo to fill it as opaque + // by handling the copy target as a BGRA surface. + dt = Factory::CreateDrawTargetForData(BackendType::CAIRO, + imageSurface->Data(), + imageSurface->GetSize(), + imageSurface->Stride(), + SurfaceFormat::B8G8R8A8); + } else { + // Copy helper surface content to target + dt = CreateDrawTargetForSurface(aSurface); + } + if (dt && dt->IsValid()) { + RefPtr<SourceSurface> surface = + gfxPlatform::GetSourceSurfaceForSurface(dt, renderSurface); + dt->CopySurface(surface, aRect, aRect.TopLeft()); + } else { + gfxWarning() << "PluginInstanceChild::PaintRectToSurface failure"; + } + } +} + +void +PluginInstanceChild::PaintRectWithAlphaExtraction(const nsIntRect& aRect, + gfxASurface* aSurface) +{ + MOZ_ASSERT(aSurface->GetContentType() == gfxContentType::COLOR_ALPHA, + "Refusing to pointlessly recover alpha"); + + nsIntRect rect(aRect); + // If |aSurface| can be used to paint and can have alpha values + // recovered directly to it, do that to save a tmp surface and + // copy. + bool useSurfaceSubimageForBlack = false; + if (gfxSurfaceType::Image == aSurface->GetType()) { + gfxImageSurface* surfaceAsImage = + static_cast<gfxImageSurface*>(aSurface); + useSurfaceSubimageForBlack = + (surfaceAsImage->Format() == SurfaceFormat::A8R8G8B8_UINT32); + // If we're going to use a subimage, nudge the rect so that we + // can use optimal alpha recovery. If we're not using a + // subimage, the temporaries should automatically get + // fast-path alpha recovery so we don't need to do anything. + if (useSurfaceSubimageForBlack) { + rect = + gfxAlphaRecovery::AlignRectForSubimageRecovery(aRect, + surfaceAsImage); + } + } + + RefPtr<gfxImageSurface> whiteImage; + RefPtr<gfxImageSurface> blackImage; + gfxRect targetRect(rect.x, rect.y, rect.width, rect.height); + IntSize targetSize(rect.width, rect.height); + gfxPoint deviceOffset = -targetRect.TopLeft(); + + // We always use a temporary "white image" + whiteImage = new gfxImageSurface(targetSize, SurfaceFormat::X8R8G8B8_UINT32); + if (whiteImage->CairoStatus()) { + return; + } + +#ifdef XP_WIN + // On windows, we need an HDC and so can't paint directly to + // vanilla image surfaces. Bifurcate this painting code so that + // we don't accidentally attempt that. + if (!SharedDIBSurface::IsSharedDIBSurface(aSurface)) + NS_RUNTIMEABORT("Expected SharedDIBSurface!"); + + // Paint the plugin directly onto the target, with a white + // background and copy the result + PaintRectToSurface(rect, aSurface, Color(1.f, 1.f, 1.f)); + { + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(whiteImage); + RefPtr<SourceSurface> surface = + gfxPlatform::GetSourceSurfaceForSurface(dt, aSurface); + dt->CopySurface(surface, rect, IntPoint()); + } + + // Paint the plugin directly onto the target, with a black + // background + PaintRectToSurface(rect, aSurface, Color(0.f, 0.f, 0.f)); + + // Don't copy the result, just extract a subimage so that we can + // recover alpha directly into the target + gfxImageSurface *image = static_cast<gfxImageSurface*>(aSurface); + blackImage = image->GetSubimage(targetRect); + +#else + // Paint onto white background + whiteImage->SetDeviceOffset(deviceOffset); + PaintRectToSurface(rect, whiteImage, Color(1.f, 1.f, 1.f)); + + if (useSurfaceSubimageForBlack) { + gfxImageSurface *surface = static_cast<gfxImageSurface*>(aSurface); + blackImage = surface->GetSubimage(targetRect); + } else { + blackImage = new gfxImageSurface(targetSize, + SurfaceFormat::A8R8G8B8_UINT32); + } + + // Paint onto black background + blackImage->SetDeviceOffset(deviceOffset); + PaintRectToSurface(rect, blackImage, Color(0.f, 0.f, 0.f)); +#endif + + MOZ_ASSERT(whiteImage && blackImage, "Didn't paint enough!"); + + // Extract alpha from black and white image and store to black + // image + if (!gfxAlphaRecovery::RecoverAlpha(blackImage, whiteImage)) { + return; + } + + // If we had to use a temporary black surface, copy the pixels + // with alpha back to the target + if (!useSurfaceSubimageForBlack) { + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(aSurface); + RefPtr<SourceSurface> surface = + gfxPlatform::GetSourceSurfaceForSurface(dt, blackImage); + dt->CopySurface(surface, + IntRect(0, 0, rect.width, rect.height), + rect.TopLeft()); + } +} + +bool +PluginInstanceChild::CanPaintOnBackground() +{ + return (mBackground && + mCurrentSurface && + mCurrentSurface->GetSize() == mBackground->GetSize()); +} + +bool +PluginInstanceChild::ShowPluginFrame() +{ + // mLayersRendering can be false if we somehow get here without + // receiving AsyncSetWindow() first. mPendingPluginCall is our + // re-entrancy guard; we can't paint while nested inside another + // paint. + if (!mLayersRendering || mPendingPluginCall) { + return false; + } + + // We should not attempt to asynchronously show the plugin if we're using + // direct rendering. + MOZ_ASSERT(!IsUsingDirectDrawing()); + + AutoRestore<bool> pending(mPendingPluginCall); + mPendingPluginCall = true; + + bool temporarilyMakeVisible = !IsVisible() && !mHasPainted; + if (temporarilyMakeVisible && mWindow.width && mWindow.height) { + mWindow.clipRect.right = mWindow.width; + mWindow.clipRect.bottom = mWindow.height; + } else if (!IsVisible()) { + // If we're not visible, don't bother painting a <0,0,0,0> + // rect. If we're eventually made visible, the visibility + // change will invalidate our window. + ClearCurrentSurface(); + return true; + } + + if (!EnsureCurrentBuffer()) { + return false; + } + +#ifdef MOZ_WIDGET_COCOA + // We can't use the thebes code with CoreAnimation so we will + // take a different code path. + if (mDrawingModel == NPDrawingModelCoreAnimation || + mDrawingModel == NPDrawingModelInvalidatingCoreAnimation || + mDrawingModel == NPDrawingModelCoreGraphics) { + + if (!IsVisible()) { + return true; + } + + if (!mDoubleBufferCARenderer.HasFrontSurface()) { + NS_ERROR("CARenderer not initialized for rendering"); + return false; + } + + // Clear accRect here to be able to pass + // test_invalidate_during_plugin_paint test + nsIntRect rect = mAccumulatedInvalidRect; + mAccumulatedInvalidRect.SetEmpty(); + + // Fix up old invalidations that might have been made when our + // surface was a different size + rect.IntersectRect(rect, + nsIntRect(0, 0, + mDoubleBufferCARenderer.GetFrontSurfaceWidth(), + mDoubleBufferCARenderer.GetFrontSurfaceHeight())); + + if (mDrawingModel == NPDrawingModelCoreGraphics) { + mozilla::plugins::PluginUtilsOSX::Repaint(mCGLayer, rect); + } + + mDoubleBufferCARenderer.Render(); + + NPRect r = { (uint16_t)rect.y, (uint16_t)rect.x, + (uint16_t)rect.YMost(), (uint16_t)rect.XMost() }; + SurfaceDescriptor currSurf; + currSurf = IOSurfaceDescriptor(mDoubleBufferCARenderer.GetFrontSurfaceID(), + mDoubleBufferCARenderer.GetContentsScaleFactor()); + + mHasPainted = true; + + SurfaceDescriptor returnSurf; + + if (!SendShow(r, currSurf, &returnSurf)) { + return false; + } + + SwapSurfaces(); + return true; + } else { + NS_ERROR("Unsupported drawing model for async layer rendering"); + return false; + } +#endif + + NS_ASSERTION(mWindow.width == uint32_t(mWindow.clipRect.right - mWindow.clipRect.left) && + mWindow.height == uint32_t(mWindow.clipRect.bottom - mWindow.clipRect.top), + "Clip rect should be same size as window when using layers"); + + // Clear accRect here to be able to pass + // test_invalidate_during_plugin_paint test + nsIntRect rect = mAccumulatedInvalidRect; + mAccumulatedInvalidRect.SetEmpty(); + + // Fix up old invalidations that might have been made when our + // surface was a different size + IntSize surfaceSize = mCurrentSurface->GetSize(); + rect.IntersectRect(rect, + nsIntRect(0, 0, surfaceSize.width, surfaceSize.height)); + + if (!ReadbackDifferenceRect(rect)) { + // We couldn't read back the pixels that differ between the + // current surface and last, so we have to invalidate the + // entire window. + rect.SetRect(0, 0, mWindow.width, mWindow.height); + } + + bool haveTransparentPixels = + gfxContentType::COLOR_ALPHA == mCurrentSurface->GetContentType(); + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Painting%s <x=%d,y=%d, w=%d,h=%d> on surface <w=%d,h=%d>", + this, haveTransparentPixels ? " with alpha" : "", + rect.x, rect.y, rect.width, rect.height, + mCurrentSurface->GetSize().width, mCurrentSurface->GetSize().height)); + + if (CanPaintOnBackground()) { + PLUGIN_LOG_DEBUG((" (on background)")); + // Source the background pixels ... + { + RefPtr<gfxASurface> surface = + mHelperSurface ? mHelperSurface : mCurrentSurface; + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(surface); + RefPtr<SourceSurface> backgroundSurface = + gfxPlatform::GetSourceSurfaceForSurface(dt, mBackground); + dt->CopySurface(backgroundSurface, rect, rect.TopLeft()); + } + // ... and hand off to the plugin + // BEWARE: mBackground may die during this call + PaintRectToSurface(rect, mCurrentSurface, Color()); + } else if (!temporarilyMakeVisible && mDoAlphaExtraction) { + // We don't want to pay the expense of alpha extraction for + // phony paints. + PLUGIN_LOG_DEBUG((" (with alpha recovery)")); + PaintRectWithAlphaExtraction(rect, mCurrentSurface); + } else { + PLUGIN_LOG_DEBUG((" (onto opaque surface)")); + + // If we're on a platform that needs helper surfaces for + // plugins, and we're forcing a throwaway paint of a + // wmode=transparent plugin, then make sure to use the helper + // surface here. + RefPtr<gfxASurface> target = + (temporarilyMakeVisible && mHelperSurface) ? + mHelperSurface : mCurrentSurface; + + PaintRectToSurface(rect, target, Color()); + } + mHasPainted = true; + + if (temporarilyMakeVisible) { + mWindow.clipRect.right = mWindow.clipRect.bottom = 0; + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Undoing temporary clipping w=<x=%d,y=%d, w=%d,h=%d>, clip=<l=%d,t=%d,r=%d,b=%d>", + this, mWindow.x, mWindow.y, mWindow.width, mWindow.height, + mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, mWindow.clipRect.bottom)); + + if (mPluginIface->setwindow) { + mPluginIface->setwindow(&mData, &mWindow); + } + + // Skip forwarding the results of the phony paint to the + // browser. We may have painted a transparent plugin using + // the opaque-plugin path, which can result in wrong pixels. + // We also don't want to pay the expense of forwarding the + // surface for plugins that might really be invisible. + mAccumulatedInvalidRect.SetRect(0, 0, mWindow.width, mWindow.height); + return true; + } + + NPRect r = { (uint16_t)rect.y, (uint16_t)rect.x, + (uint16_t)rect.YMost(), (uint16_t)rect.XMost() }; + SurfaceDescriptor currSurf; +#ifdef MOZ_X11 + if (mCurrentSurface->GetType() == gfxSurfaceType::Xlib) { + gfxXlibSurface *xsurf = static_cast<gfxXlibSurface*>(mCurrentSurface.get()); + currSurf = SurfaceDescriptorX11(xsurf); + // Need to sync all pending x-paint requests + // before giving drawable to another process + XSync(mWsInfo.display, False); + } else +#endif +#ifdef XP_WIN + if (SharedDIBSurface::IsSharedDIBSurface(mCurrentSurface)) { + SharedDIBSurface* s = static_cast<SharedDIBSurface*>(mCurrentSurface.get()); + if (!mCurrentSurfaceActor) { + base::SharedMemoryHandle handle = nullptr; + s->ShareToProcess(OtherPid(), &handle); + + mCurrentSurfaceActor = + SendPPluginSurfaceConstructor(handle, + mCurrentSurface->GetSize(), + haveTransparentPixels); + } + currSurf = mCurrentSurfaceActor; + s->Flush(); + } else +#endif + if (gfxSharedImageSurface::IsSharedImage(mCurrentSurface)) { + currSurf = static_cast<gfxSharedImageSurface*>(mCurrentSurface.get())->GetShmem(); + } else { + NS_RUNTIMEABORT("Surface type is not remotable"); + return false; + } + + // Unused, except to possibly return a shmem to us + SurfaceDescriptor returnSurf; + + if (!SendShow(r, currSurf, &returnSurf)) { + return false; + } + + SwapSurfaces(); + mSurfaceDifferenceRect = rect; + return true; +} + +bool +PluginInstanceChild::ReadbackDifferenceRect(const nsIntRect& rect) +{ + if (!mBackSurface) + return false; + + // We can read safely from XSurface,SharedDIBSurface and Unsafe SharedMemory, + // because PluginHost is not able to modify that surface +#if defined(MOZ_X11) + if (mBackSurface->GetType() != gfxSurfaceType::Xlib && + !gfxSharedImageSurface::IsSharedImage(mBackSurface)) + return false; +#elif defined(XP_WIN) + if (!SharedDIBSurface::IsSharedDIBSurface(mBackSurface)) + return false; +#endif + +#if defined(MOZ_X11) || defined(XP_WIN) + if (mCurrentSurface->GetContentType() != mBackSurface->GetContentType()) + return false; + + if (mSurfaceDifferenceRect.IsEmpty()) + return true; + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Reading back part of <x=%d,y=%d, w=%d,h=%d>", + this, mSurfaceDifferenceRect.x, mSurfaceDifferenceRect.y, + mSurfaceDifferenceRect.width, mSurfaceDifferenceRect.height)); + + // Read back previous content + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(mCurrentSurface); + RefPtr<SourceSurface> source = + gfxPlatform::GetSourceSurfaceForSurface(dt, mBackSurface); + // Subtract from mSurfaceDifferenceRect area which is overlapping with rect + nsIntRegion result; + result.Sub(mSurfaceDifferenceRect, nsIntRegion(rect)); + for (auto iter = result.RectIter(); !iter.Done(); iter.Next()) { + const nsIntRect& r = iter.Get(); + dt->CopySurface(source, r, r.TopLeft()); + } + + return true; +#else + return false; +#endif +} + +void +PluginInstanceChild::InvalidateRectDelayed(void) +{ + if (!mCurrentInvalidateTask) { + return; + } + + mCurrentInvalidateTask = nullptr; + if (mAccumulatedInvalidRect.IsEmpty()) { + return; + } + + if (!ShowPluginFrame()) { + AsyncShowPluginFrame(); + } +} + +void +PluginInstanceChild::AsyncShowPluginFrame(void) +{ + if (mCurrentInvalidateTask) { + return; + } + + // When the plugin is using direct surfaces to draw, it is not driving + // paints via paint events - it will drive painting via its own events + // and/or DidComposite callbacks. + if (IsUsingDirectDrawing()) { + return; + } + + mCurrentInvalidateTask = + NewNonOwningCancelableRunnableMethod(this, &PluginInstanceChild::InvalidateRectDelayed); + RefPtr<Runnable> addrefedTask = mCurrentInvalidateTask; + MessageLoop::current()->PostTask(addrefedTask.forget()); +} + +void +PluginInstanceChild::InvalidateRect(NPRect* aInvalidRect) +{ + NS_ASSERTION(aInvalidRect, "Null pointer!"); + +#ifdef OS_WIN + // Invalidate and draw locally for windowed plugins. + if (mWindow.type == NPWindowTypeWindow) { + NS_ASSERTION(IsWindow(mPluginWindowHWND), "Bad window?!"); + RECT rect = { aInvalidRect->left, aInvalidRect->top, + aInvalidRect->right, aInvalidRect->bottom }; + ::InvalidateRect(mPluginWindowHWND, &rect, FALSE); + return; + } +#endif + + if (IsUsingDirectDrawing()) { + NS_ASSERTION(false, "Should not call InvalidateRect() in direct surface mode!"); + return; + } + + if (mLayersRendering) { + nsIntRect r(aInvalidRect->left, aInvalidRect->top, + aInvalidRect->right - aInvalidRect->left, + aInvalidRect->bottom - aInvalidRect->top); + + mAccumulatedInvalidRect.UnionRect(r, mAccumulatedInvalidRect); + // If we are able to paint and invalidate sent, then reset + // accumulated rectangle + AsyncShowPluginFrame(); + return; + } + + // If we were going to use layers rendering but it's not set up + // yet, and the plugin happens to call this first, we'll forward + // the invalidation to the browser. It's unclear whether + // non-layers plugins need this rect forwarded when their window + // width or height is 0, which it would be for layers plugins + // before their first SetWindow(). + SendNPN_InvalidateRect(*aInvalidRect); +} + +bool +PluginInstanceChild::RecvUpdateBackground(const SurfaceDescriptor& aBackground, + const nsIntRect& aRect) +{ + MOZ_ASSERT(mIsTransparent, "Only transparent plugins use backgrounds"); + + if (!mBackground) { + // XXX refactor me + switch (aBackground.type()) { +#ifdef MOZ_X11 + case SurfaceDescriptor::TSurfaceDescriptorX11: { + mBackground = aBackground.get_SurfaceDescriptorX11().OpenForeign(); + break; + } +#endif + case SurfaceDescriptor::TShmem: { + mBackground = gfxSharedImageSurface::Open(aBackground.get_Shmem()); + break; + } + default: + NS_RUNTIMEABORT("Unexpected background surface descriptor"); + } + + if (!mBackground) { + return false; + } + + IntSize bgSize = mBackground->GetSize(); + mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect, + nsIntRect(0, 0, bgSize.width, bgSize.height)); + AsyncShowPluginFrame(); + return true; + } + + // XXX refactor me + mAccumulatedInvalidRect.UnionRect(aRect, mAccumulatedInvalidRect); + + // This must be asynchronous, because we may be nested within RPC messages + // which do not expect to receiving paint events. + AsyncShowPluginFrame(); + + return true; +} + +PPluginBackgroundDestroyerChild* +PluginInstanceChild::AllocPPluginBackgroundDestroyerChild() +{ + return new PluginBackgroundDestroyerChild(); +} + +bool +PluginInstanceChild::RecvPPluginBackgroundDestroyerConstructor( + PPluginBackgroundDestroyerChild* aActor) +{ + // Our background changed, so we have to invalidate the area + // painted with the old background. If the background was + // destroyed because we have a new background, then we expect to + // be notified of that "soon", before processing the asynchronous + // invalidation here. If we're *not* getting a new background, + // our current front surface is stale and we want to repaint + // "soon" so that we can hand the browser back a surface with + // alpha values. (We should be notified of that invalidation soon + // too, but we don't assume that here.) + if (mBackground) { + IntSize bgsize = mBackground->GetSize(); + mAccumulatedInvalidRect.UnionRect( + nsIntRect(0, 0, bgsize.width, bgsize.height), mAccumulatedInvalidRect); + + // NB: we don't have to XSync here because only ShowPluginFrame() + // uses mBackground, and it always XSyncs after finishing. + mBackground = nullptr; + AsyncShowPluginFrame(); + } + + return PPluginBackgroundDestroyerChild::Send__delete__(aActor); +} + +bool +PluginInstanceChild::DeallocPPluginBackgroundDestroyerChild( + PPluginBackgroundDestroyerChild* aActor) +{ + delete aActor; + return true; +} + +uint32_t +PluginInstanceChild::ScheduleTimer(uint32_t interval, bool repeat, + TimerFunc func) +{ + ChildTimer* t = new ChildTimer(this, interval, repeat, func); + if (0 == t->ID()) { + delete t; + return 0; + } + + mTimers.AppendElement(t); + return t->ID(); +} + +void +PluginInstanceChild::UnscheduleTimer(uint32_t id) +{ + if (0 == id) + return; + + mTimers.RemoveElement(id, ChildTimer::IDComparator()); +} + +void +PluginInstanceChild::AsyncCall(PluginThreadCallback aFunc, void* aUserData) +{ + RefPtr<ChildAsyncCall> task = new ChildAsyncCall(this, aFunc, aUserData); + PostChildAsyncCall(task.forget()); +} + +void +PluginInstanceChild::PostChildAsyncCall(already_AddRefed<ChildAsyncCall> aTask) +{ + RefPtr<ChildAsyncCall> task = aTask; + + { + MutexAutoLock lock(mAsyncCallMutex); + mPendingAsyncCalls.AppendElement(task); + } + ProcessChild::message_loop()->PostTask(task.forget()); +} + +void +PluginInstanceChild::SwapSurfaces() +{ + RefPtr<gfxASurface> tmpsurf = mCurrentSurface; +#ifdef XP_WIN + PPluginSurfaceChild* tmpactor = mCurrentSurfaceActor; +#endif + + mCurrentSurface = mBackSurface; +#ifdef XP_WIN + mCurrentSurfaceActor = mBackSurfaceActor; +#endif + + mBackSurface = tmpsurf; +#ifdef XP_WIN + mBackSurfaceActor = tmpactor; +#endif + +#ifdef MOZ_WIDGET_COCOA + mDoubleBufferCARenderer.SwapSurfaces(); + + // Outdated back surface... not usable anymore due to changed plugin size. + // Dropping obsolete surface + if (mDoubleBufferCARenderer.HasFrontSurface() && + mDoubleBufferCARenderer.HasBackSurface() && + (mDoubleBufferCARenderer.GetFrontSurfaceWidth() != + mDoubleBufferCARenderer.GetBackSurfaceWidth() || + mDoubleBufferCARenderer.GetFrontSurfaceHeight() != + mDoubleBufferCARenderer.GetBackSurfaceHeight() || + mDoubleBufferCARenderer.GetFrontSurfaceContentsScaleFactor() != + mDoubleBufferCARenderer.GetBackSurfaceContentsScaleFactor())) { + + mDoubleBufferCARenderer.ClearFrontSurface(); + } +#else + if (mCurrentSurface && mBackSurface && + (mCurrentSurface->GetSize() != mBackSurface->GetSize() || + mCurrentSurface->GetContentType() != mBackSurface->GetContentType())) { + ClearCurrentSurface(); + } +#endif +} + +void +PluginInstanceChild::ClearCurrentSurface() +{ + mCurrentSurface = nullptr; +#ifdef MOZ_WIDGET_COCOA + if (mDoubleBufferCARenderer.HasFrontSurface()) { + mDoubleBufferCARenderer.ClearFrontSurface(); + } +#endif +#ifdef XP_WIN + if (mCurrentSurfaceActor) { + PPluginSurfaceChild::Send__delete__(mCurrentSurfaceActor); + mCurrentSurfaceActor = nullptr; + } +#endif + mHelperSurface = nullptr; +} + +void +PluginInstanceChild::ClearAllSurfaces() +{ + if (mBackSurface) { + // Get last surface back, and drop it + SurfaceDescriptor temp = null_t(); + NPRect r = { 0, 0, 1, 1 }; + SendShow(r, temp, &temp); + } + + if (gfxSharedImageSurface::IsSharedImage(mCurrentSurface)) + DeallocShmem(static_cast<gfxSharedImageSurface*>(mCurrentSurface.get())->GetShmem()); + if (gfxSharedImageSurface::IsSharedImage(mBackSurface)) + DeallocShmem(static_cast<gfxSharedImageSurface*>(mBackSurface.get())->GetShmem()); + mCurrentSurface = nullptr; + mBackSurface = nullptr; + +#ifdef XP_WIN + if (mCurrentSurfaceActor) { + PPluginSurfaceChild::Send__delete__(mCurrentSurfaceActor); + mCurrentSurfaceActor = nullptr; + } + if (mBackSurfaceActor) { + PPluginSurfaceChild::Send__delete__(mBackSurfaceActor); + mBackSurfaceActor = nullptr; + } +#endif + +#ifdef MOZ_WIDGET_COCOA + if (mDoubleBufferCARenderer.HasBackSurface()) { + // Get last surface back, and drop it + SurfaceDescriptor temp = null_t(); + NPRect r = { 0, 0, 1, 1 }; + SendShow(r, temp, &temp); + } + + if (mCGLayer) { + mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(mCGLayer); + mCGLayer = nullptr; + } + + mDoubleBufferCARenderer.ClearFrontSurface(); + mDoubleBufferCARenderer.ClearBackSurface(); +#endif +} + +static void +InvalidateObjects(nsTHashtable<DeletingObjectEntry>& aEntries) +{ + for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) { + DeletingObjectEntry* e = iter.Get(); + NPObject* o = e->GetKey(); + if (!e->mDeleted && o->_class && o->_class->invalidate) { + o->_class->invalidate(o); + } + } +} + +static void +DeleteObjects(nsTHashtable<DeletingObjectEntry>& aEntries) +{ + for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) { + DeletingObjectEntry* e = iter.Get(); + NPObject* o = e->GetKey(); + if (!e->mDeleted) { + e->mDeleted = true; + +#ifdef NS_BUILD_REFCNT_LOGGING + { + int32_t refcnt = o->referenceCount; + while (refcnt) { + --refcnt; + NS_LOG_RELEASE(o, refcnt, "NPObject"); + } + } +#endif + + PluginModuleChild::DeallocNPObject(o); + } + } +} + +void +PluginInstanceChild::Destroy() +{ + if (mDestroyed) { + return; + } + if (mStackDepth != 0) { + NS_RUNTIMEABORT("Destroying plugin instance on the stack."); + } + mDestroyed = true; + +#if defined(OS_WIN) + SetProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty, (HANDLE)1); +#endif + + InfallibleTArray<PBrowserStreamChild*> streams; + ManagedPBrowserStreamChild(streams); + + // First make sure none of these streams become deleted + for (uint32_t i = 0; i < streams.Length(); ) { + if (static_cast<BrowserStreamChild*>(streams[i])->InstanceDying()) + ++i; + else + streams.RemoveElementAt(i); + } + for (uint32_t i = 0; i < streams.Length(); ++i) + static_cast<BrowserStreamChild*>(streams[i])->FinishDelivery(); + + mTimers.Clear(); + + // NPP_Destroy() should be a synchronization point for plugin threads + // calling NPN_AsyncCall: after this function returns, they are no longer + // allowed to make async calls on this instance. + static_cast<PluginModuleChild *>(Manager())->NPP_Destroy(this); + mData.ndata = 0; + + if (mCurrentInvalidateTask) { + mCurrentInvalidateTask->Cancel(); + mCurrentInvalidateTask = nullptr; + } + if (mCurrentAsyncSetWindowTask) { + mCurrentAsyncSetWindowTask->Cancel(); + mCurrentAsyncSetWindowTask = nullptr; + } + { + MutexAutoLock autoLock(mAsyncInvalidateMutex); + if (mAsyncInvalidateTask) { + mAsyncInvalidateTask->Cancel(); + mAsyncInvalidateTask = nullptr; + } + } + + ClearAllSurfaces(); + mDirectBitmaps.Clear(); + + mDeletingHash = new nsTHashtable<DeletingObjectEntry>; + PluginScriptableObjectChild::NotifyOfInstanceShutdown(this); + + InvalidateObjects(*mDeletingHash); + DeleteObjects(*mDeletingHash); + + // Null out our cached actors as they should have been killed in the + // PluginInstanceDestroyed call above. + mCachedWindowActor = nullptr; + mCachedElementActor = nullptr; + +#if defined(OS_WIN) + DestroyWinlessPopupSurrogate(); + UnhookWinlessFlashThrottle(); + DestroyPluginWindow(); +#endif + + // Pending async calls are discarded, not delivered. This matches the + // in-process behavior. + for (uint32_t i = 0; i < mPendingAsyncCalls.Length(); ++i) + mPendingAsyncCalls[i]->Cancel(); + + mPendingAsyncCalls.Clear(); + +#ifdef MOZ_WIDGET_GTK + if (mWindow.type == NPWindowTypeWindow && !mXEmbed) { + xt_client_xloop_destroy(); + } +#endif +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + DeleteWindow(); +#endif +} + +bool +PluginInstanceChild::AnswerNPP_Destroy(NPError* aResult) +{ + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + *aResult = NPERR_NO_ERROR; + + Destroy(); + + return true; +} + +void +PluginInstanceChild::ActorDestroy(ActorDestroyReason why) +{ +#ifdef XP_WIN + // ClearAllSurfaces() should not try to send anything after ActorDestroy. + mCurrentSurfaceActor = nullptr; + mBackSurfaceActor = nullptr; +#endif + + Destroy(); +} diff --git a/dom/plugins/ipc/PluginInstanceChild.h b/dom/plugins/ipc/PluginInstanceChild.h new file mode 100644 index 000000000..0ad6e145d --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceChild.h @@ -0,0 +1,713 @@ +/* -*- 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 dom_plugins_PluginInstanceChild_h +#define dom_plugins_PluginInstanceChild_h 1 + +#include "mozilla/EventForwards.h" +#include "mozilla/plugins/PPluginInstanceChild.h" +#include "mozilla/plugins/PluginScriptableObjectChild.h" +#include "mozilla/plugins/StreamNotifyChild.h" +#include "mozilla/plugins/PPluginSurfaceChild.h" +#include "mozilla/ipc/CrossProcessMutex.h" +#include "nsRefPtrHashtable.h" +#if defined(OS_WIN) +#include "mozilla/gfx/SharedDIBWin.h" +#elif defined(MOZ_WIDGET_COCOA) +#include "PluginUtilsOSX.h" +#include "mozilla/gfx/QuartzSupport.h" +#include "base/timer.h" + +#endif + +#include "npfunctions.h" +#include "nsAutoPtr.h" +#include "nsTArray.h" +#include "ChildAsyncCall.h" +#include "ChildTimer.h" +#include "nsRect.h" +#include "nsTHashtable.h" +#include "mozilla/PaintTracker.h" +#include "mozilla/gfx/Types.h" + +#include <map> + +#ifdef MOZ_WIDGET_GTK +#include "gtk2xtbin.h" +#endif + +class gfxASurface; + +namespace mozilla { +namespace plugins { + +class PBrowserStreamChild; +class BrowserStreamChild; +class StreamNotifyChild; + +class PluginInstanceChild : public PPluginInstanceChild +{ + friend class BrowserStreamChild; + friend class PluginStreamChild; + friend class StreamNotifyChild; + friend class PluginScriptableObjectChild; + +#ifdef OS_WIN + friend LRESULT CALLBACK PluginWindowProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); + static LRESULT CALLBACK PluginWindowProcInternal(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); +#endif + +protected: + virtual bool + AnswerCreateChildPluginWindow(NativeWindowHandle* aChildPluginWindow) override; + + virtual bool + RecvCreateChildPopupSurrogate(const NativeWindowHandle& aNetscapeWindow) override; + + virtual bool + AnswerNPP_SetWindow(const NPRemoteWindow& window) override; + + virtual bool + AnswerNPP_GetValue_NPPVpluginWantsAllNetworkStreams(bool* wantsAllStreams, NPError* rv) override; + virtual bool + AnswerNPP_GetValue_NPPVpluginNeedsXEmbed(bool* needs, NPError* rv) override; + virtual bool + AnswerNPP_GetValue_NPPVpluginScriptableNPObject(PPluginScriptableObjectChild** value, + NPError* result) override; + virtual bool + AnswerNPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId(nsCString* aPlugId, + NPError* aResult) override; + virtual bool + AnswerNPP_SetValue_NPNVprivateModeBool(const bool& value, NPError* result) override; + virtual bool + AnswerNPP_SetValue_NPNVmuteAudioBool(const bool& value, NPError* result) override; + virtual bool + AnswerNPP_SetValue_NPNVCSSZoomFactor(const double& value, NPError* result) override; + + virtual bool + AnswerNPP_HandleEvent(const NPRemoteEvent& event, int16_t* handled) override; + virtual bool + AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event, + Shmem&& mem, + int16_t* handled, + Shmem* rtnmem) override; + virtual bool + AnswerNPP_HandleEvent_IOSurface(const NPRemoteEvent& event, + const uint32_t& surface, + int16_t* handled) override; + + // Async rendering + virtual bool + RecvAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow) override; + + virtual void + DoAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow, + bool aIsAsync); + + virtual PPluginSurfaceChild* + AllocPPluginSurfaceChild(const WindowsSharedMemoryHandle&, + const gfx::IntSize&, const bool&) override { + return new PPluginSurfaceChild(); + } + + virtual bool DeallocPPluginSurfaceChild(PPluginSurfaceChild* s) override { + delete s; + return true; + } + + virtual bool + AnswerPaint(const NPRemoteEvent& event, int16_t* handled) override + { + PaintTracker pt; + return AnswerNPP_HandleEvent(event, handled); + } + + virtual bool + RecvWindowPosChanged(const NPRemoteEvent& event) override; + + virtual bool + RecvContentsScaleFactorChanged(const double& aContentsScaleFactor) override; + + virtual bool + AnswerNPP_Destroy(NPError* result) override; + + virtual PPluginScriptableObjectChild* + AllocPPluginScriptableObjectChild() override; + + virtual bool + DeallocPPluginScriptableObjectChild(PPluginScriptableObjectChild* aObject) override; + + virtual bool + RecvPPluginScriptableObjectConstructor(PPluginScriptableObjectChild* aActor) override; + + virtual bool + RecvPBrowserStreamConstructor(PBrowserStreamChild* aActor, const nsCString& aURL, + const uint32_t& aLength, const uint32_t& aLastmodified, + PStreamNotifyChild* aNotifyData, const nsCString& aHeaders) override; + + virtual bool + AnswerNPP_NewStream( + PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, + NPError* rv, + uint16_t* stype) override; + + virtual bool + RecvAsyncNPP_NewStream( + PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable) override; + + virtual PBrowserStreamChild* + AllocPBrowserStreamChild(const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + PStreamNotifyChild* notifyData, + const nsCString& headers) override; + + virtual bool + DeallocPBrowserStreamChild(PBrowserStreamChild* stream) override; + + virtual PPluginStreamChild* + AllocPPluginStreamChild(const nsCString& mimeType, + const nsCString& target, + NPError* result) override; + + virtual bool + DeallocPPluginStreamChild(PPluginStreamChild* stream) override; + + virtual PStreamNotifyChild* + AllocPStreamNotifyChild(const nsCString& url, const nsCString& target, + const bool& post, const nsCString& buffer, + const bool& file, + NPError* result) override; + + virtual bool + DeallocPStreamNotifyChild(PStreamNotifyChild* notifyData) override; + + virtual bool + AnswerSetPluginFocus() override; + + virtual bool + AnswerUpdateWindow() override; + + virtual bool + RecvNPP_DidComposite() override; + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + bool CreateWindow(const NPRemoteWindow& aWindow); + void DeleteWindow(); +#endif + +public: + PluginInstanceChild(const NPPluginFuncs* aPluginIface, + const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray<nsCString>& aNames, + const InfallibleTArray<nsCString>& aValues); + + virtual ~PluginInstanceChild(); + + NPError DoNPP_New(); + + // Common sync+async implementation of NPP_NewStream + NPError DoNPP_NewStream(BrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, + uint16_t* stype); + + bool Initialize(); + + NPP GetNPP() + { + return &mData; + } + + NPError + NPN_GetValue(NPNVariable aVariable, void* aValue); + + NPError + NPN_SetValue(NPPVariable aVariable, void* aValue); + + PluginScriptableObjectChild* + GetActorForNPObject(NPObject* aObject); + + NPError + NPN_NewStream(NPMIMEType aMIMEType, const char* aWindow, + NPStream** aStream); + + void InvalidateRect(NPRect* aInvalidRect); + +#ifdef MOZ_WIDGET_COCOA + void Invalidate(); +#endif // definied(MOZ_WIDGET_COCOA) + + uint32_t ScheduleTimer(uint32_t interval, bool repeat, TimerFunc func); + void UnscheduleTimer(uint32_t id); + + void AsyncCall(PluginThreadCallback aFunc, void* aUserData); + // This function is a more general version of AsyncCall + void PostChildAsyncCall(already_AddRefed<ChildAsyncCall> aTask); + + int GetQuirks(); + + void NPN_URLRedirectResponse(void* notifyData, NPBool allow); + + + NPError NPN_InitAsyncSurface(NPSize *size, NPImageFormat format, + void *initData, NPAsyncSurface *surface); + NPError NPN_FinalizeAsyncSurface(NPAsyncSurface *surface); + + void NPN_SetCurrentAsyncSurface(NPAsyncSurface *surface, NPRect *changed); + + void DoAsyncRedraw(); + + virtual bool RecvHandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, + const bool& aIsConsumed) override; + +#if defined(XP_WIN) + NPError DefaultAudioDeviceChanged(NPAudioDeviceChangeDetails& details); +#endif + +private: + friend class PluginModuleChild; + + NPError + InternalGetNPObjectForValue(NPNVariable aValue, + NPObject** aObject); + + bool IsUsingDirectDrawing(); + + virtual bool RecvUpdateBackground(const SurfaceDescriptor& aBackground, + const nsIntRect& aRect) override; + + virtual PPluginBackgroundDestroyerChild* + AllocPPluginBackgroundDestroyerChild() override; + + virtual bool + RecvPPluginBackgroundDestroyerConstructor(PPluginBackgroundDestroyerChild* aActor) override; + + virtual bool + DeallocPPluginBackgroundDestroyerChild(PPluginBackgroundDestroyerChild* aActor) override; + +#if defined(OS_WIN) + static bool RegisterWindowClass(); + bool CreatePluginWindow(); + void DestroyPluginWindow(); + void SizePluginWindow(int width, int height); + int16_t WinlessHandleEvent(NPEvent& event); + void CreateWinlessPopupSurrogate(); + void DestroyWinlessPopupSurrogate(); + void InitPopupMenuHook(); + void SetupFlashMsgThrottle(); + void UnhookWinlessFlashThrottle(); + void HookSetWindowLongPtr(); + void SetUnityHooks(); + void ClearUnityHooks(); + void InitImm32Hook(); + static inline bool SetWindowLongHookCheck(HWND hWnd, + int nIndex, + LONG_PTR newLong); + void FlashThrottleMessage(HWND, UINT, WPARAM, LPARAM, bool); + static LRESULT CALLBACK DummyWindowProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); + static LRESULT CALLBACK PluginWindowProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); + static BOOL WINAPI TrackPopupHookProc(HMENU hMenu, + UINT uFlags, + int x, + int y, + int nReserved, + HWND hWnd, + CONST RECT *prcRect); + static HWND WINAPI SetCaptureHook(HWND aHwnd); + static LRESULT CALLBACK UnityGetMessageHookProc(int aCode, WPARAM aWparam, LPARAM aLParam); + static LRESULT CALLBACK UnitySendMessageHookProc(int aCode, WPARAM aWparam, LPARAM aLParam); + static BOOL CALLBACK EnumThreadWindowsCallback(HWND hWnd, + LPARAM aParam); + static LRESULT CALLBACK WinlessHiddenFlashWndProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam); +#ifdef _WIN64 + static LONG_PTR WINAPI SetWindowLongPtrAHook(HWND hWnd, + int nIndex, + LONG_PTR newLong); + static LONG_PTR WINAPI SetWindowLongPtrWHook(HWND hWnd, + int nIndex, + LONG_PTR newLong); + +#else + static LONG WINAPI SetWindowLongAHook(HWND hWnd, + int nIndex, + LONG newLong); + static LONG WINAPI SetWindowLongWHook(HWND hWnd, + int nIndex, + LONG newLong); +#endif + + static HIMC WINAPI ImmGetContextProc(HWND aWND); + static BOOL WINAPI ImmReleaseContextProc(HWND aWND, HIMC aIMC); + static LONG WINAPI ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex, + LPVOID aBuf, DWORD aLen); + static BOOL WINAPI ImmSetCandidateWindowProc(HIMC hIMC, + LPCANDIDATEFORM plCandidate); + static BOOL WINAPI ImmNotifyIME(HIMC aIMC, DWORD aAction, DWORD aIndex, + DWORD aValue); + + class FlashThrottleAsyncMsg : public ChildAsyncCall + { + public: + FlashThrottleAsyncMsg(); + FlashThrottleAsyncMsg(PluginInstanceChild* aInst, + HWND aWnd, UINT aMsg, + WPARAM aWParam, LPARAM aLParam, + bool isWindowed) + : ChildAsyncCall(aInst, nullptr, nullptr), + mWnd(aWnd), + mMsg(aMsg), + mWParam(aWParam), + mLParam(aLParam), + mWindowed(isWindowed) + {} + + NS_IMETHOD Run() override; + + WNDPROC GetProc(); + HWND GetWnd() { return mWnd; } + UINT GetMsg() { return mMsg; } + WPARAM GetWParam() { return mWParam; } + LPARAM GetLParam() { return mLParam; } + + private: + HWND mWnd; + UINT mMsg; + WPARAM mWParam; + LPARAM mLParam; + bool mWindowed; + }; + + bool ShouldPostKeyMessage(UINT message, WPARAM wParam, LPARAM lParam); + bool MaybePostKeyMessage(UINT message, WPARAM wParam, LPARAM lParam); +#endif // #if defined(OS_WIN) + const NPPluginFuncs* mPluginIface; + nsCString mMimeType; + uint16_t mMode; + InfallibleTArray<nsCString> mNames; + InfallibleTArray<nsCString> mValues; + NPP_t mData; + NPWindow mWindow; +#if defined(XP_DARWIN) || defined(XP_WIN) + double mContentsScaleFactor; +#endif + double mCSSZoomFactor; + uint32_t mPostingKeyEvents; + uint32_t mPostingKeyEventsOutdated; + int16_t mDrawingModel; + + NPAsyncSurface* mCurrentDirectSurface; + + // The surface hashtables below serve a few purposes. They let us verify + // and retain extra information about plugin surfaces, and they let us + // free shared memory that the plugin might forget to release. + struct DirectBitmap { + DirectBitmap(PluginInstanceChild* aOwner, const Shmem& shmem, + const gfx::IntSize& size, uint32_t stride, SurfaceFormat format); + + private: + ~DirectBitmap(); + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DirectBitmap); + + PluginInstanceChild* mOwner; + Shmem mShmem; + gfx::SurfaceFormat mFormat; + gfx::IntSize mSize; + uint32_t mStride; + }; + nsRefPtrHashtable<nsPtrHashKey<NPAsyncSurface>, DirectBitmap> mDirectBitmaps; + +#if defined(XP_WIN) + nsDataHashtable<nsPtrHashKey<NPAsyncSurface>, WindowsHandle> mDxgiSurfaces; +#endif + + mozilla::Mutex mAsyncInvalidateMutex; + CancelableRunnable *mAsyncInvalidateTask; + + // Cached scriptable actors to avoid IPC churn + PluginScriptableObjectChild* mCachedWindowActor; + PluginScriptableObjectChild* mCachedElementActor; + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + NPSetWindowCallbackStruct mWsInfo; +#ifdef MOZ_WIDGET_GTK + bool mXEmbed; + XtClient mXtClient; +#endif +#elif defined(OS_WIN) + HWND mPluginWindowHWND; + WNDPROC mPluginWndProc; + HWND mPluginParentHWND; + int mNestedEventLevelDepth; + HWND mCachedWinlessPluginHWND; + HWND mWinlessPopupSurrogateHWND; + nsIntPoint mPluginSize; + WNDPROC mWinlessThrottleOldWndProc; + HWND mWinlessHiddenMsgHWND; + HHOOK mUnityGetMessageHook; + HHOOK mUnitySendMessageHook; +#endif + + friend class ChildAsyncCall; + + Mutex mAsyncCallMutex; + nsTArray<ChildAsyncCall*> mPendingAsyncCalls; + nsTArray<nsAutoPtr<ChildTimer> > mTimers; + + /** + * During destruction we enumerate all remaining scriptable objects and + * invalidate/delete them. Enumeration can re-enter, so maintain a + * hash separate from PluginModuleChild.mObjectMap. + */ + nsAutoPtr< nsTHashtable<DeletingObjectEntry> > mDeletingHash; + +#if defined(MOZ_WIDGET_COCOA) +private: +#if defined(__i386__) + NPEventModel mEventModel; +#endif + CGColorSpaceRef mShColorSpace; + CGContextRef mShContext; + RefPtr<nsCARenderer> mCARenderer; + void *mCGLayer; + + // Core Animation drawing model requires a refresh timer. + uint32_t mCARefreshTimer; + +public: + const NPCocoaEvent* getCurrentEvent() { + return mCurrentEvent; + } + + bool CGDraw(CGContextRef ref, nsIntRect aUpdateRect); + +#if defined(__i386__) + NPEventModel EventModel() { return mEventModel; } +#endif + +private: + const NPCocoaEvent *mCurrentEvent; +#endif + + bool CanPaintOnBackground(); + + bool IsVisible() { +#ifdef XP_MACOSX + return mWindow.clipRect.top != mWindow.clipRect.bottom && + mWindow.clipRect.left != mWindow.clipRect.right; +#else + return mWindow.clipRect.top != 0 || + mWindow.clipRect.left != 0 || + mWindow.clipRect.bottom != 0 || + mWindow.clipRect.right != 0; +#endif + } + + // ShowPluginFrame - in general does four things: + // 1) Create mCurrentSurface optimized for rendering to parent process + // 2) Updated mCurrentSurface to be a complete copy of mBackSurface + // 3) Draw the invalidated plugin area into mCurrentSurface + // 4) Send it to parent process. + bool ShowPluginFrame(void); + + // If we can read back safely from mBackSurface, copy + // mSurfaceDifferenceRect from mBackSurface to mFrontSurface. + // @return Whether the back surface could be read. + bool ReadbackDifferenceRect(const nsIntRect& rect); + + // Post ShowPluginFrame task + void AsyncShowPluginFrame(void); + + // In the PaintRect functions, aSurface is the size of the full plugin + // window. Each PaintRect function renders into the subrectangle aRect of + // aSurface (possibly more if we're working around a Flash bug). + + // Paint plugin content rectangle to surface with bg color filling + void PaintRectToSurface(const nsIntRect& aRect, + gfxASurface* aSurface, + const gfx::Color& aColor); + + // Render plugin content to surface using + // white/black image alpha extraction algorithm + void PaintRectWithAlphaExtraction(const nsIntRect& aRect, + gfxASurface* aSurface); + + // Call plugin NPAPI function to render plugin content to surface + // @param - aSurface - should be compatible with current platform plugin rendering + // @return - FALSE if plugin not painted to surface + void PaintRectToPlatformSurface(const nsIntRect& aRect, + gfxASurface* aSurface); + + // Update NPWindow platform attributes and call plugin "setwindow" + // @param - aForceSetWindow - call setwindow even if platform attributes are the same + void UpdateWindowAttributes(bool aForceSetWindow = false); + + // Create optimized mCurrentSurface for parent process rendering + // @return FALSE if optimized surface not created + bool CreateOptSurface(void); + + // Create mHelperSurface if mCurrentSurface non compatible with plugins + // @return TRUE if helper surface created successfully, or not needed + bool MaybeCreatePlatformHelperSurface(void); + + // Make sure that we have surface for rendering + bool EnsureCurrentBuffer(void); + + // Helper function for delayed InvalidateRect call + // non null mCurrentInvalidateTask will call this function + void InvalidateRectDelayed(void); + + // Clear mCurrentSurface/mCurrentSurfaceActor/mHelperSurface + void ClearCurrentSurface(); + + // Swap mCurrentSurface/mBackSurface and their associated actors + void SwapSurfaces(); + + // Clear all surfaces in response to NPP_Destroy + void ClearAllSurfaces(); + + void Destroy(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + // Set as true when SetupLayer called + // and go with different path in InvalidateRect function + bool mLayersRendering; + + // Current surface available for rendering + RefPtr<gfxASurface> mCurrentSurface; + + // Back surface, just keeping reference to + // surface which is on ParentProcess side + RefPtr<gfxASurface> mBackSurface; + +#ifdef XP_MACOSX + // Current IOSurface available for rendering + // We can't use thebes gfxASurface like other platforms. + PluginUtilsOSX::nsDoubleBufferCARenderer mDoubleBufferCARenderer; +#endif + + // (Not to be confused with mBackSurface). This is a recent copy + // of the opaque pixels under our object frame, if + // |mIsTransparent|. We ask the plugin render directly onto a + // copy of the background pixels if available, and fall back on + // alpha recovery otherwise. + RefPtr<gfxASurface> mBackground; + +#ifdef XP_WIN + // These actors mirror mCurrentSurface/mBackSurface + PPluginSurfaceChild* mCurrentSurfaceActor; + PPluginSurfaceChild* mBackSurfaceActor; +#endif + + // Accumulated invalidate rect, while back buffer is not accessible, + // in plugin coordinates. + nsIntRect mAccumulatedInvalidRect; + + // Plugin only call SetTransparent + // and does not remember their transparent state + // and p->getvalue return always false + bool mIsTransparent; + + // Surface type optimized of parent process + gfxSurfaceType mSurfaceType; + + // Keep InvalidateRect task pointer to be able Cancel it on Destroy + RefPtr<CancelableRunnable> mCurrentInvalidateTask; + + // Keep AsyncSetWindow task pointer to be able to Cancel it on Destroy + RefPtr<CancelableRunnable> mCurrentAsyncSetWindowTask; + + // True while plugin-child in plugin call + // Use to prevent plugin paint re-enter + bool mPendingPluginCall; + + // On some platforms, plugins may not support rendering to a surface with + // alpha, or not support rendering to an image surface. + // In those cases we need to draw to a temporary platform surface; we cache + // that surface here. + RefPtr<gfxASurface> mHelperSurface; + + // true when plugin does not support painting to ARGB32 + // surface this is false if plugin supports + // NPPVpluginTransparentAlphaBool (which is not part of + // NPAPI yet) + bool mDoAlphaExtraction; + + // true when the plugin has painted at least once. We use this to ensure + // that we ask a plugin to paint at least once even if it's invisible; + // some plugin (instances) rely on this in order to work properly. + bool mHasPainted; + + // Cached rectangle rendered to previous surface(mBackSurface) + // Used for reading back to current surface and syncing data, + // in plugin coordinates. + nsIntRect mSurfaceDifferenceRect; + + // Has this instance been destroyed, either by ActorDestroy or NPP_Destroy? + bool mDestroyed; + +#ifdef XP_WIN + // WM_*CHAR messages are never consumed by chrome process's widget. + // So, if preceding keydown or keyup event is consumed by reserved + // shortcut key in the chrome process, we shouldn't send the following + // WM_*CHAR messages to the plugin. + bool mLastKeyEventConsumed; +#endif // #ifdef XP_WIN + + // While IME in the process has composition, this is set to true. + // Otherwise, false. + static bool sIsIMEComposing; + + // A counter is incremented by AutoStackHelper to indicate that there is an + // active plugin call which should be preventing shutdown. +public: + class AutoStackHelper { + public: + explicit AutoStackHelper(PluginInstanceChild* instance) + : mInstance(instance) + { + ++mInstance->mStackDepth; + } + ~AutoStackHelper() { + --mInstance->mStackDepth; + } + private: + PluginInstanceChild *const mInstance; + }; +private: + int32_t mStackDepth; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // ifndef dom_plugins_PluginInstanceChild_h diff --git a/dom/plugins/ipc/PluginInstanceParent.cpp b/dom/plugins/ipc/PluginInstanceParent.cpp new file mode 100644 index 000000000..02f0641f7 --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -0,0 +1,2509 @@ +/* -*- 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/. */ + +#include "mozilla/DebugOnly.h" +#include <stdint.h> // for intptr_t + +#include "mozilla/BasicEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "PluginInstanceParent.h" +#include "BrowserStreamParent.h" +#include "PluginAsyncSurrogate.h" +#include "PluginBackgroundDestroyer.h" +#include "PluginModuleParent.h" +#include "PluginStreamParent.h" +#include "StreamNotifyParent.h" +#include "npfunctions.h" +#include "nsAutoPtr.h" +#include "gfxASurface.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfxSharedImageSurface.h" +#include "nsNetUtil.h" +#include "nsNPAPIPluginInstance.h" +#include "nsPluginInstanceOwner.h" +#include "nsFocusManager.h" +#include "nsIDOMElement.h" +#ifdef MOZ_X11 +#include "gfxXlibSurface.h" +#endif +#include "gfxContext.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "Layers.h" +#include "ImageContainer.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "gfxPrefs.h" +#include "LayersLogging.h" +#include "mozilla/layers/TextureWrapperImage.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/layers/ImageBridgeChild.h" +#if defined(XP_WIN) +# include "mozilla/layers/D3D11ShareHandleImage.h" +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/TextureD3D11.h" +#endif + +#ifdef XP_MACOSX +#include "MacIOSurfaceImage.h" +#endif + +#if defined(OS_WIN) +#include <windowsx.h> +#include "gfxWindowsPlatform.h" +#include "mozilla/plugins/PluginSurfaceParent.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsIWidget.h" +#include "nsPluginNativeWindow.h" +#include "PluginQuirks.h" +extern const wchar_t* kFlashFullscreenClass; +#elif defined(MOZ_WIDGET_GTK) +#include "mozilla/dom/ContentChild.h" +#include <gdk/gdk.h> +#elif defined(XP_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#endif // defined(XP_MACOSX) + +using namespace mozilla::plugins; +using namespace mozilla::layers; +using namespace mozilla::gl; + +void +StreamNotifyParent::ActorDestroy(ActorDestroyReason aWhy) +{ + // Implement me! Bug 1005162 +} + +bool +StreamNotifyParent::RecvRedirectNotifyResponse(const bool& allow) +{ + PluginInstanceParent* instance = static_cast<PluginInstanceParent*>(Manager()); + instance->mNPNIface->urlredirectresponse(instance->mNPP, this, static_cast<NPBool>(allow)); + return true; +} + +#if defined(XP_WIN) +namespace mozilla { +namespace plugins { +/** + * e10s specific, used in cross referencing hwnds with plugin instances so we + * can access methods here from PluginWidgetChild. + */ +static nsClassHashtable<nsVoidPtrHashKey, PluginInstanceParent>* sPluginInstanceList; + +// static +PluginInstanceParent* +PluginInstanceParent::LookupPluginInstanceByID(uintptr_t aId) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (sPluginInstanceList) { + return sPluginInstanceList->Get((void*)aId); + } + return nullptr; +} +} +} +#endif + +PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent, + NPP npp, + const nsCString& aMimeType, + const NPNetscapeFuncs* npniface) + : mParent(parent) + , mSurrogate(PluginAsyncSurrogate::Cast(npp)) + , mUseSurrogate(true) + , mNPP(npp) + , mNPNIface(npniface) + , mWindowType(NPWindowTypeWindow) + , mDrawingModel(kDefaultDrawingModel) + , mLastRecordedDrawingModel(-1) + , mFrameID(0) +#if defined(OS_WIN) + , mPluginHWND(nullptr) + , mChildPluginHWND(nullptr) + , mChildPluginsParentHWND(nullptr) + , mPluginWndProc(nullptr) + , mNestedEventState(false) +#endif // defined(XP_WIN) +#if defined(XP_MACOSX) + , mShWidth(0) + , mShHeight(0) + , mShColorSpace(nullptr) +#endif +{ +#if defined(OS_WIN) + if (!sPluginInstanceList) { + sPluginInstanceList = new nsClassHashtable<nsVoidPtrHashKey, PluginInstanceParent>(); + } +#endif +} + +PluginInstanceParent::~PluginInstanceParent() +{ + if (mNPP) + mNPP->pdata = nullptr; + +#if defined(OS_WIN) + NS_ASSERTION(!(mPluginHWND || mPluginWndProc), + "Subclass was not reset correctly before the dtor was reached!"); +#endif +#if defined(MOZ_WIDGET_COCOA) + if (mShWidth != 0 && mShHeight != 0) { + DeallocShmem(mShSurface); + } + if (mShColorSpace) + ::CGColorSpaceRelease(mShColorSpace); +#endif +} + +bool +PluginInstanceParent::InitMetadata(const nsACString& aMimeType, + const nsACString& aSrcAttribute) +{ + if (aSrcAttribute.IsEmpty()) { + return false; + } + // Ensure that the src attribute is absolute + RefPtr<nsPluginInstanceOwner> owner = GetOwner(); + if (!owner) { + return false; + } + nsCOMPtr<nsIURI> baseUri(owner->GetBaseURI()); + return NS_SUCCEEDED(NS_MakeAbsoluteURI(mSrcAttribute, aSrcAttribute, baseUri)); +} + +void +PluginInstanceParent::ActorDestroy(ActorDestroyReason why) +{ +#if defined(OS_WIN) + if (why == AbnormalShutdown) { + // If the plugin process crashes, this is the only + // chance we get to destroy resources. + UnsubclassPluginWindow(); + } +#endif + if (mFrontSurface) { + mFrontSurface = nullptr; + if (mImageContainer) { + mImageContainer->ClearAllImages(); + } +#ifdef MOZ_X11 + FinishX(DefaultXDisplay()); +#endif + } + if (IsUsingDirectDrawing() && mImageContainer) { + mImageContainer->ClearAllImages(); + } +} + +NPError +PluginInstanceParent::Destroy() +{ + NPError retval; + { // Scope for timer + Telemetry::AutoTimer<Telemetry::BLOCKED_ON_PLUGIN_INSTANCE_DESTROY_MS> + timer(Module()->GetHistogramKey()); + if (!CallNPP_Destroy(&retval)) { + retval = NPERR_GENERIC_ERROR; + } + } + +#if defined(OS_WIN) + UnsubclassPluginWindow(); +#endif + + return retval; +} + +bool +PluginInstanceParent::IsUsingDirectDrawing() +{ + return IsDrawingModelDirect(mDrawingModel); +} + +PBrowserStreamParent* +PluginInstanceParent::AllocPBrowserStreamParent(const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + PStreamNotifyParent* notifyData, + const nsCString& headers) +{ + NS_RUNTIMEABORT("Not reachable"); + return nullptr; +} + +bool +PluginInstanceParent::DeallocPBrowserStreamParent(PBrowserStreamParent* stream) +{ + delete stream; + return true; +} + +PPluginStreamParent* +PluginInstanceParent::AllocPPluginStreamParent(const nsCString& mimeType, + const nsCString& target, + NPError* result) +{ + return new PluginStreamParent(this, mimeType, target, result); +} + +bool +PluginInstanceParent::DeallocPPluginStreamParent(PPluginStreamParent* stream) +{ + delete stream; + return true; +} + +bool +PluginInstanceParent::AnswerNPN_GetValue_NPNVnetscapeWindow(NativeWindowHandle* value, + NPError* result) +{ +#ifdef XP_WIN + HWND id; +#elif defined(MOZ_X11) + XID id; +#elif defined(XP_DARWIN) + intptr_t id; +#elif defined(ANDROID) + // TODO: Need Android impl + int id; +#else +#warning Implement me +#endif + + *result = mNPNIface->getvalue(mNPP, NPNVnetscapeWindow, &id); + *value = id; + return true; +} + +bool +PluginInstanceParent::InternalGetValueForNPObject( + NPNVariable aVariable, + PPluginScriptableObjectParent** aValue, + NPError* aResult) +{ + NPObject* npobject; + NPError result = mNPNIface->getvalue(mNPP, aVariable, (void*)&npobject); + if (result == NPERR_NO_ERROR) { + NS_ASSERTION(npobject, "Shouldn't return null and NPERR_NO_ERROR!"); + + PluginScriptableObjectParent* actor = GetActorForNPObject(npobject); + mNPNIface->releaseobject(npobject); + if (actor) { + *aValue = actor; + *aResult = NPERR_NO_ERROR; + return true; + } + + NS_ERROR("Failed to get actor!"); + result = NPERR_GENERIC_ERROR; + } + + *aValue = nullptr; + *aResult = result; + return true; +} + +bool +PluginInstanceParent::AnswerNPN_GetValue_NPNVWindowNPObject( + PPluginScriptableObjectParent** aValue, + NPError* aResult) +{ + return InternalGetValueForNPObject(NPNVWindowNPObject, aValue, aResult); +} + +bool +PluginInstanceParent::AnswerNPN_GetValue_NPNVPluginElementNPObject( + PPluginScriptableObjectParent** aValue, + NPError* aResult) +{ + return InternalGetValueForNPObject(NPNVPluginElementNPObject, aValue, + aResult); +} + +bool +PluginInstanceParent::AnswerNPN_GetValue_NPNVprivateModeBool(bool* value, + NPError* result) +{ + NPBool v; + *result = mNPNIface->getvalue(mNPP, NPNVprivateModeBool, &v); + *value = v; + return true; +} + +bool +PluginInstanceParent::AnswerNPN_GetValue_DrawingModelSupport(const NPNVariable& model, bool* value) +{ + *value = false; + return true; +} + +bool +PluginInstanceParent::AnswerNPN_GetValue_NPNVdocumentOrigin(nsCString* value, + NPError* result) +{ + void *v = nullptr; + *result = mNPNIface->getvalue(mNPP, NPNVdocumentOrigin, &v); + if (*result == NPERR_NO_ERROR && v) { + value->Adopt(static_cast<char*>(v)); + } + return true; +} + +static inline bool +AllowDirectBitmapSurfaceDrawing() +{ + if (!gfxPrefs::PluginAsyncDrawingEnabled()) { + return false; + } + return gfxPlatform::GetPlatform()->SupportsPluginDirectBitmapDrawing(); +} + +static inline bool +AllowDirectDXGISurfaceDrawing() +{ + if (!gfxPrefs::PluginAsyncDrawingEnabled()) { + return false; + } +#if defined(XP_WIN) + return gfxWindowsPlatform::GetPlatform()->SupportsPluginDirectDXGIDrawing(); +#else + return false; +#endif +} + +bool +PluginInstanceParent::AnswerNPN_GetValue_SupportsAsyncBitmapSurface(bool* value) +{ + *value = AllowDirectBitmapSurfaceDrawing(); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_GetValue_SupportsAsyncDXGISurface(bool* value) +{ + *value = AllowDirectDXGISurfaceDrawing(); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_GetValue_PreferredDXGIAdapter(DxgiAdapterDesc* aOutDesc) +{ + PodZero(aOutDesc); +#ifdef XP_WIN + if (!AllowDirectDXGISurfaceDrawing()) { + return false; + } + + RefPtr<ID3D11Device> device = DeviceManagerDx::Get()->GetContentDevice(); + if (!device) { + return false; + } + + RefPtr<IDXGIDevice> dxgi; + if (FAILED(device->QueryInterface(__uuidof(IDXGIDevice), getter_AddRefs(dxgi))) || !dxgi) { + return false; + } + RefPtr<IDXGIAdapter> adapter; + if (FAILED(dxgi->GetAdapter(getter_AddRefs(adapter))) || !adapter) { + return false; + } + + DXGI_ADAPTER_DESC desc; + if (FAILED(adapter->GetDesc(&desc))) { + return false; + } + + *aOutDesc = DxgiAdapterDesc::From(desc); +#endif + return true; +} + +bool +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginWindow( + const bool& windowed, NPError* result) +{ + // Yes, we are passing a boolean as a void*. We have to cast to intptr_t + // first to avoid gcc warnings about casting to a pointer from a + // non-pointer-sized integer. + *result = mNPNIface->setvalue(mNPP, NPPVpluginWindowBool, + (void*)(intptr_t)windowed); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginTransparent( + const bool& transparent, NPError* result) +{ + *result = mNPNIface->setvalue(mNPP, NPPVpluginTransparentBool, + (void*)(intptr_t)transparent); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginUsesDOMForCursor( + const bool& useDOMForCursor, NPError* result) +{ + *result = mNPNIface->setvalue(mNPP, NPPVpluginUsesDOMForCursorBool, + (void*)(intptr_t)useDOMForCursor); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginDrawingModel( + const int& drawingModel, NPError* result) +{ + bool allowed = false; + + switch (drawingModel) { +#if defined(XP_MACOSX) + case NPDrawingModelCoreAnimation: + case NPDrawingModelInvalidatingCoreAnimation: + case NPDrawingModelOpenGL: + case NPDrawingModelCoreGraphics: + allowed = true; + break; +#elif defined(XP_WIN) + case NPDrawingModelSyncWin: + allowed = true; + break; + case NPDrawingModelAsyncWindowsDXGISurface: + allowed = AllowDirectDXGISurfaceDrawing(); + break; +#elif defined(MOZ_X11) + case NPDrawingModelSyncX: + allowed = true; + break; +#endif + case NPDrawingModelAsyncBitmapSurface: + allowed = AllowDirectBitmapSurfaceDrawing(); + break; + default: + allowed = false; + break; + } + + if (!allowed) { + *result = NPERR_GENERIC_ERROR; + return true; + } + + mDrawingModel = drawingModel; + + int requestModel = drawingModel; + +#ifdef XP_MACOSX + if (drawingModel == NPDrawingModelCoreAnimation || + drawingModel == NPDrawingModelInvalidatingCoreAnimation) { + // We need to request CoreGraphics otherwise + // the nsPluginFrame will try to draw a CALayer + // that can not be shared across process. + requestModel = NPDrawingModelCoreGraphics; + } +#endif + + *result = mNPNIface->setvalue(mNPP, NPPVpluginDrawingModel, + (void*)(intptr_t)requestModel); + + return true; +} + +bool +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginEventModel( + const int& eventModel, NPError* result) +{ +#ifdef XP_MACOSX + *result = mNPNIface->setvalue(mNPP, NPPVpluginEventModel, + (void*)(intptr_t)eventModel); + return true; +#else + *result = NPERR_GENERIC_ERROR; + return true; +#endif +} + +bool +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginIsPlayingAudio( + const bool& isAudioPlaying, NPError* result) +{ + *result = mNPNIface->setvalue(mNPP, NPPVpluginIsPlayingAudio, + (void*)(intptr_t)isAudioPlaying); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_GetURL(const nsCString& url, + const nsCString& target, + NPError* result) +{ + *result = mNPNIface->geturl(mNPP, + NullableStringGet(url), + NullableStringGet(target)); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_PostURL(const nsCString& url, + const nsCString& target, + const nsCString& buffer, + const bool& file, + NPError* result) +{ + *result = mNPNIface->posturl(mNPP, url.get(), NullableStringGet(target), + buffer.Length(), buffer.get(), file); + return true; +} + +PStreamNotifyParent* +PluginInstanceParent::AllocPStreamNotifyParent(const nsCString& url, + const nsCString& target, + const bool& post, + const nsCString& buffer, + const bool& file, + NPError* result) +{ + return new StreamNotifyParent(); +} + +bool +PluginInstanceParent::AnswerPStreamNotifyConstructor(PStreamNotifyParent* actor, + const nsCString& url, + const nsCString& target, + const bool& post, + const nsCString& buffer, + const bool& file, + NPError* result) +{ + bool streamDestroyed = false; + static_cast<StreamNotifyParent*>(actor)-> + SetDestructionFlag(&streamDestroyed); + + if (!post) { + *result = mNPNIface->geturlnotify(mNPP, + NullableStringGet(url), + NullableStringGet(target), + actor); + } + else { + *result = mNPNIface->posturlnotify(mNPP, + NullableStringGet(url), + NullableStringGet(target), + buffer.Length(), + NullableStringGet(buffer), + file, actor); + } + + if (streamDestroyed) { + // If the stream was destroyed, we must return an error code in the + // constructor. + *result = NPERR_GENERIC_ERROR; + } + else { + static_cast<StreamNotifyParent*>(actor)->ClearDestructionFlag(); + if (*result != NPERR_NO_ERROR) + return PStreamNotifyParent::Send__delete__(actor, + NPERR_GENERIC_ERROR); + } + + return true; +} + +bool +PluginInstanceParent::DeallocPStreamNotifyParent(PStreamNotifyParent* notifyData) +{ + delete notifyData; + return true; +} + +bool +PluginInstanceParent::RecvNPN_InvalidateRect(const NPRect& rect) +{ + mNPNIface->invalidaterect(mNPP, const_cast<NPRect*>(&rect)); + return true; +} + +static inline NPRect +IntRectToNPRect(const gfx::IntRect& rect) +{ + NPRect r; + r.left = rect.x; + r.top = rect.y; + r.right = rect.x + rect.width; + r.bottom = rect.y + rect.height; + return r; +} + +bool +PluginInstanceParent::RecvRevokeCurrentDirectSurface() +{ + ImageContainer *container = GetImageContainer(); + if (!container) { + return true; + } + + container->ClearAllImages(); + + PLUGIN_LOG_DEBUG((" (RecvRevokeCurrentDirectSurface)")); + return true; +} + +bool +PluginInstanceParent::RecvInitDXGISurface(const gfx::SurfaceFormat& format, + const gfx::IntSize& size, + WindowsHandle* outHandle, + NPError* outError) +{ + *outHandle = 0; + *outError = NPERR_GENERIC_ERROR; + +#if defined(XP_WIN) + if (format != SurfaceFormat::B8G8R8A8 && format != SurfaceFormat::B8G8R8X8) { + *outError = NPERR_INVALID_PARAM; + return true; + } + if (size.width <= 0 || size.height <= 0) { + *outError = NPERR_INVALID_PARAM; + return true; + } + + ImageContainer *container = GetImageContainer(); + if (!container) { + return true; + } + + RefPtr<ImageBridgeChild> forwarder = ImageBridgeChild::GetSingleton(); + if (!forwarder) { + return true; + } + + RefPtr<ID3D11Device> d3d11 = DeviceManagerDx::Get()->GetContentDevice(); + if (!d3d11) { + return true; + } + + // Create the texture we'll give to the plugin process. + HANDLE sharedHandle = 0; + RefPtr<ID3D11Texture2D> back; + { + CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, size.width, size.height, 1, 1); + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + if (FAILED(d3d11->CreateTexture2D(&desc, nullptr, getter_AddRefs(back))) || !back) { + return true; + } + + RefPtr<IDXGIResource> resource; + if (FAILED(back->QueryInterface(IID_IDXGIResource, getter_AddRefs(resource))) || !resource) { + return true; + } + if (FAILED(resource->GetSharedHandle(&sharedHandle) || !sharedHandle)) { + return true; + } + } + + RefPtr<D3D11SurfaceHolder> holder = new D3D11SurfaceHolder(back, format, size); + mD3D11Surfaces.Put(reinterpret_cast<void*>(sharedHandle), holder); + + *outHandle = reinterpret_cast<uintptr_t>(sharedHandle); + *outError = NPERR_NO_ERROR; +#endif + return true; +} + +bool +PluginInstanceParent::RecvFinalizeDXGISurface(const WindowsHandle& handle) +{ +#if defined(XP_WIN) + mD3D11Surfaces.Remove(reinterpret_cast<void*>(handle)); +#endif + return true; +} + +bool +PluginInstanceParent::RecvShowDirectBitmap(Shmem&& buffer, + const SurfaceFormat& format, + const uint32_t& stride, + const IntSize& size, + const IntRect& dirty) +{ + // Validate format. + if (format != SurfaceFormat::B8G8R8A8 && format != SurfaceFormat::B8G8R8X8) { + MOZ_ASSERT_UNREACHABLE("bad format type"); + return false; + } + if (size.width <= 0 || size.height <= 0) { + MOZ_ASSERT_UNREACHABLE("bad image size"); + return false; + } + if (mDrawingModel != NPDrawingModelAsyncBitmapSurface) { + MOZ_ASSERT_UNREACHABLE("plugin did not set a bitmap drawing model"); + return false; + } + + // Validate buffer and size. + CheckedInt<uint32_t> nbytes = CheckedInt<uint32_t>(uint32_t(size.height)) * stride; + if (!nbytes.isValid() || nbytes.value() != buffer.Size<uint8_t>()) { + MOZ_ASSERT_UNREACHABLE("bad shmem size"); + return false; + } + + ImageContainer* container = GetImageContainer(); + if (!container) { + return false; + } + + RefPtr<gfx::DataSourceSurface> source = + gfx::Factory::CreateWrappingDataSourceSurface(buffer.get<uint8_t>(), stride, size, format); + if (!source) { + return false; + } + + // Allocate a texture for the compositor. + RefPtr<TextureClientRecycleAllocator> allocator = mParent->EnsureTextureAllocatorForDirectBitmap(); + RefPtr<TextureClient> texture = allocator->CreateOrRecycle( + format, size, BackendSelector::Content, + TextureFlags::NO_FLAGS, + TextureAllocationFlags(ALLOC_FOR_OUT_OF_BAND_CONTENT | ALLOC_UPDATE_FROM_SURFACE)); + if (!texture) { + NS_WARNING("Could not allocate a TextureClient for plugin!"); + return false; + } + + // Upload the plugin buffer. + { + TextureClientAutoLock autoLock(texture, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return false; + } + texture->UpdateFromSurface(source); + } + + // Wrap the texture in an image and ship it off. + RefPtr<TextureWrapperImage> image = + new TextureWrapperImage(texture, gfx::IntRect(gfx::IntPoint(0, 0), size)); + SetCurrentImage(image); + + PLUGIN_LOG_DEBUG((" (RecvShowDirectBitmap received shmem=%p stride=%d size=%s dirty=%s)", + buffer.get<unsigned char>(), stride, Stringify(size).c_str(), Stringify(dirty).c_str())); + return true; +} + +void +PluginInstanceParent::SetCurrentImage(Image* aImage) +{ + MOZ_ASSERT(IsUsingDirectDrawing()); + ImageContainer::NonOwningImage holder(aImage); + holder.mFrameID = ++mFrameID; + + AutoTArray<ImageContainer::NonOwningImage,1> imageList; + imageList.AppendElement(holder); + mImageContainer->SetCurrentImages(imageList); + + // Invalidate our area in the page so the image gets flushed. + gfx::IntRect rect = aImage->GetPictureRect(); + NPRect nprect = {uint16_t(rect.x), uint16_t(rect.y), uint16_t(rect.width), uint16_t(rect.height)}; + RecvNPN_InvalidateRect(nprect); + + RecordDrawingModel(); +} + +bool +PluginInstanceParent::RecvShowDirectDXGISurface(const WindowsHandle& handle, + const gfx::IntRect& dirty) +{ +#if defined(XP_WIN) + RefPtr<D3D11SurfaceHolder> surface; + if (!mD3D11Surfaces.Get(reinterpret_cast<void*>(handle), getter_AddRefs(surface))) { + return false; + } + if (!surface->IsValid()) { + return false; + } + + ImageContainer* container = GetImageContainer(); + if (!container) { + return false; + } + + RefPtr<TextureClientRecycleAllocator> allocator = mParent->EnsureTextureAllocatorForDXGISurface(); + RefPtr<TextureClient> texture = allocator->CreateOrRecycle( + surface->GetFormat(), surface->GetSize(), + BackendSelector::Content, + TextureFlags::NO_FLAGS, + ALLOC_FOR_OUT_OF_BAND_CONTENT); + if (!texture) { + NS_WARNING("Could not allocate a TextureClient for plugin!"); + return false; + } + + surface->CopyToTextureClient(texture); + + gfx::IntSize size(surface->GetSize()); + gfx::IntRect pictureRect(gfx::IntPoint(0, 0), size); + + // Wrap the texture in an image and ship it off. + RefPtr<TextureWrapperImage> image = new TextureWrapperImage(texture, pictureRect); + SetCurrentImage(image); + + PLUGIN_LOG_DEBUG((" (RecvShowDirect3D10Surface received handle=%p rect=%s)", + reinterpret_cast<void*>(handle), Stringify(dirty).c_str())); + return true; +#else + return false; +#endif +} + +bool +PluginInstanceParent::RecvShow(const NPRect& updatedRect, + const SurfaceDescriptor& newSurface, + SurfaceDescriptor* prevSurface) +{ + PLUGIN_LOG_DEBUG( + ("[InstanceParent][%p] RecvShow for <x=%d,y=%d, w=%d,h=%d>", + this, updatedRect.left, updatedRect.top, + updatedRect.right - updatedRect.left, + updatedRect.bottom - updatedRect.top)); + + MOZ_ASSERT(!IsUsingDirectDrawing()); + + // XXXjwatt rewrite to use Moz2D + RefPtr<gfxASurface> surface; + if (newSurface.type() == SurfaceDescriptor::TShmem) { + if (!newSurface.get_Shmem().IsReadable()) { + NS_WARNING("back surface not readable"); + return false; + } + surface = gfxSharedImageSurface::Open(newSurface.get_Shmem()); + } +#ifdef XP_MACOSX + else if (newSurface.type() == SurfaceDescriptor::TIOSurfaceDescriptor) { + IOSurfaceDescriptor iodesc = newSurface.get_IOSurfaceDescriptor(); + + RefPtr<MacIOSurface> newIOSurface = + MacIOSurface::LookupSurface(iodesc.surfaceId(), + iodesc.contentsScaleFactor()); + + if (!newIOSurface) { + NS_WARNING("Got bad IOSurfaceDescriptor in RecvShow"); + return false; + } + + if (mFrontIOSurface) + *prevSurface = IOSurfaceDescriptor(mFrontIOSurface->GetIOSurfaceID(), + mFrontIOSurface->GetContentsScaleFactor()); + else + *prevSurface = null_t(); + + mFrontIOSurface = newIOSurface; + + RecvNPN_InvalidateRect(updatedRect); + + PLUGIN_LOG_DEBUG((" (RecvShow invalidated for surface %p)", + mFrontSurface.get())); + + return true; + } +#endif +#ifdef MOZ_X11 + else if (newSurface.type() == SurfaceDescriptor::TSurfaceDescriptorX11) { + surface = newSurface.get_SurfaceDescriptorX11().OpenForeign(); + } +#endif +#ifdef XP_WIN + else if (newSurface.type() == SurfaceDescriptor::TPPluginSurfaceParent) { + PluginSurfaceParent* s = + static_cast<PluginSurfaceParent*>(newSurface.get_PPluginSurfaceParent()); + surface = s->Surface(); + } +#endif + + if (mFrontSurface) { + // This is the "old front buffer" we're about to hand back to + // the plugin. We might still have drawing operations + // referencing it. +#ifdef MOZ_X11 + if (mFrontSurface->GetType() == gfxSurfaceType::Xlib) { + // Finish with the surface and XSync here to ensure the server has + // finished operations on the surface before the plugin starts + // scribbling on it again, or worse, destroys it. + mFrontSurface->Finish(); + FinishX(DefaultXDisplay()); + } else +#endif + { + mFrontSurface->Flush(); + } + } + + if (mFrontSurface && gfxSharedImageSurface::IsSharedImage(mFrontSurface)) + *prevSurface = static_cast<gfxSharedImageSurface*>(mFrontSurface.get())->GetShmem(); + else + *prevSurface = null_t(); + + if (surface) { + // Notify the cairo backend that this surface has changed behind + // its back. + gfxRect ur(updatedRect.left, updatedRect.top, + updatedRect.right - updatedRect.left, + updatedRect.bottom - updatedRect.top); + surface->MarkDirty(ur); + + bool isPlugin = true; + RefPtr<gfx::SourceSurface> sourceSurface = + gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface, isPlugin); + RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(surface->GetSize(), sourceSurface); + + AutoTArray<ImageContainer::NonOwningImage,1> imageList; + imageList.AppendElement( + ImageContainer::NonOwningImage(image)); + + ImageContainer *container = GetImageContainer(); + container->SetCurrentImages(imageList); + } + else if (mImageContainer) { + mImageContainer->ClearAllImages(); + } + + mFrontSurface = surface; + RecvNPN_InvalidateRect(updatedRect); + + PLUGIN_LOG_DEBUG((" (RecvShow invalidated for surface %p)", + mFrontSurface.get())); + + RecordDrawingModel(); + return true; +} + +nsresult +PluginInstanceParent::AsyncSetWindow(NPWindow* aWindow) +{ + NPRemoteWindow window; + mWindowType = aWindow->type; + window.window = reinterpret_cast<uint64_t>(aWindow->window); + window.x = aWindow->x; + window.y = aWindow->y; + window.width = aWindow->width; + window.height = aWindow->height; + window.clipRect = aWindow->clipRect; + window.type = aWindow->type; +#if defined(XP_MACOSX) || defined(XP_WIN) + double scaleFactor = 1.0; + mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &scaleFactor); + window.contentsScaleFactor = scaleFactor; +#endif + +#if defined(OS_WIN) + MaybeCreateChildPopupSurrogate(); +#endif + + if (!SendAsyncSetWindow(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->GetType(), + window)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +PluginInstanceParent::GetImageContainer(ImageContainer** aContainer) +{ + if (IsUsingDirectDrawing()) { + // Use the image container created by the most recent direct surface + // call, if any. We don't create one if no surfaces were presented + // yet. + ImageContainer *container = mImageContainer; + NS_IF_ADDREF(container); + *aContainer = container; + return NS_OK; + } + +#ifdef XP_MACOSX + MacIOSurface* ioSurface = nullptr; + + if (mFrontIOSurface) { + ioSurface = mFrontIOSurface; + } else if (mIOSurface) { + ioSurface = mIOSurface; + } + + if (!mFrontSurface && !ioSurface) +#else + if (!mFrontSurface) +#endif + return NS_ERROR_NOT_AVAILABLE; + + ImageContainer *container = GetImageContainer(); + + if (!container) { + return NS_ERROR_FAILURE; + } + +#ifdef XP_MACOSX + if (ioSurface) { + RefPtr<Image> image = new MacIOSurfaceImage(ioSurface); + container->SetCurrentImageInTransaction(image); + + NS_IF_ADDREF(container); + *aContainer = container; + return NS_OK; + } +#endif + + NS_IF_ADDREF(container); + *aContainer = container; + return NS_OK; +} + +nsresult +PluginInstanceParent::GetImageSize(nsIntSize* aSize) +{ + if (IsUsingDirectDrawing()) { + if (!mImageContainer) { + return NS_ERROR_NOT_AVAILABLE; + } + *aSize = mImageContainer->GetCurrentSize(); + return NS_OK; + } + + if (mFrontSurface) { + mozilla::gfx::IntSize size = mFrontSurface->GetSize(); + *aSize = nsIntSize(size.width, size.height); + return NS_OK; + } + +#ifdef XP_MACOSX + if (mFrontIOSurface) { + *aSize = nsIntSize(mFrontIOSurface->GetWidth(), mFrontIOSurface->GetHeight()); + return NS_OK; + } else if (mIOSurface) { + *aSize = nsIntSize(mIOSurface->GetWidth(), mIOSurface->GetHeight()); + return NS_OK; + } +#endif + + return NS_ERROR_NOT_AVAILABLE; +} + +void +PluginInstanceParent::DidComposite() +{ + if (!IsUsingDirectDrawing()) { + return; + } + Unused << SendNPP_DidComposite(); +} + +#ifdef XP_MACOSX +nsresult +PluginInstanceParent::IsRemoteDrawingCoreAnimation(bool *aDrawing) +{ + *aDrawing = (NPDrawingModelCoreAnimation == (NPDrawingModel)mDrawingModel || + NPDrawingModelInvalidatingCoreAnimation == (NPDrawingModel)mDrawingModel); + return NS_OK; +} +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +nsresult +PluginInstanceParent::ContentsScaleFactorChanged(double aContentsScaleFactor) +{ + bool rv = SendContentsScaleFactorChanged(aContentsScaleFactor); + return rv ? NS_OK : NS_ERROR_FAILURE; +} +#endif // #ifdef XP_MACOSX + +nsresult +PluginInstanceParent::SetBackgroundUnknown() +{ + PLUGIN_LOG_DEBUG(("[InstanceParent][%p] SetBackgroundUnknown", this)); + + if (mBackground) { + DestroyBackground(); + MOZ_ASSERT(!mBackground, "Background not destroyed"); + } + + return NS_OK; +} + +nsresult +PluginInstanceParent::BeginUpdateBackground(const nsIntRect& aRect, + DrawTarget** aDrawTarget) +{ + PLUGIN_LOG_DEBUG( + ("[InstanceParent][%p] BeginUpdateBackground for <x=%d,y=%d, w=%d,h=%d>", + this, aRect.x, aRect.y, aRect.width, aRect.height)); + + if (!mBackground) { + // XXX if we failed to create a background surface on one + // update, there's no guarantee that later updates will be for + // the entire background area until successful. We might want + // to fix that eventually. + MOZ_ASSERT(aRect.TopLeft() == nsIntPoint(0, 0), + "Expecting rect for whole frame"); + if (!CreateBackground(aRect.Size())) { + *aDrawTarget = nullptr; + return NS_OK; + } + } + + mozilla::gfx::IntSize sz = mBackground->GetSize(); +#ifdef DEBUG + MOZ_ASSERT(nsIntRect(0, 0, sz.width, sz.height).Contains(aRect), + "Update outside of background area"); +#endif + + RefPtr<gfx::DrawTarget> dt = gfxPlatform::GetPlatform()-> + CreateDrawTargetForSurface(mBackground, gfx::IntSize(sz.width, sz.height)); + dt.forget(aDrawTarget); + + return NS_OK; +} + +nsresult +PluginInstanceParent::EndUpdateBackground(const nsIntRect& aRect) +{ + PLUGIN_LOG_DEBUG( + ("[InstanceParent][%p] EndUpdateBackground for <x=%d,y=%d, w=%d,h=%d>", + this, aRect.x, aRect.y, aRect.width, aRect.height)); + +#ifdef MOZ_X11 + // Have to XSync here to avoid the plugin trying to draw with this + // surface racing with its creation in the X server. We also want + // to avoid the plugin drawing onto stale pixels, then handing us + // back a front surface from those pixels that we might + // recomposite for "a while" until the next update. This XSync + // still doesn't guarantee that the plugin draws onto a consistent + // view of its background, but it does mean that the plugin is + // drawing onto pixels no older than those in the latest + // EndUpdateBackground(). + XSync(DefaultXDisplay(), False); +#endif + + Unused << SendUpdateBackground(BackgroundDescriptor(), aRect); + + return NS_OK; +} + +#if defined(XP_WIN) +nsresult +PluginInstanceParent::SetScrollCaptureId(uint64_t aScrollCaptureId) +{ + if (aScrollCaptureId == ImageContainer::sInvalidAsyncContainerId) { + return NS_ERROR_FAILURE; + } + + mImageContainer = new ImageContainer(aScrollCaptureId); + return NS_OK; +} + +nsresult +PluginInstanceParent::GetScrollCaptureContainer(ImageContainer** aContainer) +{ + if (!aContainer || !mImageContainer) { + return NS_ERROR_FAILURE; + } + + RefPtr<ImageContainer> container = GetImageContainer(); + container.forget(aContainer); + + return NS_OK; +} +#endif // XP_WIN + +PluginAsyncSurrogate* +PluginInstanceParent::GetAsyncSurrogate() +{ + return mSurrogate; +} + +bool +PluginInstanceParent::CreateBackground(const nsIntSize& aSize) +{ + MOZ_ASSERT(!mBackground, "Already have a background"); + + // XXX refactor me + +#if defined(MOZ_X11) + Screen* screen = DefaultScreenOfDisplay(DefaultXDisplay()); + Visual* visual = DefaultVisualOfScreen(screen); + mBackground = gfxXlibSurface::Create(screen, visual, + mozilla::gfx::IntSize(aSize.width, aSize.height)); + return !!mBackground; + +#elif defined(XP_WIN) + // We have chosen to create an unsafe surface in which the plugin + // can read from the region while we're writing to it. + mBackground = + gfxSharedImageSurface::CreateUnsafe( + this, + mozilla::gfx::IntSize(aSize.width, aSize.height), + mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32); + return !!mBackground; +#else + return false; +#endif +} + +void +PluginInstanceParent::DestroyBackground() +{ + if (!mBackground) { + return; + } + + // Relinquish ownership of |mBackground| to its destroyer + PPluginBackgroundDestroyerParent* pbd = + new PluginBackgroundDestroyerParent(mBackground); + mBackground = nullptr; + + // If this fails, there's no problem: |bd| will be destroyed along + // with the old background surface. + Unused << SendPPluginBackgroundDestroyerConstructor(pbd); +} + +mozilla::plugins::SurfaceDescriptor +PluginInstanceParent::BackgroundDescriptor() +{ + MOZ_ASSERT(mBackground, "Need a background here"); + + // XXX refactor me + +#ifdef MOZ_X11 + gfxXlibSurface* xsurf = static_cast<gfxXlibSurface*>(mBackground.get()); + return SurfaceDescriptorX11(xsurf); +#endif + +#ifdef XP_WIN + MOZ_ASSERT(gfxSharedImageSurface::IsSharedImage(mBackground), + "Expected shared image surface"); + gfxSharedImageSurface* shmem = + static_cast<gfxSharedImageSurface*>(mBackground.get()); + return shmem->GetShmem(); +#endif + + // If this is ever used, which it shouldn't be, it will trigger a + // hard assertion in IPDL-generated code. + return mozilla::plugins::SurfaceDescriptor(); +} + +ImageContainer* +PluginInstanceParent::GetImageContainer() +{ + if (mImageContainer) { + return mImageContainer; + } + + if (IsUsingDirectDrawing()) { + mImageContainer = LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS); + } else { + mImageContainer = LayerManager::CreateImageContainer(); + } + return mImageContainer; +} + +PPluginBackgroundDestroyerParent* +PluginInstanceParent::AllocPPluginBackgroundDestroyerParent() +{ + NS_RUNTIMEABORT("'Power-user' ctor is used exclusively"); + return nullptr; +} + +bool +PluginInstanceParent::DeallocPPluginBackgroundDestroyerParent( + PPluginBackgroundDestroyerParent* aActor) +{ + delete aActor; + return true; +} + +NPError +PluginInstanceParent::NPP_SetWindow(const NPWindow* aWindow) +{ + PLUGIN_LOG_DEBUG(("%s (aWindow=%p)", FULLFUNCTION, (void*) aWindow)); + + NS_ENSURE_TRUE(aWindow, NPERR_GENERIC_ERROR); + + NPRemoteWindow window; + mWindowType = aWindow->type; + +#if defined(OS_WIN) + // On windowless controls, reset the shared memory surface as needed. + if (mWindowType == NPWindowTypeDrawable) { + MaybeCreateChildPopupSurrogate(); + } else { + SubclassPluginWindow(reinterpret_cast<HWND>(aWindow->window)); + + // Skip SetWindow call for hidden QuickTime plugins. + if ((mParent->GetQuirks() & QUIRK_QUICKTIME_AVOID_SETWINDOW) && + aWindow->width == 0 && aWindow->height == 0) { + return NPERR_NO_ERROR; + } + + window.window = reinterpret_cast<uint64_t>(aWindow->window); + window.x = aWindow->x; + window.y = aWindow->y; + window.width = aWindow->width; + window.height = aWindow->height; + window.type = aWindow->type; + + // On Windows we need to create and set the parent before we set the + // window on the plugin, or keyboard interaction will not work. + if (!MaybeCreateAndParentChildPluginWindow()) { + return NPERR_GENERIC_ERROR; + } + } +#else + window.window = reinterpret_cast<uint64_t>(aWindow->window); + window.x = aWindow->x; + window.y = aWindow->y; + window.width = aWindow->width; + window.height = aWindow->height; + window.clipRect = aWindow->clipRect; // MacOS specific + window.type = aWindow->type; +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + double floatScaleFactor = 1.0; + mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &floatScaleFactor); + int scaleFactor = ceil(floatScaleFactor); + window.contentsScaleFactor = floatScaleFactor; +#endif +#if defined(XP_MACOSX) + if (mShWidth != window.width * scaleFactor || mShHeight != window.height * scaleFactor) { + if (mDrawingModel == NPDrawingModelCoreAnimation || + mDrawingModel == NPDrawingModelInvalidatingCoreAnimation) { + mIOSurface = MacIOSurface::CreateIOSurface(window.width, window.height, + floatScaleFactor); + } else if (uint32_t(mShWidth * mShHeight) != + window.width * scaleFactor * window.height * scaleFactor) { + if (mShWidth != 0 && mShHeight != 0) { + DeallocShmem(mShSurface); + mShWidth = 0; + mShHeight = 0; + } + + if (window.width != 0 && window.height != 0) { + if (!AllocShmem(window.width * scaleFactor * window.height*4 * scaleFactor, + SharedMemory::TYPE_BASIC, &mShSurface)) { + PLUGIN_LOG_DEBUG(("Shared memory could not be allocated.")); + return NPERR_GENERIC_ERROR; + } + } + } + mShWidth = window.width * scaleFactor; + mShHeight = window.height * scaleFactor; + } +#endif + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + const NPSetWindowCallbackStruct* ws_info = + static_cast<NPSetWindowCallbackStruct*>(aWindow->ws_info); + window.visualID = ws_info->visual ? ws_info->visual->visualid : 0; + window.colormap = ws_info->colormap; +#endif + + if (!CallNPP_SetWindow(window)) { + return NPERR_GENERIC_ERROR; + } + + RecordDrawingModel(); + return NPERR_NO_ERROR; +} + +NPError +PluginInstanceParent::NPP_GetValue(NPPVariable aVariable, + void* _retval) +{ + switch (aVariable) { + + case NPPVpluginWantsAllNetworkStreams: { + bool wantsAllStreams; + NPError rv; + + if (!CallNPP_GetValue_NPPVpluginWantsAllNetworkStreams(&wantsAllStreams, &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + (*(NPBool*)_retval) = wantsAllStreams; + return NPERR_NO_ERROR; + } + +#ifdef MOZ_X11 + case NPPVpluginNeedsXEmbed: { + bool needsXEmbed; + NPError rv; + + if (!CallNPP_GetValue_NPPVpluginNeedsXEmbed(&needsXEmbed, &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + (*(NPBool*)_retval) = needsXEmbed; + return NPERR_NO_ERROR; + } +#endif + + case NPPVpluginScriptableNPObject: { + PPluginScriptableObjectParent* actor; + NPError rv; + if (!CallNPP_GetValue_NPPVpluginScriptableNPObject(&actor, &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + if (!actor) { + NS_ERROR("NPPVpluginScriptableNPObject succeeded but null."); + return NPERR_GENERIC_ERROR; + } + + const NPNetscapeFuncs* npn = mParent->GetNetscapeFuncs(); + if (!npn) { + NS_WARNING("No netscape functions?!"); + return NPERR_GENERIC_ERROR; + } + + NPObject* object = + static_cast<PluginScriptableObjectParent*>(actor)->GetObject(true); + NS_ASSERTION(object, "This shouldn't ever be null!"); + + (*(NPObject**)_retval) = npn->retainobject(object); + return NPERR_NO_ERROR; + } + +#ifdef MOZ_ACCESSIBILITY_ATK + case NPPVpluginNativeAccessibleAtkPlugId: { + nsCString plugId; + NPError rv; + if (!CallNPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId(&plugId, &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + (*(nsCString*)_retval) = plugId; + return NPERR_NO_ERROR; + } +#endif + + default: + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceParent::NPP_GetValue: Unhandled NPPVariable %i (%s)", + (int) aVariable, NPPVariableToString(aVariable))); + return NPERR_GENERIC_ERROR; + } +} + +NPError +PluginInstanceParent::NPP_SetValue(NPNVariable variable, void* value) +{ + NPError result; + switch (variable) { + case NPNVprivateModeBool: + if (!CallNPP_SetValue_NPNVprivateModeBool(*static_cast<NPBool*>(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + case NPNVmuteAudioBool: + if (!CallNPP_SetValue_NPNVmuteAudioBool(*static_cast<NPBool*>(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + case NPNVCSSZoomFactor: + if (!CallNPP_SetValue_NPNVCSSZoomFactor(*static_cast<double*>(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + default: + NS_ERROR("Unhandled NPNVariable in NPP_SetValue"); + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceParent::NPP_SetValue: Unhandled NPNVariable %i (%s)", + (int) variable, NPNVariableToString(variable))); + return NPERR_GENERIC_ERROR; + } +} + +void +PluginInstanceParent::NPP_URLRedirectNotify(const char* url, int32_t status, + void* notifyData) +{ + if (!notifyData) + return; + + PStreamNotifyParent* streamNotify = static_cast<PStreamNotifyParent*>(notifyData); + Unused << streamNotify->SendRedirectNotify(NullableString(url), status); +} + +int16_t +PluginInstanceParent::NPP_HandleEvent(void* event) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + +#if defined(XP_MACOSX) + NPCocoaEvent* npevent = reinterpret_cast<NPCocoaEvent*>(event); +#else + NPEvent* npevent = reinterpret_cast<NPEvent*>(event); +#endif + NPRemoteEvent npremoteevent; + npremoteevent.event = *npevent; +#if defined(XP_MACOSX) || defined(XP_WIN) + double scaleFactor = 1.0; + mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &scaleFactor); + npremoteevent.contentsScaleFactor = scaleFactor; +#endif + int16_t handled = 0; + +#if defined(OS_WIN) + if (mWindowType == NPWindowTypeDrawable) { + switch (npevent->event) { + case WM_KILLFOCUS: + { + // When the user selects fullscreen mode in Flash video players, + // WM_KILLFOCUS will be delayed by deferred event processing: + // WM_LBUTTONUP results in a call to CreateWindow within Flash, + // which fires WM_KILLFOCUS. Delayed delivery causes Flash to + // misinterpret the event, dropping back out of fullscreen. Trap + // this event and drop it. + wchar_t szClass[26]; + HWND hwnd = GetForegroundWindow(); + if (hwnd && hwnd != mPluginHWND && + GetClassNameW(hwnd, szClass, + sizeof(szClass)/sizeof(char16_t)) && + !wcscmp(szClass, kFlashFullscreenClass)) { + return 0; + } + } + break; + + case WM_WINDOWPOSCHANGED: + { + // We send this in nsPluginFrame just before painting + return SendWindowPosChanged(npremoteevent); + } + + case WM_IME_STARTCOMPOSITION: + case WM_IME_COMPOSITION: + case WM_IME_ENDCOMPOSITION: + if (!(mParent->GetQuirks() & QUIRK_WINLESS_HOOK_IME)) { + // IME message will be posted on allowed plugins only such as + // Flash. Because if we cannot know that plugin can handle + // IME correctly. + return 0; + } + break; + } + } +#endif + +#if defined(MOZ_X11) + switch (npevent->type) { + case GraphicsExpose: + PLUGIN_LOG_DEBUG((" schlepping drawable 0x%lx across the pipe\n", + npevent->xgraphicsexpose.drawable)); + // Make sure the X server has created the Drawable and completes any + // drawing before the plugin draws on top. + // + // XSync() waits for the X server to complete. Really this parent + // process does not need to wait; the child is the process that needs + // to wait. A possibly-slightly-better alternative would be to send + // an X event to the child that the child would wait for. + FinishX(DefaultXDisplay()); + + return CallPaint(npremoteevent, &handled) ? handled : 0; + + case ButtonPress: + // Release any active pointer grab so that the plugin X client can + // grab the pointer if it wishes. + Display *dpy = DefaultXDisplay(); +# ifdef MOZ_WIDGET_GTK + // GDK attempts to (asynchronously) track whether there is an active + // grab so ungrab through GDK. + // + // This call needs to occur in the same process that receives the event in + // the first place (chrome process) + if (XRE_IsContentProcess()) { + dom::ContentChild* cp = dom::ContentChild::GetSingleton(); + cp->SendUngrabPointer(npevent->xbutton.time); + } else { + gdk_pointer_ungrab(npevent->xbutton.time); + } +# else + XUngrabPointer(dpy, npevent->xbutton.time); +# endif + // Wait for the ungrab to complete. + XSync(dpy, False); + break; + } +#endif + +#ifdef XP_MACOSX + if (npevent->type == NPCocoaEventDrawRect) { + if (mDrawingModel == NPDrawingModelCoreAnimation || + mDrawingModel == NPDrawingModelInvalidatingCoreAnimation) { + if (!mIOSurface) { + NS_ERROR("No IOSurface allocated."); + return false; + } + if (!CallNPP_HandleEvent_IOSurface(npremoteevent, + mIOSurface->GetIOSurfaceID(), + &handled)) + return false; // no good way to handle errors here... + + CGContextRef cgContext = npevent->data.draw.context; + if (!mShColorSpace) { + mShColorSpace = CreateSystemColorSpace(); + } + if (!mShColorSpace) { + PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace.")); + return false; + } + if (cgContext) { + nsCARenderer::DrawSurfaceToCGContext(cgContext, mIOSurface, + mShColorSpace, + npevent->data.draw.x, + npevent->data.draw.y, + npevent->data.draw.width, + npevent->data.draw.height); + } + return true; + } else if (mFrontIOSurface) { + CGContextRef cgContext = npevent->data.draw.context; + if (!mShColorSpace) { + mShColorSpace = CreateSystemColorSpace(); + } + if (!mShColorSpace) { + PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace.")); + return false; + } + if (cgContext) { + nsCARenderer::DrawSurfaceToCGContext(cgContext, mFrontIOSurface, + mShColorSpace, + npevent->data.draw.x, + npevent->data.draw.y, + npevent->data.draw.width, + npevent->data.draw.height); + } + return true; + } else { + if (mShWidth == 0 && mShHeight == 0) { + PLUGIN_LOG_DEBUG(("NPCocoaEventDrawRect on window of size 0.")); + return false; + } + if (!mShSurface.IsReadable()) { + PLUGIN_LOG_DEBUG(("Shmem is not readable.")); + return false; + } + + if (!CallNPP_HandleEvent_Shmem(npremoteevent, mShSurface, + &handled, &mShSurface)) + return false; // no good way to handle errors here... + + if (!mShSurface.IsReadable()) { + PLUGIN_LOG_DEBUG(("Shmem not returned. Either the plugin crashed " + "or we have a bug.")); + return false; + } + + char* shContextByte = mShSurface.get<char>(); + + if (!mShColorSpace) { + mShColorSpace = CreateSystemColorSpace(); + } + if (!mShColorSpace) { + PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace.")); + return false; + } + CGContextRef shContext = ::CGBitmapContextCreate(shContextByte, + mShWidth, mShHeight, 8, + mShWidth*4, mShColorSpace, + kCGImageAlphaPremultipliedFirst | + kCGBitmapByteOrder32Host); + if (!shContext) { + PLUGIN_LOG_DEBUG(("Could not allocate CGBitmapContext.")); + return false; + } + + CGImageRef shImage = ::CGBitmapContextCreateImage(shContext); + if (shImage) { + CGContextRef cgContext = npevent->data.draw.context; + + ::CGContextDrawImage(cgContext, + CGRectMake(0,0,mShWidth,mShHeight), + shImage); + ::CGImageRelease(shImage); + } else { + ::CGContextRelease(shContext); + return false; + } + ::CGContextRelease(shContext); + return true; + } + } +#endif + + if (!CallNPP_HandleEvent(npremoteevent, &handled)) + return 0; // no good way to handle errors here... + + return handled; +} + +NPError +PluginInstanceParent::NPP_NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype) +{ + PLUGIN_LOG_DEBUG(("%s (type=%s, stream=%p, seekable=%i)", + FULLFUNCTION, (char*) type, (void*) stream, (int) seekable)); + + BrowserStreamParent* bs = new BrowserStreamParent(this, stream); + + if (!SendPBrowserStreamConstructor(bs, + NullableString(stream->url), + stream->end, + stream->lastmodified, + static_cast<PStreamNotifyParent*>(stream->notifyData), + NullableString(stream->headers))) { + return NPERR_GENERIC_ERROR; + } + + Telemetry::AutoTimer<Telemetry::BLOCKED_ON_PLUGIN_STREAM_INIT_MS> + timer(Module()->GetHistogramKey()); + + NPError err = NPERR_NO_ERROR; + if (mParent->IsStartingAsync()) { + MOZ_ASSERT(mSurrogate); + mSurrogate->AsyncCallDeparting(); + if (SendAsyncNPP_NewStream(bs, NullableString(type), seekable)) { + *stype = nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN; + } else { + err = NPERR_GENERIC_ERROR; + } + } else { + bs->SetAlive(); + if (!CallNPP_NewStream(bs, NullableString(type), seekable, &err, stype)) { + err = NPERR_GENERIC_ERROR; + } + if (NPERR_NO_ERROR != err) { + Unused << PBrowserStreamParent::Send__delete__(bs); + } + } + + return err; +} + +NPError +PluginInstanceParent::NPP_DestroyStream(NPStream* stream, NPReason reason) +{ + PLUGIN_LOG_DEBUG(("%s (stream=%p, reason=%i)", + FULLFUNCTION, (void*) stream, (int) reason)); + + AStream* s = static_cast<AStream*>(stream->pdata); + if (!s) { + // The stream has already been deleted by other means. + // With async plugin init this could happen if async NPP_NewStream + // returns an error code. + return NPERR_NO_ERROR; + } + if (s->IsBrowserStream()) { + BrowserStreamParent* sp = + static_cast<BrowserStreamParent*>(s); + if (sp->mNPP != this) + NS_RUNTIMEABORT("Mismatched plugin data"); + + sp->NPP_DestroyStream(reason); + return NPERR_NO_ERROR; + } + else { + PluginStreamParent* sp = + static_cast<PluginStreamParent*>(s); + if (sp->mInstance != this) + NS_RUNTIMEABORT("Mismatched plugin data"); + + return PPluginStreamParent::Call__delete__(sp, reason, false) ? + NPERR_NO_ERROR : NPERR_GENERIC_ERROR; + } +} + +void +PluginInstanceParent::NPP_Print(NPPrint* platformPrint) +{ + // TODO: implement me + NS_ERROR("Not implemented"); +} + +PPluginScriptableObjectParent* +PluginInstanceParent::AllocPPluginScriptableObjectParent() +{ + return new PluginScriptableObjectParent(Proxy); +} + +bool +PluginInstanceParent::DeallocPPluginScriptableObjectParent( + PPluginScriptableObjectParent* aObject) +{ + PluginScriptableObjectParent* actor = + static_cast<PluginScriptableObjectParent*>(aObject); + + NPObject* object = actor->GetObject(false); + if (object) { + NS_ASSERTION(mScriptableObjects.Get(object, nullptr), + "NPObject not in the hash!"); + mScriptableObjects.Remove(object); + } +#ifdef DEBUG + else { + for (auto iter = mScriptableObjects.Iter(); !iter.Done(); iter.Next()) { + NS_ASSERTION(actor != iter.UserData(), + "Actor in the hash with a null NPObject!"); + } + } +#endif + + delete actor; + return true; +} + +bool +PluginInstanceParent::RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectParent* aActor) +{ + // This is only called in response to the child process requesting the + // creation of an actor. This actor will represent an NPObject that is + // created by the plugin and returned to the browser. + PluginScriptableObjectParent* actor = + static_cast<PluginScriptableObjectParent*>(aActor); + NS_ASSERTION(!actor->GetObject(false), "Actor already has an object?!"); + + actor->InitializeProxy(); + NS_ASSERTION(actor->GetObject(false), "Actor should have an object!"); + + return true; +} + +void +PluginInstanceParent::NPP_URLNotify(const char* url, NPReason reason, + void* notifyData) +{ + PLUGIN_LOG_DEBUG(("%s (%s, %i, %p)", + FULLFUNCTION, url, (int) reason, notifyData)); + + PStreamNotifyParent* streamNotify = + static_cast<PStreamNotifyParent*>(notifyData); + Unused << PStreamNotifyParent::Send__delete__(streamNotify, reason); +} + +bool +PluginInstanceParent::RegisterNPObjectForActor( + NPObject* aObject, + PluginScriptableObjectParent* aActor) +{ + NS_ASSERTION(aObject && aActor, "Null pointers!"); + NS_ASSERTION(!mScriptableObjects.Get(aObject, nullptr), "Duplicate entry!"); + mScriptableObjects.Put(aObject, aActor); + return true; +} + +void +PluginInstanceParent::UnregisterNPObject(NPObject* aObject) +{ + NS_ASSERTION(aObject, "Null pointer!"); + NS_ASSERTION(mScriptableObjects.Get(aObject, nullptr), "Unknown entry!"); + mScriptableObjects.Remove(aObject); +} + +PluginScriptableObjectParent* +PluginInstanceParent::GetActorForNPObject(NPObject* aObject) +{ + NS_ASSERTION(aObject, "Null pointer!"); + + if (aObject->_class == PluginScriptableObjectParent::GetClass()) { + // One of ours! + ParentNPObject* object = static_cast<ParentNPObject*>(aObject); + NS_ASSERTION(object->parent, "Null actor!"); + return object->parent; + } + + PluginScriptableObjectParent* actor; + if (mScriptableObjects.Get(aObject, &actor)) { + return actor; + } + + actor = new PluginScriptableObjectParent(LocalObject); + if (!SendPPluginScriptableObjectConstructor(actor)) { + NS_WARNING("Failed to send constructor message!"); + return nullptr; + } + + actor->InitializeLocal(aObject); + return actor; +} + +PPluginSurfaceParent* +PluginInstanceParent::AllocPPluginSurfaceParent(const WindowsSharedMemoryHandle& handle, + const mozilla::gfx::IntSize& size, + const bool& transparent) +{ +#ifdef XP_WIN + return new PluginSurfaceParent(handle, size, transparent); +#else + NS_ERROR("This shouldn't be called!"); + return nullptr; +#endif +} + +bool +PluginInstanceParent::DeallocPPluginSurfaceParent(PPluginSurfaceParent* s) +{ +#ifdef XP_WIN + delete s; + return true; +#else + return false; +#endif +} + +bool +PluginInstanceParent::AnswerNPN_PushPopupsEnabledState(const bool& aState) +{ + mNPNIface->pushpopupsenabledstate(mNPP, aState ? 1 : 0); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_PopPopupsEnabledState() +{ + mNPNIface->poppopupsenabledstate(mNPP); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_GetValueForURL(const NPNURLVariable& variable, + const nsCString& url, + nsCString* value, + NPError* result) +{ + char* v; + uint32_t len; + + *result = mNPNIface->getvalueforurl(mNPP, (NPNURLVariable) variable, + url.get(), &v, &len); + if (NPERR_NO_ERROR == *result) + value->Adopt(v, len); + + return true; +} + +bool +PluginInstanceParent::AnswerNPN_SetValueForURL(const NPNURLVariable& variable, + const nsCString& url, + const nsCString& value, + NPError* result) +{ + *result = mNPNIface->setvalueforurl(mNPP, (NPNURLVariable) variable, + url.get(), value.get(), + value.Length()); + return true; +} + +bool +PluginInstanceParent::AnswerNPN_GetAuthenticationInfo(const nsCString& protocol, + const nsCString& host, + const int32_t& port, + const nsCString& scheme, + const nsCString& realm, + nsCString* username, + nsCString* password, + NPError* result) +{ + char* u; + uint32_t ulen; + char* p; + uint32_t plen; + + *result = mNPNIface->getauthenticationinfo(mNPP, protocol.get(), + host.get(), port, + scheme.get(), realm.get(), + &u, &ulen, &p, &plen); + if (NPERR_NO_ERROR == *result) { + username->Adopt(u, ulen); + password->Adopt(p, plen); + } + return true; +} + +bool +PluginInstanceParent::AnswerNPN_ConvertPoint(const double& sourceX, + const bool& ignoreDestX, + const double& sourceY, + const bool& ignoreDestY, + const NPCoordinateSpace& sourceSpace, + const NPCoordinateSpace& destSpace, + double *destX, + double *destY, + bool *result) +{ + *result = mNPNIface->convertpoint(mNPP, sourceX, sourceY, sourceSpace, + ignoreDestX ? nullptr : destX, + ignoreDestY ? nullptr : destY, + destSpace); + + return true; +} + +bool +PluginInstanceParent::RecvRedrawPlugin() +{ + nsNPAPIPluginInstance *inst = static_cast<nsNPAPIPluginInstance*>(mNPP->ndata); + if (!inst) { + return false; + } + + inst->RedrawPlugin(); + return true; +} + +bool +PluginInstanceParent::RecvNegotiatedCarbon() +{ + nsNPAPIPluginInstance *inst = static_cast<nsNPAPIPluginInstance*>(mNPP->ndata); + if (!inst) { + return false; + } + inst->CarbonNPAPIFailure(); + return true; +} + +nsPluginInstanceOwner* +PluginInstanceParent::GetOwner() +{ + nsNPAPIPluginInstance* inst = static_cast<nsNPAPIPluginInstance*>(mNPP->ndata); + if (!inst) { + return nullptr; + } + return inst->GetOwner(); +} + +bool +PluginInstanceParent::RecvAsyncNPP_NewResult(const NPError& aResult) +{ + // NB: mUseSurrogate must be cleared before doing anything else, especially + // calling NPP_SetWindow! + mUseSurrogate = false; + + mSurrogate->AsyncCallArriving(); + if (aResult == NPERR_NO_ERROR) { + mSurrogate->SetAcceptingCalls(true); + } + + // It is possible for a plugin instance to outlive its owner (eg. When a + // PluginDestructionGuard was on the stack at the time the owner was being + // destroyed). We need to handle that case. + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner) { + // We can't do anything at this point, just return. Any pending browser + // streams will be cleaned up when the plugin instance is destroyed. + return true; + } + + if (aResult != NPERR_NO_ERROR) { + mSurrogate->NotifyAsyncInitFailed(); + return true; + } + + // Now we need to do a bunch of exciting post-NPP_New housekeeping. + owner->NotifyHostCreateWidget(); + + MOZ_ASSERT(mSurrogate); + mSurrogate->OnInstanceCreated(this); + + return true; +} + +bool +PluginInstanceParent::RecvSetNetscapeWindowAsParent(const NativeWindowHandle& childWindow) +{ +#if defined(XP_WIN) + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner || NS_FAILED(owner->SetNetscapeWindowAsParent(childWindow))) { + NS_WARNING("Failed to set Netscape window as parent."); + } + + return true; +#else + NS_NOTREACHED("PluginInstanceParent::RecvSetNetscapeWindowAsParent not implemented!"); + return false; +#endif +} + +#if defined(OS_WIN) + +/* + plugin focus changes between processes + + focus from dom -> child: + Focus manager calls on widget to set the focus on the window. + We pick up the resulting wm_setfocus event here, and forward + that over ipc to the child which calls set focus on itself. + + focus from child -> focus manager: + Child picks up the local wm_setfocus and sends it via ipc over + here. We then post a custom event to widget/windows/nswindow + which fires off a gui event letting the browser know. +*/ + +static const wchar_t kPluginInstanceParentProperty[] = + L"PluginInstanceParentProperty"; + +// static +LRESULT CALLBACK +PluginInstanceParent::PluginWindowHookProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + PluginInstanceParent* self = reinterpret_cast<PluginInstanceParent*>( + ::GetPropW(hWnd, kPluginInstanceParentProperty)); + if (!self) { + NS_NOTREACHED("PluginInstanceParent::PluginWindowHookProc null this ptr!"); + return DefWindowProc(hWnd, message, wParam, lParam); + } + + NS_ASSERTION(self->mPluginHWND == hWnd, "Wrong window!"); + + switch (message) { + case WM_SETFOCUS: + // Let the child plugin window know it should take focus. + Unused << self->CallSetPluginFocus(); + break; + + case WM_CLOSE: + self->UnsubclassPluginWindow(); + break; + } + + if (self->mPluginWndProc == PluginWindowHookProc) { + NS_NOTREACHED( + "PluginWindowHookProc invoking mPluginWndProc w/" + "mPluginWndProc == PluginWindowHookProc????"); + return DefWindowProc(hWnd, message, wParam, lParam); + } + return ::CallWindowProc(self->mPluginWndProc, hWnd, message, wParam, + lParam); +} + +void +PluginInstanceParent::SubclassPluginWindow(HWND aWnd) +{ + if ((aWnd && mPluginHWND == aWnd) || (!aWnd && mPluginHWND)) { + return; + } + + if (XRE_IsContentProcess()) { + if (!aWnd) { + NS_WARNING("PluginInstanceParent::SubclassPluginWindow unexpected null window"); + return; + } + mPluginHWND = aWnd; // now a remote window, we can't subclass this + mPluginWndProc = nullptr; + // Note sPluginInstanceList wil delete 'this' if we do not remove + // it on shutdown. + sPluginInstanceList->Put((void*)mPluginHWND, this); + return; + } + + NS_ASSERTION(!(mPluginHWND && aWnd != mPluginHWND), + "PluginInstanceParent::SubclassPluginWindow hwnd is not our window!"); + + mPluginHWND = aWnd; + mPluginWndProc = + (WNDPROC)::SetWindowLongPtrA(mPluginHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(PluginWindowHookProc)); + DebugOnly<bool> bRes = ::SetPropW(mPluginHWND, kPluginInstanceParentProperty, this); + NS_ASSERTION(mPluginWndProc, + "PluginInstanceParent::SubclassPluginWindow failed to set subclass!"); + NS_ASSERTION(bRes, + "PluginInstanceParent::SubclassPluginWindow failed to set prop!"); +} + +void +PluginInstanceParent::UnsubclassPluginWindow() +{ + if (XRE_IsContentProcess()) { + if (mPluginHWND) { + // Remove 'this' from the plugin list safely + nsAutoPtr<PluginInstanceParent> tmp; + MOZ_ASSERT(sPluginInstanceList); + sPluginInstanceList->RemoveAndForget((void*)mPluginHWND, tmp); + tmp.forget(); + if (!sPluginInstanceList->Count()) { + delete sPluginInstanceList; + sPluginInstanceList = nullptr; + } + } + mPluginHWND = nullptr; + return; + } + + if (mPluginHWND && mPluginWndProc) { + ::SetWindowLongPtrA(mPluginHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(mPluginWndProc)); + + ::RemovePropW(mPluginHWND, kPluginInstanceParentProperty); + + mPluginWndProc = nullptr; + mPluginHWND = nullptr; + } +} + +/* windowless drawing helpers */ + +/* + * Origin info: + * + * windowless, offscreen: + * + * WM_WINDOWPOSCHANGED: origin is relative to container + * setwindow: origin is 0,0 + * WM_PAINT: origin is 0,0 + * + * windowless, native: + * + * WM_WINDOWPOSCHANGED: origin is relative to container + * setwindow: origin is relative to container + * WM_PAINT: origin is relative to container + * + * PluginInstanceParent: + * + * painting: mPluginPort (nsIntRect, saved in SetWindow) + */ + +bool +PluginInstanceParent::MaybeCreateAndParentChildPluginWindow() +{ + // On Windows we need to create and set the parent before we set the + // window on the plugin, or keyboard interaction will not work. + if (!mChildPluginHWND) { + if (!CallCreateChildPluginWindow(&mChildPluginHWND) || + !mChildPluginHWND) { + return false; + } + } + + // It's not clear if the parent window would ever change, but when this + // was done in the NPAPI child it used to allow for this. + if (mPluginHWND == mChildPluginsParentHWND) { + return true; + } + + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner) { + // We can't reparent without an owner, the plugin is probably shutting + // down, just return true to allow any calls to continue. + return true; + } + + // Note that this call will probably cause a sync native message to the + // process that owns the child window. + owner->SetWidgetWindowAsParent(mChildPluginHWND); + mChildPluginsParentHWND = mPluginHWND; + return true; +} + +void +PluginInstanceParent::MaybeCreateChildPopupSurrogate() +{ + // Already created or not required for this plugin. + if (mChildPluginHWND || mWindowType != NPWindowTypeDrawable || + !(mParent->GetQuirks() & QUIRK_WINLESS_TRACKPOPUP_HOOK)) { + return; + } + + // We need to pass the netscape window down to be cached as part of the call + // to create the surrogate, because the reparenting of the surrogate in the + // main process can cause sync Windows messages to the plugin process, which + // then cause sync messages from the plugin child for the netscape window + // which causes a deadlock. + NativeWindowHandle netscapeWindow; + NPError result = mNPNIface->getvalue(mNPP, NPNVnetscapeWindow, + &netscapeWindow); + if (NPERR_NO_ERROR != result) { + NS_WARNING("Can't get netscape window to pass to plugin child."); + return; + } + + if (!SendCreateChildPopupSurrogate(netscapeWindow)) { + NS_WARNING("Failed to create popup surrogate in child."); + } +} + +#endif // defined(OS_WIN) + +bool +PluginInstanceParent::AnswerPluginFocusChange(const bool& gotFocus) +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + // Currently only in use on windows - an event we receive from the child + // when it's plugin window (or one of it's children) receives keyboard + // focus. We detect this and forward a notification here so we can update + // focus. +#if defined(OS_WIN) + if (gotFocus) { + nsPluginInstanceOwner* owner = GetOwner(); + if (owner) { + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + nsCOMPtr<nsIDOMElement> element; + owner->GetDOMElement(getter_AddRefs(element)); + if (fm && element) { + fm->SetFocus(element, 0); + } + } + } + return true; +#else + NS_NOTREACHED("PluginInstanceParent::AnswerPluginFocusChange not implemented!"); + return false; +#endif +} + +PluginInstanceParent* +PluginInstanceParent::Cast(NPP aInstance, PluginAsyncSurrogate** aSurrogate) +{ + PluginDataResolver* resolver = + static_cast<PluginDataResolver*>(aInstance->pdata); + + // If the plugin crashed and the PluginInstanceParent was deleted, + // aInstance->pdata will be nullptr. + if (!resolver) { + return nullptr; + } + + PluginInstanceParent* instancePtr = resolver->GetInstance(); + + if (instancePtr && aInstance != instancePtr->mNPP) { + NS_RUNTIMEABORT("Corrupted plugin data."); + } + + if (aSurrogate) { + *aSurrogate = resolver->GetAsyncSurrogate(); + } + + return instancePtr; +} + +bool +PluginInstanceParent::RecvGetCompositionString(const uint32_t& aIndex, + nsTArray<uint8_t>* aDist, + int32_t* aLength) +{ +#if defined(OS_WIN) + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner) { + *aLength = IMM_ERROR_GENERAL; + return true; + } + + if (!owner->GetCompositionString(aIndex, aDist, aLength)) { + *aLength = IMM_ERROR_NODATA; + } +#endif + return true; +} + +bool +PluginInstanceParent::RecvSetCandidateWindow( + const mozilla::widget::CandidateWindowPosition& aPosition) +{ +#if defined(OS_WIN) + nsPluginInstanceOwner* owner = GetOwner(); + if (owner) { + owner->SetCandidateWindow(aPosition); + } +#endif + return true; +} + +bool +PluginInstanceParent::RecvRequestCommitOrCancel(const bool& aCommitted) +{ +#if defined(OS_WIN) + nsPluginInstanceOwner* owner = GetOwner(); + if (owner) { + owner->RequestCommitOrCancel(aCommitted); + } +#endif + return true; +} + +nsresult +PluginInstanceParent::HandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, + bool aIsConsumed) +{ + if (NS_WARN_IF(!SendHandledWindowedPluginKeyEvent(aKeyEventData, + aIsConsumed))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +bool +PluginInstanceParent::RecvOnWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData) +{ + nsPluginInstanceOwner* owner = GetOwner(); + if (NS_WARN_IF(!owner)) { + // Notifies the plugin process of the key event being not consumed + // by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return true; + } + owner->OnWindowedPluginKeyEvent(aKeyEventData); + return true; +} + +void +PluginInstanceParent::RecordDrawingModel() +{ + int mode = -1; + switch (mWindowType) { + case NPWindowTypeWindow: + // We use 0=windowed since there is no specific NPDrawingModel value. + mode = 0; + break; + case NPWindowTypeDrawable: + mode = mDrawingModel + 1; + break; + default: + MOZ_ASSERT_UNREACHABLE("bad window type"); + return; + } + + if (mode == mLastRecordedDrawingModel) { + return; + } + MOZ_ASSERT(mode >= 0); + + Telemetry::Accumulate(Telemetry::PLUGIN_DRAWING_MODEL, mode); + mLastRecordedDrawingModel = mode; +} diff --git a/dom/plugins/ipc/PluginInstanceParent.h b/dom/plugins/ipc/PluginInstanceParent.h new file mode 100644 index 000000000..b2feafacc --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceParent.h @@ -0,0 +1,475 @@ +/* -*- 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 dom_plugins_PluginInstanceParent_h +#define dom_plugins_PluginInstanceParent_h 1 + +#include "mozilla/plugins/PPluginInstanceParent.h" +#include "mozilla/plugins/PluginScriptableObjectParent.h" +#if defined(OS_WIN) +#include "mozilla/gfx/SharedDIBWin.h" +#include <d3d10_1.h> +#include "nsRefPtrHashtable.h" +#elif defined(MOZ_WIDGET_COCOA) +#include "mozilla/gfx/QuartzSupport.h" +#endif + +#include "npfunctions.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsRect.h" +#include "PluginDataResolver.h" + +#include "mozilla/Unused.h" +#include "mozilla/EventForwards.h" + +class gfxASurface; +class gfxContext; +class nsPluginInstanceOwner; + +namespace mozilla { +namespace layers { +class Image; +class ImageContainer; +class TextureClientRecycleAllocator; +} // namespace layers +namespace plugins { + +class PBrowserStreamParent; +class PluginModuleParent; +class D3D11SurfaceHolder; + +class PluginInstanceParent : public PPluginInstanceParent + , public PluginDataResolver +{ + friend class PluginModuleParent; + friend class BrowserStreamParent; + friend class PluginStreamParent; + friend class StreamNotifyParent; + +#if defined(XP_WIN) +public: + /** + * Helper method for looking up instances based on a supplied id. + */ + static PluginInstanceParent* + LookupPluginInstanceByID(uintptr_t aId); +#endif // defined(XP_WIN) + +public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + PluginInstanceParent(PluginModuleParent* parent, + NPP npp, + const nsCString& mimeType, + const NPNetscapeFuncs* npniface); + + virtual ~PluginInstanceParent(); + + bool InitMetadata(const nsACString& aMimeType, + const nsACString& aSrcAttribute); + NPError Destroy(); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + virtual PPluginScriptableObjectParent* + AllocPPluginScriptableObjectParent() override; + + virtual bool + RecvPPluginScriptableObjectConstructor(PPluginScriptableObjectParent* aActor) override; + + virtual bool + DeallocPPluginScriptableObjectParent(PPluginScriptableObjectParent* aObject) override; + virtual PBrowserStreamParent* + AllocPBrowserStreamParent(const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + PStreamNotifyParent* notifyData, + const nsCString& headers) override; + virtual bool + DeallocPBrowserStreamParent(PBrowserStreamParent* stream) override; + + virtual PPluginStreamParent* + AllocPPluginStreamParent(const nsCString& mimeType, + const nsCString& target, + NPError* result) override; + virtual bool + DeallocPPluginStreamParent(PPluginStreamParent* stream) override; + + virtual bool + AnswerNPN_GetValue_NPNVnetscapeWindow(NativeWindowHandle* value, + NPError* result) override; + virtual bool + AnswerNPN_GetValue_NPNVWindowNPObject( + PPluginScriptableObjectParent** value, + NPError* result) override; + virtual bool + AnswerNPN_GetValue_NPNVPluginElementNPObject( + PPluginScriptableObjectParent** value, + NPError* result) override; + virtual bool + AnswerNPN_GetValue_NPNVprivateModeBool(bool* value, NPError* result) override; + + virtual bool + AnswerNPN_GetValue_DrawingModelSupport(const NPNVariable& model, bool* value) override; + + virtual bool + AnswerNPN_GetValue_NPNVdocumentOrigin(nsCString* value, NPError* result) override; + + virtual bool + AnswerNPN_GetValue_SupportsAsyncBitmapSurface(bool* value) override; + + virtual bool + AnswerNPN_GetValue_SupportsAsyncDXGISurface(bool* value) override; + + virtual bool + AnswerNPN_GetValue_PreferredDXGIAdapter(DxgiAdapterDesc* desc) override; + + virtual bool + AnswerNPN_SetValue_NPPVpluginWindow(const bool& windowed, NPError* result) override; + virtual bool + AnswerNPN_SetValue_NPPVpluginTransparent(const bool& transparent, + NPError* result) override; + virtual bool + AnswerNPN_SetValue_NPPVpluginUsesDOMForCursor(const bool& useDOMForCursor, + NPError* result) override; + virtual bool + AnswerNPN_SetValue_NPPVpluginDrawingModel(const int& drawingModel, + NPError* result) override; + virtual bool + AnswerNPN_SetValue_NPPVpluginEventModel(const int& eventModel, + NPError* result) override; + virtual bool + AnswerNPN_SetValue_NPPVpluginIsPlayingAudio(const bool& isAudioPlaying, + NPError* result) override; + + virtual bool + AnswerNPN_GetURL(const nsCString& url, const nsCString& target, + NPError *result) override; + + virtual bool + AnswerNPN_PostURL(const nsCString& url, const nsCString& target, + const nsCString& buffer, const bool& file, + NPError* result) override; + + virtual PStreamNotifyParent* + AllocPStreamNotifyParent(const nsCString& url, const nsCString& target, + const bool& post, const nsCString& buffer, + const bool& file, + NPError* result) override; + + virtual bool + AnswerPStreamNotifyConstructor(PStreamNotifyParent* actor, + const nsCString& url, + const nsCString& target, + const bool& post, const nsCString& buffer, + const bool& file, + NPError* result) override; + + virtual bool + DeallocPStreamNotifyParent(PStreamNotifyParent* notifyData) override; + + virtual bool + RecvNPN_InvalidateRect(const NPRect& rect) override; + + virtual bool + RecvRevokeCurrentDirectSurface() override; + + virtual bool + RecvInitDXGISurface(const gfx::SurfaceFormat& format, + const gfx::IntSize& size, + WindowsHandle* outHandle, + NPError* outError) override; + virtual bool + RecvFinalizeDXGISurface(const WindowsHandle& handle) override; + + virtual bool + RecvShowDirectBitmap(Shmem&& buffer, + const gfx::SurfaceFormat& format, + const uint32_t& stride, + const gfx::IntSize& size, + const gfx::IntRect& dirty) override; + + virtual bool + RecvShowDirectDXGISurface(const WindowsHandle& handle, + const gfx::IntRect& rect) override; + + // Async rendering + virtual bool + RecvShow(const NPRect& updatedRect, + const SurfaceDescriptor& newSurface, + SurfaceDescriptor* prevSurface) override; + + virtual PPluginSurfaceParent* + AllocPPluginSurfaceParent(const WindowsSharedMemoryHandle& handle, + const mozilla::gfx::IntSize& size, + const bool& transparent) override; + + virtual bool + DeallocPPluginSurfaceParent(PPluginSurfaceParent* s) override; + + virtual bool + AnswerNPN_PushPopupsEnabledState(const bool& aState) override; + + virtual bool + AnswerNPN_PopPopupsEnabledState() override; + + virtual bool + AnswerNPN_GetValueForURL(const NPNURLVariable& variable, + const nsCString& url, + nsCString* value, NPError* result) override; + + virtual bool + AnswerNPN_SetValueForURL(const NPNURLVariable& variable, + const nsCString& url, + const nsCString& value, NPError* result) override; + + virtual bool + AnswerNPN_GetAuthenticationInfo(const nsCString& protocol, + const nsCString& host, + const int32_t& port, + const nsCString& scheme, + const nsCString& realm, + nsCString* username, + nsCString* password, + NPError* result) override; + + virtual bool + AnswerNPN_ConvertPoint(const double& sourceX, + const bool& ignoreDestX, + const double& sourceY, + const bool& ignoreDestY, + const NPCoordinateSpace& sourceSpace, + const NPCoordinateSpace& destSpace, + double *destX, + double *destY, + bool *result) override; + + virtual bool + RecvRedrawPlugin() override; + + virtual bool + RecvNegotiatedCarbon() override; + + virtual bool + RecvAsyncNPP_NewResult(const NPError& aResult) override; + + virtual bool + RecvSetNetscapeWindowAsParent(const NativeWindowHandle& childWindow) override; + + NPError NPP_SetWindow(const NPWindow* aWindow); + + NPError NPP_GetValue(NPPVariable variable, void* retval); + NPError NPP_SetValue(NPNVariable variable, void* value); + + void NPP_URLRedirectNotify(const char* url, int32_t status, + void* notifyData); + + NPError NPP_NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype); + NPError NPP_DestroyStream(NPStream* stream, NPReason reason); + + void NPP_Print(NPPrint* platformPrint); + + int16_t NPP_HandleEvent(void* event); + + void NPP_URLNotify(const char* url, NPReason reason, void* notifyData); + + PluginModuleParent* Module() + { + return mParent; + } + + const NPNetscapeFuncs* GetNPNIface() + { + return mNPNIface; + } + + bool + RegisterNPObjectForActor(NPObject* aObject, + PluginScriptableObjectParent* aActor); + + void + UnregisterNPObject(NPObject* aObject); + + PluginScriptableObjectParent* + GetActorForNPObject(NPObject* aObject); + + NPP + GetNPP() + { + return mNPP; + } + + bool + UseSurrogate() const + { + return mUseSurrogate; + } + + void + GetSrcAttribute(nsACString& aOutput) const + { + aOutput = mSrcAttribute; + } + + virtual bool + AnswerPluginFocusChange(const bool& gotFocus) override; + + nsresult AsyncSetWindow(NPWindow* window); + nsresult GetImageContainer(mozilla::layers::ImageContainer** aContainer); + nsresult GetImageSize(nsIntSize* aSize); +#ifdef XP_MACOSX + nsresult IsRemoteDrawingCoreAnimation(bool *aDrawing); +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + nsresult ContentsScaleFactorChanged(double aContentsScaleFactor); +#endif + nsresult SetBackgroundUnknown(); + nsresult BeginUpdateBackground(const nsIntRect& aRect, + DrawTarget** aDrawTarget); + nsresult EndUpdateBackground(const nsIntRect& aRect); +#if defined(XP_WIN) + nsresult SetScrollCaptureId(uint64_t aScrollCaptureId); + nsresult GetScrollCaptureContainer(mozilla::layers::ImageContainer** aContainer); +#endif + void DidComposite(); + + bool IsUsingDirectDrawing(); + + virtual PluginAsyncSurrogate* GetAsyncSurrogate() override; + + virtual PluginInstanceParent* GetInstance() override { return this; } + + static PluginInstanceParent* Cast(NPP instance, + PluginAsyncSurrogate** aSurrogate = nullptr); + + // for IME hook + virtual bool + RecvGetCompositionString(const uint32_t& aIndex, + nsTArray<uint8_t>* aBuffer, + int32_t* aLength) override; + virtual bool + RecvSetCandidateWindow( + const mozilla::widget::CandidateWindowPosition& aPosition) override; + virtual bool + RecvRequestCommitOrCancel(const bool& aCommitted) override; + + // for reserved shortcut key handling with windowed plugin on Windows + nsresult HandledWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, + bool aIsConsumed); + virtual bool + RecvOnWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData) override; + +private: + // Create an appropriate platform surface for a background of size + // |aSize|. Return true if successful. + bool CreateBackground(const nsIntSize& aSize); + void DestroyBackground(); + SurfaceDescriptor BackgroundDescriptor() /*const*/; + + typedef mozilla::layers::ImageContainer ImageContainer; + ImageContainer *GetImageContainer(); + + virtual PPluginBackgroundDestroyerParent* + AllocPPluginBackgroundDestroyerParent() override; + + virtual bool + DeallocPPluginBackgroundDestroyerParent(PPluginBackgroundDestroyerParent* aActor) override; + + bool InternalGetValueForNPObject(NPNVariable aVariable, + PPluginScriptableObjectParent** aValue, + NPError* aResult); + + nsPluginInstanceOwner* GetOwner(); + + void SetCurrentImage(layers::Image* aImage); + + // Update Telemetry with the current drawing model. + void RecordDrawingModel(); + +private: + PluginModuleParent* mParent; + RefPtr<PluginAsyncSurrogate> mSurrogate; + bool mUseSurrogate; + NPP mNPP; + const NPNetscapeFuncs* mNPNIface; + nsCString mSrcAttribute; + NPWindowType mWindowType; + int16_t mDrawingModel; + + // Since plugins may request different drawing models to find a compatible + // one, we only record the drawing model after a SetWindow call and if the + // drawing model has changed. + int mLastRecordedDrawingModel; + + nsDataHashtable<nsPtrHashKey<NPObject>, PluginScriptableObjectParent*> mScriptableObjects; + + // This is used to tell the compositor that it should invalidate the ImageLayer. + uint32_t mFrameID; + +#if defined(XP_WIN) + // Note: DXGI 1.1 surface handles are global across all processes, and are not + // marshaled. As long as we haven't freed a texture its handle should be valid + // as a unique cross-process identifier for the texture. + nsRefPtrHashtable<nsPtrHashKey<void>, D3D11SurfaceHolder> mD3D11Surfaces; +#endif + +#if defined(OS_WIN) +private: + // Used in handling parent/child forwarding of events. + static LRESULT CALLBACK PluginWindowHookProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam); + void SubclassPluginWindow(HWND aWnd); + void UnsubclassPluginWindow(); + + bool MaybeCreateAndParentChildPluginWindow(); + void MaybeCreateChildPopupSurrogate(); + +private: + nsIntRect mPluginPort; + nsIntRect mSharedSize; + HWND mPluginHWND; + // This is used for the normal child plugin HWND for windowed plugins and, + // if needed, also the child popup surrogate HWND for windowless plugins. + HWND mChildPluginHWND; + HWND mChildPluginsParentHWND; + WNDPROC mPluginWndProc; + bool mNestedEventState; +#endif // defined(XP_WIN) +#if defined(MOZ_WIDGET_COCOA) +private: + Shmem mShSurface; + uint16_t mShWidth; + uint16_t mShHeight; + CGColorSpaceRef mShColorSpace; + RefPtr<MacIOSurface> mIOSurface; + RefPtr<MacIOSurface> mFrontIOSurface; +#endif // definied(MOZ_WIDGET_COCOA) + + // ObjectFrame layer wrapper + RefPtr<gfxASurface> mFrontSurface; + // For windowless+transparent instances, this surface contains a + // "pretty recent" copy of the pixels under its <object> frame. + // On the plugin side, we use this surface to avoid doing alpha + // recovery when possible. This surface is created and owned by + // the browser, but a "read-only" reference is sent to the plugin. + // + // We have explicitly chosen not to provide any guarantees about + // the consistency of the pixels in |mBackground|. A plugin may + // be able to observe partial updates to the background. + RefPtr<gfxASurface> mBackground; + + RefPtr<ImageContainer> mImageContainer; +}; + + +} // namespace plugins +} // namespace mozilla + +#endif // ifndef dom_plugins_PluginInstanceParent_h diff --git a/dom/plugins/ipc/PluginInterposeOSX.h b/dom/plugins/ipc/PluginInterposeOSX.h new file mode 100644 index 000000000..2a742b8aa --- /dev/null +++ b/dom/plugins/ipc/PluginInterposeOSX.h @@ -0,0 +1,137 @@ +// vim:set ts=2 sts=2 sw=2 et cin: +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H +#define DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H + +#include "base/basictypes.h" +#include "nsPoint.h" +#include "npapi.h" + +// Make this includable from non-Objective-C code. +#ifndef __OBJC__ +class NSCursor; +#else +#import <Cocoa/Cocoa.h> +#endif + +// The header file QuickdrawAPI.h is missing on OS X 10.7 and up (though the +// QuickDraw APIs defined in it are still present) -- so we need to supply the +// relevant parts of its contents here. It's likely that Apple will eventually +// remove the APIs themselves (probably in OS X 10.8), so we need to make them +// weak imports, and test for their presence before using them. +#if !defined(__QUICKDRAWAPI__) + +typedef short Bits16[16]; +struct Cursor { + Bits16 data; + Bits16 mask; + Point hotSpot; +}; +typedef struct Cursor Cursor; + +#endif /* __QUICKDRAWAPI__ */ + +namespace mac_plugin_interposing { + +// Class used to serialize NSCursor objects over IPC between processes. +class NSCursorInfo { +public: + enum Type { + TypeCustom, + TypeArrow, + TypeClosedHand, + TypeContextualMenu, // Only supported on OS X 10.6 and up + TypeCrosshair, + TypeDisappearingItem, + TypeDragCopy, // Only supported on OS X 10.6 and up + TypeDragLink, // Only supported on OS X 10.6 and up + TypeIBeam, + TypeNotAllowed, // Only supported on OS X 10.6 and up + TypeOpenHand, + TypePointingHand, + TypeResizeDown, + TypeResizeLeft, + TypeResizeLeftRight, + TypeResizeRight, + TypeResizeUp, + TypeResizeUpDown, + TypeTransparent // Special type + }; + + NSCursorInfo(); + explicit NSCursorInfo(NSCursor* aCursor); + explicit NSCursorInfo(const Cursor* aCursor); + ~NSCursorInfo(); + + NSCursor* GetNSCursor() const; + Type GetType() const; + const char* GetTypeName() const; + nsPoint GetHotSpot() const; + uint8_t* GetCustomImageData() const; + uint32_t GetCustomImageDataLength() const; + + void SetType(Type aType); + void SetHotSpot(nsPoint aHotSpot); + void SetCustomImageData(uint8_t* aData, uint32_t aDataLength); + + static bool GetNativeCursorsSupported(); + +private: + NSCursor* GetTransparentCursor() const; + + Type mType; + // The hot spot's coordinate system is the cursor's coordinate system, and + // has an upper-left origin (in both Cocoa and pre-Cocoa systems). + nsPoint mHotSpot; + uint8_t* mCustomImageData; + uint32_t mCustomImageDataLength; + static int32_t mNativeCursorsSupported; +}; + +namespace parent { + +void OnPluginShowWindow(uint32_t window_id, CGRect window_bounds, bool modal); +void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid); +void OnSetCursor(const NSCursorInfo& cursorInfo); +void OnShowCursor(bool show); +void OnPushCursor(const NSCursorInfo& cursorInfo); +void OnPopCursor(); + +} // namespace parent + +namespace child { + +void SetUpCocoaInterposing(); + +} // namespace child + +} // namespace mac_plugin_interposing + +#endif /* DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H */ diff --git a/dom/plugins/ipc/PluginInterposeOSX.mm b/dom/plugins/ipc/PluginInterposeOSX.mm new file mode 100644 index 000000000..bf24d2b0d --- /dev/null +++ b/dom/plugins/ipc/PluginInterposeOSX.mm @@ -0,0 +1,1158 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/basictypes.h" +#include "nsCocoaUtils.h" +#include "PluginModuleChild.h" +#include "nsDebug.h" +#include "PluginInterposeOSX.h" +#include <set> +#import <AppKit/AppKit.h> +#import <objc/runtime.h> +#import <Carbon/Carbon.h> + +using namespace mozilla::plugins; + +namespace mac_plugin_interposing { + +int32_t NSCursorInfo::mNativeCursorsSupported = -1; + +// This constructor may be called from the browser process or the plugin +// process. +NSCursorInfo::NSCursorInfo() + : mType(TypeArrow) + , mHotSpot(nsPoint(0, 0)) + , mCustomImageData(NULL) + , mCustomImageDataLength(0) +{ +} + +NSCursorInfo::NSCursorInfo(NSCursor* aCursor) + : mType(TypeArrow) + , mHotSpot(nsPoint(0, 0)) + , mCustomImageData(NULL) + , mCustomImageDataLength(0) +{ + // This constructor is only ever called from the plugin process, so the + // following is safe. + if (!GetNativeCursorsSupported()) { + return; + } + + NSPoint hotSpotCocoa = [aCursor hotSpot]; + mHotSpot = nsPoint(hotSpotCocoa.x, hotSpotCocoa.y); + + Class nsCursorClass = [NSCursor class]; + if ([aCursor isEqual:[NSCursor arrowCursor]]) { + mType = TypeArrow; + } else if ([aCursor isEqual:[NSCursor closedHandCursor]]) { + mType = TypeClosedHand; + } else if ([aCursor isEqual:[NSCursor crosshairCursor]]) { + mType = TypeCrosshair; + } else if ([aCursor isEqual:[NSCursor disappearingItemCursor]]) { + mType = TypeDisappearingItem; + } else if ([aCursor isEqual:[NSCursor IBeamCursor]]) { + mType = TypeIBeam; + } else if ([aCursor isEqual:[NSCursor openHandCursor]]) { + mType = TypeOpenHand; + } else if ([aCursor isEqual:[NSCursor pointingHandCursor]]) { + mType = TypePointingHand; + } else if ([aCursor isEqual:[NSCursor resizeDownCursor]]) { + mType = TypeResizeDown; + } else if ([aCursor isEqual:[NSCursor resizeLeftCursor]]) { + mType = TypeResizeLeft; + } else if ([aCursor isEqual:[NSCursor resizeLeftRightCursor]]) { + mType = TypeResizeLeftRight; + } else if ([aCursor isEqual:[NSCursor resizeRightCursor]]) { + mType = TypeResizeRight; + } else if ([aCursor isEqual:[NSCursor resizeUpCursor]]) { + mType = TypeResizeUp; + } else if ([aCursor isEqual:[NSCursor resizeUpDownCursor]]) { + mType = TypeResizeUpDown; + // The following cursor types are only supported on OS X 10.6 and up. + } else if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(contextualMenuCursor)]]) { + mType = TypeContextualMenu; + } else if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(dragCopyCursor)]]) { + mType = TypeDragCopy; + } else if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(dragLinkCursor)]]) { + mType = TypeDragLink; + } else if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(operationNotAllowedCursor)]]) { + mType = TypeNotAllowed; + } else { + NSImage* image = [aCursor image]; + NSArray* reps = image ? [image representations] : nil; + NSUInteger repsCount = reps ? [reps count] : 0; + if (!repsCount) { + // If we have a custom cursor with no image representations, assume we + // need a transparent cursor. + mType = TypeTransparent; + } else { + CGImageRef cgImage = nil; + // XXX We don't know how to deal with a cursor that doesn't have a + // bitmap image representation. For now we fall back to an arrow + // cursor. + for (NSUInteger i = 0; i < repsCount; ++i) { + id rep = [reps objectAtIndex:i]; + if ([rep isKindOfClass:[NSBitmapImageRep class]]) { + cgImage = [(NSBitmapImageRep*)rep CGImage]; + break; + } + } + if (cgImage) { + CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); + if (data) { + CGImageDestinationRef dest = ::CGImageDestinationCreateWithData(data, + kUTTypePNG, + 1, + NULL); + if (dest) { + ::CGImageDestinationAddImage(dest, cgImage, NULL); + if (::CGImageDestinationFinalize(dest)) { + uint32_t dataLength = (uint32_t) ::CFDataGetLength(data); + mCustomImageData = (uint8_t*) moz_xmalloc(dataLength); + ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData); + mCustomImageDataLength = dataLength; + mType = TypeCustom; + } + ::CFRelease(dest); + } + ::CFRelease(data); + } + } + if (!mCustomImageData) { + mType = TypeArrow; + } + } + } +} + +NSCursorInfo::NSCursorInfo(const Cursor* aCursor) + : mType(TypeArrow) + , mHotSpot(nsPoint(0, 0)) + , mCustomImageData(NULL) + , mCustomImageDataLength(0) +{ + // This constructor is only ever called from the plugin process, so the + // following is safe. + if (!GetNativeCursorsSupported()) { + return; + } + + mHotSpot = nsPoint(aCursor->hotSpot.h, aCursor->hotSpot.v); + + int width = 16, height = 16; + int bytesPerPixel = 4; + int rowBytes = width * bytesPerPixel; + int bitmapSize = height * rowBytes; + + bool isTransparent = true; + + uint8_t* bitmap = (uint8_t*) moz_xmalloc(bitmapSize); + // The way we create 'bitmap' is largely "borrowed" from Chrome's + // WebCursor::InitFromCursor(). + for (int y = 0; y < height; ++y) { + unsigned short data = aCursor->data[y]; + unsigned short mask = aCursor->mask[y]; + // Change 'data' and 'mask' from big-endian to little-endian, but output + // big-endian data below. + data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF); + mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF); + // It'd be nice to use a gray-scale bitmap. But + // CGBitmapContextCreateImage() (used below) won't work with one that also + // has alpha values. + for (int x = 0; x < width; ++x) { + int offset = (y * rowBytes) + (x * bytesPerPixel); + // Color value + if (data & 0x8000) { + bitmap[offset] = 0x0; + bitmap[offset + 1] = 0x0; + bitmap[offset + 2] = 0x0; + } else { + bitmap[offset] = 0xFF; + bitmap[offset + 1] = 0xFF; + bitmap[offset + 2] = 0xFF; + } + // Mask value + if (mask & 0x8000) { + bitmap[offset + 3] = 0xFF; + isTransparent = false; + } else { + bitmap[offset + 3] = 0x0; + } + data <<= 1; + mask <<= 1; + } + } + + if (isTransparent) { + // If aCursor is transparent, we don't need to serialize custom cursor + // data over IPC. + mType = TypeTransparent; + } else { + CGColorSpaceRef color = ::CGColorSpaceCreateDeviceRGB(); + if (color) { + CGContextRef context = + ::CGBitmapContextCreate(bitmap, + width, + height, + 8, + rowBytes, + color, + kCGImageAlphaPremultipliedLast | + kCGBitmapByteOrder32Big); + if (context) { + CGImageRef image = ::CGBitmapContextCreateImage(context); + if (image) { + ::CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); + if (data) { + CGImageDestinationRef dest = + ::CGImageDestinationCreateWithData(data, + kUTTypePNG, + 1, + NULL); + if (dest) { + ::CGImageDestinationAddImage(dest, image, NULL); + if (::CGImageDestinationFinalize(dest)) { + uint32_t dataLength = (uint32_t) ::CFDataGetLength(data); + mCustomImageData = (uint8_t*) moz_xmalloc(dataLength); + ::CFDataGetBytes(data, + ::CFRangeMake(0, dataLength), + mCustomImageData); + mCustomImageDataLength = dataLength; + mType = TypeCustom; + } + ::CFRelease(dest); + } + ::CFRelease(data); + } + ::CGImageRelease(image); + } + ::CGContextRelease(context); + } + ::CGColorSpaceRelease(color); + } + } + + free(bitmap); +} + +NSCursorInfo::~NSCursorInfo() +{ + if (mCustomImageData) { + free(mCustomImageData); + } +} + +NSCursor* NSCursorInfo::GetNSCursor() const +{ + NSCursor* retval = nil; + + Class nsCursorClass = [NSCursor class]; + switch(mType) { + case TypeArrow: + retval = [NSCursor arrowCursor]; + break; + case TypeClosedHand: + retval = [NSCursor closedHandCursor]; + break; + case TypeCrosshair: + retval = [NSCursor crosshairCursor]; + break; + case TypeDisappearingItem: + retval = [NSCursor disappearingItemCursor]; + break; + case TypeIBeam: + retval = [NSCursor IBeamCursor]; + break; + case TypeOpenHand: + retval = [NSCursor openHandCursor]; + break; + case TypePointingHand: + retval = [NSCursor pointingHandCursor]; + break; + case TypeResizeDown: + retval = [NSCursor resizeDownCursor]; + break; + case TypeResizeLeft: + retval = [NSCursor resizeLeftCursor]; + break; + case TypeResizeLeftRight: + retval = [NSCursor resizeLeftRightCursor]; + break; + case TypeResizeRight: + retval = [NSCursor resizeRightCursor]; + break; + case TypeResizeUp: + retval = [NSCursor resizeUpCursor]; + break; + case TypeResizeUpDown: + retval = [NSCursor resizeUpDownCursor]; + break; + // The following four cursor types are only supported on OS X 10.6 and up. + case TypeContextualMenu: { + if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)]) { + retval = [nsCursorClass performSelector:@selector(contextualMenuCursor)]; + } + break; + } + case TypeDragCopy: { + if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)]) { + retval = [nsCursorClass performSelector:@selector(dragCopyCursor)]; + } + break; + } + case TypeDragLink: { + if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)]) { + retval = [nsCursorClass performSelector:@selector(dragLinkCursor)]; + } + break; + } + case TypeNotAllowed: { + if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)]) { + retval = [nsCursorClass performSelector:@selector(operationNotAllowedCursor)]; + } + break; + } + case TypeTransparent: + retval = GetTransparentCursor(); + break; + default: + break; + } + + if (!retval && mCustomImageData && mCustomImageDataLength) { + CGDataProviderRef provider = ::CGDataProviderCreateWithData(NULL, + (const void*)mCustomImageData, + mCustomImageDataLength, + NULL); + if (provider) { + CGImageRef cgImage = ::CGImageCreateWithPNGDataProvider(provider, + NULL, + false, + kCGRenderingIntentDefault); + if (cgImage) { + NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; + if (rep) { + NSImage* image = [[NSImage alloc] init]; + if (image) { + [image addRepresentation:rep]; + retval = [[[NSCursor alloc] initWithImage:image + hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] + autorelease]; + [image release]; + } + [rep release]; + } + ::CGImageRelease(cgImage); + } + ::CFRelease(provider); + } + } + + // Fall back to an arrow cursor if need be. + if (!retval) { + retval = [NSCursor arrowCursor]; + } + + return retval; +} + +// Get a transparent cursor with the appropriate hot spot. We need one if +// (for example) we have a custom cursor with no image data. +NSCursor* NSCursorInfo::GetTransparentCursor() const +{ + NSCursor* retval = nil; + + int width = 16, height = 16; + int bytesPerPixel = 2; + int rowBytes = width * bytesPerPixel; + int dataSize = height * rowBytes; + + uint8_t* data = (uint8_t*) moz_xmalloc(dataSize); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + int offset = (y * rowBytes) + (x * bytesPerPixel); + data[offset] = 0x7E; // Arbitrary gray-scale value + data[offset + 1] = 0; // Alpha value to make us transparent + } + } + + NSBitmapImageRep* imageRep = + [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:2 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedWhiteColorSpace + bytesPerRow:rowBytes + bitsPerPixel:16] + autorelease]; + if (imageRep) { + uint8_t* repDataPtr = [imageRep bitmapData]; + if (repDataPtr) { + memcpy(repDataPtr, data, dataSize); + NSImage *image = + [[[NSImage alloc] initWithSize:NSMakeSize(width, height)] + autorelease]; + if (image) { + [image addRepresentation:imageRep]; + retval = + [[[NSCursor alloc] initWithImage:image + hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] + autorelease]; + } + } + } + + free(data); + + // Fall back to an arrow cursor if (for some reason) the above code failed. + if (!retval) { + retval = [NSCursor arrowCursor]; + } + + return retval; +} + +NSCursorInfo::Type NSCursorInfo::GetType() const +{ + return mType; +} + +const char* NSCursorInfo::GetTypeName() const +{ + switch(mType) { + case TypeCustom: + return "TypeCustom"; + case TypeArrow: + return "TypeArrow"; + case TypeClosedHand: + return "TypeClosedHand"; + case TypeContextualMenu: + return "TypeContextualMenu"; + case TypeCrosshair: + return "TypeCrosshair"; + case TypeDisappearingItem: + return "TypeDisappearingItem"; + case TypeDragCopy: + return "TypeDragCopy"; + case TypeDragLink: + return "TypeDragLink"; + case TypeIBeam: + return "TypeIBeam"; + case TypeNotAllowed: + return "TypeNotAllowed"; + case TypeOpenHand: + return "TypeOpenHand"; + case TypePointingHand: + return "TypePointingHand"; + case TypeResizeDown: + return "TypeResizeDown"; + case TypeResizeLeft: + return "TypeResizeLeft"; + case TypeResizeLeftRight: + return "TypeResizeLeftRight"; + case TypeResizeRight: + return "TypeResizeRight"; + case TypeResizeUp: + return "TypeResizeUp"; + case TypeResizeUpDown: + return "TypeResizeUpDown"; + case TypeTransparent: + return "TypeTransparent"; + default: + break; + } + return "TypeUnknown"; +} + +nsPoint NSCursorInfo::GetHotSpot() const +{ + return mHotSpot; +} + +uint8_t* NSCursorInfo::GetCustomImageData() const +{ + return mCustomImageData; +} + +uint32_t NSCursorInfo::GetCustomImageDataLength() const +{ + return mCustomImageDataLength; +} + +void NSCursorInfo::SetType(Type aType) +{ + mType = aType; +} + +void NSCursorInfo::SetHotSpot(nsPoint aHotSpot) +{ + mHotSpot = aHotSpot; +} + +void NSCursorInfo::SetCustomImageData(uint8_t* aData, uint32_t aDataLength) +{ + if (mCustomImageData) { + free(mCustomImageData); + } + if (aDataLength) { + mCustomImageData = (uint8_t*) moz_xmalloc(aDataLength); + memcpy(mCustomImageData, aData, aDataLength); + } else { + mCustomImageData = NULL; + } + mCustomImageDataLength = aDataLength; +} + +// This should never be called from the browser process -- only from the +// plugin process. +bool NSCursorInfo::GetNativeCursorsSupported() +{ + if (mNativeCursorsSupported == -1) { + ENSURE_PLUGIN_THREAD(false); + PluginModuleChild *pmc = PluginModuleChild::GetChrome(); + if (pmc) { + bool result = pmc->GetNativeCursorsSupported(); + if (result) { + mNativeCursorsSupported = 1; + } else { + mNativeCursorsSupported = 0; + } + } + } + return (mNativeCursorsSupported == 1); +} + +} // namespace mac_plugin_interposing + +namespace mac_plugin_interposing { +namespace parent { + +// Tracks plugin windows currently visible. +std::set<uint32_t> plugin_visible_windows_set_; +// Tracks full screen windows currently visible. +std::set<uint32_t> plugin_fullscreen_windows_set_; +// Tracks modal windows currently visible. +std::set<uint32_t> plugin_modal_windows_set_; + +void OnPluginShowWindow(uint32_t window_id, + CGRect window_bounds, + bool modal) { + plugin_visible_windows_set_.insert(window_id); + + if (modal) + plugin_modal_windows_set_.insert(window_id); + + CGRect main_display_bounds = ::CGDisplayBounds(CGMainDisplayID()); + + if (CGRectEqualToRect(window_bounds, main_display_bounds) && + (plugin_fullscreen_windows_set_.find(window_id) == + plugin_fullscreen_windows_set_.end())) { + plugin_fullscreen_windows_set_.insert(window_id); + + nsCocoaUtils::HideOSChromeOnScreen(true); + } +} + +static void ActivateProcess(pid_t pid) { + ProcessSerialNumber process; + OSStatus status = ::GetProcessForPID(pid, &process); + + if (status == noErr) { + SetFrontProcess(&process); + } else { + NS_WARNING("Unable to get process for pid."); + } +} + +// Must be called on the UI thread. +// If plugin_pid is -1, the browser will be the active process on return, +// otherwise that process will be given focus back before this function returns. +static void ReleasePluginFullScreen(pid_t plugin_pid) { + // Releasing full screen only works if we are the frontmost process; grab + // focus, but give it back to the plugin process if requested. + ActivateProcess(base::GetCurrentProcId()); + + nsCocoaUtils::HideOSChromeOnScreen(false); + + if (plugin_pid != -1) { + ActivateProcess(plugin_pid); + } +} + +void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid) { + bool had_windows = !plugin_visible_windows_set_.empty(); + plugin_visible_windows_set_.erase(window_id); + bool browser_needs_activation = had_windows && + plugin_visible_windows_set_.empty(); + + plugin_modal_windows_set_.erase(window_id); + if (plugin_fullscreen_windows_set_.find(window_id) != + plugin_fullscreen_windows_set_.end()) { + plugin_fullscreen_windows_set_.erase(window_id); + pid_t plugin_pid = browser_needs_activation ? -1 : aPluginPid; + browser_needs_activation = false; + ReleasePluginFullScreen(plugin_pid); + } + + if (browser_needs_activation) { + ActivateProcess(getpid()); + } +} + +void OnSetCursor(const NSCursorInfo& cursorInfo) +{ + NSCursor* aCursor = cursorInfo.GetNSCursor(); + if (aCursor) { + [aCursor set]; + } +} + +void OnShowCursor(bool show) +{ + if (show) { + [NSCursor unhide]; + } else { + [NSCursor hide]; + } +} + +void OnPushCursor(const NSCursorInfo& cursorInfo) +{ + NSCursor* aCursor = cursorInfo.GetNSCursor(); + if (aCursor) { + [aCursor push]; + } +} + +void OnPopCursor() +{ + [NSCursor pop]; +} + +} // namespace parent +} // namespace mac_plugin_interposing + +namespace mac_plugin_interposing { +namespace child { + +// TODO(stuartmorgan): Make this an IPC to order the plugin process above the +// browser process only if the browser is current frontmost. +void FocusPluginProcess() { + ProcessSerialNumber this_process, front_process; + if ((GetCurrentProcess(&this_process) != noErr) || + (GetFrontProcess(&front_process) != noErr)) { + return; + } + + Boolean matched = false; + if ((SameProcess(&this_process, &front_process, &matched) == noErr) && + !matched) { + SetFrontProcess(&this_process); + } +} + +void NotifyBrowserOfPluginShowWindow(uint32_t window_id, CGRect bounds, + bool modal) { + ENSURE_PLUGIN_THREAD_VOID(); + + PluginModuleChild *pmc = PluginModuleChild::GetChrome(); + if (pmc) + pmc->PluginShowWindow(window_id, modal, bounds); +} + +void NotifyBrowserOfPluginHideWindow(uint32_t window_id, CGRect bounds) { + ENSURE_PLUGIN_THREAD_VOID(); + + PluginModuleChild *pmc = PluginModuleChild::GetChrome(); + if (pmc) + pmc->PluginHideWindow(window_id); +} + +void NotifyBrowserOfSetCursor(NSCursorInfo& aCursorInfo) +{ + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild *pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->SetCursor(aCursorInfo); + } +} + +void NotifyBrowserOfShowCursor(bool show) +{ + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild *pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->ShowCursor(show); + } +} + +void NotifyBrowserOfPushCursor(NSCursorInfo& aCursorInfo) +{ + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild *pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->PushCursor(aCursorInfo); + } +} + +void NotifyBrowserOfPopCursor() +{ + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild *pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->PopCursor(); + } +} + +struct WindowInfo { + uint32_t window_id; + CGRect bounds; + explicit WindowInfo(NSWindow* aWindow) { + NSInteger window_num = [aWindow windowNumber]; + window_id = window_num > 0 ? window_num : 0; + bounds = NSRectToCGRect([aWindow frame]); + } +}; + +static void OnPluginWindowClosed(const WindowInfo& window_info) { + if (window_info.window_id == 0) + return; + mac_plugin_interposing::child::NotifyBrowserOfPluginHideWindow(window_info.window_id, + window_info.bounds); +} + +static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) { + // The window id is 0 if it has never been shown (including while it is the + // process of being shown for the first time); when that happens, we'll catch + // it in _setWindowNumber instead. + static BOOL s_pending_display_is_modal = NO; + if (window_info.window_id == 0) { + if (is_modal) + s_pending_display_is_modal = YES; + return; + } + if (s_pending_display_is_modal) { + is_modal = YES; + s_pending_display_is_modal = NO; + } + mac_plugin_interposing::child::NotifyBrowserOfPluginShowWindow( + window_info.window_id, window_info.bounds, is_modal); +} + +static BOOL OnSetCursor(NSCursorInfo &aInfo) +{ + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfSetCursor(aInfo); + return YES; + } + return NO; +} + +static BOOL OnHideCursor() +{ + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfShowCursor(NO); + return YES; + } + return NO; +} + +static BOOL OnUnhideCursor() +{ + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfShowCursor(YES); + return YES; + } + return NO; +} + +static BOOL OnPushCursor(NSCursorInfo &aInfo) +{ + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfPushCursor(aInfo); + return YES; + } + return NO; +} + +static BOOL OnPopCursor() +{ + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfPopCursor(); + return YES; + } + return NO; +} + +} // namespace child +} // namespace mac_plugin_interposing + +using namespace mac_plugin_interposing::child; + +@interface NSWindow (PluginInterposing) +- (void)pluginInterpose_orderOut:(id)sender; +- (void)pluginInterpose_orderFront:(id)sender; +- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender; +- (void)pluginInterpose_setWindowNumber:(NSInteger)num; +@end + +@implementation NSWindow (PluginInterposing) + +- (void)pluginInterpose_orderOut:(id)sender { + WindowInfo window_info(self); + [self pluginInterpose_orderOut:sender]; + OnPluginWindowClosed(window_info); +} + +- (void)pluginInterpose_orderFront:(id)sender { + mac_plugin_interposing::child::FocusPluginProcess(); + [self pluginInterpose_orderFront:sender]; + OnPluginWindowShown(WindowInfo(self), NO); +} + +- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender { + mac_plugin_interposing::child::FocusPluginProcess(); + [self pluginInterpose_makeKeyAndOrderFront:sender]; + OnPluginWindowShown(WindowInfo(self), NO); +} + +- (void)pluginInterpose_setWindowNumber:(NSInteger)num { + if (num > 0) + mac_plugin_interposing::child::FocusPluginProcess(); + [self pluginInterpose_setWindowNumber:num]; + if (num > 0) + OnPluginWindowShown(WindowInfo(self), NO); +} + +@end + +@interface NSApplication (PluginInterposing) +- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window; +@end + +@implementation NSApplication (PluginInterposing) + +- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window { + mac_plugin_interposing::child::FocusPluginProcess(); + // This is out-of-order relative to the other calls, but runModalForWindow: + // won't return until the window closes, and the order only matters for + // full-screen windows. + OnPluginWindowShown(WindowInfo(window), YES); + return [self pluginInterpose_runModalForWindow:window]; +} + +@end + +// Hook commands to manipulate the current cursor, so that they can be passed +// from the child process to the parent process. These commands have no +// effect unless they're performed in the parent process. +@interface NSCursor (PluginInterposing) +- (void)pluginInterpose_set; +- (void)pluginInterpose_push; +- (void)pluginInterpose_pop; ++ (NSCursor*)pluginInterpose_currentCursor; ++ (void)pluginInterpose_hide; ++ (void)pluginInterpose_unhide; ++ (void)pluginInterpose_pop; +@end + +// Cache the results of [NSCursor set], [NSCursor push] and [NSCursor pop]. +// The last element is always the current cursor. +static NSMutableArray* gCursorStack = nil; + +static BOOL initCursorStack() +{ + if (!gCursorStack) { + gCursorStack = [[NSMutableArray arrayWithCapacity:5] retain]; + } + return (gCursorStack != NULL); +} + +static NSCursor* currentCursorFromCache() +{ + if (!initCursorStack()) + return nil; + return (NSCursor*) [gCursorStack lastObject]; +} + +static void setCursorInCache(NSCursor* aCursor) +{ + if (!initCursorStack() || !aCursor) + return; + NSUInteger count = [gCursorStack count]; + if (count) { + [gCursorStack replaceObjectAtIndex:count - 1 withObject:aCursor]; + } else { + [gCursorStack addObject:aCursor]; + } +} + +static void pushCursorInCache(NSCursor* aCursor) +{ + if (!initCursorStack() || !aCursor) + return; + [gCursorStack addObject:aCursor]; +} + +static void popCursorInCache() +{ + if (!initCursorStack()) + return; + // Apple's doc on the +[NSCursor pop] method says: "If the current cursor + // is the only cursor on the stack, this method does nothing." + if ([gCursorStack count] > 1) { + [gCursorStack removeLastObject]; + } +} + +@implementation NSCursor (PluginInterposing) + +- (void)pluginInterpose_set +{ + NSCursorInfo info(self); + OnSetCursor(info); + setCursorInCache(self); + [self pluginInterpose_set]; +} + +- (void)pluginInterpose_push +{ + NSCursorInfo info(self); + OnPushCursor(info); + pushCursorInCache(self); + [self pluginInterpose_push]; +} + +- (void)pluginInterpose_pop +{ + OnPopCursor(); + popCursorInCache(); + [self pluginInterpose_pop]; +} + +// The currentCursor method always returns nil when running in a background +// process. But this may confuse plugins (notably Flash, see bug 621117). So +// if we get a nil return from the "call to super", we return a cursor that's +// been cached by previous calls to set or push. According to Apple's docs, +// currentCursor "only returns the cursor set by your application using +// NSCursor methods". So we don't need to worry about changes to the cursor +// made by other methods like SetThemeCursor(). ++ (NSCursor*)pluginInterpose_currentCursor +{ + NSCursor* retval = [self pluginInterpose_currentCursor]; + if (!retval) { + retval = currentCursorFromCache(); + } + return retval; +} + ++ (void)pluginInterpose_hide +{ + OnHideCursor(); + [self pluginInterpose_hide]; +} + ++ (void)pluginInterpose_unhide +{ + OnUnhideCursor(); + [self pluginInterpose_unhide]; +} + ++ (void)pluginInterpose_pop +{ + OnPopCursor(); + popCursorInCache(); + [self pluginInterpose_pop]; +} + +@end + +static void ExchangeMethods(Class target_class, + BOOL class_method, + SEL original, + SEL replacement) { + Method m1; + Method m2; + if (class_method) { + m1 = class_getClassMethod(target_class, original); + m2 = class_getClassMethod(target_class, replacement); + } else { + m1 = class_getInstanceMethod(target_class, original); + m2 = class_getInstanceMethod(target_class, replacement); + } + + if (m1 == m2) + return; + + if (m1 && m2) + method_exchangeImplementations(m1, m2); + else + NS_NOTREACHED("Cocoa swizzling failed"); +} + +namespace mac_plugin_interposing { +namespace child { + +void SetUpCocoaInterposing() { + Class nswindow_class = [NSWindow class]; + ExchangeMethods(nswindow_class, NO, @selector(orderOut:), + @selector(pluginInterpose_orderOut:)); + ExchangeMethods(nswindow_class, NO, @selector(orderFront:), + @selector(pluginInterpose_orderFront:)); + ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:), + @selector(pluginInterpose_makeKeyAndOrderFront:)); + ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:), + @selector(pluginInterpose_setWindowNumber:)); + + ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:), + @selector(pluginInterpose_runModalForWindow:)); + + Class nscursor_class = [NSCursor class]; + ExchangeMethods(nscursor_class, NO, @selector(set), + @selector(pluginInterpose_set)); + ExchangeMethods(nscursor_class, NO, @selector(push), + @selector(pluginInterpose_push)); + ExchangeMethods(nscursor_class, NO, @selector(pop), + @selector(pluginInterpose_pop)); + ExchangeMethods(nscursor_class, YES, @selector(currentCursor), + @selector(pluginInterpose_currentCursor)); + ExchangeMethods(nscursor_class, YES, @selector(hide), + @selector(pluginInterpose_hide)); + ExchangeMethods(nscursor_class, YES, @selector(unhide), + @selector(pluginInterpose_unhide)); + ExchangeMethods(nscursor_class, YES, @selector(pop), + @selector(pluginInterpose_pop)); +} + +} // namespace child +} // namespace mac_plugin_interposing + +// Called from plugin_child_interpose.mm, which hooks calls to +// SetCursor() (the QuickDraw call) from the plugin child process. +extern "C" NS_VISIBILITY_DEFAULT BOOL +mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor) +{ + NSCursorInfo info(cursor); + return OnSetCursor(info); +} + +// Called from plugin_child_interpose.mm, which hooks calls to +// SetThemeCursor() (the Appearance Manager call) from the plugin child +// process. +extern "C" NS_VISIBILITY_DEFAULT BOOL +mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor) +{ + NSCursorInfo info; + switch (cursor) { + case kThemeArrowCursor: + info.SetType(NSCursorInfo::TypeArrow); + break; + case kThemeCopyArrowCursor: + info.SetType(NSCursorInfo::TypeDragCopy); + break; + case kThemeAliasArrowCursor: + info.SetType(NSCursorInfo::TypeDragLink); + break; + case kThemeContextualMenuArrowCursor: + info.SetType(NSCursorInfo::TypeContextualMenu); + break; + case kThemeIBeamCursor: + info.SetType(NSCursorInfo::TypeIBeam); + break; + case kThemeCrossCursor: + case kThemePlusCursor: + info.SetType(NSCursorInfo::TypeCrosshair); + break; + case kThemeWatchCursor: + case kThemeSpinningCursor: + info.SetType(NSCursorInfo::TypeArrow); + break; + case kThemeClosedHandCursor: + info.SetType(NSCursorInfo::TypeClosedHand); + break; + case kThemeOpenHandCursor: + info.SetType(NSCursorInfo::TypeOpenHand); + break; + case kThemePointingHandCursor: + case kThemeCountingUpHandCursor: + case kThemeCountingDownHandCursor: + case kThemeCountingUpAndDownHandCursor: + info.SetType(NSCursorInfo::TypePointingHand); + break; + case kThemeResizeLeftCursor: + info.SetType(NSCursorInfo::TypeResizeLeft); + break; + case kThemeResizeRightCursor: + info.SetType(NSCursorInfo::TypeResizeRight); + break; + case kThemeResizeLeftRightCursor: + info.SetType(NSCursorInfo::TypeResizeLeftRight); + break; + case kThemeNotAllowedCursor: + info.SetType(NSCursorInfo::TypeNotAllowed); + break; + case kThemeResizeUpCursor: + info.SetType(NSCursorInfo::TypeResizeUp); + break; + case kThemeResizeDownCursor: + info.SetType(NSCursorInfo::TypeResizeDown); + break; + case kThemeResizeUpDownCursor: + info.SetType(NSCursorInfo::TypeResizeUpDown); + break; + case kThemePoofCursor: + info.SetType(NSCursorInfo::TypeDisappearingItem); + break; + default: + info.SetType(NSCursorInfo::TypeArrow); + break; + } + return OnSetCursor(info); +} + +extern "C" NS_VISIBILITY_DEFAULT BOOL +mac_plugin_interposing_child_OnHideCursor() +{ + return OnHideCursor(); +} + +extern "C" NS_VISIBILITY_DEFAULT BOOL +mac_plugin_interposing_child_OnShowCursor() +{ + return OnUnhideCursor(); +} diff --git a/dom/plugins/ipc/PluginLibrary.h b/dom/plugins/ipc/PluginLibrary.h new file mode 100644 index 000000000..c9499ee0d --- /dev/null +++ b/dom/plugins/ipc/PluginLibrary.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 mozilla_PluginLibrary_h +#define mozilla_PluginLibrary_h 1 + +#include "prlink.h" +#include "npapi.h" +#include "npfunctions.h" +#include "nscore.h" +#include "nsTArray.h" +#include "nsError.h" +#include "mozilla/EventForwards.h" +#include "nsSize.h" +#include "nsRect.h" + +class nsCString; +class nsNPAPIPlugin; + +namespace mozilla { +namespace gfx { +class DrawTarget; +} +namespace layers { +class Image; +class ImageContainer; +} // namespace layers +} // namespace mozilla + +class nsIClearSiteDataCallback; + +#define nsIGetSitesWithDataCallback_CID {0xd0028b83, 0xfdf9, 0x4c53, {0xb7, 0xbb, 0x47, 0x46, 0x0f, 0x6b, 0x83, 0x6c}} +class nsIGetSitesWithDataCallback : public nsISupports { +public: + NS_IMETHOD SitesWithData(InfallibleTArray<nsCString>& result) = 0; + NS_DECLARE_STATIC_IID_ACCESSOR(nsIGetSitesWithDataCallback_CID) +}; +NS_DEFINE_STATIC_IID_ACCESSOR(nsIGetSitesWithDataCallback, nsIGetSitesWithDataCallback_CID) + +namespace mozilla { + +class PluginLibrary +{ +public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + virtual ~PluginLibrary() { } + + /** + * Inform this library about the nsNPAPIPlugin which owns it. This + * object will hold a weak pointer to the plugin. + */ + virtual void SetPlugin(nsNPAPIPlugin* plugin) = 0; + + virtual bool HasRequiredFunctions() = 0; + +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) = 0; +#else + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) = 0; +#endif + virtual nsresult NP_Shutdown(NPError* error) = 0; + virtual nsresult NP_GetMIMEDescription(const char** mimeDesc) = 0; + virtual nsresult NP_GetValue(void *future, NPPVariable aVariable, + void *aValue, NPError* error) = 0; +#if defined(XP_WIN) || defined(XP_MACOSX) + virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) = 0; +#endif + virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance, + uint16_t mode, int16_t argc, char* argn[], + char* argv[], NPSavedData* saved, + NPError* error) = 0; + + virtual nsresult NPP_ClearSiteData(const char* site, uint64_t flags, + uint64_t maxAge, nsCOMPtr<nsIClearSiteDataCallback> callback) = 0; + virtual nsresult NPP_GetSitesWithData(nsCOMPtr<nsIGetSitesWithDataCallback> callback) = 0; + + virtual nsresult AsyncSetWindow(NPP instance, NPWindow* window) = 0; + virtual nsresult GetImageContainer(NPP instance, mozilla::layers::ImageContainer** aContainer) = 0; + virtual nsresult GetImageSize(NPP instance, nsIntSize* aSize) = 0; + virtual void DidComposite(NPP instance) = 0; + virtual bool IsOOP() = 0; +#if defined(XP_MACOSX) + virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) = 0; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + virtual nsresult ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) = 0; +#endif +#if defined(XP_WIN) + virtual nsresult GetScrollCaptureContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) = 0; +#endif + virtual nsresult HandledWindowedPluginKeyEvent( + NPP aInstance, + const mozilla::NativeEventData& aNativeKeyData, + bool aIsCOnsumed) = 0; + + /** + * The next three methods are the third leg in the trip to + * PluginInstanceParent. They approximately follow the ReadbackSink + * API. + */ + virtual nsresult SetBackgroundUnknown(NPP instance) = 0; + virtual nsresult BeginUpdateBackground(NPP instance, + const nsIntRect&, DrawTarget**) = 0; + virtual nsresult EndUpdateBackground(NPP instance, const nsIntRect&) = 0; + virtual nsresult GetRunID(uint32_t* aRunID) = 0; + virtual void SetHasLocalInstance() = 0; +}; + +} // namespace mozilla + +#endif // ifndef mozilla_PluginLibrary_h diff --git a/dom/plugins/ipc/PluginMessageUtils.cpp b/dom/plugins/ipc/PluginMessageUtils.cpp new file mode 100644 index 000000000..47653fe6e --- /dev/null +++ b/dom/plugins/ipc/PluginMessageUtils.cpp @@ -0,0 +1,155 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8; -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* 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 "PluginMessageUtils.h" +#include "nsIRunnable.h" +#include "nsThreadUtils.h" + +#include "PluginInstanceParent.h" +#include "PluginInstanceChild.h" +#include "PluginScriptableObjectParent.h" +#include "PluginScriptableObjectChild.h" + +using std::string; + +using mozilla::ipc::MessageChannel; + +namespace { + +class DeferNPObjectReleaseRunnable : public mozilla::Runnable +{ +public: + DeferNPObjectReleaseRunnable(const NPNetscapeFuncs* f, NPObject* o) + : mFuncs(f) + , mObject(o) + { + NS_ASSERTION(o, "no release null objects"); + } + + NS_IMETHOD Run(); + +private: + const NPNetscapeFuncs* mFuncs; + NPObject* mObject; +}; + +NS_IMETHODIMP +DeferNPObjectReleaseRunnable::Run() +{ + mFuncs->releaseobject(mObject); + return NS_OK; +} + +} // namespace + +namespace mozilla { +namespace plugins { + +NPRemoteWindow::NPRemoteWindow() : + window(0), x(0), y(0), width(0), height(0), type(NPWindowTypeDrawable) +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + , visualID(0) + , colormap(0) +#endif /* XP_UNIX */ +#if defined(XP_MACOSX) + ,contentsScaleFactor(1.0) +#endif +{ + clipRect.top = 0; + clipRect.left = 0; + clipRect.bottom = 0; + clipRect.right = 0; +} + +ipc::RacyInterruptPolicy +MediateRace(const MessageChannel::MessageInfo& parent, + const MessageChannel::MessageInfo& child) +{ + switch (parent.type()) { + case PPluginInstance::Msg_Paint__ID: + case PPluginInstance::Msg_NPP_SetWindow__ID: + case PPluginInstance::Msg_NPP_HandleEvent_Shmem__ID: + case PPluginInstance::Msg_NPP_HandleEvent_IOSurface__ID: + // our code relies on the frame list not changing during paints and + // reflows + return ipc::RIPParentWins; + + default: + return ipc::RIPChildWins; + } +} + +#if defined(OS_LINUX) +static string +ReplaceAll(const string& haystack, const string& needle, const string& with) +{ + string munged = haystack; + string::size_type i = 0; + + while (string::npos != (i = munged.find(needle, i))) { + munged.replace(i, needle.length(), with); + i += with.length(); + } + + return munged; +} +#endif + +string +MungePluginDsoPath(const string& path) +{ +#if defined(OS_LINUX) + // https://bugzilla.mozilla.org/show_bug.cgi?id=519601 + return ReplaceAll(path, "netscape", "netsc@pe"); +#else + return path; +#endif +} + +string +UnmungePluginDsoPath(const string& munged) +{ +#if defined(OS_LINUX) + return ReplaceAll(munged, "netsc@pe", "netscape"); +#else + return munged; +#endif +} + + +LogModule* +GetPluginLog() +{ + static LazyLogModule sLog("IPCPlugins"); + return sLog; +} + +void +DeferNPObjectLastRelease(const NPNetscapeFuncs* f, NPObject* o) +{ + if (!o) + return; + + if (o->referenceCount > 1) { + f->releaseobject(o); + return; + } + + NS_DispatchToCurrentThread(new DeferNPObjectReleaseRunnable(f, o)); +} + +void DeferNPVariantLastRelease(const NPNetscapeFuncs* f, NPVariant* v) +{ + if (!NPVARIANT_IS_OBJECT(*v)) { + f->releasevariantvalue(v); + return; + } + DeferNPObjectLastRelease(f, v->value.objectValue); + VOID_TO_NPVARIANT(*v); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginMessageUtils.h b/dom/plugins/ipc/PluginMessageUtils.h new file mode 100644 index 000000000..55be59d62 --- /dev/null +++ b/dom/plugins/ipc/PluginMessageUtils.h @@ -0,0 +1,750 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 DOM_PLUGINS_PLUGINMESSAGEUTILS_H +#define DOM_PLUGINS_PLUGINMESSAGEUTILS_H + +#include "ipc/IPCMessageUtils.h" +#include "base/message_loop.h" + +#include "mozilla/ipc/CrossProcessMutex.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/UniquePtr.h" +#include "gfxipc/ShadowLayerUtils.h" + +#include "npapi.h" +#include "npruntime.h" +#include "npfunctions.h" +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/Logging.h" +#include "nsHashKeys.h" +#ifdef MOZ_CRASHREPORTER +# include "nsExceptionHandler.h" +#endif +#ifdef XP_MACOSX +#include "PluginInterposeOSX.h" +#else +namespace mac_plugin_interposing { class NSCursorInfo { }; } +#endif +using mac_plugin_interposing::NSCursorInfo; + +namespace mozilla { +namespace plugins { + +using layers::SurfaceDescriptorX11; + +enum ScriptableObjectType +{ + LocalObject, + Proxy +}; + +mozilla::ipc::RacyInterruptPolicy +MediateRace(const mozilla::ipc::MessageChannel::MessageInfo& parent, + const mozilla::ipc::MessageChannel::MessageInfo& child); + +std::string +MungePluginDsoPath(const std::string& path); +std::string +UnmungePluginDsoPath(const std::string& munged); + +extern mozilla::LogModule* GetPluginLog(); + +#if defined(_MSC_VER) +#define FULLFUNCTION __FUNCSIG__ +#elif defined(__GNUC__) +#define FULLFUNCTION __PRETTY_FUNCTION__ +#else +#define FULLFUNCTION __FUNCTION__ +#endif + +#define PLUGIN_LOG_DEBUG(args) MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, args) +#define PLUGIN_LOG_DEBUG_FUNCTION MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, ("%s", FULLFUNCTION)) +#define PLUGIN_LOG_DEBUG_METHOD MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, ("%s [%p]", FULLFUNCTION, (void*) this)) + +/** + * This is NPByteRange without the linked list. + */ +struct IPCByteRange +{ + int32_t offset; + uint32_t length; +}; + +typedef nsTArray<IPCByteRange> IPCByteRanges; + +typedef nsCString Buffer; + +struct NPRemoteWindow +{ + NPRemoteWindow(); + uint64_t window; + int32_t x; + int32_t y; + uint32_t width; + uint32_t height; + NPRect clipRect; + NPWindowType type; +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + VisualID visualID; + Colormap colormap; +#endif /* XP_UNIX */ +#if defined(XP_MACOSX) || defined(XP_WIN) + double contentsScaleFactor; +#endif +}; + +// This struct is like NPAudioDeviceChangeDetails, only it uses a
+// std::wstring instead of a const wchar_t* for the defaultDevice.
+// This gives us the necessary memory-ownership semantics without
+// requiring C++ objects in npapi.h.
+struct NPAudioDeviceChangeDetailsIPC
+{
+ int32_t flow;
+ int32_t role;
+ std::wstring defaultDevice;
+}; + +#ifdef XP_WIN +typedef HWND NativeWindowHandle; +#elif defined(MOZ_X11) +typedef XID NativeWindowHandle; +#elif defined(XP_DARWIN) || defined(ANDROID) +typedef intptr_t NativeWindowHandle; // never actually used, will always be 0 +#else +#error Need NativeWindowHandle for this platform +#endif + +#ifdef XP_WIN +typedef base::SharedMemoryHandle WindowsSharedMemoryHandle; +typedef HANDLE DXGISharedSurfaceHandle; +#else +typedef mozilla::null_t WindowsSharedMemoryHandle; +typedef mozilla::null_t DXGISharedSurfaceHandle; +#endif + +// XXX maybe not the best place for these. better one? + +#define VARSTR(v_) case v_: return #v_ +inline const char* +NPPVariableToString(NPPVariable aVar) +{ + switch (aVar) { + VARSTR(NPPVpluginNameString); + VARSTR(NPPVpluginDescriptionString); + VARSTR(NPPVpluginWindowBool); + VARSTR(NPPVpluginTransparentBool); + VARSTR(NPPVjavaClass); + VARSTR(NPPVpluginWindowSize); + VARSTR(NPPVpluginTimerInterval); + + VARSTR(NPPVpluginScriptableInstance); + VARSTR(NPPVpluginScriptableIID); + + VARSTR(NPPVjavascriptPushCallerBool); + + VARSTR(NPPVpluginKeepLibraryInMemory); + VARSTR(NPPVpluginNeedsXEmbed); + + VARSTR(NPPVpluginScriptableNPObject); + + VARSTR(NPPVformValue); + + VARSTR(NPPVpluginUrlRequestsDisplayedBool); + + VARSTR(NPPVpluginWantsAllNetworkStreams); + +#ifdef XP_MACOSX + VARSTR(NPPVpluginDrawingModel); + VARSTR(NPPVpluginEventModel); +#endif + +#ifdef XP_WIN + VARSTR(NPPVpluginRequiresAudioDeviceChanges); +#endif + + default: return "???"; + } +} + +inline const char* +NPNVariableToString(NPNVariable aVar) +{ + switch(aVar) { + VARSTR(NPNVxDisplay); + VARSTR(NPNVxtAppContext); + VARSTR(NPNVnetscapeWindow); + VARSTR(NPNVjavascriptEnabledBool); + VARSTR(NPNVasdEnabledBool); + VARSTR(NPNVisOfflineBool); + + VARSTR(NPNVserviceManager); + VARSTR(NPNVDOMElement); + VARSTR(NPNVDOMWindow); + VARSTR(NPNVToolkit); + VARSTR(NPNVSupportsXEmbedBool); + + VARSTR(NPNVWindowNPObject); + + VARSTR(NPNVPluginElementNPObject); + + VARSTR(NPNVSupportsWindowless); + + VARSTR(NPNVprivateModeBool); + VARSTR(NPNVdocumentOrigin); + +#ifdef XP_WIN + VARSTR(NPNVaudioDeviceChangeDetails); +#endif + + default: return "???"; + } +} +#undef VARSTR + +inline bool IsPluginThread() +{ + MessageLoop* loop = MessageLoop::current(); + if (!loop) + return false; + return (loop->type() == MessageLoop::TYPE_UI); +} + +inline void AssertPluginThread() +{ + MOZ_RELEASE_ASSERT(IsPluginThread(), "Should be on the plugin's main thread!"); +} + +#define ENSURE_PLUGIN_THREAD(retval) \ + PR_BEGIN_MACRO \ + if (!IsPluginThread()) { \ + NS_WARNING("Not running on the plugin's main thread!"); \ + return (retval); \ + } \ + PR_END_MACRO + +#define ENSURE_PLUGIN_THREAD_VOID() \ + PR_BEGIN_MACRO \ + if (!IsPluginThread()) { \ + NS_WARNING("Not running on the plugin's main thread!"); \ + return; \ + } \ + PR_END_MACRO + +void DeferNPObjectLastRelease(const NPNetscapeFuncs* f, NPObject* o); +void DeferNPVariantLastRelease(const NPNetscapeFuncs* f, NPVariant* v); + +inline bool IsDrawingModelDirect(int16_t aModel) +{ + return aModel == NPDrawingModelAsyncBitmapSurface +#if defined(XP_WIN) + || aModel == NPDrawingModelAsyncWindowsDXGISurface +#endif + ; +} + +// in NPAPI, char* == nullptr is sometimes meaningful. the following is +// helper code for dealing with nullable nsCString's +inline nsCString +NullableString(const char* aString) +{ + if (!aString) { + return NullCString(); + } + return nsCString(aString); +} + +inline const char* +NullableStringGet(const nsCString& str) +{ + if (str.IsVoid()) + return nullptr; + + return str.get(); +} + +struct DeletingObjectEntry : public nsPtrHashKey<NPObject> +{ + explicit DeletingObjectEntry(const NPObject* key) + : nsPtrHashKey<NPObject>(key) + , mDeleted(false) + { } + + bool mDeleted; +}; + +} /* namespace plugins */ + +} /* namespace mozilla */ + +namespace IPC { + +template <> +struct ParamTraits<NPRect> +{ + typedef NPRect paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.top); + WriteParam(aMsg, aParam.left); + WriteParam(aMsg, aParam.bottom); + WriteParam(aMsg, aParam.right); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + uint16_t top, left, bottom, right; + if (ReadParam(aMsg, aIter, &top) && + ReadParam(aMsg, aIter, &left) && + ReadParam(aMsg, aIter, &bottom) && + ReadParam(aMsg, aIter, &right)) { + aResult->top = top; + aResult->left = left; + aResult->bottom = bottom; + aResult->right = right; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(StringPrintf(L"[%u, %u, %u, %u]", aParam.top, aParam.left, + aParam.bottom, aParam.right)); + } +}; + +template <> +struct ParamTraits<NPWindowType> +{ + typedef NPWindowType paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + aMsg->WriteInt16(int16_t(aParam)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + int16_t result; + if (aMsg->ReadInt16(aIter, &result)) { + *aResult = paramType(result); + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(StringPrintf(L"%d", int16_t(aParam))); + } +}; + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteWindow> +{ + typedef mozilla::plugins::NPRemoteWindow paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + aMsg->WriteUInt64(aParam.window); + WriteParam(aMsg, aParam.x); + WriteParam(aMsg, aParam.y); + WriteParam(aMsg, aParam.width); + WriteParam(aMsg, aParam.height); + WriteParam(aMsg, aParam.clipRect); + WriteParam(aMsg, aParam.type); +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + aMsg->WriteULong(aParam.visualID); + aMsg->WriteULong(aParam.colormap); +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + aMsg->WriteDouble(aParam.contentsScaleFactor); +#endif + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + uint64_t window; + int32_t x, y; + uint32_t width, height; + NPRect clipRect; + NPWindowType type; + if (!(aMsg->ReadUInt64(aIter, &window) && + ReadParam(aMsg, aIter, &x) && + ReadParam(aMsg, aIter, &y) && + ReadParam(aMsg, aIter, &width) && + ReadParam(aMsg, aIter, &height) && + ReadParam(aMsg, aIter, &clipRect) && + ReadParam(aMsg, aIter, &type))) + return false; + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + unsigned long visualID; + unsigned long colormap; + if (!(aMsg->ReadULong(aIter, &visualID) && + aMsg->ReadULong(aIter, &colormap))) + return false; +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + double contentsScaleFactor; + if (!aMsg->ReadDouble(aIter, &contentsScaleFactor)) + return false; +#endif + + aResult->window = window; + aResult->x = x; + aResult->y = y; + aResult->width = width; + aResult->height = height; + aResult->clipRect = clipRect; + aResult->type = type; +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + aResult->visualID = visualID; + aResult->colormap = colormap; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + aResult->contentsScaleFactor = contentsScaleFactor; +#endif + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(StringPrintf(L"[%u, %d, %d, %u, %u, %d", + (unsigned long)aParam.window, + aParam.x, aParam.y, aParam.width, + aParam.height, (long)aParam.type)); + } +}; + +#ifdef XP_MACOSX +template <> +struct ParamTraits<NPNSString*> +{ + typedef NPNSString* paramType; + + // Empty string writes a length of 0 and no buffer. + // We don't write a nullptr terminating character in buffers. + static void Write(Message* aMsg, const paramType& aParam) + { + CFStringRef cfString = (CFStringRef)aParam; + + // Write true if we have a string, false represents nullptr. + aMsg->WriteBool(!!cfString); + if (!cfString) { + return; + } + + long length = ::CFStringGetLength(cfString); + WriteParam(aMsg, length); + if (length == 0) { + return; + } + + // Attempt to get characters without any allocation/conversion. + if (::CFStringGetCharactersPtr(cfString)) { + aMsg->WriteBytes(::CFStringGetCharactersPtr(cfString), length * sizeof(UniChar)); + } else { + UniChar *buffer = (UniChar*)moz_xmalloc(length * sizeof(UniChar)); + ::CFStringGetCharacters(cfString, ::CFRangeMake(0, length), buffer); + aMsg->WriteBytes(buffer, length * sizeof(UniChar)); + free(buffer); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + bool haveString = false; + if (!aMsg->ReadBool(aIter, &haveString)) { + return false; + } + if (!haveString) { + *aResult = nullptr; + return true; + } + + long length; + if (!ReadParam(aMsg, aIter, &length)) { + return false; + } + + // Avoid integer multiplication overflow. + if (length > INT_MAX / static_cast<long>(sizeof(UniChar))) { + return false; + } + + auto chars = mozilla::MakeUnique<UniChar[]>(length); + if (length != 0) { + if (!aMsg->ReadBytesInto(aIter, chars.get(), length * sizeof(UniChar))) { + return false; + } + } + + *aResult = (NPNSString*)::CFStringCreateWithBytes(kCFAllocatorDefault, (UInt8*)chars.get(), + length * sizeof(UniChar), + kCFStringEncodingUTF16, false); + if (!*aResult) { + return false; + } + + return true; + } +}; +#endif + +#ifdef XP_MACOSX +template <> +struct ParamTraits<NSCursorInfo> +{ + typedef NSCursorInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + NSCursorInfo::Type type = aParam.GetType(); + + aMsg->WriteInt(type); + + nsPoint hotSpot = aParam.GetHotSpot(); + WriteParam(aMsg, hotSpot.x); + WriteParam(aMsg, hotSpot.y); + + uint32_t dataLength = aParam.GetCustomImageDataLength(); + WriteParam(aMsg, dataLength); + if (dataLength == 0) { + return; + } + + uint8_t* buffer = (uint8_t*)moz_xmalloc(dataLength); + memcpy(buffer, aParam.GetCustomImageData(), dataLength); + aMsg->WriteBytes(buffer, dataLength); + free(buffer); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + NSCursorInfo::Type type; + if (!aMsg->ReadInt(aIter, (int*)&type)) { + return false; + } + + nscoord hotSpotX, hotSpotY; + if (!ReadParam(aMsg, aIter, &hotSpotX) || + !ReadParam(aMsg, aIter, &hotSpotY)) { + return false; + } + + uint32_t dataLength; + if (!ReadParam(aMsg, aIter, &dataLength)) { + return false; + } + + auto data = mozilla::MakeUnique<uint8_t[]>(dataLength); + if (dataLength != 0) { + if (!aMsg->ReadBytesInto(aIter, data.get(), dataLength)) { + return false; + } + } + + aResult->SetType(type); + aResult->SetHotSpot(nsPoint(hotSpotX, hotSpotY)); + aResult->SetCustomImageData(data.get(), dataLength); + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + const char* typeName = aParam.GetTypeName(); + nsPoint hotSpot = aParam.GetHotSpot(); + int hotSpotX, hotSpotY; +#ifdef NS_COORD_IS_FLOAT + hotSpotX = rint(hotSpot.x); + hotSpotY = rint(hotSpot.y); +#else + hotSpotX = hotSpot.x; + hotSpotY = hotSpot.y; +#endif + uint32_t dataLength = aParam.GetCustomImageDataLength(); + uint8_t* data = aParam.GetCustomImageData(); + + aLog->append(StringPrintf(L"[%s, (%i %i), %u, %p]", + typeName, hotSpotX, hotSpotY, dataLength, data)); + } +}; +#else +template<> +struct ParamTraits<NSCursorInfo> +{ + typedef NSCursorInfo paramType; + static void Write(Message* aMsg, const paramType& aParam) { + NS_RUNTIMEABORT("NSCursorInfo isn't meaningful on this platform"); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) { + NS_RUNTIMEABORT("NSCursorInfo isn't meaningful on this platform"); + return false; + } +}; +#endif // #ifdef XP_MACOSX + +template <> +struct ParamTraits<mozilla::plugins::IPCByteRange> +{ + typedef mozilla::plugins::IPCByteRange paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.offset); + WriteParam(aMsg, aParam.length); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + paramType p; + if (ReadParam(aMsg, aIter, &p.offset) && + ReadParam(aMsg, aIter, &p.length)) { + *aResult = p; + return true; + } + return false; + } +}; + +template <> +struct ParamTraits<NPNVariable> +{ + typedef NPNVariable paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, int(aParam)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + int intval; + if (ReadParam(aMsg, aIter, &intval)) { + *aResult = paramType(intval); + return true; + } + return false; + } +}; + +template<> +struct ParamTraits<NPNURLVariable> +{ + typedef NPNURLVariable paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, int(aParam)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + int intval; + if (ReadParam(aMsg, aIter, &intval)) { + switch (intval) { + case NPNURLVCookie: + case NPNURLVProxy: + *aResult = paramType(intval); + return true; + } + } + return false; + } +}; + + +template<> +struct ParamTraits<NPCoordinateSpace> +{ + typedef NPCoordinateSpace paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, int32_t(aParam)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + int32_t intval; + if (ReadParam(aMsg, aIter, &intval)) { + switch (intval) { + case NPCoordinateSpacePlugin: + case NPCoordinateSpaceWindow: + case NPCoordinateSpaceFlippedWindow: + case NPCoordinateSpaceScreen: + case NPCoordinateSpaceFlippedScreen: + *aResult = paramType(intval); + return true; + } + } + return false; + } +}; + +template <> +struct ParamTraits<mozilla::plugins::NPAudioDeviceChangeDetailsIPC> +{ + typedef mozilla::plugins::NPAudioDeviceChangeDetailsIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.flow); + WriteParam(aMsg, aParam.role); + WriteParam(aMsg, aParam.defaultDevice); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + int32_t flow, role; + std::wstring defaultDevice; + if (ReadParam(aMsg, aIter, &flow) && + ReadParam(aMsg, aIter, &role) && + ReadParam(aMsg, aIter, &defaultDevice)) { + aResult->flow = flow; + aResult->role = role; + aResult->defaultDevice = defaultDevice; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(StringPrintf(L"[%d, %d, %S]", aParam.flow, aParam.role, + aParam.defaultDevice.c_str())); + } +}; + +} /* namespace IPC */ + + +// Serializing NPEvents is completely platform-specific and can be rather +// intricate depending on the platform. So for readability we split it +// into separate files and have the only macro crud live here. +// +// NB: these guards are based on those where struct NPEvent is defined +// in npapi.h. They should be kept in sync. +#if defined(XP_MACOSX) +# include "mozilla/plugins/NPEventOSX.h" +#elif defined(XP_WIN) +# include "mozilla/plugins/NPEventWindows.h" +#elif defined(ANDROID) +# include "mozilla/plugins/NPEventAndroid.h" +#elif defined(XP_UNIX) +# include "mozilla/plugins/NPEventUnix.h" +#else +# error Unsupported platform +#endif + +#endif /* DOM_PLUGINS_PLUGINMESSAGEUTILS_H */ diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp new file mode 100644 index 000000000..84dc7c71f --- /dev/null +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -0,0 +1,2681 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set 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/. */ + +#include "mozilla/plugins/PluginModuleChild.h" + +/* This must occur *after* plugins/PluginModuleChild.h to avoid typedefs conflicts. */ +#include "mozilla/ArrayUtils.h" + +#include "mozilla/ipc/MessageChannel.h" + +#ifdef MOZ_WIDGET_GTK +#include <gtk/gtk.h> +#endif + +#include "nsIFile.h" + +#include "pratom.h" +#include "nsDebug.h" +#include "nsCOMPtr.h" +#include "nsPluginsDir.h" +#include "nsXULAppAPI.h" + +#ifdef MOZ_X11 +# include "nsX11ErrorHandler.h" +# include "mozilla/X11Util.h" +#endif +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/plugins/PluginInstanceChild.h" +#include "mozilla/plugins/StreamNotifyChild.h" +#include "mozilla/plugins/BrowserStreamChild.h" +#include "mozilla/plugins/PluginStreamChild.h" +#include "mozilla/dom/CrashReporterChild.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" + +#include "nsNPAPIPlugin.h" + +#ifdef XP_WIN +#include "nsWindowsDllInterceptor.h" +#include "mozilla/widget/AudioSession.h" +#include "WinUtils.h" +#include <knownfolders.h> +#endif + +#ifdef MOZ_WIDGET_COCOA +#include "PluginInterposeOSX.h" +#include "PluginUtilsOSX.h" +#endif + +#include "GeckoProfiler.h" + +using namespace mozilla; +using namespace mozilla::ipc; +using namespace mozilla::plugins; +using namespace mozilla::widget; +using mozilla::dom::CrashReporterChild; +using mozilla::dom::PCrashReporterChild; + +#if defined(XP_WIN) +const wchar_t * kFlashFullscreenClass = L"ShockwaveFlashFullScreen"; +const wchar_t * kMozillaWindowClass = L"MozillaWindowClass"; +#endif + +namespace { +// see PluginModuleChild::GetChrome() +PluginModuleChild* gChromeInstance = nullptr; +} // namespace + +#ifdef XP_WIN +// Hooking CreateFileW for protected-mode magic +static WindowsDllInterceptor sKernel32Intercept; +typedef HANDLE (WINAPI *CreateFileWPtr)(LPCWSTR fname, DWORD access, + DWORD share, + LPSECURITY_ATTRIBUTES security, + DWORD creation, DWORD flags, + HANDLE ftemplate); +static CreateFileWPtr sCreateFileWStub = nullptr; +typedef HANDLE (WINAPI *CreateFileAPtr)(LPCSTR fname, DWORD access, + DWORD share, + LPSECURITY_ATTRIBUTES security, + DWORD creation, DWORD flags, + HANDLE ftemplate); +static CreateFileAPtr sCreateFileAStub = nullptr; + +// Used with fix for flash fullscreen window loosing focus. +static bool gDelayFlashFocusReplyUntilEval = false; +// Used to fix GetWindowInfo problems with internal flash settings dialogs +static WindowsDllInterceptor sUser32Intercept; +typedef BOOL (WINAPI *GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi); +static GetWindowInfoPtr sGetWindowInfoPtrStub = nullptr; +static HWND sBrowserHwnd = nullptr; +// sandbox process doesn't get current key states. So we need get it on chrome. +typedef SHORT (WINAPI *GetKeyStatePtr)(int); +static GetKeyStatePtr sGetKeyStatePtrStub = nullptr; +#endif + +/* static */ +PluginModuleChild* +PluginModuleChild::CreateForContentProcess(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherPid) +{ + PluginModuleChild* child = new PluginModuleChild(false); + + if (!child->InitForContent(aOtherPid, XRE_GetIOMessageLoop(), aTransport)) { + return nullptr; + } + + return child; +} + +PluginModuleChild::PluginModuleChild(bool aIsChrome) + : mLibrary(0) + , mPluginFilename("") + , mQuirks(QUIRKS_NOT_INITIALIZED) + , mIsChrome(aIsChrome) + , mHasShutdown(false) + , mTransport(nullptr) + , mShutdownFunc(0) + , mInitializeFunc(0) +#if defined(OS_WIN) || defined(OS_MACOSX) + , mGetEntryPointsFunc(0) +#elif defined(MOZ_WIDGET_GTK) + , mNestedLoopTimerId(0) +#endif +#ifdef OS_WIN + , mNestedEventHook(nullptr) + , mGlobalCallWndProcHook(nullptr) + , mAsyncRenderSupport(false) +#endif +{ + memset(&mFunctions, 0, sizeof(mFunctions)); + if (mIsChrome) { + MOZ_ASSERT(!gChromeInstance); + gChromeInstance = this; + } + +#ifdef XP_MACOSX + if (aIsChrome) { + mac_plugin_interposing::child::SetUpCocoaInterposing(); + } +#endif +} + +PluginModuleChild::~PluginModuleChild() +{ + if (mIsChrome) { + MOZ_ASSERT(gChromeInstance == this); + + // We don't unload the plugin library in case it uses atexit handlers or + // other similar hooks. + + DeinitGraphics(); + PluginScriptableObjectChild::ClearIdentifiers(); + + gChromeInstance = nullptr; + } +} + +// static +PluginModuleChild* +PluginModuleChild::GetChrome() +{ + // A special PluginModuleChild instance that talks to the chrome process + // during startup and shutdown. Synchronous messages to or from this actor + // should be avoided because they may lead to hangs. + MOZ_ASSERT(gChromeInstance); + return gChromeInstance; +} + +bool +PluginModuleChild::CommonInit(base::ProcessId aParentPid, + MessageLoop* aIOLoop, + IPC::Channel* aChannel) +{ + PLUGIN_LOG_DEBUG_METHOD; + + // Request Windows message deferral behavior on our channel. This + // applies to the top level and all sub plugin protocols since they + // all share the same channel. + // Bug 1090573 - Don't do this for connections to content processes. + GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); + + if (!Open(aChannel, aParentPid, aIOLoop)) { + return false; + } + + memset((void*) &mFunctions, 0, sizeof(mFunctions)); + mFunctions.size = sizeof(mFunctions); + mFunctions.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + + return true; +} + +bool +PluginModuleChild::InitForContent(base::ProcessId aParentPid, + MessageLoop* aIOLoop, + IPC::Channel* aChannel) +{ + if (!CommonInit(aParentPid, aIOLoop, aChannel)) { + return false; + } + + mTransport = aChannel; + + mLibrary = GetChrome()->mLibrary; + mFunctions = GetChrome()->mFunctions; + + return true; +} + +bool +PluginModuleChild::RecvDisableFlashProtectedMode() +{ + MOZ_ASSERT(mIsChrome); +#ifdef XP_WIN + HookProtectedMode(); +#else + MOZ_ASSERT(false, "Should not be called"); +#endif + return true; +} + +bool +PluginModuleChild::InitForChrome(const std::string& aPluginFilename, + base::ProcessId aParentPid, + MessageLoop* aIOLoop, + IPC::Channel* aChannel) +{ + NS_ASSERTION(aChannel, "need a channel"); + + if (!InitGraphics()) + return false; + + mPluginFilename = aPluginFilename.c_str(); + nsCOMPtr<nsIFile> localFile; + NS_NewLocalFile(NS_ConvertUTF8toUTF16(mPluginFilename), + true, + getter_AddRefs(localFile)); + + if (!localFile) + return false; + + bool exists; + localFile->Exists(&exists); + NS_ASSERTION(exists, "plugin file ain't there"); + + nsPluginFile pluginFile(localFile); + + nsPluginInfo info = nsPluginInfo(); + if (NS_FAILED(pluginFile.GetPluginInfo(info, &mLibrary))) { + return false; + } + +#if defined(XP_WIN) + // XXX quirks isn't initialized yet + mAsyncRenderSupport = info.fSupportsAsyncRender; +#endif +#if defined(MOZ_X11) + NS_NAMED_LITERAL_CSTRING(flash10Head, "Shockwave Flash 10."); + if (StringBeginsWith(nsDependentCString(info.fDescription), flash10Head)) { + AddQuirk(QUIRK_FLASH_EXPOSE_COORD_TRANSLATION); + } +#endif +#if defined(XP_MACOSX) + const char* namePrefix = "Plugin Content"; + char nameBuffer[80]; + SprintfLiteral(nameBuffer, "%s (%s)", namePrefix, info.fName); + mozilla::plugins::PluginUtilsOSX::SetProcessName(nameBuffer); +#endif + pluginFile.FreePluginInfo(info); +#if defined(MOZ_X11) || defined(XP_MACOSX) + if (!mLibrary) +#endif + { + nsresult rv = pluginFile.LoadPlugin(&mLibrary); + if (NS_FAILED(rv)) + return false; + } + NS_ASSERTION(mLibrary, "couldn't open shared object"); + + if (!CommonInit(aParentPid, aIOLoop, aChannel)) { + return false; + } + + GetIPCChannel()->SetAbortOnError(true); + + // TODO: use PluginPRLibrary here + +#if defined(OS_LINUX) || defined(OS_BSD) + mShutdownFunc = + (NP_PLUGINSHUTDOWN) PR_FindFunctionSymbol(mLibrary, "NP_Shutdown"); + + // create the new plugin handler + + mInitializeFunc = + (NP_PLUGINUNIXINIT) PR_FindFunctionSymbol(mLibrary, "NP_Initialize"); + NS_ASSERTION(mInitializeFunc, "couldn't find NP_Initialize()"); + +#elif defined(OS_WIN) || defined(OS_MACOSX) + mShutdownFunc = + (NP_PLUGINSHUTDOWN)PR_FindFunctionSymbol(mLibrary, "NP_Shutdown"); + + mGetEntryPointsFunc = + (NP_GETENTRYPOINTS)PR_FindSymbol(mLibrary, "NP_GetEntryPoints"); + NS_ENSURE_TRUE(mGetEntryPointsFunc, false); + + mInitializeFunc = + (NP_PLUGININIT)PR_FindFunctionSymbol(mLibrary, "NP_Initialize"); + NS_ENSURE_TRUE(mInitializeFunc, false); +#else + +# error Please copy the initialization code from nsNPAPIPlugin.cpp + +#endif + + return true; +} + +#if defined(MOZ_WIDGET_GTK) + +typedef void (*GObjectDisposeFn)(GObject*); +typedef gboolean (*GtkWidgetScrollEventFn)(GtkWidget*, GdkEventScroll*); +typedef void (*GtkPlugEmbeddedFn)(GtkPlug*); + +static GObjectDisposeFn real_gtk_plug_dispose; +static GtkPlugEmbeddedFn real_gtk_plug_embedded; + +static void +undo_bogus_unref(gpointer data, GObject* object, gboolean is_last_ref) { + if (!is_last_ref) // recursion in g_object_ref + return; + + g_object_ref(object); +} + +static void +wrap_gtk_plug_dispose(GObject* object) { + // Work around Flash Player bug described in bug 538914. + // + // This function is called during gtk_widget_destroy and/or before + // the object's last reference is removed. A reference to the + // object is held during the call so the ref count should not drop + // to zero. However, Flash Player tries to destroy the GtkPlug + // using g_object_unref instead of gtk_widget_destroy. The + // reference that Flash is removing actually belongs to the + // GtkPlug. During real_gtk_plug_dispose, the GtkPlug removes its + // reference. + // + // A toggle ref is added to prevent premature deletion of the object + // caused by Flash Player's extra unref, and to detect when there are + // unexpectedly no other references. + g_object_add_toggle_ref(object, undo_bogus_unref, nullptr); + (*real_gtk_plug_dispose)(object); + g_object_remove_toggle_ref(object, undo_bogus_unref, nullptr); +} + +static gboolean +gtk_plug_scroll_event(GtkWidget *widget, GdkEventScroll *gdk_event) +{ + if (!gtk_widget_is_toplevel(widget)) // in same process as its GtkSocket + return FALSE; // event not handled; propagate to GtkSocket + + GdkWindow* socket_window = gtk_plug_get_socket_window(GTK_PLUG(widget)); + if (!socket_window) + return FALSE; + + // Propagate the event to the embedder. + GdkScreen* screen = gdk_window_get_screen(socket_window); + GdkWindow* plug_window = gtk_widget_get_window(widget); + GdkWindow* event_window = gdk_event->window; + gint x = gdk_event->x; + gint y = gdk_event->y; + unsigned int button; + unsigned int button_mask = 0; + XEvent xevent; + Display* dpy = GDK_WINDOW_XDISPLAY(socket_window); + + /* Translate the event coordinates to the plug window, + * which should be aligned with the socket window. + */ + while (event_window != plug_window) + { + gint dx, dy; + + gdk_window_get_position(event_window, &dx, &dy); + x += dx; + y += dy; + + event_window = gdk_window_get_parent(event_window); + if (!event_window) + return FALSE; + } + + switch (gdk_event->direction) { + case GDK_SCROLL_UP: + button = 4; + button_mask = Button4Mask; + break; + case GDK_SCROLL_DOWN: + button = 5; + button_mask = Button5Mask; + break; + case GDK_SCROLL_LEFT: + button = 6; + break; + case GDK_SCROLL_RIGHT: + button = 7; + break; + default: + return FALSE; // unknown GdkScrollDirection + } + + memset(&xevent, 0, sizeof(xevent)); + xevent.xbutton.type = ButtonPress; + xevent.xbutton.window = gdk_x11_window_get_xid(socket_window); + xevent.xbutton.root = gdk_x11_window_get_xid(gdk_screen_get_root_window(screen)); + xevent.xbutton.subwindow = gdk_x11_window_get_xid(plug_window); + xevent.xbutton.time = gdk_event->time; + xevent.xbutton.x = x; + xevent.xbutton.y = y; + xevent.xbutton.x_root = gdk_event->x_root; + xevent.xbutton.y_root = gdk_event->y_root; + xevent.xbutton.state = gdk_event->state; + xevent.xbutton.button = button; + xevent.xbutton.same_screen = True; + + gdk_error_trap_push(); + + XSendEvent(dpy, xevent.xbutton.window, + True, ButtonPressMask, &xevent); + + xevent.xbutton.type = ButtonRelease; + xevent.xbutton.state |= button_mask; + XSendEvent(dpy, xevent.xbutton.window, + True, ButtonReleaseMask, &xevent); + + gdk_display_sync(gdk_screen_get_display(screen)); + gdk_error_trap_pop(); + + return TRUE; // event handled +} + +static void +wrap_gtk_plug_embedded(GtkPlug* plug) { + GdkWindow* socket_window = gtk_plug_get_socket_window(plug); + if (socket_window) { + if (gtk_check_version(2,18,7) != nullptr // older + && g_object_get_data(G_OBJECT(socket_window), + "moz-existed-before-set-window")) { + // Add missing reference for + // https://bugzilla.gnome.org/show_bug.cgi?id=607061 + g_object_ref(socket_window); + } + + // Ensure the window exists to make this GtkPlug behave like an + // in-process GtkPlug for Flash Player. (Bugs 561308 and 539138). + gtk_widget_realize(GTK_WIDGET(plug)); + } + + if (*real_gtk_plug_embedded) { + (*real_gtk_plug_embedded)(plug); + } +} + +// +// The next four constants are knobs that can be tuned. They trade +// off potential UI lag from delayed event processing with CPU time. +// +static const gint kNestedLoopDetectorPriority = G_PRIORITY_HIGH_IDLE; +// 90ms so that we can hopefully break livelocks before the user +// notices UI lag (100ms) +static const guint kNestedLoopDetectorIntervalMs = 90; + +static const gint kBrowserEventPriority = G_PRIORITY_HIGH_IDLE; +static const guint kBrowserEventIntervalMs = 10; + +// static +gboolean +PluginModuleChild::DetectNestedEventLoop(gpointer data) +{ + PluginModuleChild* pmc = static_cast<PluginModuleChild*>(data); + + MOZ_ASSERT(0 != pmc->mNestedLoopTimerId, + "callback after descheduling"); + MOZ_ASSERT(pmc->mTopLoopDepth < g_main_depth(), + "not canceled before returning to main event loop!"); + + PLUGIN_LOG_DEBUG(("Detected nested glib event loop")); + + // just detected a nested loop; start a timer that will + // periodically rpc-call back into the browser and process some + // events + pmc->mNestedLoopTimerId = + g_timeout_add_full(kBrowserEventPriority, + kBrowserEventIntervalMs, + PluginModuleChild::ProcessBrowserEvents, + data, + nullptr); + // cancel the nested-loop detection timer + return FALSE; +} + +// static +gboolean +PluginModuleChild::ProcessBrowserEvents(gpointer data) +{ + PluginModuleChild* pmc = static_cast<PluginModuleChild*>(data); + + MOZ_ASSERT(pmc->mTopLoopDepth < g_main_depth(), + "not canceled before returning to main event loop!"); + + pmc->CallProcessSomeEvents(); + + return TRUE; +} + +void +PluginModuleChild::EnteredCxxStack() +{ + MOZ_ASSERT(0 == mNestedLoopTimerId, + "previous timer not descheduled"); + + mNestedLoopTimerId = + g_timeout_add_full(kNestedLoopDetectorPriority, + kNestedLoopDetectorIntervalMs, + PluginModuleChild::DetectNestedEventLoop, + this, + nullptr); + +#ifdef DEBUG + mTopLoopDepth = g_main_depth(); +#endif +} + +void +PluginModuleChild::ExitedCxxStack() +{ + MOZ_ASSERT(0 < mNestedLoopTimerId, + "nested loop timeout not scheduled"); + + g_source_remove(mNestedLoopTimerId); + mNestedLoopTimerId = 0; +} + +#endif + +bool +PluginModuleChild::RecvSetParentHangTimeout(const uint32_t& aSeconds) +{ +#ifdef XP_WIN + SetReplyTimeoutMs(((aSeconds > 0) ? (1000 * aSeconds) : 0)); +#endif + return true; +} + +bool +PluginModuleChild::ShouldContinueFromReplyTimeout() +{ +#ifdef XP_WIN + NS_RUNTIMEABORT("terminating child process"); +#endif + return true; +} + +bool +PluginModuleChild::InitGraphics() +{ +#if defined(MOZ_WIDGET_GTK) + // Work around plugins that don't interact well with GDK + // client-side windows. + PR_SetEnv("GDK_NATIVE_WINDOWS=1"); + + gtk_init(0, 0); + + // GtkPlug is a static class so will leak anyway but this ref makes sure. + gpointer gtk_plug_class = g_type_class_ref(GTK_TYPE_PLUG); + + // The dispose method is a good place to hook into the destruction process + // because the reference count should be 1 the last time dispose is + // called. (Toggle references wouldn't detect if the reference count + // might be higher.) + GObjectDisposeFn* dispose = &G_OBJECT_CLASS(gtk_plug_class)->dispose; + MOZ_ASSERT(*dispose != wrap_gtk_plug_dispose, + "InitGraphics called twice"); + real_gtk_plug_dispose = *dispose; + *dispose = wrap_gtk_plug_dispose; + + // If we ever stop setting GDK_NATIVE_WINDOWS, we'll also need to + // gtk_widget_add_events GDK_SCROLL_MASK or GDK client-side windows will + // not tell us about the scroll events that it intercepts. With native + // windows, this is called when GDK intercepts the events; if GDK doesn't + // intercept the events, then the X server will instead send them directly + // to an ancestor (embedder) window. + GtkWidgetScrollEventFn* scroll_event = + >K_WIDGET_CLASS(gtk_plug_class)->scroll_event; + if (!*scroll_event) { + *scroll_event = gtk_plug_scroll_event; + } + + GtkPlugEmbeddedFn* embedded = >K_PLUG_CLASS(gtk_plug_class)->embedded; + real_gtk_plug_embedded = *embedded; + *embedded = wrap_gtk_plug_embedded; + +#else + // may not be necessary on all platforms +#endif +#ifdef MOZ_X11 + // Do this after initializing GDK, or GDK will install its own handler. + InstallX11ErrorHandler(); +#endif + return true; +} + +void +PluginModuleChild::DeinitGraphics() +{ +#if defined(MOZ_X11) && defined(NS_FREE_PERMANENT_DATA) + // We free some data off of XDisplay close hooks, ensure they're + // run. Closing the display is pretty scary, so we only do it to + // silence leak checkers. + XCloseDisplay(DefaultXDisplay()); +#endif +} + +NPError +PluginModuleChild::NP_Shutdown() +{ + AssertPluginThread(); + MOZ_ASSERT(mIsChrome); + + if (mHasShutdown) { + return NPERR_NO_ERROR; + } + +#if defined XP_WIN + mozilla::widget::StopAudioSession(); +#endif + + // the PluginModuleParent shuts down this process after this interrupt + // call pops off its stack + + NPError rv = mShutdownFunc ? mShutdownFunc() : NPERR_NO_ERROR; + + // weakly guard against re-entry after NP_Shutdown + memset(&mFunctions, 0, sizeof(mFunctions)); + +#ifdef OS_WIN + ResetEventHooks(); +#endif + + GetIPCChannel()->SetAbortOnError(false); + + mHasShutdown = true; + + return rv; +} + +bool +PluginModuleChild::AnswerNP_Shutdown(NPError *rv) +{ + *rv = NP_Shutdown(); + return true; +} + +bool +PluginModuleChild::AnswerOptionalFunctionsSupported(bool *aURLRedirectNotify, + bool *aClearSiteData, + bool *aGetSitesWithData) +{ + *aURLRedirectNotify = !!mFunctions.urlredirectnotify; + *aClearSiteData = !!mFunctions.clearsitedata; + *aGetSitesWithData = !!mFunctions.getsiteswithdata; + return true; +} + +bool +PluginModuleChild::RecvNPP_ClearSiteData(const nsCString& aSite, + const uint64_t& aFlags, + const uint64_t& aMaxAge, + const uint64_t& aCallbackId) +{ + NPError result = + mFunctions.clearsitedata(NullableStringGet(aSite), aFlags, aMaxAge); + SendReturnClearSiteData(result, aCallbackId); + return true; +} + +bool +PluginModuleChild::RecvNPP_GetSitesWithData(const uint64_t& aCallbackId) +{ + char** result = mFunctions.getsiteswithdata(); + InfallibleTArray<nsCString> array; + if (!result) { + SendReturnSitesWithData(array, aCallbackId); + return true; + } + char** iterator = result; + while (*iterator) { + array.AppendElement(*iterator); + free(*iterator); + ++iterator; + } + SendReturnSitesWithData(array, aCallbackId); + free(result); + return true; +} + +bool +PluginModuleChild::RecvSetAudioSessionData(const nsID& aId, + const nsString& aDisplayName, + const nsString& aIconPath) +{ +#if !defined XP_WIN + NS_RUNTIMEABORT("Not Reached!"); + return false; +#else + nsresult rv = mozilla::widget::RecvAudioSessionData(aId, aDisplayName, aIconPath); + NS_ENSURE_SUCCESS(rv, true); // Bail early if this fails + + // Ignore failures here; we can't really do anything about them + mozilla::widget::StartAudioSession(); + return true; +#endif +} + +PPluginModuleChild* +PluginModuleChild::AllocPPluginModuleChild(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherPid) +{ + return PluginModuleChild::CreateForContentProcess(aTransport, aOtherPid); +} + +PCrashReporterChild* +PluginModuleChild::AllocPCrashReporterChild(mozilla::dom::NativeThreadId* id, + uint32_t* processType) +{ + return new CrashReporterChild(); +} + +bool +PluginModuleChild::DeallocPCrashReporterChild(PCrashReporterChild* actor) +{ + delete actor; + return true; +} + +bool +PluginModuleChild::AnswerPCrashReporterConstructor( + PCrashReporterChild* actor, + mozilla::dom::NativeThreadId* id, + uint32_t* processType) +{ +#ifdef MOZ_CRASHREPORTER + *id = CrashReporter::CurrentThreadId(); + *processType = XRE_GetProcessType(); +#endif + return true; +} + +void +PluginModuleChild::ActorDestroy(ActorDestroyReason why) +{ + if (!mIsChrome) { + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome(); + if (chromeInstance) { + chromeInstance->SendNotifyContentModuleDestroyed(); + } + + // Destroy ourselves once we finish other teardown activities. + RefPtr<DeleteTask<PluginModuleChild>> task = + new DeleteTask<PluginModuleChild>(this); + MessageLoop::current()->PostTask(task.forget()); + return; + } + + if (AbnormalShutdown == why) { + NS_WARNING("shutting down early because of crash!"); + ProcessChild::QuickExit(); + } + + if (!mHasShutdown) { + MOZ_ASSERT(gChromeInstance == this); + NP_Shutdown(); + } + + // doesn't matter why we're being destroyed; it's up to us to + // initiate (clean) shutdown + XRE_ShutdownChildProcess(); +} + +void +PluginModuleChild::CleanUp() +{ +} + +const char* +PluginModuleChild::GetUserAgent() +{ + return NullableStringGet(Settings().userAgent()); +} + +//----------------------------------------------------------------------------- +// FIXME/cjones: just getting this out of the way for the moment ... + +namespace mozilla { +namespace plugins { +namespace child { + +static NPError +_requestread(NPStream *pstream, NPByteRange *rangeList); + +static NPError +_geturlnotify(NPP aNPP, const char* relativeURL, const char* target, + void* notifyData); + +static NPError +_getvalue(NPP aNPP, NPNVariable variable, void *r_value); + +static NPError +_setvalue(NPP aNPP, NPPVariable variable, void *r_value); + +static NPError +_geturl(NPP aNPP, const char* relativeURL, const char* target); + +static NPError +_posturlnotify(NPP aNPP, const char* relativeURL, const char *target, + uint32_t len, const char *buf, NPBool file, void* notifyData); + +static NPError +_posturl(NPP aNPP, const char* relativeURL, const char *target, uint32_t len, + const char *buf, NPBool file); + +static NPError +_newstream(NPP aNPP, NPMIMEType type, const char* window, NPStream** pstream); + +static int32_t +_write(NPP aNPP, NPStream *pstream, int32_t len, void *buffer); + +static NPError +_destroystream(NPP aNPP, NPStream *pstream, NPError reason); + +static void +_status(NPP aNPP, const char *message); + +static void +_memfree (void *ptr); + +static uint32_t +_memflush(uint32_t size); + +static void +_reloadplugins(NPBool reloadPages); + +static void +_invalidaterect(NPP aNPP, NPRect *invalidRect); + +static void +_invalidateregion(NPP aNPP, NPRegion invalidRegion); + +static void +_forceredraw(NPP aNPP); + +static const char* +_useragent(NPP aNPP); + +static void* +_memalloc (uint32_t size); + +// Deprecated entry points for the old Java plugin. +static void* /* OJI type: JRIEnv* */ +_getjavaenv(void); + +// Deprecated entry points for the old Java plugin. +static void* /* OJI type: jref */ +_getjavapeer(NPP aNPP); + +static bool +_invoke(NPP aNPP, NPObject* npobj, NPIdentifier method, const NPVariant *args, + uint32_t argCount, NPVariant *result); + +static bool +_invokedefault(NPP aNPP, NPObject* npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result); + +static bool +_evaluate(NPP aNPP, NPObject* npobj, NPString *script, NPVariant *result); + +static bool +_getproperty(NPP aNPP, NPObject* npobj, NPIdentifier property, + NPVariant *result); + +static bool +_setproperty(NPP aNPP, NPObject* npobj, NPIdentifier property, + const NPVariant *value); + +static bool +_removeproperty(NPP aNPP, NPObject* npobj, NPIdentifier property); + +static bool +_hasproperty(NPP aNPP, NPObject* npobj, NPIdentifier propertyName); + +static bool +_hasmethod(NPP aNPP, NPObject* npobj, NPIdentifier methodName); + +static bool +_enumerate(NPP aNPP, NPObject *npobj, NPIdentifier **identifier, + uint32_t *count); + +static bool +_construct(NPP aNPP, NPObject* npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result); + +static void +_releasevariantvalue(NPVariant *variant); + +static void +_setexception(NPObject* npobj, const NPUTF8 *message); + +static void +_pushpopupsenabledstate(NPP aNPP, NPBool enabled); + +static void +_poppopupsenabledstate(NPP aNPP); + +static void +_pluginthreadasynccall(NPP instance, PluginThreadCallback func, + void *userData); + +static NPError +_getvalueforurl(NPP npp, NPNURLVariable variable, const char *url, + char **value, uint32_t *len); + +static NPError +_setvalueforurl(NPP npp, NPNURLVariable variable, const char *url, + const char *value, uint32_t len); + +static NPError +_getauthenticationinfo(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); + +static uint32_t +_scheduletimer(NPP instance, uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)); + +static void +_unscheduletimer(NPP instance, uint32_t timerID); + +static NPError +_popupcontextmenu(NPP instance, NPMenu* menu); + +static NPBool +_convertpoint(NPP instance, + double sourceX, double sourceY, NPCoordinateSpace sourceSpace, + double *destX, double *destY, NPCoordinateSpace destSpace); + +static void +_urlredirectresponse(NPP instance, void* notifyData, NPBool allow); + +static NPError +_initasyncsurface(NPP instance, NPSize *size, + NPImageFormat format, void *initData, + NPAsyncSurface *surface); + +static NPError +_finalizeasyncsurface(NPP instance, NPAsyncSurface *surface); + +static void +_setcurrentasyncsurface(NPP instance, NPAsyncSurface *surface, NPRect *changed); + +} /* namespace child */ +} /* namespace plugins */ +} /* namespace mozilla */ + +const NPNetscapeFuncs PluginModuleChild::sBrowserFuncs = { + sizeof(sBrowserFuncs), + (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR, + mozilla::plugins::child::_geturl, + mozilla::plugins::child::_posturl, + mozilla::plugins::child::_requestread, + mozilla::plugins::child::_newstream, + mozilla::plugins::child::_write, + mozilla::plugins::child::_destroystream, + mozilla::plugins::child::_status, + mozilla::plugins::child::_useragent, + mozilla::plugins::child::_memalloc, + mozilla::plugins::child::_memfree, + mozilla::plugins::child::_memflush, + mozilla::plugins::child::_reloadplugins, + mozilla::plugins::child::_getjavaenv, + mozilla::plugins::child::_getjavapeer, + mozilla::plugins::child::_geturlnotify, + mozilla::plugins::child::_posturlnotify, + mozilla::plugins::child::_getvalue, + mozilla::plugins::child::_setvalue, + mozilla::plugins::child::_invalidaterect, + mozilla::plugins::child::_invalidateregion, + mozilla::plugins::child::_forceredraw, + PluginModuleChild::NPN_GetStringIdentifier, + PluginModuleChild::NPN_GetStringIdentifiers, + PluginModuleChild::NPN_GetIntIdentifier, + PluginModuleChild::NPN_IdentifierIsString, + PluginModuleChild::NPN_UTF8FromIdentifier, + PluginModuleChild::NPN_IntFromIdentifier, + PluginModuleChild::NPN_CreateObject, + PluginModuleChild::NPN_RetainObject, + PluginModuleChild::NPN_ReleaseObject, + mozilla::plugins::child::_invoke, + mozilla::plugins::child::_invokedefault, + mozilla::plugins::child::_evaluate, + mozilla::plugins::child::_getproperty, + mozilla::plugins::child::_setproperty, + mozilla::plugins::child::_removeproperty, + mozilla::plugins::child::_hasproperty, + mozilla::plugins::child::_hasmethod, + mozilla::plugins::child::_releasevariantvalue, + mozilla::plugins::child::_setexception, + mozilla::plugins::child::_pushpopupsenabledstate, + mozilla::plugins::child::_poppopupsenabledstate, + mozilla::plugins::child::_enumerate, + mozilla::plugins::child::_pluginthreadasynccall, + mozilla::plugins::child::_construct, + mozilla::plugins::child::_getvalueforurl, + mozilla::plugins::child::_setvalueforurl, + mozilla::plugins::child::_getauthenticationinfo, + mozilla::plugins::child::_scheduletimer, + mozilla::plugins::child::_unscheduletimer, + mozilla::plugins::child::_popupcontextmenu, + mozilla::plugins::child::_convertpoint, + nullptr, // handleevent, unimplemented + nullptr, // unfocusinstance, unimplemented + mozilla::plugins::child::_urlredirectresponse, + mozilla::plugins::child::_initasyncsurface, + mozilla::plugins::child::_finalizeasyncsurface, + mozilla::plugins::child::_setcurrentasyncsurface, +}; + +PluginInstanceChild* +InstCast(NPP aNPP) +{ + MOZ_ASSERT(!!(aNPP->ndata), "nil instance"); + return static_cast<PluginInstanceChild*>(aNPP->ndata); +} + +namespace mozilla { +namespace plugins { +namespace child { + +NPError +_requestread(NPStream* aStream, + NPByteRange* aRangeList) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + BrowserStreamChild* bs = + static_cast<BrowserStreamChild*>(static_cast<AStream*>(aStream->ndata)); + bs->EnsureCorrectStream(aStream); + return bs->NPN_RequestRead(aRangeList); +} + +NPError +_geturlnotify(NPP aNPP, + const char* aRelativeURL, + const char* aTarget, + void* aNotifyData) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + if (!aNPP) // nullptr check for nspluginwrapper (bug 561690) + return NPERR_INVALID_INSTANCE_ERROR; + + nsCString url = NullableString(aRelativeURL); + StreamNotifyChild* sn = new StreamNotifyChild(url); + + NPError err; + InstCast(aNPP)->CallPStreamNotifyConstructor( + sn, url, NullableString(aTarget), false, nsCString(), false, &err); + + if (NPERR_NO_ERROR == err) { + // If NPN_PostURLNotify fails, the parent will immediately send us + // a PStreamNotifyDestructor, which should not call NPP_URLNotify. + sn->SetValid(aNotifyData); + } + + return err; +} + +NPError +_getvalue(NPP aNPP, + NPNVariable aVariable, + void* aValue) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + switch (aVariable) { + // Copied from nsNPAPIPlugin.cpp + case NPNVToolkit: +#if defined(MOZ_WIDGET_GTK) + *static_cast<NPNToolkitType*>(aValue) = NPNVGtk2; + return NPERR_NO_ERROR; +#endif + return NPERR_GENERIC_ERROR; + + case NPNVjavascriptEnabledBool: + *(NPBool*)aValue = PluginModuleChild::GetChrome()->Settings().javascriptEnabled(); + return NPERR_NO_ERROR; + case NPNVasdEnabledBool: + *(NPBool*)aValue = PluginModuleChild::GetChrome()->Settings().asdEnabled(); + return NPERR_NO_ERROR; + case NPNVisOfflineBool: + *(NPBool*)aValue = PluginModuleChild::GetChrome()->Settings().isOffline(); + return NPERR_NO_ERROR; + case NPNVSupportsXEmbedBool: + *(NPBool*)aValue = PluginModuleChild::GetChrome()->Settings().supportsXembed(); + return NPERR_NO_ERROR; + case NPNVSupportsWindowless: + *(NPBool*)aValue = PluginModuleChild::GetChrome()->Settings().supportsWindowless(); + return NPERR_NO_ERROR; +#if defined(MOZ_WIDGET_GTK) + case NPNVxDisplay: { + if (aNPP) { + return InstCast(aNPP)->NPN_GetValue(aVariable, aValue); + } + else { + *(void **)aValue = xt_client_get_display(); + } + return NPERR_NO_ERROR; + } + case NPNVxtAppContext: + return NPERR_GENERIC_ERROR; +#endif + default: { + if (aNPP) { + return InstCast(aNPP)->NPN_GetValue(aVariable, aValue); + } + + NS_WARNING("Null NPP!"); + return NPERR_INVALID_INSTANCE_ERROR; + } + } + + NS_NOTREACHED("Shouldn't get here!"); + return NPERR_GENERIC_ERROR; +} + +NPError +_setvalue(NPP aNPP, + NPPVariable aVariable, + void* aValue) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + return InstCast(aNPP)->NPN_SetValue(aVariable, aValue); +} + +NPError +_geturl(NPP aNPP, + const char* aRelativeURL, + const char* aTarget) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + NPError err; + InstCast(aNPP)->CallNPN_GetURL(NullableString(aRelativeURL), + NullableString(aTarget), &err); + return err; +} + +NPError +_posturlnotify(NPP aNPP, + const char* aRelativeURL, + const char* aTarget, + uint32_t aLength, + const char* aBuffer, + NPBool aIsFile, + void* aNotifyData) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + if (!aBuffer) + return NPERR_INVALID_PARAM; + + nsCString url = NullableString(aRelativeURL); + StreamNotifyChild* sn = new StreamNotifyChild(url); + + NPError err; + InstCast(aNPP)->CallPStreamNotifyConstructor( + sn, url, NullableString(aTarget), true, + nsCString(aBuffer, aLength), aIsFile, &err); + + if (NPERR_NO_ERROR == err) { + // If NPN_PostURLNotify fails, the parent will immediately send us + // a PStreamNotifyDestructor, which should not call NPP_URLNotify. + sn->SetValid(aNotifyData); + } + + return err; +} + +NPError +_posturl(NPP aNPP, + const char* aRelativeURL, + const char* aTarget, + uint32_t aLength, + const char* aBuffer, + NPBool aIsFile) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + NPError err; + // FIXME what should happen when |aBuffer| is null? + InstCast(aNPP)->CallNPN_PostURL(NullableString(aRelativeURL), + NullableString(aTarget), + nsDependentCString(aBuffer, aLength), + aIsFile, &err); + return err; +} + +NPError +_newstream(NPP aNPP, + NPMIMEType aMIMEType, + const char* aWindow, + NPStream** aStream) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + return InstCast(aNPP)->NPN_NewStream(aMIMEType, aWindow, aStream); +} + +int32_t +_write(NPP aNPP, + NPStream* aStream, + int32_t aLength, + void* aBuffer) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(0); + + PluginStreamChild* ps = + static_cast<PluginStreamChild*>(static_cast<AStream*>(aStream->ndata)); + ps->EnsureCorrectInstance(InstCast(aNPP)); + ps->EnsureCorrectStream(aStream); + return ps->NPN_Write(aLength, aBuffer); +} + +NPError +_destroystream(NPP aNPP, + NPStream* aStream, + NPError aReason) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + PluginInstanceChild* p = InstCast(aNPP); + AStream* s = static_cast<AStream*>(aStream->ndata); + if (s->IsBrowserStream()) { + BrowserStreamChild* bs = static_cast<BrowserStreamChild*>(s); + bs->EnsureCorrectInstance(p); + bs->NPN_DestroyStream(aReason); + } + else { + PluginStreamChild* ps = static_cast<PluginStreamChild*>(s); + ps->EnsureCorrectInstance(p); + PPluginStreamChild::Call__delete__(ps, aReason, false); + } + return NPERR_NO_ERROR; +} + +void +_status(NPP aNPP, + const char* aMessage) +{ + // NPN_Status is no longer supported. +} + +void +_memfree(void* aPtr) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + free(aPtr); +} + +uint32_t +_memflush(uint32_t aSize) +{ + return 0; +} + +void +_reloadplugins(NPBool aReloadPages) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + // Send the reload message to all modules. Chrome will need to reload from + // disk and content will need to request a new list of plugin tags from + // chrome. + PluginModuleChild::GetChrome()->SendNPN_ReloadPlugins(!!aReloadPages); +} + +void +_invalidaterect(NPP aNPP, + NPRect* aInvalidRect) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + // nullptr check for nspluginwrapper (bug 548434) + if (aNPP) { + InstCast(aNPP)->InvalidateRect(aInvalidRect); + } +} + +void +_invalidateregion(NPP aNPP, + NPRegion aInvalidRegion) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + NS_WARNING("Not yet implemented!"); +} + +void +_forceredraw(NPP aNPP) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + // We ignore calls to NPN_ForceRedraw. Such calls should + // never be necessary. +} + +const char* +_useragent(NPP aNPP) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(nullptr); + return PluginModuleChild::GetChrome()->GetUserAgent(); +} + +void* +_memalloc(uint32_t aSize) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + return moz_xmalloc(aSize); +} + +// Deprecated entry points for the old Java plugin. +void* /* OJI type: JRIEnv* */ +_getjavaenv(void) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + return 0; +} + +void* /* OJI type: jref */ +_getjavapeer(NPP aNPP) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + return 0; +} + +bool +_invoke(NPP aNPP, + NPObject* aNPObj, + NPIdentifier aMethod, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->invoke) + return false; + + return aNPObj->_class->invoke(aNPObj, aMethod, aArgs, aArgCount, aResult); +} + +bool +_invokedefault(NPP aNPP, + NPObject* aNPObj, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->invokeDefault) + return false; + + return aNPObj->_class->invokeDefault(aNPObj, aArgs, aArgCount, aResult); +} + +bool +_evaluate(NPP aNPP, + NPObject* aObject, + NPString* aScript, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!(aNPP && aObject && aScript && aResult)) { + NS_ERROR("Bad arguments!"); + return false; + } + + PluginScriptableObjectChild* actor = + InstCast(aNPP)->GetActorForNPObject(aObject); + if (!actor) { + NS_ERROR("Failed to create actor?!"); + return false; + } + +#ifdef XP_WIN + if (gDelayFlashFocusReplyUntilEval) { + ReplyMessage(0); + gDelayFlashFocusReplyUntilEval = false; + } +#endif + + return actor->Evaluate(aScript, aResult); +} + +bool +_getproperty(NPP aNPP, + NPObject* aNPObj, + NPIdentifier aPropertyName, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->getProperty) + return false; + + return aNPObj->_class->getProperty(aNPObj, aPropertyName, aResult); +} + +bool +_setproperty(NPP aNPP, + NPObject* aNPObj, + NPIdentifier aPropertyName, + const NPVariant* aValue) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->setProperty) + return false; + + return aNPObj->_class->setProperty(aNPObj, aPropertyName, aValue); +} + +bool +_removeproperty(NPP aNPP, + NPObject* aNPObj, + NPIdentifier aPropertyName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->removeProperty) + return false; + + return aNPObj->_class->removeProperty(aNPObj, aPropertyName); +} + +bool +_hasproperty(NPP aNPP, + NPObject* aNPObj, + NPIdentifier aPropertyName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->hasProperty) + return false; + + return aNPObj->_class->hasProperty(aNPObj, aPropertyName); +} + +bool +_hasmethod(NPP aNPP, + NPObject* aNPObj, + NPIdentifier aMethodName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->hasMethod) + return false; + + return aNPObj->_class->hasMethod(aNPObj, aMethodName); +} + +bool +_enumerate(NPP aNPP, + NPObject* aNPObj, + NPIdentifier** aIdentifiers, + uint32_t* aCount) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class) + return false; + + if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(aNPObj->_class) || + !aNPObj->_class->enumerate) { + *aIdentifiers = 0; + *aCount = 0; + return true; + } + + return aNPObj->_class->enumerate(aNPObj, aIdentifiers, aCount); +} + +bool +_construct(NPP aNPP, + NPObject* aNPObj, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || + !NP_CLASS_STRUCT_VERSION_HAS_CTOR(aNPObj->_class) || + !aNPObj->_class->construct) { + return false; + } + + return aNPObj->_class->construct(aNPObj, aArgs, aArgCount, aResult); +} + +void +_releasevariantvalue(NPVariant* aVariant) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + // Only assert plugin thread here for consistency with in-process plugins. + AssertPluginThread(); + + if (NPVARIANT_IS_STRING(*aVariant)) { + NPString str = NPVARIANT_TO_STRING(*aVariant); + free(const_cast<NPUTF8*>(str.UTF8Characters)); + } + else if (NPVARIANT_IS_OBJECT(*aVariant)) { + NPObject* object = NPVARIANT_TO_OBJECT(*aVariant); + if (object) { + PluginModuleChild::NPN_ReleaseObject(object); + } + } + VOID_TO_NPVARIANT(*aVariant); +} + +void +_setexception(NPObject* aNPObj, + const NPUTF8* aMessage) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + // Do nothing. We no longer support this API. +} + +void +_pushpopupsenabledstate(NPP aNPP, + NPBool aEnabled) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + InstCast(aNPP)->CallNPN_PushPopupsEnabledState(aEnabled ? true : false); +} + +void +_poppopupsenabledstate(NPP aNPP) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + InstCast(aNPP)->CallNPN_PopPopupsEnabledState(); +} + +void +_pluginthreadasynccall(NPP aNPP, + PluginThreadCallback aFunc, + void* aUserData) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (!aFunc) + return; + + InstCast(aNPP)->AsyncCall(aFunc, aUserData); +} + +NPError +_getvalueforurl(NPP npp, NPNURLVariable variable, const char *url, + char **value, uint32_t *len) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + if (!url) + return NPERR_INVALID_URL; + + if (!npp || !value || !len) + return NPERR_INVALID_PARAM; + + switch (variable) { + case NPNURLVCookie: + case NPNURLVProxy: + nsCString v; + NPError result; + InstCast(npp)-> + CallNPN_GetValueForURL(variable, nsCString(url), &v, &result); + if (NPERR_NO_ERROR == result) { + *value = ToNewCString(v); + *len = v.Length(); + } + return result; + } + + return NPERR_INVALID_PARAM; +} + +NPError +_setvalueforurl(NPP npp, NPNURLVariable variable, const char *url, + const char *value, uint32_t len) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + if (!value) + return NPERR_INVALID_PARAM; + + if (!url) + return NPERR_INVALID_URL; + + switch (variable) { + case NPNURLVCookie: + case NPNURLVProxy: + NPError result; + InstCast(npp)->CallNPN_SetValueForURL(variable, nsCString(url), + nsDependentCString(value, len), + &result); + return result; + } + + return NPERR_INVALID_PARAM; +} + +NPError +_getauthenticationinfo(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) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + if (!protocol || !host || !scheme || !realm || !username || !ulen || + !password || !plen) + return NPERR_INVALID_PARAM; + + nsCString u; + nsCString p; + NPError result; + InstCast(npp)-> + CallNPN_GetAuthenticationInfo(nsDependentCString(protocol), + nsDependentCString(host), + port, + nsDependentCString(scheme), + nsDependentCString(realm), + &u, &p, &result); + if (NPERR_NO_ERROR == result) { + *username = ToNewCString(u); + *ulen = u.Length(); + *password = ToNewCString(p); + *plen = p.Length(); + } + return result; +} + +uint32_t +_scheduletimer(NPP npp, uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + return InstCast(npp)->ScheduleTimer(interval, repeat, timerFunc); +} + +void +_unscheduletimer(NPP npp, uint32_t timerID) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + InstCast(npp)->UnscheduleTimer(timerID); +} + + +#ifdef OS_MACOSX +static void ProcessBrowserEvents(void* pluginModule) { + PluginModuleChild* pmc = static_cast<PluginModuleChild*>(pluginModule); + + if (!pmc) + return; + + pmc->CallProcessSomeEvents(); +} +#endif + +NPError +_popupcontextmenu(NPP instance, NPMenu* menu) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + +#ifdef MOZ_WIDGET_COCOA + double pluginX, pluginY; + double screenX, screenY; + + const NPCocoaEvent* currentEvent = InstCast(instance)->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, + InstCast(instance)->Manager(), + ProcessBrowserEvents); + } 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) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (!IsPluginThread()) { + NS_WARNING("Not running on the plugin's main thread!"); + return false; + } + + double rDestX = 0; + bool ignoreDestX = !destX; + double rDestY = 0; + bool ignoreDestY = !destY; + bool result = false; + InstCast(instance)->CallNPN_ConvertPoint(sourceX, ignoreDestX, sourceY, ignoreDestY, sourceSpace, destSpace, + &rDestX, &rDestY, &result); + if (result) { + if (destX) + *destX = rDestX; + if (destY) + *destY = rDestY; + } + + return result; +} + +void +_urlredirectresponse(NPP instance, void* notifyData, NPBool allow) +{ + InstCast(instance)->NPN_URLRedirectResponse(notifyData, allow); +} + +NPError +_initasyncsurface(NPP instance, NPSize *size, + NPImageFormat format, void *initData, + NPAsyncSurface *surface) +{ + return InstCast(instance)->NPN_InitAsyncSurface(size, format, initData, surface); +} + +NPError +_finalizeasyncsurface(NPP instance, NPAsyncSurface *surface) +{ + return InstCast(instance)->NPN_FinalizeAsyncSurface(surface); +} + +void +_setcurrentasyncsurface(NPP instance, NPAsyncSurface *surface, NPRect *changed) +{ + InstCast(instance)->NPN_SetCurrentAsyncSurface(surface, changed); +} + +} /* namespace child */ +} /* namespace plugins */ +} /* namespace mozilla */ + +//----------------------------------------------------------------------------- + +bool +PluginModuleChild::RecvSettingChanged(const PluginSettings& aSettings) +{ + mCachedSettings = aSettings; + return true; +} + +bool +PluginModuleChild::AnswerNP_GetEntryPoints(NPError* _retval) +{ + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + MOZ_ASSERT(mIsChrome); + +#if defined(OS_LINUX) || defined(OS_BSD) + return true; +#elif defined(OS_WIN) || defined(OS_MACOSX) + *_retval = mGetEntryPointsFunc(&mFunctions); + return true; +#else +# error Please implement me for your platform +#endif +} + +bool +PluginModuleChild::AnswerNP_Initialize(const PluginSettings& aSettings, NPError* rv) +{ + *rv = DoNP_Initialize(aSettings); + return true; +} + +bool +PluginModuleChild::RecvAsyncNP_Initialize(const PluginSettings& aSettings) +{ + NPError error = DoNP_Initialize(aSettings); + return SendNP_InitializeResult(error); +} + +NPError +PluginModuleChild::DoNP_Initialize(const PluginSettings& aSettings) +{ + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + MOZ_ASSERT(mIsChrome); + + mCachedSettings = aSettings; + +#ifdef OS_WIN + SetEventHooks(); +#endif + +#ifdef MOZ_X11 + // Send the parent our X socket to act as a proxy reference for our X + // resources. + int xSocketFd = ConnectionNumber(DefaultXDisplay()); + SendBackUpXResources(FileDescriptor(xSocketFd)); +#endif + + NPError result; +#if defined(OS_LINUX) || defined(OS_BSD) + result = mInitializeFunc(&sBrowserFuncs, &mFunctions); +#elif defined(OS_WIN) || defined(OS_MACOSX) + result = mInitializeFunc(&sBrowserFuncs); +#else +# error Please implement me for your platform +#endif + + return result; +} + +#if defined(XP_WIN) + +// Windows 8 RTM (kernelbase's version is 6.2.9200.16384) doesn't call +// CreateFileW from CreateFileA. +// So we hook CreateFileA too to use CreateFileW hook. + +static HANDLE WINAPI +CreateFileAHookFn(LPCSTR fname, DWORD access, DWORD share, + LPSECURITY_ATTRIBUTES security, DWORD creation, DWORD flags, + HANDLE ftemplate) +{ + while (true) { // goto out + // Our hook is for mms.cfg into \Windows\System32\Macromed\Flash + // We don't requrie supporting too long path. + WCHAR unicodeName[MAX_PATH]; + size_t len = strlen(fname); + + if (len >= MAX_PATH) { + break; + } + + // We call to CreateFileW for workaround of Windows 8 RTM + int newLen = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, fname, + len, unicodeName, MAX_PATH); + if (newLen == 0 || newLen >= MAX_PATH) { + break; + } + unicodeName[newLen] = '\0'; + + return CreateFileW(unicodeName, access, share, security, creation, flags, ftemplate); + } + + return sCreateFileAStub(fname, access, share, security, creation, flags, + ftemplate); +} + +static bool +GetLocalLowTempPath(size_t aLen, LPWSTR aPath) +{ + NS_NAMED_LITERAL_STRING(tempname, "\\Temp"); + LPWSTR path; + if (SUCCEEDED(WinUtils::SHGetKnownFolderPath(FOLDERID_LocalAppDataLow, 0, + nullptr, &path))) { + if (wcslen(path) + tempname.Length() < aLen) { + wcscpy(aPath, path); + wcscat(aPath, tempname.get()); + ::CoTaskMemFree(path); + return true; + } + ::CoTaskMemFree(path); + } + + // XP doesn't support SHGetKnownFolderPath and LocalLow + if (!GetTempPathW(aLen, aPath)) { + return false; + } + return true; +} + +HANDLE WINAPI +CreateFileWHookFn(LPCWSTR fname, DWORD access, DWORD share, + LPSECURITY_ATTRIBUTES security, DWORD creation, DWORD flags, + HANDLE ftemplate) +{ + static const WCHAR kConfigFile[] = L"mms.cfg"; + static const size_t kConfigLength = ArrayLength(kConfigFile) - 1; + + while (true) { // goto out, in sheep's clothing + size_t len = wcslen(fname); + if (len < kConfigLength) { + break; + } + if (wcscmp(fname + len - kConfigLength, kConfigFile) != 0) { + break; + } + + // This is the config file we want to rewrite + WCHAR tempPath[MAX_PATH+1]; + if (GetLocalLowTempPath(MAX_PATH, tempPath) == 0) { + break; + } + WCHAR tempFile[MAX_PATH+1]; + if (GetTempFileNameW(tempPath, L"fx", 0, tempFile) == 0) { + break; + } + HANDLE replacement = + sCreateFileWStub(tempFile, GENERIC_READ | GENERIC_WRITE, share, + security, TRUNCATE_EXISTING, + FILE_ATTRIBUTE_TEMPORARY | + FILE_FLAG_DELETE_ON_CLOSE, + NULL); + if (replacement == INVALID_HANDLE_VALUE) { + break; + } + + HANDLE original = sCreateFileWStub(fname, access, share, security, + creation, flags, ftemplate); + if (original != INVALID_HANDLE_VALUE) { + // copy original to replacement + static const size_t kBufferSize = 1024; + char buffer[kBufferSize]; + DWORD bytes; + while (ReadFile(original, buffer, kBufferSize, &bytes, NULL)) { + if (bytes == 0) { + break; + } + DWORD wbytes; + WriteFile(replacement, buffer, bytes, &wbytes, NULL); + if (bytes < kBufferSize) { + break; + } + } + CloseHandle(original); + } + static const char kSettingString[] = "\nProtectedMode=0\n"; + DWORD wbytes; + WriteFile(replacement, static_cast<const void*>(kSettingString), + sizeof(kSettingString) - 1, &wbytes, NULL); + SetFilePointer(replacement, 0, NULL, FILE_BEGIN); + return replacement; + } + return sCreateFileWStub(fname, access, share, security, creation, flags, + ftemplate); +} + +void +PluginModuleChild::HookProtectedMode() +{ + sKernel32Intercept.Init("kernel32.dll"); + sKernel32Intercept.AddHook("CreateFileW", + reinterpret_cast<intptr_t>(CreateFileWHookFn), + (void**) &sCreateFileWStub); + sKernel32Intercept.AddHook("CreateFileA", + reinterpret_cast<intptr_t>(CreateFileAHookFn), + (void**) &sCreateFileAStub); +} + +BOOL WINAPI +PMCGetWindowInfoHook(HWND hWnd, PWINDOWINFO pwi) +{ + if (!pwi) + return FALSE; + + if (!sGetWindowInfoPtrStub) { + NS_ASSERTION(FALSE, "Something is horribly wrong in PMCGetWindowInfoHook!"); + return FALSE; + } + + if (!sBrowserHwnd) { + wchar_t szClass[20]; + if (GetClassNameW(hWnd, szClass, ArrayLength(szClass)) && + !wcscmp(szClass, kMozillaWindowClass)) { + sBrowserHwnd = hWnd; + } + } + // Oddity: flash does strange rect comparisons for mouse input destined for + // it's internal settings window. Post removing sub widgets for tabs, touch + // this up so they get the rect they expect. + // XXX potentially tie this to a specific major version? + BOOL result = sGetWindowInfoPtrStub(hWnd, pwi); + if (sBrowserHwnd && sBrowserHwnd == hWnd) + pwi->rcWindow = pwi->rcClient; + return result; +} + +SHORT WINAPI PMCGetKeyState(int aVirtKey); + +// Runnable that performs GetKeyState on the main thread so that it can be +// synchronously run on the PluginModuleParent via IPC. +// The task alerts the given semaphore when it is finished. +class GetKeyStateTask : public Runnable +{ + SHORT* mKeyState; + int mVirtKey; + HANDLE mSemaphore; + +public: + explicit GetKeyStateTask(int aVirtKey, HANDLE aSemaphore, SHORT* aKeyState) : + mVirtKey(aVirtKey), mSemaphore(aSemaphore), mKeyState(aKeyState) + {} + + NS_IMETHOD Run() override + { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + *mKeyState = PMCGetKeyState(mVirtKey); + if (!ReleaseSemaphore(mSemaphore, 1, nullptr)) { + return NS_ERROR_FAILURE; + } + return NS_OK; + } +}; + +// static +SHORT WINAPI +PMCGetKeyState(int aVirtKey) +{ + if (!IsPluginThread()) { + // synchronously request the key state from the main thread + + // Start a semaphore at 0. We Release the semaphore (bringing its count to 1) + // when the synchronous call is done. + HANDLE semaphore = CreateSemaphore(NULL, 0, 1, NULL); + if (semaphore == nullptr) { + MOZ_ASSERT(semaphore != nullptr); + return 0; + } + + SHORT keyState; + RefPtr<GetKeyStateTask> task = new GetKeyStateTask(aVirtKey, semaphore, &keyState); + ProcessChild::message_loop()->PostTask(task.forget()); + DWORD err = WaitForSingleObject(semaphore, INFINITE); + if (err != WAIT_FAILED) { + CloseHandle(semaphore); + return keyState; + } + PLUGIN_LOG_DEBUG(("Error while waiting for GetKeyState semaphore: %d", + GetLastError())); + MOZ_ASSERT(err != WAIT_FAILED); + CloseHandle(semaphore); + return 0; + } + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome(); + if (chromeInstance) { + int16_t ret = 0; + if (chromeInstance->CallGetKeyState(aVirtKey, &ret)) { + return ret; + } + } + return sGetKeyStatePtrStub(aVirtKey); +} +#endif + +PPluginInstanceChild* +PluginModuleChild::AllocPPluginInstanceChild(const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray<nsCString>& aNames, + const InfallibleTArray<nsCString>& aValues) +{ + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + + // In e10s, gChromeInstance hands out quirks to instances, but never + // allocates an instance on its own. Make sure it gets the latest copy + // of quirks once we have them. Also note, with process-per-tab, we may + // have multiple PluginModuleChilds in the same plugin process, so only + // initialize this once in gChromeInstance, which is a singleton. + GetChrome()->InitQuirksModes(aMimeType); + mQuirks = GetChrome()->mQuirks; + +#ifdef XP_WIN + sUser32Intercept.Init("user32.dll"); + if ((mQuirks & QUIRK_FLASH_HOOK_GETWINDOWINFO) && + !sGetWindowInfoPtrStub) { + sUser32Intercept.AddHook("GetWindowInfo", reinterpret_cast<intptr_t>(PMCGetWindowInfoHook), + (void**) &sGetWindowInfoPtrStub); + } + + if ((mQuirks & QUIRK_FLASH_HOOK_GETKEYSTATE) && + !sGetKeyStatePtrStub) { + sUser32Intercept.AddHook("GetKeyState", reinterpret_cast<intptr_t>(PMCGetKeyState), + (void**) &sGetKeyStatePtrStub); + } +#endif + + return new PluginInstanceChild(&mFunctions, aMimeType, aMode, aNames, + aValues); +} + +void +PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) +{ + if (mQuirks != QUIRKS_NOT_INITIALIZED) { + return; + } + + mQuirks = GetQuirksFromMimeTypeAndFilename(aMimeType, mPluginFilename); +} + +bool +PluginModuleChild::AnswerModuleSupportsAsyncRender(bool* aResult) +{ +#if defined(XP_WIN) + *aResult = gChromeInstance->mAsyncRenderSupport; + return true; +#else + NS_NOTREACHED("Shouldn't get here!"); + return false; +#endif +} + +bool +PluginModuleChild::RecvPPluginInstanceConstructor(PPluginInstanceChild* aActor, + const nsCString& aMimeType, + const uint16_t& aMode, + InfallibleTArray<nsCString>&& aNames, + InfallibleTArray<nsCString>&& aValues) +{ + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + + NS_ASSERTION(aActor, "Null actor!"); + return true; +} + +bool +PluginModuleChild::AnswerSyncNPP_New(PPluginInstanceChild* aActor, NPError* rv) +{ + PLUGIN_LOG_DEBUG_METHOD; + PluginInstanceChild* childInstance = + reinterpret_cast<PluginInstanceChild*>(aActor); + AssertPluginThread(); + *rv = childInstance->DoNPP_New(); + return true; +} + +class AsyncNewResultSender : public ChildAsyncCall +{ +public: + AsyncNewResultSender(PluginInstanceChild* aInstance, NPError aResult) + : ChildAsyncCall(aInstance, nullptr, nullptr) + , mResult(aResult) + { + } + + NS_IMETHOD Run() override + { + RemoveFromAsyncList(); + DebugOnly<bool> sendOk = mInstance->SendAsyncNPP_NewResult(mResult); + MOZ_ASSERT(sendOk); + return NS_OK; + } + +private: + NPError mResult; +}; + +static void +RunAsyncNPP_New(void* aChildInstance) +{ + MOZ_ASSERT(aChildInstance); + PluginInstanceChild* childInstance = + static_cast<PluginInstanceChild*>(aChildInstance); + NPError rv = childInstance->DoNPP_New(); + RefPtr<AsyncNewResultSender> task = + new AsyncNewResultSender(childInstance, rv); + childInstance->PostChildAsyncCall(task.forget()); +} + +bool +PluginModuleChild::RecvAsyncNPP_New(PPluginInstanceChild* aActor) +{ + PLUGIN_LOG_DEBUG_METHOD; + PluginInstanceChild* childInstance = + reinterpret_cast<PluginInstanceChild*>(aActor); + AssertPluginThread(); + // We don't want to run NPP_New async from within nested calls + childInstance->AsyncCall(&RunAsyncNPP_New, childInstance); + return true; +} + +bool +PluginModuleChild::DeallocPPluginInstanceChild(PPluginInstanceChild* aActor) +{ + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + + delete aActor; + + return true; +} + +NPObject* +PluginModuleChild::NPN_CreateObject(NPP aNPP, NPClass* aClass) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(nullptr); + + PluginInstanceChild* i = InstCast(aNPP); + if (i->mDeletingHash) { + NS_ERROR("Plugin used NPP after NPP_Destroy"); + return nullptr; + } + + NPObject* newObject; + if (aClass && aClass->allocate) { + newObject = aClass->allocate(aNPP, aClass); + } + else { + newObject = reinterpret_cast<NPObject*>(child::_memalloc(sizeof(NPObject))); + } + + if (newObject) { + newObject->_class = aClass; + newObject->referenceCount = 1; + NS_LOG_ADDREF(newObject, 1, "NPObject", sizeof(NPObject)); + } + + PluginScriptableObjectChild::RegisterObject(newObject, i); + + return newObject; +} + +NPObject* +PluginModuleChild::NPN_RetainObject(NPObject* aNPObj) +{ + AssertPluginThread(); + +#ifdef NS_BUILD_REFCNT_LOGGING + int32_t refCnt = +#endif + PR_ATOMIC_INCREMENT((int32_t*)&aNPObj->referenceCount); + NS_LOG_ADDREF(aNPObj, refCnt, "NPObject", sizeof(NPObject)); + + return aNPObj; +} + +void +PluginModuleChild::NPN_ReleaseObject(NPObject* aNPObj) +{ + AssertPluginThread(); + + PluginInstanceChild* instance = PluginScriptableObjectChild::GetInstanceForNPObject(aNPObj); + if (!instance) { + NS_ERROR("Releasing object not in mObjectMap?"); + return; + } + + DeletingObjectEntry* doe = nullptr; + if (instance->mDeletingHash) { + doe = instance->mDeletingHash->GetEntry(aNPObj); + if (!doe) { + NS_ERROR("An object for a destroyed instance isn't in the instance deletion hash"); + return; + } + if (doe->mDeleted) + return; + } + + int32_t refCnt = PR_ATOMIC_DECREMENT((int32_t*)&aNPObj->referenceCount); + NS_LOG_RELEASE(aNPObj, refCnt, "NPObject"); + + if (refCnt == 0) { + DeallocNPObject(aNPObj); + if (doe) + doe->mDeleted = true; + } + return; +} + +void +PluginModuleChild::DeallocNPObject(NPObject* aNPObj) +{ + if (aNPObj->_class && aNPObj->_class->deallocate) { + aNPObj->_class->deallocate(aNPObj); + } else { + child::_memfree(aNPObj); + } + + PluginScriptableObjectChild* actor = PluginScriptableObjectChild::GetActorForNPObject(aNPObj); + if (actor) + actor->NPObjectDestroyed(); + + PluginScriptableObjectChild::UnregisterObject(aNPObj); +} + +NPIdentifier +PluginModuleChild::NPN_GetStringIdentifier(const NPUTF8* aName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + if (!aName) + return 0; + + nsDependentCString name(aName); + PluginIdentifier ident(name); + PluginScriptableObjectChild::StackIdentifier stackID(ident); + stackID.MakePermanent(); + return stackID.ToNPIdentifier(); +} + +void +PluginModuleChild::NPN_GetStringIdentifiers(const NPUTF8** aNames, + int32_t aNameCount, + NPIdentifier* aIdentifiers) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + if (!(aNames && aNameCount > 0 && aIdentifiers)) { + NS_RUNTIMEABORT("Bad input! Headed for a crash!"); + } + + for (int32_t index = 0; index < aNameCount; ++index) { + if (!aNames[index]) { + aIdentifiers[index] = 0; + continue; + } + nsDependentCString name(aNames[index]); + PluginIdentifier ident(name); + PluginScriptableObjectChild::StackIdentifier stackID(ident); + stackID.MakePermanent(); + aIdentifiers[index] = stackID.ToNPIdentifier(); + } +} + +bool +PluginModuleChild::NPN_IdentifierIsString(NPIdentifier aIdentifier) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + PluginScriptableObjectChild::StackIdentifier stack(aIdentifier); + return stack.IsString(); +} + +NPIdentifier +PluginModuleChild::NPN_GetIntIdentifier(int32_t aIntId) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + PluginIdentifier ident(aIntId); + PluginScriptableObjectChild::StackIdentifier stackID(ident); + stackID.MakePermanent(); + return stackID.ToNPIdentifier(); +} + +NPUTF8* +PluginModuleChild::NPN_UTF8FromIdentifier(NPIdentifier aIdentifier) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + PluginScriptableObjectChild::StackIdentifier stackID(aIdentifier); + if (stackID.IsString()) { + return ToNewCString(stackID.GetString()); + } + return nullptr; +} + +int32_t +PluginModuleChild::NPN_IntFromIdentifier(NPIdentifier aIdentifier) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + PluginScriptableObjectChild::StackIdentifier stackID(aIdentifier); + if (!stackID.IsString()) { + return stackID.GetInt(); + } + return INT32_MIN; +} + +#ifdef OS_WIN +void +PluginModuleChild::EnteredCall() +{ + mIncallPumpingStack.AppendElement(); +} + +void +PluginModuleChild::ExitedCall() +{ + NS_ASSERTION(mIncallPumpingStack.Length(), "mismatched entered/exited"); + uint32_t len = mIncallPumpingStack.Length(); + const IncallFrame& f = mIncallPumpingStack[len - 1]; + if (f._spinning) + MessageLoop::current()->SetNestableTasksAllowed(f._savedNestableTasksAllowed); + + mIncallPumpingStack.TruncateLength(len - 1); +} + +LRESULT CALLBACK +PluginModuleChild::CallWindowProcHook(int nCode, WPARAM wParam, LPARAM lParam) +{ + // Trap and reply to anything we recognize as the source of a + // potential send message deadlock. + if (nCode >= 0 && + (InSendMessageEx(nullptr)&(ISMEX_REPLIED|ISMEX_SEND)) == ISMEX_SEND) { + CWPSTRUCT* pCwp = reinterpret_cast<CWPSTRUCT*>(lParam); + if (pCwp->message == WM_KILLFOCUS) { + // Fix for flash fullscreen window loosing focus. On single + // core systems, sync killfocus events need to be handled + // after the flash fullscreen window procedure processes this + // message, otherwise fullscreen focus will not work correctly. + wchar_t szClass[26]; + if (GetClassNameW(pCwp->hwnd, szClass, + sizeof(szClass)/sizeof(char16_t)) && + !wcscmp(szClass, kFlashFullscreenClass)) { + gDelayFlashFocusReplyUntilEval = true; + } + } + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +LRESULT CALLBACK +PluginModuleChild::NestedInputEventHook(int nCode, WPARAM wParam, LPARAM lParam) +{ + PluginModuleChild* self = GetChrome(); + uint32_t len = self->mIncallPumpingStack.Length(); + if (nCode >= 0 && len && !self->mIncallPumpingStack[len - 1]._spinning) { + MessageLoop* loop = MessageLoop::current(); + self->SendProcessNativeEventsInInterruptCall(); + IncallFrame& f = self->mIncallPumpingStack[len - 1]; + f._spinning = true; + f._savedNestableTasksAllowed = loop->NestableTasksAllowed(); + loop->SetNestableTasksAllowed(true); + loop->set_os_modal_loop(true); + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +void +PluginModuleChild::SetEventHooks() +{ + NS_ASSERTION(!mNestedEventHook, + "mNestedEventHook already setup in call to SetNestedInputEventHook?"); + NS_ASSERTION(!mGlobalCallWndProcHook, + "mGlobalCallWndProcHook already setup in call to CallWindowProcHook?"); + + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + // WH_MSGFILTER event hook for detecting modal loops in the child. + mNestedEventHook = SetWindowsHookEx(WH_MSGFILTER, + NestedInputEventHook, + nullptr, + GetCurrentThreadId()); + + // WH_CALLWNDPROC event hook for trapping sync messages sent from + // parent that can cause deadlocks. + mGlobalCallWndProcHook = SetWindowsHookEx(WH_CALLWNDPROC, + CallWindowProcHook, + nullptr, + GetCurrentThreadId()); +} + +void +PluginModuleChild::ResetEventHooks() +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + if (mNestedEventHook) + UnhookWindowsHookEx(mNestedEventHook); + mNestedEventHook = nullptr; + if (mGlobalCallWndProcHook) + UnhookWindowsHookEx(mGlobalCallWndProcHook); + mGlobalCallWndProcHook = nullptr; +} +#endif + +bool +PluginModuleChild::RecvProcessNativeEventsInInterruptCall() +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(OS_WIN) + ProcessNativeEventsInInterruptCall(); + return true; +#else + NS_RUNTIMEABORT( + "PluginModuleChild::RecvProcessNativeEventsInInterruptCall not implemented!"); + return false; +#endif +} + +#ifdef MOZ_WIDGET_COCOA +void +PluginModuleChild::ProcessNativeEvents() { + CallProcessSomeEvents(); +} +#endif + +bool +PluginModuleChild::RecvStartProfiler(const ProfilerInitParams& params) +{ + nsTArray<const char*> featureArray; + for (size_t i = 0; i < params.features().Length(); ++i) { + featureArray.AppendElement(params.features()[i].get()); + } + + nsTArray<const char*> threadNameFilterArray; + for (size_t i = 0; i < params.threadFilters().Length(); ++i) { + threadNameFilterArray.AppendElement(params.threadFilters()[i].get()); + } + + profiler_start(params.entries(), params.interval(), + featureArray.Elements(), featureArray.Length(), + threadNameFilterArray.Elements(), threadNameFilterArray.Length()); + + return true; +} + +bool +PluginModuleChild::RecvStopProfiler() +{ + profiler_stop(); + return true; +} + +bool +PluginModuleChild::RecvGatherProfile() +{ + nsCString profileCString; + UniquePtr<char[]> profile = profiler_get_profile(); + if (profile != nullptr) { + profileCString = nsCString(profile.get(), strlen(profile.get())); + } else { + profileCString = nsCString("", 0); + } + + Unused << SendProfile(profileCString); + return true; +} + +NPError +PluginModuleChild::PluginRequiresAudioDeviceChanges( + PluginInstanceChild* aInstance, + NPBool aShouldRegister) +{ +#ifdef XP_WIN + // Maintain a set of PluginInstanceChildren that we need to tell when the + // default audio device has changed. + NPError rv = NPERR_NO_ERROR; + if (aShouldRegister) { + if (mAudioNotificationSet.IsEmpty()) { + // We are registering the first plugin. Notify the PluginModuleParent + // that it needs to start sending us audio device notifications. + if (!CallNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + aShouldRegister, &rv)) { + return NPERR_GENERIC_ERROR; + } + } + if (rv == NPERR_NO_ERROR) { + mAudioNotificationSet.PutEntry(aInstance); + } + } + else if (!mAudioNotificationSet.IsEmpty()) { + mAudioNotificationSet.RemoveEntry(aInstance); + if (mAudioNotificationSet.IsEmpty()) { + // We released the last plugin. Unregister from the PluginModuleParent. + if (!CallNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + aShouldRegister, &rv)) { + return NPERR_GENERIC_ERROR; + } + } + } + return rv; +#else + NS_RUNTIMEABORT("PluginRequiresAudioDeviceChanges is not available on this platform."); + return NPERR_GENERIC_ERROR; +#endif // XP_WIN +} + +bool +PluginModuleChild::RecvNPP_SetValue_NPNVaudioDeviceChangeDetails( + const NPAudioDeviceChangeDetailsIPC& detailsIPC) +{ +#if defined(XP_WIN) + NPAudioDeviceChangeDetails details; + details.flow = detailsIPC.flow; + details.role = detailsIPC.role; + details.defaultDevice = detailsIPC.defaultDevice.c_str(); + for (auto iter = mAudioNotificationSet.ConstIter(); !iter.Done(); iter.Next()) { + PluginInstanceChild* pluginInst = iter.Get()->GetKey(); + pluginInst->DefaultAudioDeviceChanged(details); + } + return true; +#else + NS_RUNTIMEABORT("NPP_SetValue_NPNVaudioDeviceChangeDetails is a Windows-only message"); + return false; +#endif +} diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h new file mode 100644 index 000000000..233a95369 --- /dev/null +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -0,0 +1,387 @@ +/* -*- 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 dom_plugins_PluginModuleChild_h +#define dom_plugins_PluginModuleChild_h 1 + +#include "mozilla/Attributes.h" + +#include <string> +#include <vector> + +#include "base/basictypes.h" + +#include "prlink.h" + +#include "npapi.h" +#include "npfunctions.h" + +#include "nsDataHashtable.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" + +#ifdef MOZ_WIDGET_COCOA +#include "PluginInterposeOSX.h" +#endif + +#include "mozilla/plugins/PPluginModuleChild.h" +#include "mozilla/plugins/PluginInstanceChild.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/plugins/PluginQuirks.h" + +// NOTE: stolen from nsNPAPIPlugin.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) (void); + +namespace mozilla { +namespace dom { +class PCrashReporterChild; +} // namespace dom + +namespace plugins { + +class PluginInstanceChild; + +class PluginModuleChild : public PPluginModuleChild +{ + typedef mozilla::dom::PCrashReporterChild PCrashReporterChild; +protected: + virtual mozilla::ipc::RacyInterruptPolicy + MediateInterruptRace(const MessageInfo& parent, + const MessageInfo& child) override + { + return MediateRace(parent, child); + } + + virtual bool ShouldContinueFromReplyTimeout() override; + + virtual bool RecvSettingChanged(const PluginSettings& aSettings) override; + + // Implement the PPluginModuleChild interface + virtual bool RecvDisableFlashProtectedMode() override; + virtual bool AnswerNP_GetEntryPoints(NPError* rv) override; + virtual bool AnswerNP_Initialize(const PluginSettings& aSettings, NPError* rv) override; + virtual bool RecvAsyncNP_Initialize(const PluginSettings& aSettings) override; + virtual bool AnswerSyncNPP_New(PPluginInstanceChild* aActor, NPError* rv) + override; + virtual bool RecvAsyncNPP_New(PPluginInstanceChild* aActor) override; + + virtual PPluginModuleChild* + AllocPPluginModuleChild(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess) override; + + virtual PPluginInstanceChild* + AllocPPluginInstanceChild(const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray<nsCString>& aNames, + const InfallibleTArray<nsCString>& aValues) + override; + + virtual bool + DeallocPPluginInstanceChild(PPluginInstanceChild* aActor) override; + + virtual bool + RecvPPluginInstanceConstructor(PPluginInstanceChild* aActor, + const nsCString& aMimeType, + const uint16_t& aMode, + InfallibleTArray<nsCString>&& aNames, + InfallibleTArray<nsCString>&& aValues) + override; + virtual bool + AnswerNP_Shutdown(NPError *rv) override; + + virtual bool + AnswerOptionalFunctionsSupported(bool *aURLRedirectNotify, + bool *aClearSiteData, + bool *aGetSitesWithData) override; + + virtual bool + RecvNPP_ClearSiteData(const nsCString& aSite, + const uint64_t& aFlags, + const uint64_t& aMaxAge, + const uint64_t& aCallbackId) override; + + virtual bool + RecvNPP_GetSitesWithData(const uint64_t& aCallbackId) override; + + virtual bool + RecvSetAudioSessionData(const nsID& aId, + const nsString& aDisplayName, + const nsString& aIconPath) override; + + virtual bool + RecvSetParentHangTimeout(const uint32_t& aSeconds) override; + + virtual PCrashReporterChild* + AllocPCrashReporterChild(mozilla::dom::NativeThreadId* id, + uint32_t* processType) override; + virtual bool + DeallocPCrashReporterChild(PCrashReporterChild* actor) override; + virtual bool + AnswerPCrashReporterConstructor(PCrashReporterChild* actor, + mozilla::dom::NativeThreadId* id, + uint32_t* processType) override; + + virtual void + ActorDestroy(ActorDestroyReason why) override; + + virtual bool + RecvProcessNativeEventsInInterruptCall() override; + + virtual bool RecvStartProfiler(const ProfilerInitParams& params) override; + virtual bool RecvStopProfiler() override; + virtual bool RecvGatherProfile() override; + + virtual bool + AnswerModuleSupportsAsyncRender(bool* aResult) override; +public: + explicit PluginModuleChild(bool aIsChrome); + virtual ~PluginModuleChild(); + + bool CommonInit(base::ProcessId aParentPid, + MessageLoop* aIOLoop, + IPC::Channel* aChannel); + + // aPluginFilename is UTF8, not native-charset! + bool InitForChrome(const std::string& aPluginFilename, + base::ProcessId aParentPid, + MessageLoop* aIOLoop, + IPC::Channel* aChannel); + + bool InitForContent(base::ProcessId aParentPid, + MessageLoop* aIOLoop, + IPC::Channel* aChannel); + + static PluginModuleChild* + CreateForContentProcess(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess); + + void CleanUp(); + + NPError NP_Shutdown(); + + const char* GetUserAgent(); + + static const NPNetscapeFuncs sBrowserFuncs; + + static PluginModuleChild* GetChrome(); + + /** + * The child implementation of NPN_CreateObject. + */ + static NPObject* NPN_CreateObject(NPP aNPP, NPClass* aClass); + /** + * The child implementation of NPN_RetainObject. + */ + static NPObject* NPN_RetainObject(NPObject* aNPObj); + /** + * The child implementation of NPN_ReleaseObject. + */ + static void NPN_ReleaseObject(NPObject* aNPObj); + + /** + * The child implementations of NPIdentifier-related functions. + */ + static NPIdentifier NPN_GetStringIdentifier(const NPUTF8* aName); + static void NPN_GetStringIdentifiers(const NPUTF8** aNames, + int32_t aNameCount, + NPIdentifier* aIdentifiers); + static NPIdentifier NPN_GetIntIdentifier(int32_t aIntId); + static bool NPN_IdentifierIsString(NPIdentifier aIdentifier); + static NPUTF8* NPN_UTF8FromIdentifier(NPIdentifier aIdentifier); + static int32_t NPN_IntFromIdentifier(NPIdentifier aIdentifier); + +#ifdef MOZ_WIDGET_COCOA + void ProcessNativeEvents(); + + void PluginShowWindow(uint32_t window_id, bool modal, CGRect r) { + SendPluginShowWindow(window_id, modal, r.origin.x, r.origin.y, r.size.width, r.size.height); + } + + void PluginHideWindow(uint32_t window_id) { + SendPluginHideWindow(window_id); + } + + void SetCursor(NSCursorInfo& cursorInfo) { + SendSetCursor(cursorInfo); + } + + void ShowCursor(bool show) { + SendShowCursor(show); + } + + void PushCursor(NSCursorInfo& cursorInfo) { + SendPushCursor(cursorInfo); + } + + void PopCursor() { + SendPopCursor(); + } + + bool GetNativeCursorsSupported() { + return Settings().nativeCursorsSupported(); + } +#endif + + int GetQuirks() { return mQuirks; } + + const PluginSettings& Settings() const { return mCachedSettings; } + + NPError PluginRequiresAudioDeviceChanges(PluginInstanceChild* aInstance, + NPBool aShouldRegister); + bool RecvNPP_SetValue_NPNVaudioDeviceChangeDetails( + const NPAudioDeviceChangeDetailsIPC& detailsIPC) override; + +private: + NPError DoNP_Initialize(const PluginSettings& aSettings); + void AddQuirk(PluginQuirks quirk) { + if (mQuirks == QUIRKS_NOT_INITIALIZED) + mQuirks = 0; + mQuirks |= quirk; + } + void InitQuirksModes(const nsCString& aMimeType); + bool InitGraphics(); + void DeinitGraphics(); + +#if defined(OS_WIN) + void HookProtectedMode(); +#endif + +#if defined(MOZ_WIDGET_GTK) + static gboolean DetectNestedEventLoop(gpointer data); + static gboolean ProcessBrowserEvents(gpointer data); + + virtual void EnteredCxxStack() override; + virtual void ExitedCxxStack() override; +#endif + + PRLibrary* mLibrary; + nsCString mPluginFilename; // UTF8 + int mQuirks; + + bool mIsChrome; + bool mHasShutdown; // true if NP_Shutdown has run + Transport* mTransport; + + // we get this from the plugin + NP_PLUGINSHUTDOWN mShutdownFunc; +#if defined(OS_LINUX) || defined(OS_BSD) + NP_PLUGINUNIXINIT mInitializeFunc; +#elif defined(OS_WIN) || defined(OS_MACOSX) + NP_PLUGININIT mInitializeFunc; + NP_GETENTRYPOINTS mGetEntryPointsFunc; +#endif + + NPPluginFuncs mFunctions; + + PluginSettings mCachedSettings; + +#if defined(MOZ_WIDGET_GTK) + // If a plugin spins a nested glib event loop in response to a + // synchronous IPC message from the browser, the loop might break + // only after the browser responds to a request sent by the + // plugin. This can happen if a plugin uses gtk's synchronous + // copy/paste, for example. But because the browser is blocked on + // a condvar, it can't respond to the request. This situation + // isn't technically a deadlock, but the symptoms are basically + // the same from the user's perspective. + // + // We take two steps to prevent this + // + // (1) Detect nested event loops spun by the plugin. This is + // done by scheduling a glib timer event in the plugin + // process whenever the browser might block on the plugin. + // If the plugin indeed spins a nested loop, this timer event + // will fire "soon" thereafter. + // + // (2) When a nested loop is detected, deschedule the + // nested-loop-detection timer and in its place, schedule + // another timer that periodically calls back into the + // browser and spins a mini event loop. This mini event loop + // processes a handful of pending native events. + // + // Because only timer (1) or (2) (or neither) may be active at any + // point in time, we use the same member variable + // |mNestedLoopTimerId| to refer to both. + // + // When the browser no longer might be blocked on a plugin's IPC + // response, we deschedule whichever of (1) or (2) is active. + guint mNestedLoopTimerId; +# ifdef DEBUG + // Depth of the stack of calls to g_main_context_dispatch before any + // nested loops are run. This is 1 when IPC calls are dispatched from + // g_main_context_iteration, or 0 when dispatched directly from + // MessagePumpForUI. + int mTopLoopDepth; +# endif +#endif + +#if defined(XP_WIN) + typedef nsTHashtable<nsPtrHashKey<PluginInstanceChild>> PluginInstanceSet; + // Set of plugins that have registered to be notified when the audio device + // changes. + PluginInstanceSet mAudioNotificationSet; +#endif + +public: // called by PluginInstanceChild + /** + * Dealloc an NPObject after last-release or when the associated instance + * is destroyed. This function will remove the object from mObjectMap. + */ + static void DeallocNPObject(NPObject* o); + + NPError NPP_Destroy(PluginInstanceChild* instance) { + return mFunctions.destroy(instance->GetNPP(), 0); + } + +private: +#if defined(OS_WIN) + virtual void EnteredCall() override; + virtual void ExitedCall() override; + + // Entered/ExitedCall notifications keep track of whether the plugin has + // entered a nested event loop within this interrupt call. + struct IncallFrame + { + IncallFrame() + : _spinning(false) + , _savedNestableTasksAllowed(false) + { } + + bool _spinning; + bool _savedNestableTasksAllowed; + }; + + AutoTArray<IncallFrame, 8> mIncallPumpingStack; + + static LRESULT CALLBACK NestedInputEventHook(int code, + WPARAM wParam, + LPARAM lParam); + static LRESULT CALLBACK CallWindowProcHook(int code, + WPARAM wParam, + LPARAM lParam); + void SetEventHooks(); + void ResetEventHooks(); + HHOOK mNestedEventHook; + HHOOK mGlobalCallWndProcHook; +public: + bool mAsyncRenderSupport; +#endif +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif // ifndef dom_plugins_PluginModuleChild_h diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp new file mode 100755 index 000000000..b85a3e94b --- /dev/null +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -0,0 +1,3384 @@ +/* -*- 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/. */ + +#include "mozilla/plugins/PluginModuleParent.h" + +#include "base/process_util.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/PCrashReporterParent.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/plugins/BrowserStreamParent.h" +#include "mozilla/plugins/PluginAsyncSurrogate.h" +#include "mozilla/plugins/PluginBridge.h" +#include "mozilla/plugins/PluginInstanceParent.h" +#include "mozilla/Preferences.h" +#ifdef MOZ_ENABLE_PROFILER_SPS +#include "mozilla/ProfileGatherer.h" +#endif +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "nsAutoPtr.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsIObserverService.h" +#include "nsIXULRuntime.h" +#include "nsNPAPIPlugin.h" +#include "nsPrintfCString.h" +#include "prsystem.h" +#include "PluginQuirks.h" +#include "GeckoProfiler.h" +#include "nsPluginTags.h" +#include "nsUnicharUtils.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" + +#ifdef XP_WIN +#include "mozilla/plugins/PluginSurfaceParent.h" +#include "mozilla/widget/AudioSession.h" +#include "PluginHangUIParent.h" +#include "PluginUtilsWin.h" +#endif + +#ifdef MOZ_ENABLE_PROFILER_SPS +#include "nsIProfiler.h" +#include "nsIProfileSaveEvent.h" +#endif + +#ifdef MOZ_WIDGET_GTK +#include <glib.h> +#elif XP_MACOSX +#include "PluginInterposeOSX.h" +#include "PluginUtilsOSX.h" +#endif + +using base::KillProcess; + +using mozilla::PluginLibrary; +#ifdef MOZ_ENABLE_PROFILER_SPS +using mozilla::ProfileGatherer; +#endif +using mozilla::ipc::MessageChannel; +using mozilla::ipc::GeckoChildProcessHost; +using mozilla::dom::PCrashReporterParent; +using mozilla::dom::CrashReporterParent; + +using namespace mozilla; +using namespace mozilla::plugins; +using namespace mozilla::plugins::parent; + +#ifdef MOZ_CRASHREPORTER +#include "mozilla/dom/CrashReporterParent.h" + +using namespace CrashReporter; +#endif + +static const char kContentTimeoutPref[] = "dom.ipc.plugins.contentTimeoutSecs"; +static const char kChildTimeoutPref[] = "dom.ipc.plugins.timeoutSecs"; +static const char kParentTimeoutPref[] = "dom.ipc.plugins.parentTimeoutSecs"; +static const char kLaunchTimeoutPref[] = "dom.ipc.plugins.processLaunchTimeoutSecs"; +static const char kAsyncInitPref[] = "dom.ipc.plugins.asyncInit.enabled"; +#ifdef XP_WIN +static const char kHangUITimeoutPref[] = "dom.ipc.plugins.hangUITimeoutSecs"; +static const char kHangUIMinDisplayPref[] = "dom.ipc.plugins.hangUIMinDisplaySecs"; +#define CHILD_TIMEOUT_PREF kHangUITimeoutPref +#else +#define CHILD_TIMEOUT_PREF kChildTimeoutPref +#endif + +bool +mozilla::plugins::SetupBridge(uint32_t aPluginId, + dom::ContentParent* aContentParent, + bool aForceBridgeNow, + nsresult* rv, + uint32_t* runID) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + if (NS_WARN_IF(!rv) || NS_WARN_IF(!runID)) { + return false; + } + + PluginModuleChromeParent::ClearInstantiationFlag(); + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + RefPtr<nsNPAPIPlugin> plugin; + *rv = host->GetPluginForContentProcess(aPluginId, getter_AddRefs(plugin)); + if (NS_FAILED(*rv)) { + return true; + } + PluginModuleChromeParent* chromeParent = static_cast<PluginModuleChromeParent*>(plugin->GetLibrary()); + /* + * We can't accumulate BLOCKED_ON_PLUGIN_MODULE_INIT_MS until here because + * its histogram key is not available until *after* NP_Initialize. + */ + chromeParent->AccumulateModuleInitBlockedTime(); + *rv = chromeParent->GetRunID(runID); + if (NS_FAILED(*rv)) { + return true; + } + if (chromeParent->IsStartingAsync()) { + chromeParent->SetContentParent(aContentParent); + } + if (!aForceBridgeNow && chromeParent->IsStartingAsync() && + PluginModuleChromeParent::DidInstantiate()) { + // We'll handle the bridging asynchronously + return true; + } + *rv = PPluginModule::Bridge(aContentParent, chromeParent); + return true; +} + +#ifdef MOZ_CRASHREPORTER_INJECTOR + +/** + * Use for executing CreateToolhelp32Snapshot off main thread + */ +class mozilla::plugins::FinishInjectorInitTask : public mozilla::CancelableRunnable +{ +public: + FinishInjectorInitTask() + : mMutex("FlashInjectorInitTask::mMutex") + , mParent(nullptr) + , mMainThreadMsgLoop(MessageLoop::current()) + { + MOZ_ASSERT(NS_IsMainThread()); + } + + void Init(PluginModuleChromeParent* aParent) + { + MOZ_ASSERT(aParent); + mParent = aParent; + } + + void PostToMainThread() + { + RefPtr<Runnable> self = this; + mSnapshot.own(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); + { // Scope for lock + mozilla::MutexAutoLock lock(mMutex); + if (mMainThreadMsgLoop) { + mMainThreadMsgLoop->PostTask(self.forget()); + } + } + } + + NS_IMETHOD Run() override + { + mParent->DoInjection(mSnapshot); + // We don't need to hold this lock during DoInjection, but we do need + // to obtain it before returning from Run() to ensure that + // PostToMainThread has completed before we return. + mozilla::MutexAutoLock lock(mMutex); + return NS_OK; + } + + nsresult Cancel() override + { + mozilla::MutexAutoLock lock(mMutex); + mMainThreadMsgLoop = nullptr; + return NS_OK; + } + +private: + mozilla::Mutex mMutex; + nsAutoHandle mSnapshot; + PluginModuleChromeParent* mParent; + MessageLoop* mMainThreadMsgLoop; +}; + +#endif // MOZ_CRASHREPORTER_INJECTOR + +namespace { + +/** + * Objects of this class remain linked until either an error occurs in the + * plugin initialization sequence, or until + * PluginModuleContentParent::OnLoadPluginResult has completed executing. + */ +class PluginModuleMapping : public PRCList +{ +public: + explicit PluginModuleMapping(uint32_t aPluginId, bool aAllowAsyncInit) + : mPluginId(aPluginId) + , mAllowAsyncInit(aAllowAsyncInit) + , mProcessIdValid(false) + , mModule(nullptr) + , mChannelOpened(false) + { + MOZ_COUNT_CTOR(PluginModuleMapping); + PR_INIT_CLIST(this); + PR_APPEND_LINK(this, &sModuleListHead); + } + + ~PluginModuleMapping() + { + PR_REMOVE_LINK(this); + MOZ_COUNT_DTOR(PluginModuleMapping); + } + + bool + IsChannelOpened() const + { + return mChannelOpened; + } + + void + SetChannelOpened() + { + mChannelOpened = true; + } + + PluginModuleContentParent* + GetModule() + { + if (!mModule) { + mModule = new PluginModuleContentParent(mAllowAsyncInit); + } + return mModule; + } + + static PluginModuleMapping* + AssociateWithProcessId(uint32_t aPluginId, base::ProcessId aProcessId) + { + PluginModuleMapping* mapping = + static_cast<PluginModuleMapping*>(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + mapping->AssociateWithProcessId(aProcessId); + return mapping; + } + mapping = static_cast<PluginModuleMapping*>(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static PluginModuleMapping* + Resolve(base::ProcessId aProcessId) + { + PluginModuleMapping* mapping = nullptr; + + if (sIsLoadModuleOnStack) { + // Special case: If loading synchronously, we just need to access + // the tail entry of the list. + mapping = + static_cast<PluginModuleMapping*>(PR_LIST_TAIL(&sModuleListHead)); + MOZ_ASSERT(mapping); + return mapping; + } + + mapping = + static_cast<PluginModuleMapping*>(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mProcessIdValid && mapping->mProcessId == aProcessId) { + return mapping; + } + mapping = static_cast<PluginModuleMapping*>(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static PluginModuleMapping* + FindModuleByPluginId(uint32_t aPluginId) + { + PluginModuleMapping* mapping = + static_cast<PluginModuleMapping*>(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + return mapping; + } + mapping = static_cast<PluginModuleMapping*>(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static bool + IsLoadModuleOnStack() + { + return sIsLoadModuleOnStack; + } + + class MOZ_RAII NotifyLoadingModule + { + public: + explicit NotifyLoadingModule(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + PluginModuleMapping::sIsLoadModuleOnStack = true; + } + + ~NotifyLoadingModule() + { + PluginModuleMapping::sIsLoadModuleOnStack = false; + } + + private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + }; + +private: + void + AssociateWithProcessId(base::ProcessId aProcessId) + { + MOZ_ASSERT(!mProcessIdValid); + mProcessId = aProcessId; + mProcessIdValid = true; + } + + uint32_t mPluginId; + bool mAllowAsyncInit; + bool mProcessIdValid; + base::ProcessId mProcessId; + PluginModuleContentParent* mModule; + bool mChannelOpened; + + friend class NotifyLoadingModule; + + static PRCList sModuleListHead; + static bool sIsLoadModuleOnStack; +}; + +PRCList PluginModuleMapping::sModuleListHead = + PR_INIT_STATIC_CLIST(&PluginModuleMapping::sModuleListHead); + +bool PluginModuleMapping::sIsLoadModuleOnStack = false; + +} // namespace + +static PluginModuleChromeParent* +PluginModuleChromeParentForId(const uint32_t aPluginId) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + nsPluginTag* pluginTag = host->PluginWithId(aPluginId); + if (!pluginTag || !pluginTag->mPlugin) { + return nullptr; + } + RefPtr<nsNPAPIPlugin> plugin = pluginTag->mPlugin; + + return static_cast<PluginModuleChromeParent*>(plugin->GetLibrary()); +} + +void +mozilla::plugins::TakeFullMinidump(uint32_t aPluginId, + base::ProcessId aContentProcessId, + const nsAString& aBrowserDumpId, + nsString& aDumpId) +{ + PluginModuleChromeParent* chromeParent = + PluginModuleChromeParentForId(aPluginId); + + if (chromeParent) { + chromeParent->TakeFullMinidump(aContentProcessId, aBrowserDumpId, aDumpId); + } +} + +void +mozilla::plugins::TerminatePlugin(uint32_t aPluginId, + base::ProcessId aContentProcessId, + const nsCString& aMonitorDescription, + const nsAString& aDumpId) +{ + PluginModuleChromeParent* chromeParent = + PluginModuleChromeParentForId(aPluginId); + + if (chromeParent) { + chromeParent->TerminateChildProcess(MessageLoop::current(), + aContentProcessId, + aMonitorDescription, + aDumpId); + } +} + +/* static */ PluginLibrary* +PluginModuleContentParent::LoadModule(uint32_t aPluginId, + nsPluginTag* aPluginTag) +{ + PluginModuleMapping::NotifyLoadingModule loadingModule; + nsAutoPtr<PluginModuleMapping> mapping( + new PluginModuleMapping(aPluginId, aPluginTag->mSupportsAsyncInit)); + + MOZ_ASSERT(XRE_IsContentProcess()); + + /* + * We send a LoadPlugin message to the chrome process using an intr + * message. Before it sends its response, it sends a message to create + * PluginModuleParent instance. That message is handled by + * PluginModuleContentParent::Initialize, which saves the instance in + * its module mapping. We fetch it from there after LoadPlugin finishes. + */ + dom::ContentChild* cp = dom::ContentChild::GetSingleton(); + nsresult rv; + uint32_t runID; + TimeStamp sendLoadPluginStart = TimeStamp::Now(); + if (!cp->SendLoadPlugin(aPluginId, &rv, &runID) || + NS_FAILED(rv)) { + return nullptr; + } + TimeStamp sendLoadPluginEnd = TimeStamp::Now(); + + PluginModuleContentParent* parent = mapping->GetModule(); + MOZ_ASSERT(parent); + parent->mTimeBlocked += (sendLoadPluginEnd - sendLoadPluginStart); + + if (!mapping->IsChannelOpened()) { + // mapping is linked into PluginModuleMapping::sModuleListHead and is + // needed later, so since this function is returning successfully we + // forget it here. + mapping.forget(); + } + + parent->mPluginId = aPluginId; + parent->mRunID = runID; + + return parent; +} + +/* static */ void +PluginModuleContentParent::AssociatePluginId(uint32_t aPluginId, + base::ProcessId aOtherPid) +{ + DebugOnly<PluginModuleMapping*> mapping = + PluginModuleMapping::AssociateWithProcessId(aPluginId, aOtherPid); + MOZ_ASSERT(mapping); +} + +/* static */ PluginModuleContentParent* +PluginModuleContentParent::Initialize(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherPid) +{ + nsAutoPtr<PluginModuleMapping> moduleMapping( + PluginModuleMapping::Resolve(aOtherPid)); + MOZ_ASSERT(moduleMapping); + PluginModuleContentParent* parent = moduleMapping->GetModule(); + MOZ_ASSERT(parent); + + DebugOnly<bool> ok = parent->Open(aTransport, aOtherPid, + XRE_GetIOMessageLoop(), + mozilla::ipc::ParentSide); + MOZ_ASSERT(ok); + + moduleMapping->SetChannelOpened(); + + // Request Windows message deferral behavior on our channel. This + // applies to the top level and all sub plugin protocols since they + // all share the same channel. + parent->GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); + + TimeoutChanged(kContentTimeoutPref, parent); + + // moduleMapping is linked into PluginModuleMapping::sModuleListHead and is + // needed later, so since this function is returning successfully we + // forget it here. + moduleMapping.forget(); + return parent; +} + +/* static */ void +PluginModuleContentParent::OnLoadPluginResult(const uint32_t& aPluginId, + const bool& aResult) +{ + nsAutoPtr<PluginModuleMapping> moduleMapping( + PluginModuleMapping::FindModuleByPluginId(aPluginId)); + MOZ_ASSERT(moduleMapping); + PluginModuleContentParent* parent = moduleMapping->GetModule(); + MOZ_ASSERT(parent); + parent->RecvNP_InitializeResult(aResult ? NPERR_NO_ERROR + : NPERR_GENERIC_ERROR); +} + +void +PluginModuleChromeParent::SetContentParent(dom::ContentParent* aContentParent) +{ + // mContentParent is to be used ONLY during async plugin init! + MOZ_ASSERT(aContentParent && mIsStartingAsync); + mContentParent = aContentParent; +} + +bool +PluginModuleChromeParent::SendAssociatePluginId() +{ + MOZ_ASSERT(mContentParent); + return mContentParent->SendAssociatePluginId(mPluginId, OtherPid()); +} + +// static +PluginLibrary* +PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId, + nsPluginTag* aPluginTag) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + nsAutoPtr<PluginModuleChromeParent> parent( + new PluginModuleChromeParent(aFilePath, aPluginId, + aPluginTag->mSandboxLevel, + aPluginTag->mSupportsAsyncInit)); + UniquePtr<LaunchCompleteTask> onLaunchedRunnable(new LaunchedTask(parent)); + parent->mSubprocess->SetCallRunnableImmediately(!parent->mIsStartingAsync); + TimeStamp launchStart = TimeStamp::Now(); + bool launched = parent->mSubprocess->Launch(Move(onLaunchedRunnable), + aPluginTag->mSandboxLevel); + if (!launched) { + // We never reached open + parent->mShutdown = true; + return nullptr; + } + parent->mIsFlashPlugin = aPluginTag->mIsFlashPlugin; + uint32_t blocklistState; + nsresult rv = aPluginTag->GetBlocklistState(&blocklistState); + parent->mIsBlocklisted = NS_FAILED(rv) || blocklistState != 0; + if (!parent->mIsStartingAsync) { + int32_t launchTimeoutSecs = Preferences::GetInt(kLaunchTimeoutPref, 0); + if (!parent->mSubprocess->WaitUntilConnected(launchTimeoutSecs * 1000)) { + parent->mShutdown = true; + return nullptr; + } + } + TimeStamp launchEnd = TimeStamp::Now(); + parent->mTimeBlocked = (launchEnd - launchStart); + return parent.forget(); +} + +void +PluginModuleChromeParent::OnProcessLaunched(const bool aSucceeded) +{ + if (!aSucceeded) { + mShutdown = true; + OnInitFailure(); + return; + } + // We may have already been initialized by another call that was waiting + // for process connect. If so, this function doesn't need to run. + if (mAsyncInitRv != NS_ERROR_NOT_INITIALIZED || mShutdown) { + return; + } + + Open(mSubprocess->GetChannel(), + base::GetProcId(mSubprocess->GetChildProcessHandle())); + + // Request Windows message deferral behavior on our channel. This + // applies to the top level and all sub plugin protocols since they + // all share the same channel. + GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); + + TimeoutChanged(CHILD_TIMEOUT_PREF, this); + + Preferences::RegisterCallback(TimeoutChanged, kChildTimeoutPref, this); + Preferences::RegisterCallback(TimeoutChanged, kParentTimeoutPref, this); +#ifdef XP_WIN + Preferences::RegisterCallback(TimeoutChanged, kHangUITimeoutPref, this); + Preferences::RegisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this); +#endif + + RegisterSettingsCallbacks(); + +#ifdef MOZ_CRASHREPORTER + // If this fails, we're having IPC troubles, and we're doomed anyways. + if (!CrashReporterParent::CreateCrashReporter(this)) { + mShutdown = true; + Close(); + OnInitFailure(); + return; + } + CrashReporterParent* crashReporter = CrashReporter(); + if (crashReporter) { + crashReporter->AnnotateCrashReport(NS_LITERAL_CSTRING("AsyncPluginInit"), + mIsStartingAsync ? + NS_LITERAL_CSTRING("1") : + NS_LITERAL_CSTRING("0")); + } +#ifdef XP_WIN + { // Scope for lock + mozilla::MutexAutoLock lock(mCrashReporterMutex); + mCrashReporter = CrashReporter(); + } +#endif +#endif + +#if defined(XP_WIN) && defined(_X86_) + // Protected mode only applies to Windows and only to x86. + if (!mIsBlocklisted && mIsFlashPlugin && + (Preferences::GetBool("dom.ipc.plugins.flash.disable-protected-mode", false) || + mSandboxLevel >= 2)) { + SendDisableFlashProtectedMode(); + } +#endif + + if (mInitOnAsyncConnect) { + mInitOnAsyncConnect = false; +#if defined(XP_WIN) + mAsyncInitRv = NP_GetEntryPoints(mNPPIface, + &mAsyncInitError); + if (NS_SUCCEEDED(mAsyncInitRv)) +#endif + { +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + mAsyncInitRv = NP_Initialize(mNPNIface, + mNPPIface, + &mAsyncInitError); +#else + mAsyncInitRv = NP_Initialize(mNPNIface, + &mAsyncInitError); +#endif + } + +#if defined(XP_MACOSX) + if (NS_SUCCEEDED(mAsyncInitRv)) { + mAsyncInitRv = NP_GetEntryPoints(mNPPIface, + &mAsyncInitError); + } +#endif + } + +#ifdef MOZ_ENABLE_PROFILER_SPS + nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1")); + bool profilerActive = false; + DebugOnly<nsresult> rv = profiler->IsActive(&profilerActive); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (profilerActive) { + nsCOMPtr<nsIProfilerStartParams> currentProfilerParams; + rv = profiler->GetStartParams(getter_AddRefs(currentProfilerParams)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + nsCOMPtr<nsISupports> gatherer; + rv = profiler->GetProfileGatherer(getter_AddRefs(gatherer)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + mGatherer = static_cast<ProfileGatherer*>(gatherer.get()); + + StartProfiler(currentProfilerParams); + } +#endif +} + +bool +PluginModuleChromeParent::WaitForIPCConnection() +{ + PluginProcessParent* process = Process(); + MOZ_ASSERT(process); + process->SetCallRunnableImmediately(true); + if (!process->WaitUntilConnected()) { + return false; + } + return true; +} + +PluginModuleParent::PluginModuleParent(bool aIsChrome, bool aAllowAsyncInit) + : mQuirks(QUIRKS_NOT_INITIALIZED) + , mIsChrome(aIsChrome) + , mShutdown(false) + , mHadLocalInstance(false) + , mClearSiteDataSupported(false) + , mGetSitesWithDataSupported(false) + , mNPNIface(nullptr) + , mNPPIface(nullptr) + , mPlugin(nullptr) + , mTaskFactory(this) + , mSandboxLevel(0) + , mIsFlashPlugin(false) + , mIsStartingAsync(false) + , mNPInitialized(false) + , mIsNPShutdownPending(false) + , mAsyncNewRv(NS_ERROR_NOT_INITIALIZED) +{ +#if defined(MOZ_CRASHREPORTER) + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AsyncPluginInit"), + mIsStartingAsync ? + NS_LITERAL_CSTRING("1") : + NS_LITERAL_CSTRING("0")); +#endif +} + +PluginModuleParent::~PluginModuleParent() +{ + if (!OkToCleanup()) { + NS_RUNTIMEABORT("unsafe destruction"); + } + + if (!mShutdown) { + NS_WARNING("Plugin host deleted the module without shutting down."); + NPError err; + NP_Shutdown(&err); + } +} + +PluginModuleContentParent::PluginModuleContentParent(bool aAllowAsyncInit) + : PluginModuleParent(false, aAllowAsyncInit) +{ + Preferences::RegisterCallback(TimeoutChanged, kContentTimeoutPref, this); +} + +PluginModuleContentParent::~PluginModuleContentParent() +{ + Preferences::UnregisterCallback(TimeoutChanged, kContentTimeoutPref, this); +} + +bool PluginModuleChromeParent::sInstantiated = false; + +PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, + uint32_t aPluginId, + int32_t aSandboxLevel, + bool aAllowAsyncInit) + : PluginModuleParent(true, aAllowAsyncInit) + , mSubprocess(new PluginProcessParent(aFilePath)) + , mPluginId(aPluginId) + , mChromeTaskFactory(this) + , mHangAnnotationFlags(0) +#ifdef XP_WIN + , mPluginCpuUsageOnHang() + , mHangUIParent(nullptr) + , mHangUIEnabled(true) + , mIsTimerReset(true) +#ifdef MOZ_CRASHREPORTER + , mCrashReporterMutex("PluginModuleChromeParent::mCrashReporterMutex") + , mCrashReporter(nullptr) +#endif +#endif +#ifdef MOZ_CRASHREPORTER_INJECTOR + , mFlashProcess1(0) + , mFlashProcess2(0) + , mFinishInitTask(nullptr) +#endif + , mInitOnAsyncConnect(false) + , mAsyncInitRv(NS_ERROR_NOT_INITIALIZED) + , mAsyncInitError(NPERR_NO_ERROR) + , mContentParent(nullptr) +{ + NS_ASSERTION(mSubprocess, "Out of memory!"); + sInstantiated = true; + mSandboxLevel = aSandboxLevel; + mRunID = GeckoChildProcessHost::GetUniqueID(); + +#ifdef MOZ_ENABLE_PROFILER_SPS + InitPluginProfiling(); +#endif + + mozilla::HangMonitor::RegisterAnnotator(*this); +} + +PluginModuleChromeParent::~PluginModuleChromeParent() +{ + if (!OkToCleanup()) { + NS_RUNTIMEABORT("unsafe destruction"); + } + +#ifdef MOZ_ENABLE_PROFILER_SPS + ShutdownPluginProfiling(); +#endif + +#ifdef XP_WIN + // If we registered for audio notifications, stop. + mozilla::plugins::PluginUtilsWin::RegisterForAudioDeviceChanges(this, + false); +#endif + + if (!mShutdown) { + NS_WARNING("Plugin host deleted the module without shutting down."); + NPError err; + NP_Shutdown(&err); + } + + NS_ASSERTION(mShutdown, "NP_Shutdown didn't"); + + if (mSubprocess) { + mSubprocess->Delete(); + mSubprocess = nullptr; + } + +#ifdef MOZ_CRASHREPORTER_INJECTOR + if (mFlashProcess1) + UnregisterInjectorCallback(mFlashProcess1); + if (mFlashProcess2) + UnregisterInjectorCallback(mFlashProcess2); + if (mFinishInitTask) { + // mFinishInitTask will be deleted by the main thread message_loop + mFinishInitTask->Cancel(); + } +#endif + + UnregisterSettingsCallbacks(); + + Preferences::UnregisterCallback(TimeoutChanged, kChildTimeoutPref, this); + Preferences::UnregisterCallback(TimeoutChanged, kParentTimeoutPref, this); +#ifdef XP_WIN + Preferences::UnregisterCallback(TimeoutChanged, kHangUITimeoutPref, this); + Preferences::UnregisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this); + + if (mHangUIParent) { + delete mHangUIParent; + mHangUIParent = nullptr; + } +#endif + + mozilla::HangMonitor::UnregisterAnnotator(*this); +} + +#ifdef MOZ_CRASHREPORTER +void +PluginModuleChromeParent::WriteExtraDataForMinidump(AnnotationTable& notes) +{ +#ifdef XP_WIN + // mCrashReporterMutex is already held by the caller + mCrashReporterMutex.AssertCurrentThreadOwns(); +#endif + typedef nsDependentCString CS; + + // Get the plugin filename, try to get just the file leafname + const std::string& pluginFile = mSubprocess->GetPluginFilePath(); + size_t filePos = pluginFile.rfind(FILE_PATH_SEPARATOR); + if (filePos == std::string::npos) + filePos = 0; + else + filePos++; + notes.Put(NS_LITERAL_CSTRING("PluginFilename"), CS(pluginFile.substr(filePos).c_str())); + + notes.Put(NS_LITERAL_CSTRING("PluginName"), mPluginName); + notes.Put(NS_LITERAL_CSTRING("PluginVersion"), mPluginVersion); + + CrashReporterParent* crashReporter = CrashReporter(); + if (crashReporter) { +#ifdef XP_WIN + if (mPluginCpuUsageOnHang.Length() > 0) { + notes.Put(NS_LITERAL_CSTRING("NumberOfProcessors"), + nsPrintfCString("%d", PR_GetNumberOfProcessors())); + + nsCString cpuUsageStr; + cpuUsageStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[0] * 100) / 100); + notes.Put(NS_LITERAL_CSTRING("PluginCpuUsage"), cpuUsageStr); + +#ifdef MOZ_CRASHREPORTER_INJECTOR + for (uint32_t i=1; i<mPluginCpuUsageOnHang.Length(); ++i) { + nsCString tempStr; + tempStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[i] * 100) / 100); + notes.Put(nsPrintfCString("CpuUsageFlashProcess%d", i), tempStr); + } +#endif + } +#endif + } +} +#endif // MOZ_CRASHREPORTER + +void +PluginModuleParent::SetChildTimeout(const int32_t aChildTimeout) +{ + int32_t timeoutMs = (aChildTimeout > 0) ? (1000 * aChildTimeout) : + MessageChannel::kNoTimeout; + SetReplyTimeoutMs(timeoutMs); +} + +void +PluginModuleParent::TimeoutChanged(const char* aPref, void* aModule) +{ + PluginModuleParent* module = static_cast<PluginModuleParent*>(aModule); + + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); +#ifndef XP_WIN + if (!strcmp(aPref, kChildTimeoutPref)) { + MOZ_ASSERT(module->IsChrome()); + // The timeout value used by the parent for children + int32_t timeoutSecs = Preferences::GetInt(kChildTimeoutPref, 0); + module->SetChildTimeout(timeoutSecs); +#else + if (!strcmp(aPref, kChildTimeoutPref) || + !strcmp(aPref, kHangUIMinDisplayPref) || + !strcmp(aPref, kHangUITimeoutPref)) { + MOZ_ASSERT(module->IsChrome()); + static_cast<PluginModuleChromeParent*>(module)->EvaluateHangUIState(true); +#endif // XP_WIN + } else if (!strcmp(aPref, kParentTimeoutPref)) { + // The timeout value used by the child for its parent + MOZ_ASSERT(module->IsChrome()); + int32_t timeoutSecs = Preferences::GetInt(kParentTimeoutPref, 0); + Unused << static_cast<PluginModuleChromeParent*>(module)->SendSetParentHangTimeout(timeoutSecs); + } else if (!strcmp(aPref, kContentTimeoutPref)) { + MOZ_ASSERT(!module->IsChrome()); + int32_t timeoutSecs = Preferences::GetInt(kContentTimeoutPref, 0); + module->SetChildTimeout(timeoutSecs); + } +} + +void +PluginModuleChromeParent::CleanupFromTimeout(const bool aFromHangUI) +{ + if (mShutdown) { + return; + } + + if (!OkToCleanup()) { + // there's still plugin code on the C++ stack, try again + MessageLoop::current()->PostDelayedTask( + mChromeTaskFactory.NewRunnableMethod( + &PluginModuleChromeParent::CleanupFromTimeout, aFromHangUI), 10); + return; + } + + /* If the plugin container was terminated by the Plugin Hang UI, + then either the I/O thread detects a channel error, or the + main thread must set the error (whomever gets there first). + OTOH, if we terminate and return false from + ShouldContinueFromReplyTimeout, then the channel state has + already been set to ChannelTimeout and we should call the + regular Close function. */ + if (aFromHangUI) { + GetIPCChannel()->CloseWithError(); + } else { + Close(); + } +} + +#ifdef XP_WIN +namespace { + +uint64_t +FileTimeToUTC(const FILETIME& ftime) +{ + ULARGE_INTEGER li; + li.LowPart = ftime.dwLowDateTime; + li.HighPart = ftime.dwHighDateTime; + return li.QuadPart; +} + +struct CpuUsageSamples +{ + uint64_t sampleTimes[2]; + uint64_t cpuTimes[2]; +}; + +bool +GetProcessCpuUsage(const InfallibleTArray<base::ProcessHandle>& processHandles, InfallibleTArray<float>& cpuUsage) +{ + InfallibleTArray<CpuUsageSamples> samples(processHandles.Length()); + FILETIME creationTime, exitTime, kernelTime, userTime, currentTime; + BOOL res; + + for (uint32_t i = 0; i < processHandles.Length(); ++i) { + ::GetSystemTimeAsFileTime(¤tTime); + res = ::GetProcessTimes(processHandles[i], &creationTime, &exitTime, &kernelTime, &userTime); + if (!res) { + NS_WARNING("failed to get process times"); + return false; + } + + CpuUsageSamples s; + s.sampleTimes[0] = FileTimeToUTC(currentTime); + s.cpuTimes[0] = FileTimeToUTC(kernelTime) + FileTimeToUTC(userTime); + samples.AppendElement(s); + } + + // we already hung for a while, a little bit longer won't matter + ::Sleep(50); + + const int32_t numberOfProcessors = PR_GetNumberOfProcessors(); + + for (uint32_t i = 0; i < processHandles.Length(); ++i) { + ::GetSystemTimeAsFileTime(¤tTime); + res = ::GetProcessTimes(processHandles[i], &creationTime, &exitTime, &kernelTime, &userTime); + if (!res) { + NS_WARNING("failed to get process times"); + return false; + } + + samples[i].sampleTimes[1] = FileTimeToUTC(currentTime); + samples[i].cpuTimes[1] = FileTimeToUTC(kernelTime) + FileTimeToUTC(userTime); + + const uint64_t deltaSampleTime = samples[i].sampleTimes[1] - samples[i].sampleTimes[0]; + const uint64_t deltaCpuTime = samples[i].cpuTimes[1] - samples[i].cpuTimes[0]; + const float usage = 100.f * (float(deltaCpuTime) / deltaSampleTime) / numberOfProcessors; + cpuUsage.AppendElement(usage); + } + + return true; +} + +} // namespace + +#endif // #ifdef XP_WIN + +/** + * This function converts the topmost routing id on the call stack (as recorded + * by the MessageChannel) into a pointer to a IProtocol object. + */ +mozilla::ipc::IProtocol* +PluginModuleChromeParent::GetInvokingProtocol() +{ + int32_t routingId = GetIPCChannel()->GetTopmostMessageRoutingId(); + // Nothing being routed. No protocol. Just return nullptr. + if (routingId == MSG_ROUTING_NONE) { + return nullptr; + } + // If routingId is MSG_ROUTING_CONTROL then we're dealing with control + // messages that were initiated by the topmost managing protocol, ie. this. + if (routingId == MSG_ROUTING_CONTROL) { + return this; + } + // Otherwise we can look up the protocol object by the routing id. + mozilla::ipc::IProtocol* protocol = Lookup(routingId); + return protocol; +} + +/** + * This function examines the IProtocol object parameter and converts it into + * the PluginInstanceParent object that is associated with that protocol, if + * any. Since PluginInstanceParent manages subprotocols, this function needs + * to determine whether |aProtocol| is a subprotocol, and if so it needs to + * obtain the protocol's manager. + * + * This function needs to be updated if the subprotocols are modified in + * PPluginInstance.ipdl. + */ +PluginInstanceParent* +PluginModuleChromeParent::GetManagingInstance(mozilla::ipc::IProtocol* aProtocol) +{ + MOZ_ASSERT(aProtocol); + mozilla::ipc::IProtocol* listener = aProtocol; + switch (listener->GetProtocolTypeId()) { + case PPluginInstanceMsgStart: + // In this case, aProtocol is the instance itself. Just cast it. + return static_cast<PluginInstanceParent*>(aProtocol); + case PPluginBackgroundDestroyerMsgStart: { + PPluginBackgroundDestroyerParent* actor = + static_cast<PPluginBackgroundDestroyerParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } + case PPluginScriptableObjectMsgStart: { + PPluginScriptableObjectParent* actor = + static_cast<PPluginScriptableObjectParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } + case PBrowserStreamMsgStart: { + PBrowserStreamParent* actor = + static_cast<PBrowserStreamParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } + case PPluginStreamMsgStart: { + PPluginStreamParent* actor = + static_cast<PPluginStreamParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } + case PStreamNotifyMsgStart: { + PStreamNotifyParent* actor = + static_cast<PStreamNotifyParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } +#ifdef XP_WIN + case PPluginSurfaceMsgStart: { + PPluginSurfaceParent* actor = + static_cast<PPluginSurfaceParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } +#endif + default: + return nullptr; + } +} + +void +PluginModuleChromeParent::EnteredCxxStack() +{ + mHangAnnotationFlags |= kInPluginCall; +} + +void +PluginModuleChromeParent::ExitedCxxStack() +{ + mHangAnnotationFlags = 0; +#ifdef XP_WIN + FinishHangUI(); +#endif +} + +/** + * This function is always called by the HangMonitor thread. + */ +void +PluginModuleChromeParent::AnnotateHang(mozilla::HangMonitor::HangAnnotations& aAnnotations) +{ + uint32_t flags = mHangAnnotationFlags; + if (flags) { + /* We don't actually annotate anything specifically for kInPluginCall; + we use it to determine whether to annotate other things. It will + be pretty obvious from the ChromeHang stack that we're in a plugin + call when the hang occurred. */ + if (flags & kHangUIShown) { + aAnnotations.AddAnnotation(NS_LITERAL_STRING("HangUIShown"), + true); + } + if (flags & kHangUIContinued) { + aAnnotations.AddAnnotation(NS_LITERAL_STRING("HangUIContinued"), + true); + } + if (flags & kHangUIDontShow) { + aAnnotations.AddAnnotation(NS_LITERAL_STRING("HangUIDontShow"), + true); + } + aAnnotations.AddAnnotation(NS_LITERAL_STRING("pluginName"), mPluginName); + aAnnotations.AddAnnotation(NS_LITERAL_STRING("pluginVersion"), + mPluginVersion); + } +} + +#ifdef MOZ_CRASHREPORTER +static bool +CreatePluginMinidump(base::ProcessId processId, ThreadId childThread, + nsIFile* parentMinidump, const nsACString& name) +{ + mozilla::ipc::ScopedProcessHandle handle; + if (processId == 0 || + !base::OpenPrivilegedProcessHandle(processId, &handle.rwget())) { + return false; + } + return CreateAdditionalChildMinidump(handle, 0, parentMinidump, name); +} +#endif + +bool +PluginModuleChromeParent::ShouldContinueFromReplyTimeout() +{ + if (mIsFlashPlugin) { + MessageLoop::current()->PostTask( + mTaskFactory.NewRunnableMethod( + &PluginModuleChromeParent::NotifyFlashHang)); + } + +#ifdef XP_WIN + if (LaunchHangUI()) { + return true; + } + // If LaunchHangUI returned false then we should proceed with the + // original plugin hang behaviour and kill the plugin container. + FinishHangUI(); +#endif // XP_WIN + TerminateChildProcess(MessageLoop::current(), + mozilla::ipc::kInvalidProcessId, + NS_LITERAL_CSTRING("ModalHangUI"), + EmptyString()); + GetIPCChannel()->CloseWithTimeout(); + return false; +} + +bool +PluginModuleContentParent::ShouldContinueFromReplyTimeout() +{ + RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get(); + if (!monitor) { + return true; + } + monitor->NotifyPluginHang(mPluginId); + return true; +} + +void +PluginModuleContentParent::OnExitedSyncSend() +{ + ProcessHangMonitor::ClearHang(); +} + +void +PluginModuleChromeParent::TakeFullMinidump(base::ProcessId aContentPid, + const nsAString& aBrowserDumpId, + nsString& aDumpId) +{ +#ifdef MOZ_CRASHREPORTER +#ifdef XP_WIN + mozilla::MutexAutoLock lock(mCrashReporterMutex); +#endif // XP_WIN + + CrashReporterParent* crashReporter = CrashReporter(); + if (!crashReporter) { + return; + } + + bool reportsReady = false; + + // Check to see if we already have a browser dump id - with e10s plugin + // hangs we take this earlier (see ProcessHangMonitor) from a background + // thread. We do this before we message the main thread about the hang + // since the posted message will trash our browser stack state. + bool exists; + nsCOMPtr<nsIFile> browserDumpFile; + if (!aBrowserDumpId.IsEmpty() && + CrashReporter::GetMinidumpForID(aBrowserDumpId, getter_AddRefs(browserDumpFile)) && + browserDumpFile && + NS_SUCCEEDED(browserDumpFile->Exists(&exists)) && exists) + { + // We have a single browser report, generate a new plugin process parent + // report and pair it up with the browser report handed in. + reportsReady = crashReporter->GenerateMinidumpAndPair(this, browserDumpFile, + NS_LITERAL_CSTRING("browser")); + if (!reportsReady) { + browserDumpFile = nullptr; + CrashReporter::DeleteMinidumpFilesForID(aBrowserDumpId); + } + } + + // Generate crash report including plugin and browser process minidumps. + // The plugin process is the parent report with additional dumps including + // the browser process, content process when running under e10s, and + // various flash subprocesses if we're the flash module. + if (!reportsReady) { + reportsReady = crashReporter->GeneratePairedMinidump(this); + } + + if (reportsReady) { + // Important to set this here, it tells the ActorDestroy handler + // that we have an existing crash report that needs to be finalized. + mPluginDumpID = crashReporter->ChildDumpID(); + aDumpId = mPluginDumpID; + PLUGIN_LOG_DEBUG( + ("generated paired browser/plugin minidumps: %s)", + NS_ConvertUTF16toUTF8(mPluginDumpID).get())); + nsAutoCString additionalDumps("browser"); + nsCOMPtr<nsIFile> pluginDumpFile; + if (GetMinidumpForID(mPluginDumpID, getter_AddRefs(pluginDumpFile)) && + pluginDumpFile) { +#ifdef MOZ_CRASHREPORTER_INJECTOR + // If we have handles to the flash sandbox processes on Windows, + // include those minidumps as well. + if (CreatePluginMinidump(mFlashProcess1, 0, pluginDumpFile, + NS_LITERAL_CSTRING("flash1"))) { + additionalDumps.AppendLiteral(",flash1"); + } + if (CreatePluginMinidump(mFlashProcess2, 0, pluginDumpFile, + NS_LITERAL_CSTRING("flash2"))) { + additionalDumps.AppendLiteral(",flash2"); + } +#endif // MOZ_CRASHREPORTER_INJECTOR + if (aContentPid != mozilla::ipc::kInvalidProcessId) { + // Include the content process minidump + if (CreatePluginMinidump(aContentPid, 0, + pluginDumpFile, + NS_LITERAL_CSTRING("content"))) { + additionalDumps.AppendLiteral(",content"); + } + } + } + crashReporter->AnnotateCrashReport( + NS_LITERAL_CSTRING("additional_minidumps"), + additionalDumps); + } else { + NS_WARNING("failed to capture paired minidumps from hang"); + } +#endif // MOZ_CRASHREPORTER +} + +void +PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop, + base::ProcessId aContentPid, + const nsCString& aMonitorDescription, + const nsAString& aDumpId) +{ +#ifdef MOZ_CRASHREPORTER + // Start by taking a full minidump if necessary, this is done early + // because it also needs to lock the mCrashReporterMutex and Mutex doesn't + // support recrusive locking. + nsAutoString dumpId; + if (aDumpId.IsEmpty()) { + TakeFullMinidump(aContentPid, EmptyString(), dumpId); + } + +#ifdef XP_WIN + mozilla::MutexAutoLock lock(mCrashReporterMutex); + CrashReporterParent* crashReporter = mCrashReporter; + if (!crashReporter) { + // If mCrashReporter is null then the hang has ended, the plugin module + // is shutting down. There's nothing to do here. + return; + } +#else + CrashReporterParent* crashReporter = CrashReporter(); +#endif // XP_WIN + crashReporter->AnnotateCrashReport(NS_LITERAL_CSTRING("PluginHang"), + NS_LITERAL_CSTRING("1")); + crashReporter->AnnotateCrashReport(NS_LITERAL_CSTRING("HangMonitorDescription"), + aMonitorDescription); +#ifdef XP_WIN + if (mHangUIParent) { + unsigned int hangUIDuration = mHangUIParent->LastShowDurationMs(); + if (hangUIDuration) { + nsPrintfCString strHangUIDuration("%u", hangUIDuration); + crashReporter->AnnotateCrashReport( + NS_LITERAL_CSTRING("PluginHangUIDuration"), + strHangUIDuration); + } + } +#endif // XP_WIN +#endif // MOZ_CRASHREPORTER + + mozilla::ipc::ScopedProcessHandle geckoChildProcess; + bool childOpened = base::OpenProcessHandle(OtherPid(), + &geckoChildProcess.rwget()); + +#ifdef XP_WIN + // collect cpu usage for plugin processes + + InfallibleTArray<base::ProcessHandle> processHandles; + + if (childOpened) { + processHandles.AppendElement(geckoChildProcess); + } + +#ifdef MOZ_CRASHREPORTER_INJECTOR + mozilla::ipc::ScopedProcessHandle flashBrokerProcess; + if (mFlashProcess1 && + base::OpenProcessHandle(mFlashProcess1, &flashBrokerProcess.rwget())) { + processHandles.AppendElement(flashBrokerProcess); + } + mozilla::ipc::ScopedProcessHandle flashSandboxProcess; + if (mFlashProcess2 && + base::OpenProcessHandle(mFlashProcess2, &flashSandboxProcess.rwget())) { + processHandles.AppendElement(flashSandboxProcess); + } +#endif + + if (!GetProcessCpuUsage(processHandles, mPluginCpuUsageOnHang)) { + mPluginCpuUsageOnHang.Clear(); + } +#endif + + // this must run before the error notification from the channel, + // or not at all + bool isFromHangUI = aMsgLoop != MessageLoop::current(); + aMsgLoop->PostTask( + mChromeTaskFactory.NewRunnableMethod( + &PluginModuleChromeParent::CleanupFromTimeout, isFromHangUI)); + + if (!childOpened || !KillProcess(geckoChildProcess, 1, false)) { + NS_WARNING("failed to kill subprocess!"); + } +} + +bool +PluginModuleParent::GetPluginDetails() +{ + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + if (!host) { + return false; + } + nsPluginTag* pluginTag = host->TagForPlugin(mPlugin); + if (!pluginTag) { + return false; + } + mPluginName = pluginTag->Name(); + mPluginVersion = pluginTag->Version(); + mPluginFilename = pluginTag->FileName(); + mIsFlashPlugin = pluginTag->mIsFlashPlugin; + mSandboxLevel = pluginTag->mSandboxLevel; + return true; +} + +void +PluginModuleParent::InitQuirksModes(const nsCString& aMimeType) +{ + if (mQuirks != QUIRKS_NOT_INITIALIZED) { + return; + } + + mQuirks = GetQuirksFromMimeTypeAndFilename(aMimeType, mPluginFilename); +} + +#ifdef XP_WIN +void +PluginModuleChromeParent::EvaluateHangUIState(const bool aReset) +{ + int32_t minDispSecs = Preferences::GetInt(kHangUIMinDisplayPref, 10); + int32_t autoStopSecs = Preferences::GetInt(kChildTimeoutPref, 0); + int32_t timeoutSecs = 0; + if (autoStopSecs > 0 && autoStopSecs < minDispSecs) { + /* If we're going to automatically terminate the plugin within a + time frame shorter than minDispSecs, there's no point in + showing the hang UI; it would just flash briefly on the screen. */ + mHangUIEnabled = false; + } else { + timeoutSecs = Preferences::GetInt(kHangUITimeoutPref, 0); + mHangUIEnabled = timeoutSecs > 0; + } + if (mHangUIEnabled) { + if (aReset) { + mIsTimerReset = true; + SetChildTimeout(timeoutSecs); + return; + } else if (mIsTimerReset) { + /* The Hang UI is being shown, so now we're setting the + timeout to kChildTimeoutPref while we wait for a user + response. ShouldContinueFromReplyTimeout will fire + after (reply timeout / 2) seconds, which is not what + we want. Doubling the timeout value here so that we get + the right result. */ + autoStopSecs *= 2; + } + } + mIsTimerReset = false; + SetChildTimeout(autoStopSecs); +} + +bool +PluginModuleChromeParent::LaunchHangUI() +{ + if (!mHangUIEnabled) { + return false; + } + if (mHangUIParent) { + if (mHangUIParent->IsShowing()) { + // We've already shown the UI but the timeout has expired again. + return false; + } + if (mHangUIParent->DontShowAgain()) { + mHangAnnotationFlags |= kHangUIDontShow; + bool wasLastHangStopped = mHangUIParent->WasLastHangStopped(); + if (!wasLastHangStopped) { + mHangAnnotationFlags |= kHangUIContinued; + } + return !wasLastHangStopped; + } + delete mHangUIParent; + mHangUIParent = nullptr; + } + mHangUIParent = new PluginHangUIParent(this, + Preferences::GetInt(kHangUITimeoutPref, 0), + Preferences::GetInt(kChildTimeoutPref, 0)); + bool retval = mHangUIParent->Init(NS_ConvertUTF8toUTF16(mPluginName)); + if (retval) { + mHangAnnotationFlags |= kHangUIShown; + /* Once the UI is shown we switch the timeout over to use + kChildTimeoutPref, allowing us to terminate a hung plugin + after kChildTimeoutPref seconds if the user doesn't respond to + the hang UI. */ + EvaluateHangUIState(false); + } + return retval; +} + +void +PluginModuleChromeParent::FinishHangUI() +{ + if (mHangUIEnabled && mHangUIParent) { + bool needsCancel = mHangUIParent->IsShowing(); + // If we're still showing, send a Cancel notification + if (needsCancel) { + mHangUIParent->Cancel(); + } + /* If we cancelled the UI or if the user issued a response, + we need to reset the child process timeout. */ + if (needsCancel || + (!mIsTimerReset && mHangUIParent->WasShown())) { + /* We changed the timeout to kChildTimeoutPref when the plugin hang + UI was displayed. Now that we're finishing the UI, we need to + switch it back to kHangUITimeoutPref. */ + EvaluateHangUIState(true); + } + } +} + +void +PluginModuleChromeParent::OnHangUIContinue() +{ + mHangAnnotationFlags |= kHangUIContinued; +} +#endif // XP_WIN + +#ifdef MOZ_CRASHREPORTER +CrashReporterParent* +PluginModuleChromeParent::CrashReporter() +{ + return static_cast<CrashReporterParent*>(LoneManagedOrNullAsserts(ManagedPCrashReporterParent())); +} + +#ifdef MOZ_CRASHREPORTER_INJECTOR +static void +RemoveMinidump(nsIFile* minidump) +{ + if (!minidump) + return; + + minidump->Remove(false); + nsCOMPtr<nsIFile> extraFile; + if (GetExtraFileForMinidump(minidump, + getter_AddRefs(extraFile))) { + extraFile->Remove(true); + } +} +#endif // MOZ_CRASHREPORTER_INJECTOR + +void +PluginModuleChromeParent::ProcessFirstMinidump() +{ +#ifdef XP_WIN + mozilla::MutexAutoLock lock(mCrashReporterMutex); +#endif + CrashReporterParent* crashReporter = CrashReporter(); + if (!crashReporter) + return; + + AnnotationTable notes(4); + WriteExtraDataForMinidump(notes); + + if (!mPluginDumpID.IsEmpty()) { + // mPluginDumpID may be set in TerminateChildProcess, which means the + // process hang monitor has already collected a 3-way browser, plugin, + // content crash report. If so, update the existing report with our + // annotations and finalize it. If not, fall through for standard + // plugin crash report handling. + crashReporter->GenerateChildData(¬es); + crashReporter->FinalizeChildData(); + return; + } + + uint32_t sequence = UINT32_MAX; + nsCOMPtr<nsIFile> dumpFile; + nsAutoCString flashProcessType; + TakeMinidump(getter_AddRefs(dumpFile), &sequence); + +#ifdef MOZ_CRASHREPORTER_INJECTOR + nsCOMPtr<nsIFile> childDumpFile; + uint32_t childSequence; + + if (mFlashProcess1 && + TakeMinidumpForChild(mFlashProcess1, + getter_AddRefs(childDumpFile), + &childSequence)) { + if (childSequence < sequence) { + RemoveMinidump(dumpFile); + dumpFile = childDumpFile; + sequence = childSequence; + flashProcessType.AssignLiteral("Broker"); + } + else { + RemoveMinidump(childDumpFile); + } + } + if (mFlashProcess2 && + TakeMinidumpForChild(mFlashProcess2, + getter_AddRefs(childDumpFile), + &childSequence)) { + if (childSequence < sequence) { + RemoveMinidump(dumpFile); + dumpFile = childDumpFile; + sequence = childSequence; + flashProcessType.AssignLiteral("Sandbox"); + } + else { + RemoveMinidump(childDumpFile); + } + } +#endif + + if (!dumpFile) { + NS_WARNING("[PluginModuleParent::ActorDestroy] abnormal shutdown without minidump!"); + return; + } + + PLUGIN_LOG_DEBUG(("got child minidump: %s", + NS_ConvertUTF16toUTF8(mPluginDumpID).get())); + + GetIDFromMinidump(dumpFile, mPluginDumpID); + if (!flashProcessType.IsEmpty()) { + notes.Put(NS_LITERAL_CSTRING("FlashProcessDump"), flashProcessType); + } + crashReporter->GenerateCrashReportForMinidump(dumpFile, ¬es); +} +#endif + +void +PluginModuleParent::ActorDestroy(ActorDestroyReason why) +{ + switch (why) { + case AbnormalShutdown: { + mShutdown = true; + // Defer the PluginCrashed method so that we don't re-enter + // and potentially modify the actor child list while enumerating it. + if (mPlugin) + MessageLoop::current()->PostTask( + mTaskFactory.NewRunnableMethod( + &PluginModuleParent::NotifyPluginCrashed)); + break; + } + case NormalShutdown: + mShutdown = true; + break; + + default: + NS_RUNTIMEABORT("Unexpected shutdown reason for toplevel actor."); + } +} + +nsresult +PluginModuleParent::GetRunID(uint32_t* aRunID) +{ + if (NS_WARN_IF(!aRunID)) { + return NS_ERROR_INVALID_POINTER; + } + *aRunID = mRunID; + return NS_OK; +} + +void +PluginModuleChromeParent::ActorDestroy(ActorDestroyReason why) +{ + if (why == AbnormalShutdown) { +#ifdef MOZ_CRASHREPORTER + ProcessFirstMinidump(); +#endif + Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, + NS_LITERAL_CSTRING("plugin"), 1); + } + + // We can't broadcast settings changes anymore. + UnregisterSettingsCallbacks(); + + PluginModuleParent::ActorDestroy(why); +} + +void +PluginModuleParent::NotifyFlashHang() +{ + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "flash-plugin-hang", nullptr); + } +} + +void +PluginModuleParent::NotifyPluginCrashed() +{ + if (!OkToCleanup()) { + // there's still plugin code on the C++ stack. try again + MessageLoop::current()->PostDelayedTask( + mTaskFactory.NewRunnableMethod( + &PluginModuleParent::NotifyPluginCrashed), 10); + return; + } + + if (mPlugin) + mPlugin->PluginCrashed(mPluginDumpID, mBrowserDumpID); +} + +PPluginInstanceParent* +PluginModuleParent::AllocPPluginInstanceParent(const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray<nsCString>& aNames, + const InfallibleTArray<nsCString>& aValues) +{ + NS_ERROR("Not reachable!"); + return nullptr; +} + +bool +PluginModuleParent::DeallocPPluginInstanceParent(PPluginInstanceParent* aActor) +{ + PLUGIN_LOG_DEBUG_METHOD; + delete aActor; + return true; +} + +void +PluginModuleParent::SetPluginFuncs(NPPluginFuncs* aFuncs) +{ + MOZ_ASSERT(aFuncs); + + aFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + aFuncs->javaClass = nullptr; + + // Gecko should always call these functions through a PluginLibrary object. + aFuncs->newp = nullptr; + aFuncs->clearsitedata = nullptr; + aFuncs->getsiteswithdata = nullptr; + + aFuncs->destroy = NPP_Destroy; + aFuncs->setwindow = NPP_SetWindow; + aFuncs->newstream = NPP_NewStream; + aFuncs->destroystream = NPP_DestroyStream; + aFuncs->asfile = NPP_StreamAsFile; + aFuncs->writeready = NPP_WriteReady; + aFuncs->write = NPP_Write; + aFuncs->print = NPP_Print; + aFuncs->event = NPP_HandleEvent; + aFuncs->urlnotify = NPP_URLNotify; + aFuncs->getvalue = NPP_GetValue; + aFuncs->setvalue = NPP_SetValue; + aFuncs->gotfocus = nullptr; + aFuncs->lostfocus = nullptr; + aFuncs->urlredirectnotify = nullptr; + + // Provide 'NPP_URLRedirectNotify', 'NPP_ClearSiteData', and + // 'NPP_GetSitesWithData' functionality if it is supported by the plugin. + bool urlRedirectSupported = false; + Unused << CallOptionalFunctionsSupported(&urlRedirectSupported, + &mClearSiteDataSupported, + &mGetSitesWithDataSupported); + if (urlRedirectSupported) { + aFuncs->urlredirectnotify = NPP_URLRedirectNotify; + } +} + +#define RESOLVE_AND_CALL(instance, func) \ +NP_BEGIN_MACRO \ + PluginAsyncSurrogate* surrogate = nullptr; \ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance, &surrogate);\ + if (surrogate && (!i || i->UseSurrogate())) { \ + return surrogate->func; \ + } \ + if (!i) { \ + return NPERR_GENERIC_ERROR; \ + } \ + return i->func; \ +NP_END_MACRO + +NPError +PluginModuleParent::NPP_Destroy(NPP instance, + NPSavedData** saved) +{ + // FIXME/cjones: + // (1) send a "destroy" message to the child + // (2) the child shuts down its instance + // (3) remove both parent and child IDs from map + // (4) free parent + + PLUGIN_LOG_DEBUG_FUNCTION; + PluginAsyncSurrogate* surrogate = nullptr; + PluginInstanceParent* parentInstance = + PluginInstanceParent::Cast(instance, &surrogate); + if (surrogate && (!parentInstance || parentInstance->UseSurrogate())) { + return surrogate->NPP_Destroy(saved); + } + + if (!parentInstance) + return NPERR_NO_ERROR; + + NPError retval = parentInstance->Destroy(); + instance->pdata = nullptr; + + Unused << PluginInstanceParent::Call__delete__(parentInstance); + return retval; +} + +NPError +PluginModuleParent::NPP_NewStream(NPP instance, NPMIMEType type, + NPStream* stream, NPBool seekable, + uint16_t* stype) +{ + PROFILER_LABEL("PluginModuleParent", "NPP_NewStream", + js::ProfileEntry::Category::OTHER); + RESOLVE_AND_CALL(instance, NPP_NewStream(type, stream, seekable, stype)); +} + +NPError +PluginModuleParent::NPP_SetWindow(NPP instance, NPWindow* window) +{ + RESOLVE_AND_CALL(instance, NPP_SetWindow(window)); +} + +NPError +PluginModuleParent::NPP_DestroyStream(NPP instance, + NPStream* stream, + NPReason reason) +{ + RESOLVE_AND_CALL(instance, NPP_DestroyStream(stream, reason)); +} + +int32_t +PluginModuleParent::NPP_WriteReady(NPP instance, + NPStream* stream) +{ + PluginAsyncSurrogate* surrogate = nullptr; + BrowserStreamParent* s = StreamCast(instance, stream, &surrogate); + if (!s) { + if (surrogate) { + return surrogate->NPP_WriteReady(stream); + } + return -1; + } + + return s->WriteReady(); +} + +int32_t +PluginModuleParent::NPP_Write(NPP instance, + NPStream* stream, + int32_t offset, + int32_t len, + void* buffer) +{ + BrowserStreamParent* s = StreamCast(instance, stream); + if (!s) + return -1; + + return s->Write(offset, len, buffer); +} + +void +PluginModuleParent::NPP_StreamAsFile(NPP instance, + NPStream* stream, + const char* fname) +{ + BrowserStreamParent* s = StreamCast(instance, stream); + if (!s) + return; + + s->StreamAsFile(fname); +} + +void +PluginModuleParent::NPP_Print(NPP instance, NPPrint* platformPrint) +{ + + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + i->NPP_Print(platformPrint); +} + +int16_t +PluginModuleParent::NPP_HandleEvent(NPP instance, void* event) +{ + RESOLVE_AND_CALL(instance, NPP_HandleEvent(event)); +} + +void +PluginModuleParent::NPP_URLNotify(NPP instance, const char* url, + NPReason reason, void* notifyData) +{ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + if (!i) + return; + + i->NPP_URLNotify(url, reason, notifyData); +} + +NPError +PluginModuleParent::NPP_GetValue(NPP instance, + NPPVariable variable, void *ret_value) +{ + // The rules are slightly different for this function. + // If there is a surrogate, we *always* use it. + PluginAsyncSurrogate* surrogate = nullptr; + PluginInstanceParent* i = PluginInstanceParent::Cast(instance, &surrogate); + if (surrogate) { + return surrogate->NPP_GetValue(variable, ret_value); + } + if (!i) { + return NPERR_GENERIC_ERROR; + } + return i->NPP_GetValue(variable, ret_value); +} + +NPError +PluginModuleParent::NPP_SetValue(NPP instance, NPNVariable variable, + void *value) +{ + RESOLVE_AND_CALL(instance, NPP_SetValue(variable, value)); +} + +bool +PluginModuleChromeParent::AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, NPError* result) +{ +#ifdef XP_WIN + *result = NPERR_NO_ERROR; + nsresult err = + mozilla::plugins::PluginUtilsWin::RegisterForAudioDeviceChanges(this, + shouldRegister); + if (err != NS_OK) { + *result = NPERR_GENERIC_ERROR; + } + return true; +#else + NS_RUNTIMEABORT("NPPVpluginRequiresAudioDeviceChanges is not valid on this platform."); + *result = NPERR_GENERIC_ERROR; + return true; +#endif +} + +bool +PluginModuleParent::RecvBackUpXResources(const FileDescriptor& aXSocketFd) +{ +#ifndef MOZ_X11 + NS_RUNTIMEABORT("This message only makes sense on X11 platforms"); +#else + MOZ_ASSERT(0 > mPluginXSocketFdDup.get(), + "Already backed up X resources??"); + if (aXSocketFd.IsValid()) { + auto rawFD = aXSocketFd.ClonePlatformHandle(); + mPluginXSocketFdDup.reset(rawFD.release()); + } +#endif + return true; +} + +void +PluginModuleParent::NPP_URLRedirectNotify(NPP instance, const char* url, + int32_t status, void* notifyData) +{ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + if (!i) + return; + + i->NPP_URLRedirectNotify(url, status, notifyData); +} + +BrowserStreamParent* +PluginModuleParent::StreamCast(NPP instance, NPStream* s, + PluginAsyncSurrogate** aSurrogate) +{ + PluginInstanceParent* ip = PluginInstanceParent::Cast(instance, aSurrogate); + if (!ip || (aSurrogate && *aSurrogate && ip->UseSurrogate())) { + return nullptr; + } + + BrowserStreamParent* sp = + static_cast<BrowserStreamParent*>(static_cast<AStream*>(s->pdata)); + if (sp && (sp->mNPP != ip || s != sp->mStream)) { + NS_RUNTIMEABORT("Corrupted plugin stream data."); + } + return sp; +} + +bool +PluginModuleParent::HasRequiredFunctions() +{ + return true; +} + +nsresult +PluginModuleParent::AsyncSetWindow(NPP instance, NPWindow* window) +{ + PluginAsyncSurrogate* surrogate = nullptr; + PluginInstanceParent* i = PluginInstanceParent::Cast(instance, &surrogate); + if (surrogate && (!i || i->UseSurrogate())) { + return surrogate->AsyncSetWindow(window); + } else if (!i) { + return NS_ERROR_FAILURE; + } + return i->AsyncSetWindow(window); +} + +nsresult +PluginModuleParent::GetImageContainer(NPP instance, + mozilla::layers::ImageContainer** aContainer) +{ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + return !i ? NS_ERROR_FAILURE : i->GetImageContainer(aContainer); +} + +nsresult +PluginModuleParent::GetImageSize(NPP instance, + nsIntSize* aSize) +{ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + return !i ? NS_ERROR_FAILURE : i->GetImageSize(aSize); +} + +void +PluginModuleParent::DidComposite(NPP aInstance) +{ + if (PluginInstanceParent* i = PluginInstanceParent::Cast(aInstance)) { + i->DidComposite(); + } +} + +nsresult +PluginModuleParent::SetBackgroundUnknown(NPP instance) +{ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + if (!i) + return NS_ERROR_FAILURE; + + return i->SetBackgroundUnknown(); +} + +nsresult +PluginModuleParent::BeginUpdateBackground(NPP instance, + const nsIntRect& aRect, + DrawTarget** aDrawTarget) +{ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + if (!i) + return NS_ERROR_FAILURE; + + return i->BeginUpdateBackground(aRect, aDrawTarget); +} + +nsresult +PluginModuleParent::EndUpdateBackground(NPP instance, const nsIntRect& aRect) +{ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + if (!i) + return NS_ERROR_FAILURE; + + return i->EndUpdateBackground(aRect); +} + +#if defined(XP_WIN) +nsresult +PluginModuleParent::GetScrollCaptureContainer(NPP aInstance, + mozilla::layers::ImageContainer** aContainer) +{ + PluginInstanceParent* inst = PluginInstanceParent::Cast(aInstance); + return !inst ? NS_ERROR_FAILURE : inst->GetScrollCaptureContainer(aContainer); +} +#endif + +nsresult +PluginModuleParent::HandledWindowedPluginKeyEvent( + NPP aInstance, + const NativeEventData& aNativeKeyData, + bool aIsConsumed) +{ + PluginInstanceParent* parent = PluginInstanceParent::Cast(aInstance); + if (NS_WARN_IF(!parent)) { + return NS_ERROR_FAILURE; + } + return parent->HandledWindowedPluginKeyEvent(aNativeKeyData, aIsConsumed); +} + +void +PluginModuleParent::OnInitFailure() +{ + if (GetIPCChannel()->CanSend()) { + Close(); + } + + mShutdown = true; + + if (mIsStartingAsync) { + /* If we've failed then we need to enumerate any pending NPP_New calls + and clean them up. */ + uint32_t len = mSurrogateInstances.Length(); + for (uint32_t i = 0; i < len; ++i) { + mSurrogateInstances[i]->NotifyAsyncInitFailed(); + } + mSurrogateInstances.Clear(); + } +} + +class PluginOfflineObserver final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit PluginOfflineObserver(PluginModuleChromeParent* pmp) + : mPmp(pmp) + {} + +private: + ~PluginOfflineObserver() {} + PluginModuleChromeParent* mPmp; +}; + +NS_IMPL_ISUPPORTS(PluginOfflineObserver, nsIObserver) + +NS_IMETHODIMP +PluginOfflineObserver::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + MOZ_ASSERT(!strcmp(aTopic, "ipc:network:set-offline")); + mPmp->CachedSettingChanged(); + return NS_OK; +} + +static const char* kSettingsPrefs[] = + {"javascript.enabled", + "dom.ipc.plugins.nativeCursorSupport"}; + +void +PluginModuleChromeParent::RegisterSettingsCallbacks() +{ + for (size_t i = 0; i < ArrayLength(kSettingsPrefs); i++) { + Preferences::RegisterCallback(CachedSettingChanged, kSettingsPrefs[i], this); + } + + nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService(); + if (observerService) { + mPluginOfflineObserver = new PluginOfflineObserver(this); + observerService->AddObserver(mPluginOfflineObserver, "ipc:network:set-offline", false); + } +} + +void +PluginModuleChromeParent::UnregisterSettingsCallbacks() +{ + for (size_t i = 0; i < ArrayLength(kSettingsPrefs); i++) { + Preferences::UnregisterCallback(CachedSettingChanged, kSettingsPrefs[i], this); + } + + nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(mPluginOfflineObserver, "ipc:network:set-offline"); + mPluginOfflineObserver = nullptr; + } +} + +bool +PluginModuleParent::GetSetting(NPNVariable aVariable) +{ + NPBool boolVal = false; + mozilla::plugins::parent::_getvalue(nullptr, aVariable, &boolVal); + return boolVal; +} + +void +PluginModuleParent::GetSettings(PluginSettings* aSettings) +{ + aSettings->javascriptEnabled() = GetSetting(NPNVjavascriptEnabledBool); + aSettings->asdEnabled() = GetSetting(NPNVasdEnabledBool); + aSettings->isOffline() = GetSetting(NPNVisOfflineBool); + aSettings->supportsXembed() = GetSetting(NPNVSupportsXEmbedBool); + aSettings->supportsWindowless() = GetSetting(NPNVSupportsWindowless); + aSettings->userAgent() = NullableString(mNPNIface->uagent(nullptr)); + +#if defined(XP_MACOSX) + aSettings->nativeCursorsSupported() = + Preferences::GetBool("dom.ipc.plugins.nativeCursorSupport", false); +#else + // Need to initialize this to satisfy IPDL. + aSettings->nativeCursorsSupported() = false; +#endif +} + +void +PluginModuleChromeParent::CachedSettingChanged() +{ + PluginSettings settings; + GetSettings(&settings); + Unused << SendSettingChanged(settings); +} + +/* static */ void +PluginModuleChromeParent::CachedSettingChanged(const char* aPref, void* aModule) +{ + PluginModuleChromeParent *module = static_cast<PluginModuleChromeParent*>(aModule); + module->CachedSettingChanged(); +} + +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) +nsresult +PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) +{ + PLUGIN_LOG_DEBUG_METHOD; + + mNPNIface = bFuncs; + mNPPIface = pFuncs; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + *error = NPERR_NO_ERROR; + if (mIsStartingAsync) { + if (GetIPCChannel()->CanSend()) { + // We're already connected, so we may call this immediately. + RecvNP_InitializeResult(*error); + } else { + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); + } + } else { + SetPluginFuncs(pFuncs); + } + + return NS_OK; +} + +nsresult +PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) +{ + PLUGIN_LOG_DEBUG_METHOD; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + *error = NPERR_NO_ERROR; + + mNPNIface = bFuncs; + mNPPIface = pFuncs; + + // NB: This *MUST* be set prior to checking whether the subprocess has + // been connected! + if (mIsStartingAsync) { + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); + } + + if (!mSubprocess->IsConnected()) { + // The subprocess isn't connected yet. Defer NP_Initialize until + // OnProcessLaunched is invoked. + mInitOnAsyncConnect = true; + return NS_OK; + } + + PluginSettings settings; + GetSettings(&settings); + + TimeStamp callNpInitStart = TimeStamp::Now(); + // Asynchronous case + if (mIsStartingAsync) { + if (!SendAsyncNP_Initialize(settings)) { + Close(); + return NS_ERROR_FAILURE; + } + TimeStamp callNpInitEnd = TimeStamp::Now(); + mTimeBlocked += (callNpInitEnd - callNpInitStart); + return NS_OK; + } + + // Synchronous case + if (!CallNP_Initialize(settings, error)) { + Close(); + return NS_ERROR_FAILURE; + } + else if (*error != NPERR_NO_ERROR) { + Close(); + return NS_ERROR_FAILURE; + } + TimeStamp callNpInitEnd = TimeStamp::Now(); + mTimeBlocked += (callNpInitEnd - callNpInitStart); + + RecvNP_InitializeResult(*error); + + return NS_OK; +} + +bool +PluginModuleParent::RecvNP_InitializeResult(const NPError& aError) +{ + if (aError != NPERR_NO_ERROR) { + OnInitFailure(); + return true; + } + + SetPluginFuncs(mNPPIface); + if (mIsStartingAsync) { + InitAsyncSurrogates(); + } + + mNPInitialized = true; + return true; +} + +bool +PluginModuleChromeParent::RecvNP_InitializeResult(const NPError& aError) +{ + if (!mContentParent) { + return PluginModuleParent::RecvNP_InitializeResult(aError); + } + bool initOk = aError == NPERR_NO_ERROR; + if (initOk) { + SetPluginFuncs(mNPPIface); + if (mIsStartingAsync && !SendAssociatePluginId()) { + initOk = false; + } + } + mNPInitialized = initOk; + bool result = mContentParent->SendLoadPluginResult(mPluginId, initOk); + mContentParent = nullptr; + return result; +} + +#else + +nsresult +PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) +{ + PLUGIN_LOG_DEBUG_METHOD; + + mNPNIface = bFuncs; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + *error = NPERR_NO_ERROR; + return NS_OK; +} + +#if defined(XP_WIN) || defined(XP_MACOSX) + +nsresult +PluginModuleContentParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) +{ + PLUGIN_LOG_DEBUG_METHOD; + nsresult rv = PluginModuleParent::NP_Initialize(bFuncs, error); + if (mIsStartingAsync && GetIPCChannel()->CanSend()) { + // We're already connected, so we may call this immediately. + RecvNP_InitializeResult(*error); + } + return rv; +} + +#endif + +nsresult +PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) +{ + nsresult rv = PluginModuleParent::NP_Initialize(bFuncs, error); + if (NS_FAILED(rv)) + return rv; + +#if defined(XP_MACOSX) + if (!mSubprocess->IsConnected()) { + // The subprocess isn't connected yet. Defer NP_Initialize until + // OnProcessLaunched is invoked. + mInitOnAsyncConnect = true; + *error = NPERR_NO_ERROR; + return NS_OK; + } +#else + if (mInitOnAsyncConnect) { + *error = NPERR_NO_ERROR; + return NS_OK; + } +#endif + + PluginSettings settings; + GetSettings(&settings); + + TimeStamp callNpInitStart = TimeStamp::Now(); + if (mIsStartingAsync) { + if (!SendAsyncNP_Initialize(settings)) { + return NS_ERROR_FAILURE; + } + TimeStamp callNpInitEnd = TimeStamp::Now(); + mTimeBlocked += (callNpInitEnd - callNpInitStart); + return NS_OK; + } + + if (!CallNP_Initialize(settings, error)) { + Close(); + return NS_ERROR_FAILURE; + } + TimeStamp callNpInitEnd = TimeStamp::Now(); + mTimeBlocked += (callNpInitEnd - callNpInitStart); + RecvNP_InitializeResult(*error); + return NS_OK; +} + +bool +PluginModuleParent::RecvNP_InitializeResult(const NPError& aError) +{ + if (aError != NPERR_NO_ERROR) { + OnInitFailure(); + return true; + } + + if (mIsStartingAsync && mNPPIface) { + SetPluginFuncs(mNPPIface); + InitAsyncSurrogates(); + } + + mNPInitialized = true; + return true; +} + +bool +PluginModuleChromeParent::RecvNP_InitializeResult(const NPError& aError) +{ + bool ok = true; + if (mContentParent) { + if ((ok = SendAssociatePluginId())) { + ok = mContentParent->SendLoadPluginResult(mPluginId, + aError == NPERR_NO_ERROR); + mContentParent = nullptr; + } + } else if (aError == NPERR_NO_ERROR) { + // Initialization steps for (e10s && !asyncInit) || !e10s +#if defined XP_WIN + if (mIsStartingAsync) { + SetPluginFuncs(mNPPIface); + } + + // Send the info needed to join the browser process's audio session to the + // plugin process. + nsID id; + nsString sessionName; + nsString iconPath; + + if (NS_SUCCEEDED(mozilla::widget::GetAudioSessionData(id, sessionName, + iconPath))) { + Unused << SendSetAudioSessionData(id, sessionName, iconPath); + } +#endif + +#ifdef MOZ_CRASHREPORTER_INJECTOR + InitializeInjector(); +#endif + } + + return PluginModuleParent::RecvNP_InitializeResult(aError) && ok; +} + +#endif + +void +PluginModuleParent::InitAsyncSurrogates() +{ + if (MaybeRunDeferredShutdown()) { + // We've shut down, so the surrogates are no longer valid. Clear + // mSurrogateInstances to ensure that these aren't used. + mSurrogateInstances.Clear(); + return; + } + + uint32_t len = mSurrogateInstances.Length(); + for (uint32_t i = 0; i < len; ++i) { + NPError err; + mAsyncNewRv = mSurrogateInstances[i]->NPP_New(&err); + if (NS_FAILED(mAsyncNewRv)) { + mSurrogateInstances[i]->NotifyAsyncInitFailed(); + continue; + } + } + mSurrogateInstances.Clear(); +} + +bool +PluginModuleParent::RemovePendingSurrogate( + const RefPtr<PluginAsyncSurrogate>& aSurrogate) +{ + return mSurrogateInstances.RemoveElement(aSurrogate); +} + +bool +PluginModuleParent::MaybeRunDeferredShutdown() +{ + if (!mIsStartingAsync || !mIsNPShutdownPending) { + return false; + } + MOZ_ASSERT(!mShutdown); + NPError error; + if (!DoShutdown(&error)) { + return false; + } + mIsNPShutdownPending = false; + return true; +} + +nsresult +PluginModuleParent::NP_Shutdown(NPError* error) +{ + PLUGIN_LOG_DEBUG_METHOD; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + /* If we're still running an async NP_Initialize then we need to defer + shutdown until we've received the result of the NP_Initialize call. */ + if (mIsStartingAsync && !mNPInitialized) { + mIsNPShutdownPending = true; + *error = NPERR_NO_ERROR; + return NS_OK; + } + + if (!DoShutdown(error)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool +PluginModuleParent::DoShutdown(NPError* error) +{ + bool ok = true; + if (IsChrome() && mHadLocalInstance) { + // We synchronously call NP_Shutdown if the chrome process was using + // plugins itself. That way we can service any requests the plugin + // makes. If we're in e10s, though, the content processes will have + // already shut down and there's no one to talk to. So we shut down + // asynchronously in PluginModuleChild::ActorDestroy. + ok = CallNP_Shutdown(error); + } + + // if NP_Shutdown() is nested within another interrupt call, this will + // break things. but lord help us if we're doing that anyway; the + // plugin dso will have been unloaded on the other side by the + // CallNP_Shutdown() message + Close(); + + // mShutdown should either be initialized to false, or be transitiong from + // false to true. It is never ok to go from true to false. Using OR for + // the following assignment to ensure this. + mShutdown |= ok; + if (!ok) { + *error = NPERR_GENERIC_ERROR; + } + return ok; +} + +nsresult +PluginModuleParent::NP_GetMIMEDescription(const char** mimeDesc) +{ + PLUGIN_LOG_DEBUG_METHOD; + + *mimeDesc = "application/x-foobar"; + return NS_OK; +} + +nsresult +PluginModuleParent::NP_GetValue(void *future, NPPVariable aVariable, + void *aValue, NPError* error) +{ + MOZ_LOG(GetPluginLog(), LogLevel::Warning, ("%s Not implemented, requested variable %i", __FUNCTION__, + (int) aVariable)); + + //TODO: implement this correctly + *error = NPERR_GENERIC_ERROR; + return NS_OK; +} + +#if defined(XP_WIN) || defined(XP_MACOSX) +nsresult +PluginModuleParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) +{ + NS_ASSERTION(pFuncs, "Null pointer!"); + + *error = NPERR_NO_ERROR; + if (mIsStartingAsync && !IsChrome()) { + mNPPIface = pFuncs; +#if defined(XP_MACOSX) + if (mNPInitialized) { + SetPluginFuncs(pFuncs); + InitAsyncSurrogates(); + } else { + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); + } +#else + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); +#endif + } else { + SetPluginFuncs(pFuncs); + } + + return NS_OK; +} + +nsresult +PluginModuleChromeParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) +{ +#if defined(XP_MACOSX) + if (mInitOnAsyncConnect) { + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); + mNPPIface = pFuncs; + *error = NPERR_NO_ERROR; + return NS_OK; + } +#else + if (mIsStartingAsync) { + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); + } + if (!mSubprocess->IsConnected()) { + mNPPIface = pFuncs; + mInitOnAsyncConnect = true; + *error = NPERR_NO_ERROR; + return NS_OK; + } +#endif + + // We need to have the plugin process update its function table here by + // actually calling NP_GetEntryPoints. The parent's function table will + // reflect nullptr entries in the child's table once SetPluginFuncs is + // called. + + if (!CallNP_GetEntryPoints(error)) { + return NS_ERROR_FAILURE; + } + else if (*error != NPERR_NO_ERROR) { + return NS_OK; + } + + return PluginModuleParent::NP_GetEntryPoints(pFuncs, error); +} + +#endif + +nsresult +PluginModuleParent::NPP_New(NPMIMEType pluginType, NPP instance, + uint16_t mode, int16_t argc, char* argn[], + char* argv[], NPSavedData* saved, + NPError* error) +{ + PLUGIN_LOG_DEBUG_METHOD; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + if (mIsStartingAsync) { + if (!PluginAsyncSurrogate::Create(this, pluginType, instance, mode, + argc, argn, argv)) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + if (!mNPInitialized) { + RefPtr<PluginAsyncSurrogate> surrogate = + PluginAsyncSurrogate::Cast(instance); + mSurrogateInstances.AppendElement(surrogate); + *error = NPERR_NO_ERROR; + return NS_PLUGIN_INIT_PENDING; + } + } + + // create the instance on the other side + InfallibleTArray<nsCString> names; + InfallibleTArray<nsCString> values; + + for (int i = 0; i < argc; ++i) { + names.AppendElement(NullableString(argn[i])); + values.AppendElement(NullableString(argv[i])); + } + + nsresult rv = NPP_NewInternal(pluginType, instance, mode, names, values, + saved, error); + if (NS_FAILED(rv) || !mIsStartingAsync) { + return rv; + } + return NS_PLUGIN_INIT_PENDING; +} + +class nsCaseInsensitiveUTF8StringArrayComparator +{ +public: + template<class A, class B> + bool Equals(const A& a, const B& b) const { + return a.Equals(b.get(), nsCaseInsensitiveUTF8StringComparator()); + } +}; + +void +PluginModuleParent::AccumulateModuleInitBlockedTime() +{ + if (mPluginName.IsEmpty()) { + GetPluginDetails(); + } + Telemetry::Accumulate(Telemetry::BLOCKED_ON_PLUGIN_MODULE_INIT_MS, + GetHistogramKey(), + static_cast<uint32_t>(mTimeBlocked.ToMilliseconds())); + mTimeBlocked = TimeDuration(); +} + +nsresult +PluginModuleParent::NPP_NewInternal(NPMIMEType pluginType, NPP instance, + uint16_t mode, + InfallibleTArray<nsCString>& names, + InfallibleTArray<nsCString>& values, + NPSavedData* saved, NPError* error) +{ + MOZ_ASSERT(names.Length() == values.Length()); + if (mPluginName.IsEmpty()) { + GetPluginDetails(); + InitQuirksModes(nsDependentCString(pluginType)); + /** mTimeBlocked measures the time that the main thread has been blocked + * on plugin module initialization. As implemented, this is the sum of + * plugin-container launch + toolhelp32 snapshot + NP_Initialize. + * We don't accumulate its value until here because the plugin info + * for its histogram key is not available until *after* NP_Initialize. + */ + AccumulateModuleInitBlockedTime(); + } + + nsCaseInsensitiveUTF8StringArrayComparator comparator; + NS_NAMED_LITERAL_CSTRING(srcAttributeName, "src"); + auto srcAttributeIndex = names.IndexOf(srcAttributeName, 0, comparator); + nsAutoCString srcAttribute; + if (srcAttributeIndex != names.NoIndex) { + srcAttribute = values[srcAttributeIndex]; + } + + nsDependentCString strPluginType(pluginType); + PluginInstanceParent* parentInstance = + new PluginInstanceParent(this, instance, strPluginType, mNPNIface); + + if (mIsFlashPlugin) { + parentInstance->InitMetadata(strPluginType, srcAttribute); +#ifdef XP_WIN + bool supportsAsyncRender = + Preferences::GetBool("dom.ipc.plugins.asyncdrawing.enabled", false); + if (supportsAsyncRender) { + // Prefs indicates we want async plugin rendering, make sure + // the flash module has support. + CallModuleSupportsAsyncRender(&supportsAsyncRender); + } +#ifdef _WIN64 + // For 64-bit builds force windowless if the flash library doesn't support + // async rendering regardless of sandbox level. + if (!supportsAsyncRender) { +#else + // For 32-bit builds force windowless if the flash library doesn't support + // async rendering and the sandbox level is 2 or greater. + if (!supportsAsyncRender && mSandboxLevel >= 2) { +#endif + NS_NAMED_LITERAL_CSTRING(wmodeAttributeName, "wmode"); + NS_NAMED_LITERAL_CSTRING(opaqueAttributeValue, "opaque"); + auto wmodeAttributeIndex = + names.IndexOf(wmodeAttributeName, 0, comparator); + if (wmodeAttributeIndex != names.NoIndex) { + if (!values[wmodeAttributeIndex].EqualsLiteral("transparent")) { + values[wmodeAttributeIndex].Assign(opaqueAttributeValue); + } + } else { + names.AppendElement(wmodeAttributeName); + values.AppendElement(opaqueAttributeValue); + } + } +#endif + } + + // Release the surrogate reference that was in pdata + RefPtr<PluginAsyncSurrogate> surrogate( + dont_AddRef(PluginAsyncSurrogate::Cast(instance))); + // Now replace it with the instance + instance->pdata = static_cast<PluginDataResolver*>(parentInstance); + + if (!SendPPluginInstanceConstructor(parentInstance, + nsDependentCString(pluginType), mode, + names, values)) { + // |parentInstance| is automatically deleted. + instance->pdata = nullptr; + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + { // Scope for timer + Telemetry::AutoTimer<Telemetry::BLOCKED_ON_PLUGIN_INSTANCE_INIT_MS> + timer(GetHistogramKey()); + if (mIsStartingAsync) { + MOZ_ASSERT(surrogate); + surrogate->AsyncCallDeparting(); + if (!SendAsyncNPP_New(parentInstance)) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + *error = NPERR_NO_ERROR; + } else { + if (!CallSyncNPP_New(parentInstance, error)) { + // if IPC is down, we'll get an immediate "failed" return, but + // without *error being set. So make sure that the error + // condition is signaled to nsNPAPIPluginInstance + if (NPERR_NO_ERROR == *error) { + *error = NPERR_GENERIC_ERROR; + } + return NS_ERROR_FAILURE; + } + } + } + + if (*error != NPERR_NO_ERROR) { + if (!mIsStartingAsync) { + NPP_Destroy(instance, 0); + } + return NS_ERROR_FAILURE; + } + + UpdatePluginTimeout(); + + return NS_OK; +} + +void +PluginModuleChromeParent::UpdatePluginTimeout() +{ + TimeoutChanged(kParentTimeoutPref, this); +} + +nsresult +PluginModuleParent::NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr<nsIClearSiteDataCallback> callback) +{ + if (!mClearSiteDataSupported) + return NS_ERROR_NOT_AVAILABLE; + + static uint64_t callbackId = 0; + callbackId++; + mClearSiteDataCallbacks[callbackId] = callback; + + if (!SendNPP_ClearSiteData(NullableString(site), flags, maxAge, callbackId)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + + +nsresult +PluginModuleParent::NPP_GetSitesWithData(nsCOMPtr<nsIGetSitesWithDataCallback> callback) +{ + if (!mGetSitesWithDataSupported) + return NS_ERROR_NOT_AVAILABLE; + + static uint64_t callbackId = 0; + callbackId++; + mSitesWithDataCallbacks[callbackId] = callback; + + if (!SendNPP_GetSitesWithData(callbackId)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +#if defined(XP_MACOSX) +nsresult +PluginModuleParent::IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) +{ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + if (!i) + return NS_ERROR_FAILURE; + + return i->IsRemoteDrawingCoreAnimation(aDrawing); +} +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +nsresult +PluginModuleParent::ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) +{ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + if (!i) + return NS_ERROR_FAILURE; + + return i->ContentsScaleFactorChanged(aContentsScaleFactor); +} +#endif // #if defined(XP_MACOSX) + +#if defined(XP_MACOSX) +bool +PluginModuleParent::AnswerProcessSomeEvents() +{ + mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop(); + return true; +} + +#elif !defined(MOZ_WIDGET_GTK) +bool +PluginModuleParent::AnswerProcessSomeEvents() +{ + NS_RUNTIMEABORT("unreached"); + return false; +} + +#else +static const int kMaxChancesToProcessEvents = 20; + +bool +PluginModuleParent::AnswerProcessSomeEvents() +{ + PLUGIN_LOG_DEBUG(("Spinning mini nested loop ...")); + + int i = 0; + for (; i < kMaxChancesToProcessEvents; ++i) + if (!g_main_context_iteration(nullptr, FALSE)) + break; + + PLUGIN_LOG_DEBUG(("... quitting mini nested loop; processed %i tasks", i)); + + return true; +} +#endif + +bool +PluginModuleParent::RecvProcessNativeEventsInInterruptCall() +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(OS_WIN) + ProcessNativeEventsInInterruptCall(); + return true; +#else + NS_NOTREACHED( + "PluginModuleParent::RecvProcessNativeEventsInInterruptCall not implemented!"); + return false; +#endif +} + +void +PluginModuleParent::ProcessRemoteNativeEventsInInterruptCall() +{ +#if defined(OS_WIN) + Unused << SendProcessNativeEventsInInterruptCall(); + return; +#endif + NS_NOTREACHED( + "PluginModuleParent::ProcessRemoteNativeEventsInInterruptCall not implemented!"); +} + +bool +PluginModuleParent::RecvPluginShowWindow(const uint32_t& aWindowId, const bool& aModal, + const int32_t& aX, const int32_t& aY, + const size_t& aWidth, const size_t& aHeight) +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + CGRect windowBound = ::CGRectMake(aX, aY, aWidth, aHeight); + mac_plugin_interposing::parent::OnPluginShowWindow(aWindowId, windowBound, aModal); + return true; +#else + NS_NOTREACHED( + "PluginInstanceParent::RecvPluginShowWindow not implemented!"); + return false; +#endif +} + +bool +PluginModuleParent::RecvPluginHideWindow(const uint32_t& aWindowId) +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnPluginHideWindow(aWindowId, OtherPid()); + return true; +#else + NS_NOTREACHED( + "PluginInstanceParent::RecvPluginHideWindow not implemented!"); + return false; +#endif +} + +PCrashReporterParent* +PluginModuleParent::AllocPCrashReporterParent(mozilla::dom::NativeThreadId* id, + uint32_t* processType) +{ + MOZ_CRASH("unreachable"); +} + +bool +PluginModuleParent::DeallocPCrashReporterParent(PCrashReporterParent* actor) +{ + MOZ_CRASH("unreachable"); +} + +PCrashReporterParent* +PluginModuleChromeParent::AllocPCrashReporterParent(mozilla::dom::NativeThreadId* id, + uint32_t* processType) +{ +#ifdef MOZ_CRASHREPORTER + return new CrashReporterParent(); +#else + return nullptr; +#endif +} + +bool +PluginModuleChromeParent::DeallocPCrashReporterParent(PCrashReporterParent* actor) +{ +#ifdef MOZ_CRASHREPORTER +#ifdef XP_WIN + mozilla::MutexAutoLock lock(mCrashReporterMutex); + if (actor == static_cast<PCrashReporterParent*>(mCrashReporter)) { + mCrashReporter = nullptr; + } +#endif +#endif + delete actor; + return true; +} + +bool +PluginModuleParent::RecvSetCursor(const NSCursorInfo& aCursorInfo) +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnSetCursor(aCursorInfo); + return true; +#else + NS_NOTREACHED( + "PluginInstanceParent::RecvSetCursor not implemented!"); + return false; +#endif +} + +bool +PluginModuleParent::RecvShowCursor(const bool& aShow) +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnShowCursor(aShow); + return true; +#else + NS_NOTREACHED( + "PluginInstanceParent::RecvShowCursor not implemented!"); + return false; +#endif +} + +bool +PluginModuleParent::RecvPushCursor(const NSCursorInfo& aCursorInfo) +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnPushCursor(aCursorInfo); + return true; +#else + NS_NOTREACHED( + "PluginInstanceParent::RecvPushCursor not implemented!"); + return false; +#endif +} + +bool +PluginModuleParent::RecvPopCursor() +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnPopCursor(); + return true; +#else + NS_NOTREACHED( + "PluginInstanceParent::RecvPopCursor not implemented!"); + return false; +#endif +} + +bool +PluginModuleParent::RecvNPN_SetException(const nsCString& aMessage) +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + // This function ignores its first argument. + mozilla::plugins::parent::_setexception(nullptr, NullableStringGet(aMessage)); + return true; +} + +bool +PluginModuleParent::RecvNPN_ReloadPlugins(const bool& aReloadPages) +{ + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + mozilla::plugins::parent::_reloadplugins(aReloadPages); + return true; +} + +bool +PluginModuleChromeParent::RecvNotifyContentModuleDestroyed() +{ + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + if (host) { + host->NotifyContentModuleDestroyed(mPluginId); + } + return true; +} + +bool +PluginModuleParent::RecvReturnClearSiteData(const NPError& aRv, + const uint64_t& aCallbackId) +{ + if (mClearSiteDataCallbacks.find(aCallbackId) == mClearSiteDataCallbacks.end()) { + return true; + } + if (!!mClearSiteDataCallbacks[aCallbackId]) { + nsresult rv; + switch (aRv) { + 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; + } + mClearSiteDataCallbacks[aCallbackId]->Callback(rv); + } + mClearSiteDataCallbacks.erase(aCallbackId); + return true; +} + +bool +PluginModuleParent::RecvReturnSitesWithData(nsTArray<nsCString>&& aSites, + const uint64_t& aCallbackId) +{ + if (mSitesWithDataCallbacks.find(aCallbackId) == mSitesWithDataCallbacks.end()) { + return true; + } + + if (!!mSitesWithDataCallbacks[aCallbackId]) { + mSitesWithDataCallbacks[aCallbackId]->SitesWithData(aSites); + } + mSitesWithDataCallbacks.erase(aCallbackId); + return true; +} + +layers::TextureClientRecycleAllocator* +PluginModuleParent::EnsureTextureAllocatorForDirectBitmap() +{ + if (!mTextureAllocatorForDirectBitmap) { + mTextureAllocatorForDirectBitmap = new TextureClientRecycleAllocator(ImageBridgeChild::GetSingleton().get()); + } + return mTextureAllocatorForDirectBitmap; +} + +layers::TextureClientRecycleAllocator* +PluginModuleParent::EnsureTextureAllocatorForDXGISurface() +{ + if (!mTextureAllocatorForDXGISurface) { + mTextureAllocatorForDXGISurface = new TextureClientRecycleAllocator(ImageBridgeChild::GetSingleton().get()); + } + return mTextureAllocatorForDXGISurface; +} + + +bool +PluginModuleParent::AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, + NPError* result) { + NS_RUNTIMEABORT("SetValue_NPPVpluginRequiresAudioDeviceChanges is only valid " + "with PluginModuleChromeParent"); + *result = NPERR_GENERIC_ERROR; + return true; +} + +#ifdef MOZ_CRASHREPORTER_INJECTOR + +// We only add the crash reporter to subprocess which have the filename +// FlashPlayerPlugin* +#define FLASH_PROCESS_PREFIX "FLASHPLAYERPLUGIN" + +static DWORD +GetFlashChildOfPID(DWORD pid, HANDLE snapshot) +{ + PROCESSENTRY32 entry = { + sizeof(entry) + }; + for (BOOL ok = Process32First(snapshot, &entry); + ok; + ok = Process32Next(snapshot, &entry)) { + if (entry.th32ParentProcessID == pid) { + nsString name(entry.szExeFile); + ToUpperCase(name); + if (StringBeginsWith(name, NS_LITERAL_STRING(FLASH_PROCESS_PREFIX))) { + return entry.th32ProcessID; + } + } + } + return 0; +} + +// We only look for child processes of the Flash plugin, NPSWF* +#define FLASH_PLUGIN_PREFIX "NPSWF" + +void +PluginModuleChromeParent::InitializeInjector() +{ + if (!Preferences::GetBool("dom.ipc.plugins.flash.subprocess.crashreporter.enabled", false)) + return; + + nsCString path(Process()->GetPluginFilePath().c_str()); + ToUpperCase(path); + int32_t lastSlash = path.RFindCharInSet("\\/"); + if (kNotFound == lastSlash) + return; + + if (!StringBeginsWith(Substring(path, lastSlash + 1), + NS_LITERAL_CSTRING(FLASH_PLUGIN_PREFIX))) + return; + + TimeStamp th32Start = TimeStamp::Now(); + mFinishInitTask = mChromeTaskFactory.NewTask<FinishInjectorInitTask>(); + mFinishInitTask->Init(this); + if (!::QueueUserWorkItem(&PluginModuleChromeParent::GetToolhelpSnapshot, + mFinishInitTask, WT_EXECUTEDEFAULT)) { + mFinishInitTask = nullptr; + return; + } + TimeStamp th32End = TimeStamp::Now(); + mTimeBlocked += (th32End - th32Start); +} + +void +PluginModuleChromeParent::DoInjection(const nsAutoHandle& aSnapshot) +{ + DWORD pluginProcessPID = GetProcessId(Process()->GetChildProcessHandle()); + mFlashProcess1 = GetFlashChildOfPID(pluginProcessPID, aSnapshot); + if (mFlashProcess1) { + InjectCrashReporterIntoProcess(mFlashProcess1, this); + + mFlashProcess2 = GetFlashChildOfPID(mFlashProcess1, aSnapshot); + if (mFlashProcess2) { + InjectCrashReporterIntoProcess(mFlashProcess2, this); + } + } + mFinishInitTask = nullptr; +} + +DWORD WINAPI +PluginModuleChromeParent::GetToolhelpSnapshot(LPVOID aContext) +{ + FinishInjectorInitTask* task = static_cast<FinishInjectorInitTask*>(aContext); + MOZ_ASSERT(task); + task->PostToMainThread(); + return 0; +} + +void +PluginModuleChromeParent::OnCrash(DWORD processID) +{ + if (!mShutdown) { + GetIPCChannel()->CloseWithError(); + mozilla::ipc::ScopedProcessHandle geckoPluginChild; + if (base::OpenProcessHandle(OtherPid(), &geckoPluginChild.rwget())) { + if (!base::KillProcess(geckoPluginChild, + base::PROCESS_END_KILLED_BY_USER, false)) { + NS_ERROR("May have failed to kill child process."); + } + } else { + NS_ERROR("Failed to open child process when attempting kill."); + } + } +} + +#endif // MOZ_CRASHREPORTER_INJECTOR + +#ifdef MOZ_ENABLE_PROFILER_SPS +class PluginProfilerObserver final : public nsIObserver, + public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit PluginProfilerObserver(PluginModuleChromeParent* pmp) + : mPmp(pmp) + {} + +private: + ~PluginProfilerObserver() {} + PluginModuleChromeParent* mPmp; +}; + +NS_IMPL_ISUPPORTS(PluginProfilerObserver, nsIObserver, nsISupportsWeakReference) + +NS_IMETHODIMP +PluginProfilerObserver::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + if (!strcmp(aTopic, "profiler-started")) { + nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject)); + mPmp->StartProfiler(params); + } else if (!strcmp(aTopic, "profiler-stopped")) { + mPmp->StopProfiler(); + } else if (!strcmp(aTopic, "profiler-subprocess-gather")) { + mPmp->GatherAsyncProfile(); + } else if (!strcmp(aTopic, "profiler-subprocess")) { + nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject); + mPmp->GatheredAsyncProfile(pse); + } + return NS_OK; +} + +void +PluginModuleChromeParent::InitPluginProfiling() +{ + nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService(); + if (observerService) { + mProfilerObserver = new PluginProfilerObserver(this); + observerService->AddObserver(mProfilerObserver, "profiler-started", false); + observerService->AddObserver(mProfilerObserver, "profiler-stopped", false); + observerService->AddObserver(mProfilerObserver, "profiler-subprocess-gather", false); + observerService->AddObserver(mProfilerObserver, "profiler-subprocess", false); + } +} + +void +PluginModuleChromeParent::ShutdownPluginProfiling() +{ + nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(mProfilerObserver, "profiler-started"); + observerService->RemoveObserver(mProfilerObserver, "profiler-stopped"); + observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess-gather"); + observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess"); + } +} + +void +PluginModuleChromeParent::StartProfiler(nsIProfilerStartParams* aParams) +{ + if (NS_WARN_IF(!aParams)) { + return; + } + + ProfilerInitParams ipcParams; + + ipcParams.enabled() = true; + aParams->GetEntries(&ipcParams.entries()); + aParams->GetInterval(&ipcParams.interval()); + ipcParams.features() = aParams->GetFeatures(); + ipcParams.threadFilters() = aParams->GetThreadFilterNames(); + + Unused << SendStartProfiler(ipcParams); + + nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1")); + if (NS_WARN_IF(!profiler)) { + return; + } + nsCOMPtr<nsISupports> gatherer; + profiler->GetProfileGatherer(getter_AddRefs(gatherer)); + mGatherer = static_cast<ProfileGatherer*>(gatherer.get()); +} + +void +PluginModuleChromeParent::StopProfiler() +{ + mGatherer = nullptr; + Unused << SendStopProfiler(); +} + +void +PluginModuleChromeParent::GatherAsyncProfile() +{ + if (NS_WARN_IF(!mGatherer)) { + return; + } + mGatherer->WillGatherOOPProfile(); + Unused << SendGatherProfile(); +} + +void +PluginModuleChromeParent::GatheredAsyncProfile(nsIProfileSaveEvent* aSaveEvent) +{ + if (aSaveEvent && !mProfile.IsEmpty()) { + aSaveEvent->AddSubProfile(mProfile.get()); + mProfile.Truncate(); + } +} +#endif // MOZ_ENABLE_PROFILER_SPS + +bool +PluginModuleChromeParent::RecvProfile(const nsCString& aProfile) +{ +#ifdef MOZ_ENABLE_PROFILER_SPS + if (NS_WARN_IF(!mGatherer)) { + return true; + } + + mProfile = aProfile; + mGatherer->GatheredOOPProfile(); +#endif + return true; +} + +bool +PluginModuleParent::AnswerGetKeyState(const int32_t& aVirtKey, int16_t* aRet) +{ + return false; +} + +bool +PluginModuleChromeParent::AnswerGetKeyState(const int32_t& aVirtKey, + int16_t* aRet) +{ +#if defined(XP_WIN) + *aRet = ::GetKeyState(aVirtKey); + return true; +#else + return PluginModuleParent::AnswerGetKeyState(aVirtKey, aRet); +#endif +} diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h new file mode 100644 index 000000000..cc24d6ed2 --- /dev/null +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -0,0 +1,686 @@ +/* -*- 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 mozilla_plugins_PluginModuleParent_h +#define mozilla_plugins_PluginModuleParent_h + +#include "base/process.h" +#include "mozilla/FileUtils.h" +#include "mozilla/HangAnnotations.h" +#include "mozilla/PluginLibrary.h" +#include "mozilla/plugins/PluginProcessParent.h" +#include "mozilla/plugins/PPluginModuleParent.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/plugins/PluginTypes.h" +#include "mozilla/ipc/TaskFactory.h" +#include "mozilla/TimeStamp.h" +#include "npapi.h" +#include "npfunctions.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsIObserver.h" +#ifdef XP_WIN +#include "nsWindowsHelpers.h" +#endif + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +class nsIProfileSaveEvent; +class nsPluginTag; + +namespace mozilla { +#ifdef MOZ_ENABLE_PROFILER_SPS +class ProfileGatherer; +#endif +namespace dom { +class PCrashReporterParent; +class CrashReporterParent; +} // namespace dom + +namespace layers { +class TextureClientRecycleAllocator; +} // namespace layers + +namespace plugins { +//----------------------------------------------------------------------------- + +class BrowserStreamParent; +class PluginAsyncSurrogate; +class PluginInstanceParent; + +#ifdef XP_WIN +class PluginHangUIParent; +#endif +#ifdef MOZ_CRASHREPORTER_INJECTOR +class FinishInjectorInitTask; +#endif + +/** + * PluginModuleParent + * + * This class implements the NPP API from the perspective of the rest + * of Gecko, forwarding NPP calls along to the child process that is + * actually running the plugin. + * + * This class /also/ implements a version of the NPN API, because the + * child process needs to make these calls back into Gecko proper. + * This class is responsible for "actually" making those function calls. + * + * If a plugin is running, there will always be one PluginModuleParent for it in + * the chrome process. In addition, any content process using the plugin will + * have its own PluginModuleParent. The subclasses PluginModuleChromeParent and + * PluginModuleContentParent implement functionality that is specific to one + * case or the other. + */ +class PluginModuleParent + : public PPluginModuleParent + , public PluginLibrary +#ifdef MOZ_CRASHREPORTER_INJECTOR + , public CrashReporter::InjectorCrashCallback +#endif +{ +protected: + typedef mozilla::PluginLibrary PluginLibrary; + typedef mozilla::dom::PCrashReporterParent PCrashReporterParent; + typedef mozilla::dom::CrashReporterParent CrashReporterParent; + + PPluginInstanceParent* + AllocPPluginInstanceParent(const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray<nsCString>& aNames, + const InfallibleTArray<nsCString>& aValues) + override; + + virtual bool + DeallocPPluginInstanceParent(PPluginInstanceParent* aActor) override; + +public: + explicit PluginModuleParent(bool aIsChrome, bool aAllowAsyncInit); + virtual ~PluginModuleParent(); + + bool RemovePendingSurrogate(const RefPtr<PluginAsyncSurrogate>& aSurrogate); + + /** @return the state of the pref that controls async plugin init */ + bool IsStartingAsync() const { return mIsStartingAsync; } + /** @return whether this modules NP_Initialize has successfully completed + executing */ + bool IsInitialized() const { return mNPInitialized; } + bool IsChrome() const { return mIsChrome; } + + virtual void SetPlugin(nsNPAPIPlugin* plugin) override + { + mPlugin = plugin; + } + + virtual void ActorDestroy(ActorDestroyReason why) override; + + const NPNetscapeFuncs* GetNetscapeFuncs() { + return mNPNIface; + } + + bool OkToCleanup() const { + return !IsOnCxxStack(); + } + + void ProcessRemoteNativeEventsInInterruptCall() override; + + virtual bool WaitForIPCConnection() { return true; } + + nsCString GetHistogramKey() const { + return mPluginName + mPluginVersion; + } + + void AccumulateModuleInitBlockedTime(); + + virtual nsresult GetRunID(uint32_t* aRunID) override; + virtual void SetHasLocalInstance() override { + mHadLocalInstance = true; + } + + int GetQuirks() { return mQuirks; } + +protected: + virtual mozilla::ipc::RacyInterruptPolicy + MediateInterruptRace(const MessageInfo& parent, + const MessageInfo& child) override + { + return MediateRace(parent, child); + } + + virtual bool + RecvBackUpXResources(const FileDescriptor& aXSocketFd) override; + + virtual bool AnswerProcessSomeEvents() override; + + virtual bool + RecvProcessNativeEventsInInterruptCall() override; + + virtual bool + RecvPluginShowWindow(const uint32_t& aWindowId, const bool& aModal, + const int32_t& aX, const int32_t& aY, + const size_t& aWidth, const size_t& aHeight) override; + + virtual bool + RecvPluginHideWindow(const uint32_t& aWindowId) override; + + virtual PCrashReporterParent* + AllocPCrashReporterParent(mozilla::dom::NativeThreadId* id, + uint32_t* processType) override; + virtual bool + DeallocPCrashReporterParent(PCrashReporterParent* actor) override; + + virtual bool + RecvSetCursor(const NSCursorInfo& aCursorInfo) override; + + virtual bool + RecvShowCursor(const bool& aShow) override; + + virtual bool + RecvPushCursor(const NSCursorInfo& aCursorInfo) override; + + virtual bool + RecvPopCursor() override; + + virtual bool + RecvNPN_SetException(const nsCString& aMessage) override; + + virtual bool + RecvNPN_ReloadPlugins(const bool& aReloadPages) override; + + virtual bool + RecvNP_InitializeResult(const NPError& aError) override; + + static BrowserStreamParent* StreamCast(NPP instance, NPStream* s, + PluginAsyncSurrogate** aSurrogate = nullptr); + + virtual bool + AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, + NPError* result) override; + +protected: + void SetChildTimeout(const int32_t aChildTimeout); + static void TimeoutChanged(const char* aPref, void* aModule); + + virtual void UpdatePluginTimeout() {} + + virtual bool RecvNotifyContentModuleDestroyed() override { return true; } + + virtual bool RecvProfile(const nsCString& aProfile) override { return true; } + + virtual bool AnswerGetKeyState(const int32_t& aVirtKey, int16_t* aRet) override; + + virtual bool RecvReturnClearSiteData(const NPError& aRv, + const uint64_t& aCallbackId) override; + + virtual bool RecvReturnSitesWithData(nsTArray<nsCString>&& aSites, + const uint64_t& aCallbackId) override; + + void SetPluginFuncs(NPPluginFuncs* aFuncs); + + nsresult NPP_NewInternal(NPMIMEType pluginType, NPP instance, uint16_t mode, + InfallibleTArray<nsCString>& names, + InfallibleTArray<nsCString>& values, + NPSavedData* saved, NPError* error); + + // NPP-like API that Gecko calls are trampolined into. These + // messages then get forwarded along to the plugin instance, + // and then eventually the child process. + + static NPError NPP_Destroy(NPP instance, NPSavedData** save); + + static NPError NPP_SetWindow(NPP instance, NPWindow* window); + static NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype); + static NPError NPP_DestroyStream(NPP instance, + NPStream* stream, NPReason reason); + static int32_t NPP_WriteReady(NPP instance, NPStream* stream); + static int32_t NPP_Write(NPP instance, NPStream* stream, + int32_t offset, int32_t len, void* buffer); + static void NPP_StreamAsFile(NPP instance, + NPStream* stream, const char* fname); + static void NPP_Print(NPP instance, NPPrint* platformPrint); + static int16_t NPP_HandleEvent(NPP instance, void* event); + static void NPP_URLNotify(NPP instance, const char* url, + NPReason reason, void* notifyData); + static NPError NPP_GetValue(NPP instance, + NPPVariable variable, void *ret_value); + static NPError NPP_SetValue(NPP instance, NPNVariable variable, + void *value); + static void NPP_URLRedirectNotify(NPP instance, const char* url, + int32_t status, void* notifyData); + + virtual bool HasRequiredFunctions() 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 void DidComposite(NPP aInstance) override; + virtual bool IsOOP() override { return true; } + virtual nsresult SetBackgroundUnknown(NPP instance) override; + virtual nsresult BeginUpdateBackground(NPP instance, + const nsIntRect& aRect, + DrawTarget** aDrawTarget) override; + virtual nsresult EndUpdateBackground(NPP instance, + const nsIntRect& aRect) 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; + +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) override; +#else + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override; +#endif + virtual nsresult NP_Shutdown(NPError* error) override; + + virtual nsresult NP_GetMIMEDescription(const char** mimeDesc) override; + virtual nsresult NP_GetValue(void *future, NPPVariable aVariable, + void *aValue, NPError* error) override; +#if defined(XP_WIN) || defined(XP_MACOSX) + virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) override; +#endif + virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance, + uint16_t mode, int16_t argc, char* argn[], + char* argv[], NPSavedData* saved, + NPError* error) override; + virtual nsresult NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr<nsIClearSiteDataCallback> callback) override; + virtual nsresult NPP_GetSitesWithData(nsCOMPtr<nsIGetSitesWithDataCallback> callback) override; + +private: + std::map<uint64_t, nsCOMPtr<nsIClearSiteDataCallback>> mClearSiteDataCallbacks; + std::map<uint64_t, nsCOMPtr<nsIGetSitesWithDataCallback>> mSitesWithDataCallbacks; + + nsCString mPluginFilename; + int mQuirks; + void InitQuirksModes(const nsCString& aMimeType); + +public: + +#if defined(XP_MACOSX) + virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) override; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + virtual nsresult ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) override; +#endif + + void InitAsyncSurrogates(); + + layers::TextureClientRecycleAllocator* EnsureTextureAllocatorForDirectBitmap(); + layers::TextureClientRecycleAllocator* EnsureTextureAllocatorForDXGISurface(); + +protected: + void NotifyFlashHang(); + void NotifyPluginCrashed(); + void OnInitFailure(); + bool MaybeRunDeferredShutdown(); + bool DoShutdown(NPError* error); + + bool GetSetting(NPNVariable aVariable); + void GetSettings(PluginSettings* aSettings); + + bool mIsChrome; + bool mShutdown; + bool mHadLocalInstance; + bool mClearSiteDataSupported; + bool mGetSitesWithDataSupported; + NPNetscapeFuncs* mNPNIface; + NPPluginFuncs* mNPPIface; + nsNPAPIPlugin* mPlugin; + ipc::TaskFactory<PluginModuleParent> mTaskFactory; + nsString mPluginDumpID; + nsString mBrowserDumpID; + nsString mHangID; + RefPtr<nsIObserver> mProfilerObserver; + TimeDuration mTimeBlocked; + nsCString mPluginName; + nsCString mPluginVersion; + int32_t mSandboxLevel; + bool mIsFlashPlugin; + +#ifdef MOZ_X11 + // Dup of plugin's X socket, used to scope its resources to this + // object instead of the plugin process's lifetime + ScopedClose mPluginXSocketFdDup; +#endif + + bool + GetPluginDetails(); + + friend class mozilla::dom::CrashReporterParent; + friend class mozilla::plugins::PluginAsyncSurrogate; + + bool mIsStartingAsync; + bool mNPInitialized; + bool mIsNPShutdownPending; + nsTArray<RefPtr<PluginAsyncSurrogate>> mSurrogateInstances; + nsresult mAsyncNewRv; + uint32_t mRunID; + + RefPtr<layers::TextureClientRecycleAllocator> mTextureAllocatorForDirectBitmap; + RefPtr<layers::TextureClientRecycleAllocator> mTextureAllocatorForDXGISurface; +}; + +class PluginModuleContentParent : public PluginModuleParent +{ + public: + explicit PluginModuleContentParent(bool aAllowAsyncInit); + + static PluginLibrary* LoadModule(uint32_t aPluginId, nsPluginTag* aPluginTag); + + static PluginModuleContentParent* Initialize(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess); + + static void OnLoadPluginResult(const uint32_t& aPluginId, const bool& aResult); + static void AssociatePluginId(uint32_t aPluginId, base::ProcessId aProcessId); + + virtual ~PluginModuleContentParent(); + +#if defined(XP_WIN) || defined(XP_MACOSX) + nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override; +#endif + + private: + virtual bool ShouldContinueFromReplyTimeout() override; + virtual void OnExitedSyncSend() override; + +#ifdef MOZ_CRASHREPORTER_INJECTOR + void OnCrash(DWORD processID) override {} +#endif + + static PluginModuleContentParent* sSavedModuleParent; + + uint32_t mPluginId; +}; + +class PluginModuleChromeParent + : public PluginModuleParent + , public mozilla::HangMonitor::Annotator +{ + public: + /** + * LoadModule + * + * This may or may not launch a plugin child process, + * and may or may not be very expensive. + */ + static PluginLibrary* LoadModule(const char* aFilePath, uint32_t aPluginId, + nsPluginTag* aPluginTag); + + /** + * The following two functions are called by SetupBridge to determine + * whether an existing plugin module was reused, or whether a new module + * was instantiated by the plugin host. + */ + static void ClearInstantiationFlag() { sInstantiated = false; } + static bool DidInstantiate() { return sInstantiated; } + + virtual ~PluginModuleChromeParent(); + + /* + * Takes a full multi-process dump including the plugin process and the + * content process. If aBrowserDumpId is not empty then the browser dump + * associated with it will be paired to the resulting minidump. + * Takes ownership of the file associated with aBrowserDumpId. + * + * @param aContentPid PID of the e10s content process from which a hang was + * reported. May be kInvalidProcessId if not applicable. + * @param aBrowserDumpId (optional) previously taken browser dump id. If + * provided TakeFullMinidump will use this dump file instead of + * generating a new one. If not provided a browser dump will be taken at + * the time of this call. + * @param aDumpId Returns the ID of the newly generated crash dump. Left + * untouched upon failure. + */ + void TakeFullMinidump(base::ProcessId aContentPid, + const nsAString& aBrowserDumpId, + nsString& aDumpId); + + /* + * Terminates the plugin process associated with this plugin module. Also + * generates appropriate crash reports unless an existing one is provided. + * Takes ownership of the file associated with aDumpId on success. + * + * @param aMsgLoop the main message pump associated with the module + * protocol. + * @param aContentPid PID of the e10s content process from which a hang was + * reported. May be kInvalidProcessId if not applicable. + * @param aMonitorDescription a string describing the hang monitor that + * is making this call. This string is added to the crash reporter + * annotations for the plugin process. + * @param aDumpId (optional) previously taken dump id. If provided + * TerminateChildProcess will use this dump file instead of generating a + * multi-process crash report. If not provided a multi-process dump will + * be taken at the time of this call. + */ + void TerminateChildProcess(MessageLoop* aMsgLoop, + base::ProcessId aContentPid, + const nsCString& aMonitorDescription, + const nsAString& aDumpId); + +#ifdef XP_WIN + /** + * Called by Plugin Hang UI to notify that the user has clicked continue. + * Used for chrome hang annotations. + */ + void + OnHangUIContinue(); + + void + EvaluateHangUIState(const bool aReset); +#endif // XP_WIN + + virtual bool WaitForIPCConnection() override; + + virtual bool + RecvNP_InitializeResult(const NPError& aError) override; + + void + SetContentParent(dom::ContentParent* aContentParent); + + bool + SendAssociatePluginId(); + + void CachedSettingChanged(); + +#ifdef MOZ_ENABLE_PROFILER_SPS + void GatherAsyncProfile(); + void GatheredAsyncProfile(nsIProfileSaveEvent* aSaveEvent); + void StartProfiler(nsIProfilerStartParams* aParams); + void StopProfiler(); +#endif + + virtual bool + RecvProfile(const nsCString& aProfile) override; + + virtual bool + AnswerGetKeyState(const int32_t& aVirtKey, int16_t* aRet) override; + +private: + virtual void + EnteredCxxStack() override; + + void + ExitedCxxStack() override; + + mozilla::ipc::IProtocol* GetInvokingProtocol(); + PluginInstanceParent* GetManagingInstance(mozilla::ipc::IProtocol* aProtocol); + + virtual void + AnnotateHang(mozilla::HangMonitor::HangAnnotations& aAnnotations) override; + + virtual bool ShouldContinueFromReplyTimeout() override; + +#ifdef MOZ_CRASHREPORTER + void ProcessFirstMinidump(); + void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes); +#endif + + virtual PCrashReporterParent* + AllocPCrashReporterParent(mozilla::dom::NativeThreadId* id, + uint32_t* processType) override; + virtual bool + DeallocPCrashReporterParent(PCrashReporterParent* actor) override; + + PluginProcessParent* Process() const { return mSubprocess; } + base::ProcessHandle ChildProcessHandle() { return mSubprocess->GetChildProcessHandle(); } + +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) override; +#else + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override; +#endif + +#if defined(XP_WIN) || defined(XP_MACOSX) + virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) override; +#endif + + virtual void ActorDestroy(ActorDestroyReason why) override; + + // aFilePath is UTF8, not native! + explicit PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId, + int32_t aSandboxLevel, + bool aAllowAsyncInit); + + CrashReporterParent* CrashReporter(); + + void CleanupFromTimeout(const bool aByHangUI); + + virtual void UpdatePluginTimeout() override; + +#ifdef MOZ_ENABLE_PROFILER_SPS + void InitPluginProfiling(); + void ShutdownPluginProfiling(); +#endif + + void RegisterSettingsCallbacks(); + void UnregisterSettingsCallbacks(); + + virtual bool RecvNotifyContentModuleDestroyed() override; + + static void CachedSettingChanged(const char* aPref, void* aModule); + + virtual bool + AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, + NPError* result) override; + + PluginProcessParent* mSubprocess; + uint32_t mPluginId; + + ipc::TaskFactory<PluginModuleChromeParent> mChromeTaskFactory; + + enum HangAnnotationFlags + { + kInPluginCall = (1u << 0), + kHangUIShown = (1u << 1), + kHangUIContinued = (1u << 2), + kHangUIDontShow = (1u << 3) + }; + Atomic<uint32_t> mHangAnnotationFlags; +#ifdef XP_WIN + InfallibleTArray<float> mPluginCpuUsageOnHang; + PluginHangUIParent *mHangUIParent; + bool mHangUIEnabled; + bool mIsTimerReset; +#ifdef MOZ_CRASHREPORTER + /** + * This mutex protects the crash reporter when the Plugin Hang UI event + * handler is executing off main thread. It is intended to protect both + * the mCrashReporter variable in addition to the CrashReporterParent object + * that mCrashReporter refers to. + */ + mozilla::Mutex mCrashReporterMutex; + CrashReporterParent* mCrashReporter; +#endif // MOZ_CRASHREPORTER + + + /** + * Launches the Plugin Hang UI. + * + * @return true if plugin-hang-ui.exe has been successfully launched. + * false if the Plugin Hang UI is disabled, already showing, + * or the launch failed. + */ + bool + LaunchHangUI(); + + /** + * Finishes the Plugin Hang UI and cancels if it is being shown to the user. + */ + void + FinishHangUI(); +#endif + + friend class mozilla::dom::CrashReporterParent; + friend class mozilla::plugins::PluginAsyncSurrogate; + +#ifdef MOZ_CRASHREPORTER_INJECTOR + friend class mozilla::plugins::FinishInjectorInitTask; + + void InitializeInjector(); + void DoInjection(const nsAutoHandle& aSnapshot); + static DWORD WINAPI GetToolhelpSnapshot(LPVOID aContext); + + void OnCrash(DWORD processID) override; + + DWORD mFlashProcess1; + DWORD mFlashProcess2; + RefPtr<mozilla::plugins::FinishInjectorInitTask> mFinishInitTask; +#endif + + void OnProcessLaunched(const bool aSucceeded); + + class LaunchedTask : public LaunchCompleteTask + { + public: + explicit LaunchedTask(PluginModuleChromeParent* aModule) + : mModule(aModule) + { + MOZ_ASSERT(aModule); + } + + NS_IMETHOD Run() override + { + mModule->OnProcessLaunched(mLaunchSucceeded); + return NS_OK; + } + + private: + PluginModuleChromeParent* mModule; + }; + + friend class LaunchedTask; + + bool mInitOnAsyncConnect; + nsresult mAsyncInitRv; + NPError mAsyncInitError; + // mContentParent is to be used ONLY during the IPC dance that occurs + // when ContentParent::RecvLoadPlugin is called under async plugin init! + // In other contexts it is *unsafe*, as there might be multiple content + // processes in existence! + dom::ContentParent* mContentParent; + nsCOMPtr<nsIObserver> mPluginOfflineObserver; +#ifdef MOZ_ENABLE_PROFILER_SPS + RefPtr<mozilla::ProfileGatherer> mGatherer; +#endif + nsCString mProfile; + bool mIsBlocklisted; + static bool sInstantiated; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginModuleParent_h diff --git a/dom/plugins/ipc/PluginProcessChild.cpp b/dom/plugins/ipc/PluginProcessChild.cpp new file mode 100644 index 000000000..eb698e8af --- /dev/null +++ b/dom/plugins/ipc/PluginProcessChild.cpp @@ -0,0 +1,149 @@ +/* -*- 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/. */ + +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/plugins/PluginProcessChild.h" + +#include "prlink.h" + +#include "base/command_line.h" +#include "base/string_util.h" +#include "nsDebugImpl.h" + +#if defined(XP_MACOSX) +#include "nsCocoaFeatures.h" +// An undocumented CoreGraphics framework method, present in the same form +// since at least OS X 10.5. +extern "C" CGError CGSSetDebugOptions(int options); +#endif + +#ifdef XP_WIN +bool ShouldProtectPluginCurrentDirectory(char16ptr_t pluginFilePath); +#if defined(MOZ_SANDBOX) +#define TARGET_SANDBOX_EXPORTS +#include "mozilla/sandboxTarget.h" +#endif +#endif + +using mozilla::ipc::IOThreadChild; + +#ifdef OS_WIN +#include "nsSetDllDirectory.h" +#include <algorithm> +#endif + +namespace mozilla { +namespace plugins { + + +bool +PluginProcessChild::Init() +{ + nsDebugImpl::SetMultiprocessMode("NPAPI"); + +#if defined(XP_MACOSX) + // Remove the trigger for "dyld interposing" that we added in + // GeckoChildProcessHost::PerformAsyncLaunchInternal(), in the host + // process just before we were launched. Dyld interposing will still + // happen in our process (the plugin child process). But we don't want + // it to happen in any processes that the plugin might launch from our + // process. + nsCString interpose(PR_GetEnv("DYLD_INSERT_LIBRARIES")); + if (!interpose.IsEmpty()) { + // If we added the path to libplugin_child_interpose.dylib to an + // existing DYLD_INSERT_LIBRARIES, we appended it to the end, after a + // ":" path seperator. + int32_t lastSeparatorPos = interpose.RFind(":"); + int32_t lastTriggerPos = interpose.RFind("libplugin_child_interpose.dylib"); + bool needsReset = false; + if (lastTriggerPos != -1) { + if (lastSeparatorPos == -1) { + interpose.Truncate(); + needsReset = true; + } else if (lastTriggerPos > lastSeparatorPos) { + interpose.SetLength(lastSeparatorPos); + needsReset = true; + } + } + if (needsReset) { + nsCString setInterpose("DYLD_INSERT_LIBRARIES="); + if (!interpose.IsEmpty()) { + setInterpose.Append(interpose); + } + // Values passed to PR_SetEnv() must be seperately allocated. + char* setInterposePtr = strdup(setInterpose.get()); + PR_SetEnv(setInterposePtr); + } + } +#endif + + // Certain plugins, such as flash, steal the unhandled exception filter + // thus we never get crash reports when they fault. This call fixes it. + message_loop()->set_exception_restoration(true); + + std::string pluginFilename; + +#if defined(OS_POSIX) + // NB: need to be very careful in ensuring that the first arg + // (after the binary name) here is indeed the plugin module path. + // Keep in sync with dom/plugins/PluginModuleParent. + std::vector<std::string> values = CommandLine::ForCurrentProcess()->argv(); + MOZ_ASSERT(values.size() >= 2, "not enough args"); + + pluginFilename = UnmungePluginDsoPath(values[1]); + +#elif defined(OS_WIN) + std::vector<std::wstring> values = + CommandLine::ForCurrentProcess()->GetLooseValues(); + MOZ_ASSERT(values.size() >= 1, "not enough loose args"); + + if (ShouldProtectPluginCurrentDirectory(values[0].c_str())) { + SanitizeEnvironmentVariables(); + SetDllDirectory(L""); + } + + pluginFilename = WideToUTF8(values[0]); + +#if defined(MOZ_SANDBOX) + // This is probably the earliest we would want to start the sandbox. + // As we attempt to tighten the sandbox, we may need to consider moving this + // to later in the plugin initialization. + mozilla::SandboxTarget::Instance()->StartSandbox(); +#endif +#else +# error Sorry +#endif + + if (NS_FAILED(nsRegion::InitStatic())) { + NS_ERROR("Could not initialize nsRegion"); + return false; + } + + bool retval = mPlugin.InitForChrome(pluginFilename, ParentPid(), + IOThreadChild::message_loop(), + IOThreadChild::channel()); +#if defined(XP_MACOSX) + if (nsCocoaFeatures::OnYosemiteOrLater()) { + // Explicitly turn off CGEvent logging. This works around bug 1092855. + // If there are already CGEvents in the log, turning off logging also + // causes those events to be written to disk. But at this point no + // CGEvents have yet been processed. CGEvents are events (usually + // input events) pulled from the WindowServer. An option of 0x80000008 + // turns on CGEvent logging. + CGSSetDebugOptions(0x80000007); + } +#endif + return retval; +} + +void +PluginProcessChild::CleanUp() +{ + nsRegion::ShutdownStatic(); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginProcessChild.h b/dom/plugins/ipc/PluginProcessChild.h new file mode 100644 index 000000000..4a077ed2f --- /dev/null +++ b/dom/plugins/ipc/PluginProcessChild.h @@ -0,0 +1,55 @@ +/* -*- 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 dom_plugins_PluginProcessChild_h +#define dom_plugins_PluginProcessChild_h 1 + +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/plugins/PluginModuleChild.h" + +#if defined(XP_WIN) +#include "mozilla/mscom/MainThreadRuntime.h" +#endif + +namespace mozilla { +namespace plugins { +//----------------------------------------------------------------------------- + +class PluginProcessChild : public mozilla::ipc::ProcessChild { +protected: + typedef mozilla::ipc::ProcessChild ProcessChild; + +public: + explicit PluginProcessChild(ProcessId aParentPid) + : ProcessChild(aParentPid), mPlugin(true) + { } + + virtual ~PluginProcessChild() + { } + + virtual bool Init() override; + virtual void CleanUp() override; + +protected: + static PluginProcessChild* current() { + return static_cast<PluginProcessChild*>(ProcessChild::current()); + } + +private: +#if defined(XP_WIN) + /* Drag-and-drop and Silverlight depend on the host initializing COM. + * This object initializes and configures COM. */ + mozilla::mscom::MainThreadRuntime mCOMRuntime; +#endif + PluginModuleChild mPlugin; + + DISALLOW_EVIL_CONSTRUCTORS(PluginProcessChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // ifndef dom_plugins_PluginProcessChild_h diff --git a/dom/plugins/ipc/PluginProcessParent.cpp b/dom/plugins/ipc/PluginProcessParent.cpp new file mode 100644 index 000000000..2a73bce51 --- /dev/null +++ b/dom/plugins/ipc/PluginProcessParent.cpp @@ -0,0 +1,257 @@ +/* -*- 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/. */ + +#include "mozilla/plugins/PluginProcessParent.h" + +#include "base/string_util.h" +#include "base/process_util.h" + +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/Telemetry.h" +#include "nsThreadUtils.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +#include "nsDirectoryServiceDefs.h" +#endif + +using std::vector; +using std::string; + +using mozilla::ipc::BrowserProcessSubThread; +using mozilla::ipc::GeckoChildProcessHost; +using mozilla::plugins::LaunchCompleteTask; +using mozilla::plugins::PluginProcessParent; +using base::ProcessArchitecture; + +PluginProcessParent::PluginProcessParent(const std::string& aPluginFilePath) : + GeckoChildProcessHost(GeckoProcessType_Plugin), + mPluginFilePath(aPluginFilePath), + mTaskFactory(this), + mMainMsgLoop(MessageLoop::current()), + mRunCompleteTaskImmediately(false) +{ +} + +PluginProcessParent::~PluginProcessParent() +{ +} + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +static void +AddSandboxAllowedFile(vector<std::wstring>& aAllowedFiles, nsIProperties* aDirSvc, + const char* aDir, const nsAString& aSuffix = EmptyString()) +{ + nsCOMPtr<nsIFile> userDir; + nsresult rv = aDirSvc->Get(aDir, NS_GET_IID(nsIFile), getter_AddRefs(userDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsAutoString userDirPath; + rv = userDir->GetPath(userDirPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + if (!aSuffix.IsEmpty()) { + userDirPath.Append(aSuffix); + } + aAllowedFiles.push_back(std::wstring(userDirPath.get())); + return; +} + +static void +AddSandboxAllowedFiles(int32_t aSandboxLevel, + vector<std::wstring>& aAllowedFilesRead, + vector<std::wstring>& aAllowedFilesReadWrite, + vector<std::wstring>& aAllowedDirectories) +{ + if (aSandboxLevel < 2) { + return; + } + + nsresult rv; + nsCOMPtr<nsIProperties> dirSvc = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Higher than level 2 currently removes the users own rights. + if (aSandboxLevel > 2) { + AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR); + AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR, + NS_LITERAL_STRING("\\*")); + } + + // Level 2 and above is now using low integrity, so we need to give write + // access to the Flash directories. + // This should be made Flash specific (Bug 1171396). + AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_APPDATA_DIR, + NS_LITERAL_STRING("\\Macromedia\\Flash Player\\*")); + AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_LOCAL_APPDATA_DIR, + NS_LITERAL_STRING("\\Macromedia\\Flash Player\\*")); + AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_APPDATA_DIR, + NS_LITERAL_STRING("\\Adobe\\Flash Player\\*")); + + // Access also has to be given to create the parent directories as they may + // not exist. + AddSandboxAllowedFile(aAllowedDirectories, dirSvc, NS_WIN_APPDATA_DIR, + NS_LITERAL_STRING("\\Macromedia")); + AddSandboxAllowedFile(aAllowedDirectories, dirSvc, NS_WIN_APPDATA_DIR, + NS_LITERAL_STRING("\\Macromedia\\Flash Player")); + AddSandboxAllowedFile(aAllowedDirectories, dirSvc, NS_WIN_LOCAL_APPDATA_DIR, + NS_LITERAL_STRING("\\Macromedia")); + AddSandboxAllowedFile(aAllowedDirectories, dirSvc, NS_WIN_LOCAL_APPDATA_DIR, + NS_LITERAL_STRING("\\Macromedia\\Flash Player")); + AddSandboxAllowedFile(aAllowedDirectories, dirSvc, NS_WIN_APPDATA_DIR, + NS_LITERAL_STRING("\\Adobe")); + AddSandboxAllowedFile(aAllowedDirectories, dirSvc, NS_WIN_APPDATA_DIR, + NS_LITERAL_STRING("\\Adobe\\Flash Player")); +} +#endif + +bool +PluginProcessParent::Launch(mozilla::UniquePtr<LaunchCompleteTask> aLaunchCompleteTask, + int32_t aSandboxLevel) +{ +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + mSandboxLevel = aSandboxLevel; + AddSandboxAllowedFiles(mSandboxLevel, mAllowedFilesRead, + mAllowedFilesReadWrite, mAllowedDirectories); +#else + if (aSandboxLevel != 0) { + MOZ_ASSERT(false, + "Can't enable an NPAPI process sandbox for platform/build."); + } +#endif + + ProcessArchitecture currentArchitecture = base::GetCurrentProcessArchitecture(); + uint32_t containerArchitectures = GetSupportedArchitecturesForProcessType(GeckoProcessType_Plugin); + + uint32_t pluginLibArchitectures = currentArchitecture; +#ifdef XP_MACOSX + nsresult rv = GetArchitecturesForBinary(mPluginFilePath.c_str(), &pluginLibArchitectures); + if (NS_FAILED(rv)) { + // If the call failed just assume that we want the current architecture. + pluginLibArchitectures = currentArchitecture; + } +#endif + + ProcessArchitecture selectedArchitecture = currentArchitecture; + if (!(pluginLibArchitectures & containerArchitectures & currentArchitecture)) { + // Prefererence in order: x86_64, i386, PPC. The only particularly important thing + // about this order is that we'll prefer 64-bit architectures first. + if (base::PROCESS_ARCH_X86_64 & pluginLibArchitectures & containerArchitectures) { + selectedArchitecture = base::PROCESS_ARCH_X86_64; + } + else if (base::PROCESS_ARCH_I386 & pluginLibArchitectures & containerArchitectures) { + selectedArchitecture = base::PROCESS_ARCH_I386; + } + else if (base::PROCESS_ARCH_PPC & pluginLibArchitectures & containerArchitectures) { + selectedArchitecture = base::PROCESS_ARCH_PPC; + } + else if (base::PROCESS_ARCH_ARM & pluginLibArchitectures & containerArchitectures) { + selectedArchitecture = base::PROCESS_ARCH_ARM; + } + else if (base::PROCESS_ARCH_MIPS & pluginLibArchitectures & containerArchitectures) { + selectedArchitecture = base::PROCESS_ARCH_MIPS; + } + else { + return false; + } + } + + mLaunchCompleteTask = mozilla::Move(aLaunchCompleteTask); + + vector<string> args; + args.push_back(MungePluginDsoPath(mPluginFilePath)); + + bool result = AsyncLaunch(args, selectedArchitecture); + if (!result) { + mLaunchCompleteTask = nullptr; + } + return result; +} + +void +PluginProcessParent::Delete() +{ + MessageLoop* currentLoop = MessageLoop::current(); + MessageLoop* ioLoop = XRE_GetIOMessageLoop(); + + if (currentLoop == ioLoop) { + delete this; + return; + } + + ioLoop->PostTask(NewNonOwningRunnableMethod(this, &PluginProcessParent::Delete)); +} + +void +PluginProcessParent::SetCallRunnableImmediately(bool aCallImmediately) +{ + mRunCompleteTaskImmediately = aCallImmediately; +} + +/** + * This function exists so that we may provide an additional level of + * indirection between the task being posted to main event loop (a + * RunnableMethod) and the launch complete task itself. This is needed + * for cases when both WaitUntilConnected or OnChannel* race to invoke the + * task. + */ +void +PluginProcessParent::RunLaunchCompleteTask() +{ + if (mLaunchCompleteTask) { + mLaunchCompleteTask->Run(); + mLaunchCompleteTask = nullptr; + } +} + +bool +PluginProcessParent::WaitUntilConnected(int32_t aTimeoutMs) +{ + bool result = GeckoChildProcessHost::WaitUntilConnected(aTimeoutMs); + if (mRunCompleteTaskImmediately && mLaunchCompleteTask) { + if (result) { + mLaunchCompleteTask->SetLaunchSucceeded(); + } + RunLaunchCompleteTask(); + } + return result; +} + +void +PluginProcessParent::OnChannelConnected(int32_t peer_pid) +{ + GeckoChildProcessHost::OnChannelConnected(peer_pid); + if (mLaunchCompleteTask && !mRunCompleteTaskImmediately) { + mLaunchCompleteTask->SetLaunchSucceeded(); + mMainMsgLoop->PostTask(mTaskFactory.NewRunnableMethod( + &PluginProcessParent::RunLaunchCompleteTask)); + } +} + +void +PluginProcessParent::OnChannelError() +{ + GeckoChildProcessHost::OnChannelError(); + if (mLaunchCompleteTask && !mRunCompleteTaskImmediately) { + mMainMsgLoop->PostTask(mTaskFactory.NewRunnableMethod( + &PluginProcessParent::RunLaunchCompleteTask)); + } +} + +bool +PluginProcessParent::IsConnected() +{ + mozilla::MonitorAutoLock lock(mMonitor); + return mProcessState == PROCESS_CONNECTED; +} + diff --git a/dom/plugins/ipc/PluginProcessParent.h b/dom/plugins/ipc/PluginProcessParent.h new file mode 100644 index 000000000..b93e14bf6 --- /dev/null +++ b/dom/plugins/ipc/PluginProcessParent.h @@ -0,0 +1,94 @@ +/* -*- 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 dom_plugins_PluginProcessParent_h +#define dom_plugins_PluginProcessParent_h 1 + +#include "mozilla/Attributes.h" +#include "base/basictypes.h" + +#include "base/file_path.h" +#include "base/task.h" +#include "base/thread.h" +#include "chrome/common/child_process_host.h" + +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/TaskFactory.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" + +namespace mozilla { +namespace plugins { + +class LaunchCompleteTask : public Runnable +{ +public: + LaunchCompleteTask() + : mLaunchSucceeded(false) + { + } + + void SetLaunchSucceeded() { mLaunchSucceeded = true; } + +protected: + bool mLaunchSucceeded; +}; + +class PluginProcessParent : public mozilla::ipc::GeckoChildProcessHost +{ +public: + explicit PluginProcessParent(const std::string& aPluginFilePath); + ~PluginProcessParent(); + + /** + * Launch the plugin process. If the process fails to launch, + * this method will return false. + * + * @param aLaunchCompleteTask Task that is executed on the main + * thread once the asynchonous launch has completed. + * @param aSandboxLevel Determines the strength of the sandbox. + * <= 0 means no sandbox. + */ + bool Launch(UniquePtr<LaunchCompleteTask> aLaunchCompleteTask = UniquePtr<LaunchCompleteTask>(), + int32_t aSandboxLevel = 0); + + void Delete(); + + virtual bool CanShutdown() override + { + return true; + } + + const std::string& GetPluginFilePath() { return mPluginFilePath; } + + using mozilla::ipc::GeckoChildProcessHost::GetChannel; + + void SetCallRunnableImmediately(bool aCallImmediately); + virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0) override; + + virtual void OnChannelConnected(int32_t peer_pid) override; + virtual void OnChannelError() override; + + bool IsConnected(); + +private: + void RunLaunchCompleteTask(); + + std::string mPluginFilePath; + ipc::TaskFactory<PluginProcessParent> mTaskFactory; + UniquePtr<LaunchCompleteTask> mLaunchCompleteTask; + MessageLoop* mMainMsgLoop; + bool mRunCompleteTaskImmediately; + + DISALLOW_EVIL_CONSTRUCTORS(PluginProcessParent); +}; + + +} // namespace plugins +} // namespace mozilla + +#endif // ifndef dom_plugins_PluginProcessParent_h diff --git a/dom/plugins/ipc/PluginQuirks.cpp b/dom/plugins/ipc/PluginQuirks.cpp new file mode 100644 index 000000000..c60cf8836 --- /dev/null +++ b/dom/plugins/ipc/PluginQuirks.cpp @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +#include "PluginQuirks.h" + +#include "nsPluginHost.h" + +namespace mozilla { +namespace plugins { + +int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType, + const nsCString& aPluginFilename) +{ + int quirks = 0; + + nsPluginHost::SpecialType specialType = nsPluginHost::GetSpecialType(aMimeType); + + if (specialType == nsPluginHost::eSpecialType_Silverlight) { + quirks |= QUIRK_SILVERLIGHT_DEFAULT_TRANSPARENT; +#ifdef OS_WIN + quirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK; + quirks |= QUIRK_SILVERLIGHT_FOCUS_CHECK_PARENT; +#endif + } + + if (specialType == nsPluginHost::eSpecialType_Flash) { + quirks |= QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN; +#ifdef OS_WIN + quirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK; + quirks |= QUIRK_FLASH_THROTTLE_WMUSER_EVENTS; + quirks |= QUIRK_FLASH_HOOK_SETLONGPTR; + quirks |= QUIRK_FLASH_HOOK_GETWINDOWINFO; + quirks |= QUIRK_FLASH_FIXUP_MOUSE_CAPTURE; + quirks |= QUIRK_WINLESS_HOOK_IME; +#if defined(_M_X64) || defined(__x86_64__) + quirks |= QUIRK_FLASH_HOOK_GETKEYSTATE; +#endif +#endif + } + +#ifdef OS_WIN + // QuickTime plugin usually loaded with audio/mpeg mimetype + NS_NAMED_LITERAL_CSTRING(quicktime, "npqtplugin"); + if (FindInReadable(quicktime, aPluginFilename)) { + quirks |= QUIRK_QUICKTIME_AVOID_SETWINDOW; + } +#endif + +#ifdef XP_MACOSX + // Whitelist Flash and Quicktime to support offline renderer + NS_NAMED_LITERAL_CSTRING(quicktime, "QuickTime Plugin.plugin"); + if (specialType == nsPluginHost::eSpecialType_Flash) { + quirks |= QUIRK_ALLOW_OFFLINE_RENDERER; + } else if (FindInReadable(quicktime, aPluginFilename)) { + quirks |= QUIRK_ALLOW_OFFLINE_RENDERER; + } +#endif + +#ifdef OS_WIN + if (specialType == nsPluginHost::eSpecialType_Unity) { + quirks |= QUIRK_UNITY_FIXUP_MOUSE_CAPTURE; + } +#endif + +#ifdef OS_WIN + if (specialType == nsPluginHost::eSpecialType_Test) { + quirks |= QUIRK_WINLESS_HOOK_IME; + } +#endif + + return quirks; +} + +} /* namespace plugins */ +} /* namespace mozilla */ diff --git a/dom/plugins/ipc/PluginQuirks.h b/dom/plugins/ipc/PluginQuirks.h new file mode 100644 index 000000000..f0a6b6a30 --- /dev/null +++ b/dom/plugins/ipc/PluginQuirks.h @@ -0,0 +1,68 @@ +/* -*- 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 dom_plugins_PluginQuirks_h +#define dom_plugins_PluginQuirks_h + +namespace mozilla { +namespace plugins { + +// Quirks mode support for various plugin mime types +enum PluginQuirks { + QUIRKS_NOT_INITIALIZED = 0, + // Silverlight assumes it is transparent in windowless mode. This quirk + // matches the logic in nsNPAPIPluginInstance::SetWindowless. + QUIRK_SILVERLIGHT_DEFAULT_TRANSPARENT = 1 << 0, + // Win32: Hook TrackPopupMenu api so that we can swap out parent + // hwnds. The api will fail with parents not associated with our + // child ui thread. See WinlessHandleEvent for details. + QUIRK_WINLESS_TRACKPOPUP_HOOK = 1 << 1, + // Win32: Throttle flash WM_USER+1 heart beat messages to prevent + // flooding chromium's dispatch loop, which can cause ipc traffic + // processing lag. + QUIRK_FLASH_THROTTLE_WMUSER_EVENTS = 1 << 2, + // Win32: Catch resets on our subclass by hooking SetWindowLong. + QUIRK_FLASH_HOOK_SETLONGPTR = 1 << 3, + // X11: 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. + QUIRK_FLASH_EXPOSE_COORD_TRANSLATION = 1 << 4, + // Win32: Catch get window info calls on the browser and tweak the + // results so mouse input works when flash is displaying it's settings + // window. + QUIRK_FLASH_HOOK_GETWINDOWINFO = 1 << 5, + // Win: Addresses a flash bug with mouse capture and full screen + // windows. + QUIRK_FLASH_FIXUP_MOUSE_CAPTURE = 1 << 6, + // Win: QuickTime steals focus on SetWindow calls even if it's hidden. + // Avoid calling SetWindow in that case. + QUIRK_QUICKTIME_AVOID_SETWINDOW = 1 << 7, + // Win: Check to make sure the parent window has focus before calling + // set focus on the child. Addresses a full screen dialog prompt + // problem in Silverlight. + QUIRK_SILVERLIGHT_FOCUS_CHECK_PARENT = 1 << 8, + // Mac: Allow the plugin to use offline renderer mode. + // Use this only if the plugin is certified the support the offline renderer. + QUIRK_ALLOW_OFFLINE_RENDERER = 1 << 9, + // Work around a Flash bug where it fails to check the error code of a + // NPN_GetValue(NPNVdocumentOrigin) call before trying to dereference + // its char* output. + QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN = 1 << 10, + // Win: Addresses a Unity bug with mouse capture. + QUIRK_UNITY_FIXUP_MOUSE_CAPTURE = 1 << 11, + // Win: Hook IMM32 API to handle IME event on windowless plugin + QUIRK_WINLESS_HOOK_IME = 1 << 12, + // Win: Hook GetKeyState to get keyboard state on sandbox process + QUIRK_FLASH_HOOK_GETKEYSTATE = 1 << 13, +}; + +int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType, + const nsCString& aPluginFilename); + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif // ifndef dom_plugins_PluginQuirks_h diff --git a/dom/plugins/ipc/PluginScriptableObjectChild.cpp b/dom/plugins/ipc/PluginScriptableObjectChild.cpp new file mode 100644 index 000000000..371189170 --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectChild.cpp @@ -0,0 +1,1298 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 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 "PluginScriptableObjectChild.h" +#include "PluginScriptableObjectUtils.h" +#include "mozilla/plugins/PluginTypes.h" + +using namespace mozilla::plugins; + +/** + * NPIdentifiers in the plugin process use a tagged representation. The low bit + * stores the tag. If it's zero, the identifier is a string, and the value is a + * pointer to a StoredIdentifier. If the tag bit is 1, then the rest of the + * NPIdentifier value is the integer itself. Like the JSAPI, we require that all + * integers stored in NPIdentifier be non-negative. + * + * String identifiers are stored in the sIdentifiers hashtable to ensure + * uniqueness. The lifetime of these identifiers is only as long as the incoming + * IPC call from the chrome process. If the plugin wants to retain an + * identifier, it needs to call NPN_GetStringIdentifier, which causes the + * mPermanent flag to be set on the identifier. When this flag is set, the + * identifier is saved until the plugin process exits. + * + * The StackIdentifier RAII class is used to manage ownership of + * identifiers. Any identifier obtained from this class should not be used + * outside its scope, except when the MakePermanent() method has been called on + * it. + * + * The lifetime of an NPIdentifier in the plugin process is totally divorced + * from the lifetime of an NPIdentifier in the chrome process (where an + * NPIdentifier is stored as a jsid). The JS GC in the chrome process is able to + * trace through the entire heap, unlike in the plugin process, so there is no + * reason to retain identifiers there. + */ + +PluginScriptableObjectChild::IdentifierTable PluginScriptableObjectChild::sIdentifiers; + +/* static */ PluginScriptableObjectChild::StoredIdentifier* +PluginScriptableObjectChild::HashIdentifier(const nsCString& aIdentifier) +{ + StoredIdentifier* stored = sIdentifiers.Get(aIdentifier).get(); + if (stored) { + return stored; + } + + stored = new StoredIdentifier(aIdentifier); + sIdentifiers.Put(aIdentifier, stored); + return stored; +} + +/* static */ void +PluginScriptableObjectChild::UnhashIdentifier(StoredIdentifier* aStored) +{ + MOZ_ASSERT(sIdentifiers.Get(aStored->mIdentifier)); + sIdentifiers.Remove(aStored->mIdentifier); +} + +/* static */ void +PluginScriptableObjectChild::ClearIdentifiers() +{ + sIdentifiers.Clear(); +} + +PluginScriptableObjectChild::StackIdentifier::StackIdentifier(const PluginIdentifier& aIdentifier) +: mIdentifier(aIdentifier), + mStored(nullptr) +{ + if (aIdentifier.type() == PluginIdentifier::TnsCString) { + mStored = PluginScriptableObjectChild::HashIdentifier(mIdentifier.get_nsCString()); + } +} + +PluginScriptableObjectChild::StackIdentifier::StackIdentifier(NPIdentifier aIdentifier) +: mStored(nullptr) +{ + uintptr_t bits = reinterpret_cast<uintptr_t>(aIdentifier); + if (bits & 1) { + int32_t num = int32_t(bits >> 1); + mIdentifier = PluginIdentifier(num); + } else { + mStored = static_cast<StoredIdentifier*>(aIdentifier); + mIdentifier = mStored->mIdentifier; + } +} + +PluginScriptableObjectChild::StackIdentifier::~StackIdentifier() +{ + if (!mStored) { + return; + } + + // Each StackIdentifier owns one reference to its StoredIdentifier. In + // addition, the sIdentifiers table owns a reference. If mPermanent is false + // and sIdentifiers has the last reference, then we want to remove the + // StoredIdentifier from the table (and destroy it). + StoredIdentifier *stored = mStored; + mStored = nullptr; + if (stored->mRefCnt == 1 && !stored->mPermanent) { + PluginScriptableObjectChild::UnhashIdentifier(stored); + } +} + +NPIdentifier +PluginScriptableObjectChild::StackIdentifier::ToNPIdentifier() const +{ + if (mStored) { + MOZ_ASSERT(mIdentifier.type() == PluginIdentifier::TnsCString); + MOZ_ASSERT((reinterpret_cast<uintptr_t>(mStored.get()) & 1) == 0); + return mStored; + } + + int32_t num = mIdentifier.get_int32_t(); + // The JS engine imposes this condition on int32s in jsids, so we assume it. + MOZ_ASSERT(num >= 0); + return reinterpret_cast<NPIdentifier>((num << 1) | 1); +} + +static PluginIdentifier +FromNPIdentifier(NPIdentifier aIdentifier) +{ + PluginScriptableObjectChild::StackIdentifier stack(aIdentifier); + return stack.GetIdentifier(); +} + +// static +NPObject* +PluginScriptableObjectChild::ScriptableAllocate(NPP aInstance, + NPClass* aClass) +{ + AssertPluginThread(); + + if (aClass != GetClass()) { + NS_RUNTIMEABORT("Huh?! Wrong class!"); + } + + return new ChildNPObject(); +} + +// static +void +PluginScriptableObjectChild::ScriptableInvalidate(NPObject* aObject) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + // This can happen more than once, and is just fine. + return; + } + + object->invalidated = true; +} + +// static +void +PluginScriptableObjectChild::ScriptableDeallocate(NPObject* aObject) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + PluginScriptableObjectChild* actor = object->parent; + if (actor) { + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + actor->DropNPObject(); + } + + delete object; +} + +// static +bool +PluginScriptableObjectChild::ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + actor->CallHasMethod(FromNPIdentifier(aName), &result); + + return result; +} + +// static +bool +PluginScriptableObjectChild::ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + actor->CallInvoke(FromNPIdentifier(aName), args, + &remoteResult, &success); + + if (!success) { + return false; + } + + ConvertToVariant(remoteResult, *aResult); + return true; +} + +// static +bool +PluginScriptableObjectChild::ScriptableInvokeDefault(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + actor->CallInvokeDefault(args, &remoteResult, &success); + + if (!success) { + return false; + } + + ConvertToVariant(remoteResult, *aResult); + return true; +} + +// static +bool +PluginScriptableObjectChild::ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + actor->CallHasProperty(FromNPIdentifier(aName), &result); + + return result; +} + +// static +bool +PluginScriptableObjectChild::ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + PluginInstanceChild::AutoStackHelper guard(actor->mInstance); + + Variant result; + bool success; + actor->CallGetParentProperty(FromNPIdentifier(aName), + &result, &success); + + if (!success) { + return false; + } + + ConvertToVariant(result, *aResult); + return true; +} + +// static +bool +PluginScriptableObjectChild::ScriptableSetProperty(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aValue) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariant value(*aValue, actor->GetInstance()); + if (!value.IsOk()) { + NS_WARNING("Failed to convert variant!"); + return false; + } + + bool success; + actor->CallSetProperty(FromNPIdentifier(aName), value, + &success); + + return success; +} + +// static +bool +PluginScriptableObjectChild::ScriptableRemoveProperty(NPObject* aObject, + NPIdentifier aName) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool success; + actor->CallRemoveProperty(FromNPIdentifier(aName), + &success); + + return success; +} + +// static +bool +PluginScriptableObjectChild::ScriptableEnumerate(NPObject* aObject, + NPIdentifier** aIdentifiers, + uint32_t* aCount) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + AutoTArray<PluginIdentifier, 10> identifiers; + bool success; + actor->CallEnumerate(&identifiers, &success); + + if (!success) { + return false; + } + + *aCount = identifiers.Length(); + if (!*aCount) { + *aIdentifiers = nullptr; + return true; + } + + *aIdentifiers = reinterpret_cast<NPIdentifier*>( + PluginModuleChild::sBrowserFuncs.memalloc(*aCount * sizeof(NPIdentifier))); + if (!*aIdentifiers) { + NS_ERROR("Out of memory!"); + return false; + } + + for (uint32_t index = 0; index < *aCount; index++) { + StackIdentifier id(identifiers[index]); + // Make the id permanent in case the plugin retains it. + id.MakePermanent(); + (*aIdentifiers)[index] = id.ToNPIdentifier(); + } + return true; +} + +// static +bool +PluginScriptableObjectChild::ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + NS_RUNTIMEABORT("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + actor->CallConstruct(args, &remoteResult, &success); + + if (!success) { + return false; + } + + ConvertToVariant(remoteResult, *aResult); + return true; +} + +const NPClass PluginScriptableObjectChild::sNPClass = { + NP_CLASS_STRUCT_VERSION, + PluginScriptableObjectChild::ScriptableAllocate, + PluginScriptableObjectChild::ScriptableDeallocate, + PluginScriptableObjectChild::ScriptableInvalidate, + PluginScriptableObjectChild::ScriptableHasMethod, + PluginScriptableObjectChild::ScriptableInvoke, + PluginScriptableObjectChild::ScriptableInvokeDefault, + PluginScriptableObjectChild::ScriptableHasProperty, + PluginScriptableObjectChild::ScriptableGetProperty, + PluginScriptableObjectChild::ScriptableSetProperty, + PluginScriptableObjectChild::ScriptableRemoveProperty, + PluginScriptableObjectChild::ScriptableEnumerate, + PluginScriptableObjectChild::ScriptableConstruct +}; + +PluginScriptableObjectChild::PluginScriptableObjectChild( + ScriptableObjectType aType) +: mInstance(nullptr), + mObject(nullptr), + mInvalidated(false), + mProtectCount(0), + mType(aType) +{ + AssertPluginThread(); +} + +PluginScriptableObjectChild::~PluginScriptableObjectChild() +{ + AssertPluginThread(); + + if (mObject) { + UnregisterActor(mObject); + + if (mObject->_class == GetClass()) { + NS_ASSERTION(mType == Proxy, "Wrong type!"); + static_cast<ChildNPObject*>(mObject)->parent = nullptr; + } + else { + NS_ASSERTION(mType == LocalObject, "Wrong type!"); + PluginModuleChild::sBrowserFuncs.releaseobject(mObject); + } + } +} + +bool +PluginScriptableObjectChild::InitializeProxy() +{ + AssertPluginThread(); + NS_ASSERTION(mType == Proxy, "Bad type!"); + NS_ASSERTION(!mObject, "Calling Initialize more than once!"); + NS_ASSERTION(!mInvalidated, "Already invalidated?!"); + + mInstance = static_cast<PluginInstanceChild*>(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + NPObject* object = CreateProxyObject(); + if (!object) { + NS_ERROR("Failed to create object!"); + return false; + } + + if (!RegisterActor(object)) { + NS_ERROR("RegisterActor failed"); + return false; + } + + mObject = object; + return true; +} + +void +PluginScriptableObjectChild::InitializeLocal(NPObject* aObject) +{ + AssertPluginThread(); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + NS_ASSERTION(!mObject, "Calling Initialize more than once!"); + NS_ASSERTION(!mInvalidated, "Already invalidated?!"); + + mInstance = static_cast<PluginInstanceChild*>(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + PluginModuleChild::sBrowserFuncs.retainobject(aObject); + + NS_ASSERTION(!mProtectCount, "Should be zero!"); + mProtectCount++; + + if (!RegisterActor(aObject)) { + NS_ERROR("RegisterActor failed"); + } + + mObject = aObject; +} + +NPObject* +PluginScriptableObjectChild::CreateProxyObject() +{ + NS_ASSERTION(mInstance, "Must have an instance!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + NPClass* proxyClass = const_cast<NPClass*>(GetClass()); + NPObject* npobject = + PluginModuleChild::sBrowserFuncs.createobject(mInstance->GetNPP(), + proxyClass); + NS_ASSERTION(npobject, "Failed to create object?!"); + NS_ASSERTION(npobject->_class == GetClass(), "Wrong kind of object!"); + NS_ASSERTION(npobject->referenceCount == 1, "Some kind of live object!"); + + ChildNPObject* object = static_cast<ChildNPObject*>(npobject); + NS_ASSERTION(!object->invalidated, "Bad object!"); + NS_ASSERTION(!object->parent, "Bad object!"); + + // We don't want to have the actor own this object but rather let the object + // own this actor. Set the reference count to 0 here so that when the object + // dies we will send the destructor message to the child. + object->referenceCount = 0; + NS_LOG_RELEASE(object, 0, "NPObject"); + + object->parent = const_cast<PluginScriptableObjectChild*>(this); + return object; +} + +bool +PluginScriptableObjectChild::ResurrectProxyObject() +{ + NS_ASSERTION(mInstance, "Must have an instance already!"); + NS_ASSERTION(!mObject, "Should not have an object already!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + if (!InitializeProxy()) { + NS_ERROR("Initialize failed!"); + return false; + } + + SendProtect(); + return true; +} + +NPObject* +PluginScriptableObjectChild::GetObject(bool aCanResurrect) +{ + if (!mObject && aCanResurrect && !ResurrectProxyObject()) { + NS_ERROR("Null object!"); + return nullptr; + } + return mObject; +} + +void +PluginScriptableObjectChild::Protect() +{ + NS_ASSERTION(mObject, "No object!"); + NS_ASSERTION(mProtectCount >= 0, "Negative retain count?!"); + + if (mType == LocalObject) { + ++mProtectCount; + } +} + +void +PluginScriptableObjectChild::Unprotect() +{ + NS_ASSERTION(mObject, "Bad state!"); + NS_ASSERTION(mProtectCount >= 0, "Negative retain count?!"); + + if (mType == LocalObject) { + if (--mProtectCount == 0) { + PluginScriptableObjectChild::Send__delete__(this); + } + } +} + +void +PluginScriptableObjectChild::DropNPObject() +{ + NS_ASSERTION(mObject, "Invalidated object!"); + NS_ASSERTION(mObject->_class == GetClass(), "Wrong type of object!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + // We think we're about to be deleted, but we could be racing with the other + // process. + UnregisterActor(mObject); + mObject = nullptr; + + SendUnprotect(); +} + +void +PluginScriptableObjectChild::NPObjectDestroyed() +{ + NS_ASSERTION(LocalObject == mType, + "ScriptableDeallocate should have handled this for proxies"); + mInvalidated = true; + mObject = nullptr; +} + +bool +PluginScriptableObjectChild::AnswerInvalidate() +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + return true; + } + + mInvalidated = true; + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (mObject->_class && mObject->_class->invalidate) { + mObject->_class->invalidate(mObject); + } + + Unprotect(); + + return true; +} + +bool +PluginScriptableObjectChild::AnswerHasMethod(const PluginIdentifier& aId, + bool* aHasMethod) +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerHasMethod with an invalidated object!"); + *aHasMethod = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasMethod)) { + *aHasMethod = false; + return true; + } + + StackIdentifier id(aId); + *aHasMethod = mObject->_class->hasMethod(mObject, id.ToNPIdentifier()); + return true; +} + +bool +PluginScriptableObjectChild::AnswerInvoke(const PluginIdentifier& aId, + InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerInvoke with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->invoke)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, mozilla::fallible)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + for (uint32_t index = 0; index < argCount; index++) { + ConvertToVariant(aArgs[index], convertedArgs[index]); + } + + NPVariant result; + VOID_TO_NPVARIANT(result); + StackIdentifier id(aId); + bool success = mObject->_class->invoke(mObject, id.ToNPIdentifier(), + convertedArgs.Elements(), argCount, + &result); + + for (uint32_t index = 0; index < argCount; index++) { + PluginModuleChild::sBrowserFuncs.releasevariantvalue(&convertedArgs[index]); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, GetInstance(), + false); + + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + *aSuccess = true; + *aResult = convertedResult; + return true; +} + +bool +PluginScriptableObjectChild::AnswerInvokeDefault(InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerInvokeDefault with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->invokeDefault)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, mozilla::fallible)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + for (uint32_t index = 0; index < argCount; index++) { + ConvertToVariant(aArgs[index], convertedArgs[index]); + } + + NPVariant result; + VOID_TO_NPVARIANT(result); + bool success = mObject->_class->invokeDefault(mObject, + convertedArgs.Elements(), + argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + PluginModuleChild::sBrowserFuncs.releasevariantvalue(&convertedArgs[index]); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, GetInstance(), + false); + + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + *aResult = convertedResult; + *aSuccess = true; + return true; +} + +bool +PluginScriptableObjectChild::AnswerHasProperty(const PluginIdentifier& aId, + bool* aHasProperty) +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerHasProperty with an invalidated object!"); + *aHasProperty = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasProperty)) { + *aHasProperty = false; + return true; + } + + StackIdentifier id(aId); + *aHasProperty = mObject->_class->hasProperty(mObject, id.ToNPIdentifier()); + return true; +} + +bool +PluginScriptableObjectChild::AnswerGetChildProperty(const PluginIdentifier& aId, + bool* aHasProperty, + bool* aHasMethod, + Variant* aResult, + bool* aSuccess) +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + *aHasProperty = *aHasMethod = *aSuccess = false; + *aResult = void_t(); + + if (mInvalidated) { + NS_WARNING("Calling AnswerGetProperty with an invalidated object!"); + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasProperty && + mObject->_class->hasMethod && mObject->_class->getProperty)) { + return true; + } + + StackIdentifier stackID(aId); + NPIdentifier id = stackID.ToNPIdentifier(); + + *aHasProperty = mObject->_class->hasProperty(mObject, id); + *aHasMethod = mObject->_class->hasMethod(mObject, id); + + if (*aHasProperty) { + NPVariant result; + VOID_TO_NPVARIANT(result); + + if (!mObject->_class->getProperty(mObject, id, &result)) { + return true; + } + + Variant converted; + if ((*aSuccess = ConvertToRemoteVariant(result, converted, GetInstance(), + false))) { + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + *aResult = converted; + } + } + + return true; +} + +bool +PluginScriptableObjectChild::AnswerSetProperty(const PluginIdentifier& aId, + const Variant& aValue, + bool* aSuccess) +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerSetProperty with an invalidated object!"); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasProperty && + mObject->_class->setProperty)) { + *aSuccess = false; + return true; + } + + StackIdentifier stackID(aId); + NPIdentifier id = stackID.ToNPIdentifier(); + + if (!mObject->_class->hasProperty(mObject, id)) { + *aSuccess = false; + return true; + } + + NPVariant converted; + ConvertToVariant(aValue, converted); + + if ((*aSuccess = mObject->_class->setProperty(mObject, id, &converted))) { + PluginModuleChild::sBrowserFuncs.releasevariantvalue(&converted); + } + return true; +} + +bool +PluginScriptableObjectChild::AnswerRemoveProperty(const PluginIdentifier& aId, + bool* aSuccess) +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerRemoveProperty with an invalidated object!"); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasProperty && + mObject->_class->removeProperty)) { + *aSuccess = false; + return true; + } + + StackIdentifier stackID(aId); + NPIdentifier id = stackID.ToNPIdentifier(); + *aSuccess = mObject->_class->hasProperty(mObject, id) ? + mObject->_class->removeProperty(mObject, id) : + true; + + return true; +} + +bool +PluginScriptableObjectChild::AnswerEnumerate(InfallibleTArray<PluginIdentifier>* aProperties, + bool* aSuccess) +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerEnumerate with an invalidated object!"); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->enumerate)) { + *aSuccess = false; + return true; + } + + NPIdentifier* ids; + uint32_t idCount; + if (!mObject->_class->enumerate(mObject, &ids, &idCount)) { + *aSuccess = false; + return true; + } + + aProperties->SetCapacity(idCount); + + for (uint32_t index = 0; index < idCount; index++) { + aProperties->AppendElement(FromNPIdentifier(ids[index])); + } + + PluginModuleChild::sBrowserFuncs.memfree(ids); + *aSuccess = true; + return true; +} + +bool +PluginScriptableObjectChild::AnswerConstruct(InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) +{ + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerConstruct with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->construct)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, mozilla::fallible)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + for (uint32_t index = 0; index < argCount; index++) { + ConvertToVariant(aArgs[index], convertedArgs[index]); + } + + NPVariant result; + VOID_TO_NPVARIANT(result); + bool success = mObject->_class->construct(mObject, convertedArgs.Elements(), + argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + PluginModuleChild::sBrowserFuncs.releasevariantvalue(&convertedArgs[index]); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, GetInstance(), + false); + + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + *aResult = convertedResult; + *aSuccess = true; + return true; +} + +bool +PluginScriptableObjectChild::RecvProtect() +{ + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Protect(); + return true; +} + +bool +PluginScriptableObjectChild::RecvUnprotect() +{ + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Unprotect(); + return true; +} + +bool +PluginScriptableObjectChild::Evaluate(NPString* aScript, + NPVariant* aResult) +{ + PluginInstanceChild::AutoStackHelper guard(mInstance); + + nsDependentCString script(""); + if (aScript->UTF8Characters && aScript->UTF8Length) { + script.Rebind(aScript->UTF8Characters, aScript->UTF8Length); + } + + bool success; + Variant result; + CallNPN_Evaluate(script, &result, &success); + + if (!success) { + return false; + } + + ConvertToVariant(result, *aResult); + return true; +} + +nsTHashtable<PluginScriptableObjectChild::NPObjectData>* PluginScriptableObjectChild::sObjectMap; + +bool +PluginScriptableObjectChild::RegisterActor(NPObject* aObject) +{ + AssertPluginThread(); + MOZ_ASSERT(aObject, "Null pointer!"); + + NPObjectData* d = sObjectMap->GetEntry(aObject); + if (!d) { + NS_ERROR("NPObject not in object table"); + return false; + } + + d->actor = this; + return true; +} + +void +PluginScriptableObjectChild::UnregisterActor(NPObject* aObject) +{ + AssertPluginThread(); + MOZ_ASSERT(aObject, "Null pointer!"); + + NPObjectData* d = sObjectMap->GetEntry(aObject); + MOZ_ASSERT(d, "NPObject not in object table"); + if (d) { + d->actor = nullptr; + } +} + +/* static */ PluginScriptableObjectChild* +PluginScriptableObjectChild::GetActorForNPObject(NPObject* aObject) +{ + AssertPluginThread(); + MOZ_ASSERT(aObject, "Null pointer!"); + + NPObjectData* d = sObjectMap->GetEntry(aObject); + if (!d) { + NS_ERROR("Plugin using object not created with NPN_CreateObject?"); + return nullptr; + } + + return d->actor; +} + +/* static */ void +PluginScriptableObjectChild::RegisterObject(NPObject* aObject, PluginInstanceChild* aInstance) +{ + AssertPluginThread(); + + if (!sObjectMap) { + sObjectMap = new nsTHashtable<PluginScriptableObjectChild::NPObjectData>(); + } + + NPObjectData* d = sObjectMap->PutEntry(aObject); + MOZ_ASSERT(!d->instance, "New NPObject already mapped?"); + d->instance = aInstance; +} + +/* static */ void +PluginScriptableObjectChild::UnregisterObject(NPObject* aObject) +{ + AssertPluginThread(); + + sObjectMap->RemoveEntry(aObject); + + if (!sObjectMap->Count()) { + delete sObjectMap; + sObjectMap = nullptr; + } +} + +/* static */ PluginInstanceChild* +PluginScriptableObjectChild::GetInstanceForNPObject(NPObject* aObject) +{ + AssertPluginThread(); + NPObjectData* d = sObjectMap->GetEntry(aObject); + if (!d) { + return nullptr; + } + return d->instance; +} + +/* static */ void +PluginScriptableObjectChild::NotifyOfInstanceShutdown(PluginInstanceChild* aInstance) +{ + AssertPluginThread(); + if (!sObjectMap) { + return; + } + + for (auto iter = sObjectMap->Iter(); !iter.Done(); iter.Next()) { + NPObjectData* d = iter.Get(); + if (d->instance == aInstance) { + NPObject* o = d->GetKey(); + aInstance->mDeletingHash->PutEntry(o); + } + } +} diff --git a/dom/plugins/ipc/PluginScriptableObjectChild.h b/dom/plugins/ipc/PluginScriptableObjectChild.h new file mode 100644 index 000000000..2d2e061de --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectChild.h @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 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 dom_plugins_PluginScriptableObjectChild_h +#define dom_plugins_PluginScriptableObjectChild_h 1 + +#include "mozilla/plugins/PPluginScriptableObjectChild.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/plugins/PluginTypes.h" + +#include "npruntime.h" +#include "nsDataHashtable.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceChild; +class PluginScriptableObjectChild; + +struct ChildNPObject : NPObject +{ + ChildNPObject() + : NPObject(), parent(nullptr), invalidated(false) + { + MOZ_COUNT_CTOR(ChildNPObject); + } + + ~ChildNPObject() + { + MOZ_COUNT_DTOR(ChildNPObject); + } + + // |parent| is always valid as long as the actor is alive. Once the actor is + // destroyed this will be set to null. + PluginScriptableObjectChild* parent; + bool invalidated; +}; + +class PluginScriptableObjectChild : public PPluginScriptableObjectChild +{ + friend class PluginInstanceChild; + +public: + explicit PluginScriptableObjectChild(ScriptableObjectType aType); + virtual ~PluginScriptableObjectChild(); + + bool + InitializeProxy(); + + void + InitializeLocal(NPObject* aObject); + + + virtual bool + AnswerInvalidate() override; + + virtual bool + AnswerHasMethod(const PluginIdentifier& aId, + bool* aHasMethod) override; + + virtual bool + AnswerInvoke(const PluginIdentifier& aId, + InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + AnswerInvokeDefault(InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + AnswerHasProperty(const PluginIdentifier& aId, + bool* aHasProperty) override; + + virtual bool + AnswerGetChildProperty(const PluginIdentifier& aId, + bool* aHasProperty, + bool* aHasMethod, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + AnswerSetProperty(const PluginIdentifier& aId, + const Variant& aValue, + bool* aSuccess) override; + + virtual bool + AnswerRemoveProperty(const PluginIdentifier& aId, + bool* aSuccess) override; + + virtual bool + AnswerEnumerate(InfallibleTArray<PluginIdentifier>* aProperties, + bool* aSuccess) override; + + virtual bool + AnswerConstruct(InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + RecvProtect() override; + + virtual bool + RecvUnprotect() override; + + NPObject* + GetObject(bool aCanResurrect); + + static const NPClass* + GetClass() + { + return &sNPClass; + } + + PluginInstanceChild* + GetInstance() const + { + return mInstance; + } + + // Protect only affects LocalObject actors. It is called by the + // ProtectedVariant/Actor helper classes before the actor is used as an + // argument to an IPC call and when the parent process resurrects a + // proxy object to the NPObject associated with this actor. + void Protect(); + + // Unprotect only affects LocalObject actors. It is called by the + // ProtectedVariant/Actor helper classes after the actor is used as an + // argument to an IPC call and when the parent process is no longer using + // this actor. + void Unprotect(); + + // DropNPObject is only used for Proxy actors and is called when the child + // process is no longer using the NPObject associated with this actor. The + // parent process may subsequently use this actor again in which case a new + // NPObject will be created and associated with this actor (see + // ResurrectProxyObject). + void DropNPObject(); + + /** + * After NPP_Destroy, all NPObjects associated with an instance are + * destroyed. We are informed of this destruction. This should only be called + * on Local actors. + */ + void NPObjectDestroyed(); + + bool + Evaluate(NPString* aScript, + NPVariant* aResult); + + ScriptableObjectType + Type() const { + return mType; + } + +private: + struct StoredIdentifier + { + nsCString mIdentifier; + nsAutoRefCnt mRefCnt; + bool mPermanent; + + nsrefcnt AddRef() { + ++mRefCnt; + return mRefCnt; + } + + nsrefcnt Release() { + --mRefCnt; + if (mRefCnt == 0) { + delete this; + return 0; + } + return mRefCnt; + } + + explicit StoredIdentifier(const nsCString& aIdentifier) + : mIdentifier(aIdentifier), mRefCnt(), mPermanent(false) + { MOZ_COUNT_CTOR(StoredIdentifier); } + + ~StoredIdentifier() { MOZ_COUNT_DTOR(StoredIdentifier); } + }; + +public: + class MOZ_STACK_CLASS StackIdentifier + { + public: + explicit StackIdentifier(const PluginIdentifier& aIdentifier); + explicit StackIdentifier(NPIdentifier aIdentifier); + ~StackIdentifier(); + + void MakePermanent() + { + if (mStored) { + mStored->mPermanent = true; + } + } + NPIdentifier ToNPIdentifier() const; + + bool IsString() const { return mIdentifier.type() == PluginIdentifier::TnsCString; } + const nsCString& GetString() const { return mIdentifier.get_nsCString(); } + + int32_t GetInt() const { return mIdentifier.get_int32_t(); } + + PluginIdentifier GetIdentifier() const { return mIdentifier; } + + private: + DISALLOW_COPY_AND_ASSIGN(StackIdentifier); + + PluginIdentifier mIdentifier; + RefPtr<StoredIdentifier> mStored; + }; + + static void ClearIdentifiers(); + + bool RegisterActor(NPObject* aObject); + void UnregisterActor(NPObject* aObject); + + static PluginScriptableObjectChild* GetActorForNPObject(NPObject* aObject); + + static void RegisterObject(NPObject* aObject, PluginInstanceChild* aInstance); + static void UnregisterObject(NPObject* aObject); + + static PluginInstanceChild* GetInstanceForNPObject(NPObject* aObject); + + /** + * Fill PluginInstanceChild.mDeletingHash with all the remaining NPObjects + * associated with that instance. + */ + static void NotifyOfInstanceShutdown(PluginInstanceChild* aInstance); + +private: + static NPObject* + ScriptableAllocate(NPP aInstance, + NPClass* aClass); + + static void + ScriptableInvalidate(NPObject* aObject); + + static void + ScriptableDeallocate(NPObject* aObject); + + static bool + ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName); + + static bool + ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult); + + static bool + ScriptableInvokeDefault(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult); + + static bool + ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName); + + static bool + ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult); + + static bool + ScriptableSetProperty(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aValue); + + static bool + ScriptableRemoveProperty(NPObject* aObject, + NPIdentifier aName); + + static bool + ScriptableEnumerate(NPObject* aObject, + NPIdentifier** aIdentifiers, + uint32_t* aCount); + + static bool + ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult); + + NPObject* + CreateProxyObject(); + + // ResurrectProxyObject is only used with Proxy actors. It is called when the + // parent process uses an actor whose NPObject was deleted by the child + // process. + bool ResurrectProxyObject(); + +private: + PluginInstanceChild* mInstance; + NPObject* mObject; + bool mInvalidated; + int mProtectCount; + + ScriptableObjectType mType; + + static const NPClass sNPClass; + + static StoredIdentifier* HashIdentifier(const nsCString& aIdentifier); + static void UnhashIdentifier(StoredIdentifier* aIdentifier); + + typedef nsDataHashtable<nsCStringHashKey, RefPtr<StoredIdentifier>> IdentifierTable; + static IdentifierTable sIdentifiers; + + struct NPObjectData : public nsPtrHashKey<NPObject> + { + explicit NPObjectData(const NPObject* key) + : nsPtrHashKey<NPObject>(key), + instance(nullptr), + actor(nullptr) + { } + + // never nullptr + PluginInstanceChild* instance; + + // sometimes nullptr (no actor associated with an NPObject) + PluginScriptableObjectChild* actor; + }; + + /** + * mObjectMap contains all the currently active NPObjects (from NPN_CreateObject until the + * final release/dealloc, whether or not an actor is currently associated with the object. + */ + static nsTHashtable<NPObjectData>* sObjectMap; +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif /* dom_plugins_PluginScriptableObjectChild_h */ diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.cpp b/dom/plugins/ipc/PluginScriptableObjectParent.cpp new file mode 100644 index 000000000..6e385b98c --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectParent.cpp @@ -0,0 +1,1393 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 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 "PluginScriptableObjectParent.h" + +#include "jsapi.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/plugins/PluginTypes.h" +#include "mozilla/Unused.h" +#include "nsNPAPIPlugin.h" +#include "PluginAsyncSurrogate.h" +#include "PluginScriptableObjectUtils.h" + +using namespace mozilla; +using namespace mozilla::plugins; +using namespace mozilla::plugins::parent; + +/** + * NPIdentifiers in the chrome process are stored as jsids. The difficulty is in + * ensuring that string identifiers are rooted without pinning them all. We + * assume that all NPIdentifiers passed into nsJSNPRuntime will not be used + * outside the scope of the NPAPI call (i.e., they won't be stored in the + * heap). Rooting is done using the StackIdentifier class, which roots the + * identifier via RootedId. + * + * This system does not allow jsids to be moved, as would be needed for + * generational or compacting GC. When Firefox implements a moving GC for + * strings, we will need to ensure that no movement happens while NPAPI code is + * on the stack: although StackIdentifier roots all identifiers used, the GC has + * no way to know that a jsid cast to an NPIdentifier needs to be fixed up if it + * is moved. + */ + +class MOZ_STACK_CLASS StackIdentifier +{ +public: + explicit StackIdentifier(const PluginIdentifier& aIdentifier, + bool aAtomizeAndPin = false); + + bool Failed() const { return mFailed; } + NPIdentifier ToNPIdentifier() const { return mIdentifier; } + +private: + bool mFailed; + NPIdentifier mIdentifier; + AutoSafeJSContext mCx; + JS::RootedId mId; +}; + +StackIdentifier::StackIdentifier(const PluginIdentifier& aIdentifier, bool aAtomizeAndPin) +: mFailed(false), + mId(mCx) +{ + if (aIdentifier.type() == PluginIdentifier::TnsCString) { + // We don't call _getstringidentifier because we may not want to intern the string. + NS_ConvertUTF8toUTF16 utf16name(aIdentifier.get_nsCString()); + JS::RootedString str(mCx, JS_NewUCStringCopyN(mCx, utf16name.get(), utf16name.Length())); + if (!str) { + NS_ERROR("Id can't be allocated"); + mFailed = true; + return; + } + if (aAtomizeAndPin) { + str = JS_AtomizeAndPinJSString(mCx, str); + if (!str) { + NS_ERROR("Id can't be allocated"); + mFailed = true; + return; + } + } + if (!JS_StringToId(mCx, str, &mId)) { + NS_ERROR("Id can't be allocated"); + mFailed = true; + return; + } + mIdentifier = JSIdToNPIdentifier(mId); + return; + } + + mIdentifier = mozilla::plugins::parent::_getintidentifier(aIdentifier.get_int32_t()); +} + +static bool +FromNPIdentifier(NPIdentifier aIdentifier, PluginIdentifier* aResult) +{ + if (mozilla::plugins::parent::_identifierisstring(aIdentifier)) { + nsCString string; + NPUTF8* chars = + mozilla::plugins::parent::_utf8fromidentifier(aIdentifier); + if (!chars) { + return false; + } + string.Adopt(chars); + *aResult = PluginIdentifier(string); + return true; + } + else { + int32_t intval = mozilla::plugins::parent::_intfromidentifier(aIdentifier); + *aResult = PluginIdentifier(intval); + return true; + } +} + +namespace { + +inline void +ReleaseVariant(NPVariant& aVariant, + PluginInstanceParent* aInstance) +{ + PushSurrogateAcceptCalls acceptCalls(aInstance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(aInstance); + if (npn) { + npn->releasevariantvalue(&aVariant); + } +} + +} // namespace + +// static +NPObject* +PluginScriptableObjectParent::ScriptableAllocate(NPP aInstance, + NPClass* aClass) +{ + if (aClass != GetClass()) { + NS_ERROR("Huh?! Wrong class!"); + return nullptr; + } + + return new ParentNPObject(); +} + +// static +void +PluginScriptableObjectParent::ScriptableInvalidate(NPObject* aObject) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + // This can happen more than once, and is just fine. + return; + } + + object->invalidated = true; + + // |object->parent| may be null already if the instance has gone away. + if (object->parent && !object->parent->CallInvalidate()) { + NS_ERROR("Failed to send message!"); + } +} + +// static +void +PluginScriptableObjectParent::ScriptableDeallocate(NPObject* aObject) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + + if (object->asyncWrapperCount > 0) { + // In this case we should just drop the refcount to the asyncWrapperCount + // instead of deallocating because there are still some async wrappers + // out there that are referencing this object. + object->referenceCount = object->asyncWrapperCount; + return; + } + + PluginScriptableObjectParent* actor = object->parent; + if (actor) { + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + actor->DropNPObject(); + } + + delete object; +} + +// static +bool +PluginScriptableObjectParent::ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + if (!actor->CallHasMethod(identifier, &result)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return result; +} + +// static +bool +PluginScriptableObjectParent::ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + if (!actor->CallInvoke(identifier, args, &remoteResult, + &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + return true; +} + +// static +bool +PluginScriptableObjectParent::ScriptableInvokeDefault(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + if (!actor->CallInvokeDefault(args, &remoteResult, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + return true; +} + +// static +bool +PluginScriptableObjectParent::ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + if (!actor->CallHasProperty(identifier, &result)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return result; +} + +// static +bool +PluginScriptableObjectParent::ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult) +{ + // See GetPropertyHelper below. + NS_NOTREACHED("Shouldn't ever call this directly!"); + return false; +} + +// static +bool +PluginScriptableObjectParent::ScriptableSetProperty(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aValue) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariant value(*aValue, actor->GetInstance()); + if (!value.IsOk()) { + NS_WARNING("Failed to convert variant!"); + return false; + } + + bool success; + if (!actor->CallSetProperty(identifier, value, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return success; +} + +// static +bool +PluginScriptableObjectParent::ScriptableRemoveProperty(NPObject* aObject, + NPIdentifier aName) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool success; + if (!actor->CallRemoveProperty(identifier, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return success; +} + +// static +bool +PluginScriptableObjectParent::ScriptableEnumerate(NPObject* aObject, + NPIdentifier** aIdentifiers, + uint32_t* aCount) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(aObject); + if (!npn) { + NS_ERROR("No netscape funcs!"); + return false; + } + + AutoTArray<PluginIdentifier, 10> identifiers; + bool success; + if (!actor->CallEnumerate(&identifiers, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + *aCount = identifiers.Length(); + if (!*aCount) { + *aIdentifiers = nullptr; + return true; + } + + *aIdentifiers = (NPIdentifier*)npn->memalloc(*aCount * sizeof(NPIdentifier)); + if (!*aIdentifiers) { + NS_ERROR("Out of memory!"); + return false; + } + + for (uint32_t index = 0; index < *aCount; index++) { + // We pin the ID to avoid a GC hazard here. This could probably be fixed + // if the interface with nsJSNPRuntime were smarter. + StackIdentifier stackID(identifiers[index], true /* aAtomizeAndPin */); + if (stackID.Failed()) { + return false; + } + (*aIdentifiers)[index] = stackID.ToNPIdentifier(); + } + return true; +} + +// static +bool +PluginScriptableObjectParent::ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + if (!actor->CallConstruct(args, &remoteResult, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + return true; +} + +const NPClass PluginScriptableObjectParent::sNPClass = { + NP_CLASS_STRUCT_VERSION, + PluginScriptableObjectParent::ScriptableAllocate, + PluginScriptableObjectParent::ScriptableDeallocate, + PluginScriptableObjectParent::ScriptableInvalidate, + PluginScriptableObjectParent::ScriptableHasMethod, + PluginScriptableObjectParent::ScriptableInvoke, + PluginScriptableObjectParent::ScriptableInvokeDefault, + PluginScriptableObjectParent::ScriptableHasProperty, + PluginScriptableObjectParent::ScriptableGetProperty, + PluginScriptableObjectParent::ScriptableSetProperty, + PluginScriptableObjectParent::ScriptableRemoveProperty, + PluginScriptableObjectParent::ScriptableEnumerate, + PluginScriptableObjectParent::ScriptableConstruct +}; + +PluginScriptableObjectParent::PluginScriptableObjectParent( + ScriptableObjectType aType) +: mInstance(nullptr), + mObject(nullptr), + mProtectCount(0), + mType(aType) +{ +} + +PluginScriptableObjectParent::~PluginScriptableObjectParent() +{ + if (mObject) { + if (mObject->_class == GetClass()) { + NS_ASSERTION(mType == Proxy, "Wrong type!"); + static_cast<ParentNPObject*>(mObject)->parent = nullptr; + } + else { + NS_ASSERTION(mType == LocalObject, "Wrong type!"); + GetInstance()->GetNPNIface()->releaseobject(mObject); + } + } +} + +void +PluginScriptableObjectParent::InitializeProxy() +{ + NS_ASSERTION(mType == Proxy, "Bad type!"); + NS_ASSERTION(!mObject, "Calling Initialize more than once!"); + + mInstance = static_cast<PluginInstanceParent*>(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + NPObject* object = CreateProxyObject(); + NS_ASSERTION(object, "Failed to create object!"); + + if (!mInstance->RegisterNPObjectForActor(object, this)) { + NS_ERROR("Out of memory?"); + } + + mObject = object; +} + +void +PluginScriptableObjectParent::InitializeLocal(NPObject* aObject) +{ + NS_ASSERTION(mType == LocalObject, "Bad type!"); + NS_ASSERTION(!(mInstance && mObject), "Calling Initialize more than once!"); + + mInstance = static_cast<PluginInstanceParent*>(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + mInstance->GetNPNIface()->retainobject(aObject); + + NS_ASSERTION(!mProtectCount, "Should be zero!"); + mProtectCount++; + + if (!mInstance->RegisterNPObjectForActor(aObject, this)) { + NS_ERROR("Out of memory?"); + } + + mObject = aObject; +} + +NPObject* +PluginScriptableObjectParent::CreateProxyObject() +{ + NS_ASSERTION(mInstance, "Must have an instance!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + PushSurrogateAcceptCalls acceptCalls(mInstance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(mInstance); + + NPObject* npobject = npn->createobject(mInstance->GetNPP(), + const_cast<NPClass*>(GetClass())); + NS_ASSERTION(npobject, "Failed to create object?!"); + NS_ASSERTION(npobject->_class == GetClass(), "Wrong kind of object!"); + NS_ASSERTION(npobject->referenceCount == 1, "Some kind of live object!"); + + ParentNPObject* object = static_cast<ParentNPObject*>(npobject); + NS_ASSERTION(!object->invalidated, "Bad object!"); + NS_ASSERTION(!object->parent, "Bad object!"); + + // We don't want to have the actor own this object but rather let the object + // own this actor. Set the reference count to 0 here so that when the object + // dies we will send the destructor message to the child. + object->referenceCount = 0; + NS_LOG_RELEASE(object, 0, "BrowserNPObject"); + + object->parent = const_cast<PluginScriptableObjectParent*>(this); + return object; +} + +bool +PluginScriptableObjectParent::ResurrectProxyObject() +{ + NS_ASSERTION(mInstance, "Must have an instance already!"); + NS_ASSERTION(!mObject, "Should not have an object already!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + InitializeProxy(); + NS_ASSERTION(mObject, "Initialize failed!"); + + if (!SendProtect()) { + NS_WARNING("Failed to send message!"); + return false; + } + + return true; +} + +NPObject* +PluginScriptableObjectParent::GetObject(bool aCanResurrect) +{ + if (!mObject && aCanResurrect && !ResurrectProxyObject()) { + NS_ERROR("Null object!"); + return nullptr; + } + return mObject; +} + +void +PluginScriptableObjectParent::Protect() +{ + NS_ASSERTION(mObject, "No object!"); + NS_ASSERTION(mProtectCount >= 0, "Negative protect count?!"); + + if (mType == LocalObject) { + ++mProtectCount; + } +} + +void +PluginScriptableObjectParent::Unprotect() +{ + NS_ASSERTION(mObject, "No object!"); + NS_ASSERTION(mProtectCount >= 0, "Negative protect count?!"); + + if (mType == LocalObject) { + if (--mProtectCount == 0) { + Unused << PluginScriptableObjectParent::Send__delete__(this); + } + } +} + +void +PluginScriptableObjectParent::DropNPObject() +{ + NS_ASSERTION(mObject, "Invalidated object!"); + NS_ASSERTION(mObject->_class == GetClass(), "Wrong type of object!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + // We think we're about to be deleted, but we could be racing with the other + // process. + PluginInstanceParent* instance = GetInstance(); + NS_ASSERTION(instance, "Must have an instance!"); + + instance->UnregisterNPObject(mObject); + mObject = nullptr; + + Unused << SendUnprotect(); +} + +void +PluginScriptableObjectParent::ActorDestroy(ActorDestroyReason aWhy) +{ + // Implement me! Bug 1005163 +} + +bool +PluginScriptableObjectParent::AnswerHasMethod(const PluginIdentifier& aId, + bool* aHasMethod) +{ + if (!mObject) { + NS_WARNING("Calling AnswerHasMethod with an invalidated object!"); + *aHasMethod = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aHasMethod = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aHasMethod = false; + return true; + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aHasMethod = false; + return true; + } + *aHasMethod = npn->hasmethod(instance->GetNPP(), mObject, stackID.ToNPIdentifier()); + return true; +} + +bool +PluginScriptableObjectParent::AnswerInvoke(const PluginIdentifier& aId, + InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) +{ + if (!mObject) { + NS_WARNING("Calling AnswerInvoke with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, fallible)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + for (uint32_t index = 0; index < argCount; index++) { + if (!ConvertToVariant(aArgs[index], convertedArgs[index], instance)) { + // Don't leak things we've already converted! + while (index-- > 0) { + ReleaseVariant(convertedArgs[index], instance); + } + *aResult = void_t(); + *aSuccess = false; + return true; + } + } + + NPVariant result; + bool success = npn->invoke(instance->GetNPP(), mObject, stackID.ToNPIdentifier(), + convertedArgs.Elements(), argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + ReleaseVariant(convertedArgs[index], instance); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, GetInstance()); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + *aResult = convertedResult; + *aSuccess = true; + return true; +} + +bool +PluginScriptableObjectParent::AnswerInvokeDefault(InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) +{ + if (!mObject) { + NS_WARNING("Calling AnswerInvoke with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, fallible)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + for (uint32_t index = 0; index < argCount; index++) { + if (!ConvertToVariant(aArgs[index], convertedArgs[index], instance)) { + // Don't leak things we've already converted! + while (index-- > 0) { + ReleaseVariant(convertedArgs[index], instance); + } + *aResult = void_t(); + *aSuccess = false; + return true; + } + } + + NPVariant result; + bool success = npn->invokeDefault(instance->GetNPP(), mObject, + convertedArgs.Elements(), argCount, + &result); + + for (uint32_t index = 0; index < argCount; index++) { + ReleaseVariant(convertedArgs[index], instance); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, GetInstance()); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + *aResult = convertedResult; + *aSuccess = true; + return true; +} + +bool +PluginScriptableObjectParent::AnswerHasProperty(const PluginIdentifier& aId, + bool* aHasProperty) +{ + if (!mObject) { + NS_WARNING("Calling AnswerHasProperty with an invalidated object!"); + *aHasProperty = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aHasProperty = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aHasProperty = false; + return true; + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aHasProperty = false; + return true; + } + + *aHasProperty = npn->hasproperty(instance->GetNPP(), mObject, + stackID.ToNPIdentifier()); + return true; +} + +bool +PluginScriptableObjectParent::AnswerGetParentProperty( + const PluginIdentifier& aId, + Variant* aResult, + bool* aSuccess) +{ + if (!mObject) { + NS_WARNING("Calling AnswerGetProperty with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + NPVariant result; + if (!npn->getproperty(instance->GetNPP(), mObject, stackID.ToNPIdentifier(), + &result)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + Variant converted; + if ((*aSuccess = ConvertToRemoteVariant(result, converted, instance))) { + DeferNPVariantLastRelease(npn, &result); + *aResult = converted; + } + else { + *aResult = void_t(); + } + + return true; +} + +bool +PluginScriptableObjectParent::AnswerSetProperty(const PluginIdentifier& aId, + const Variant& aValue, + bool* aSuccess) +{ + if (!mObject) { + NS_WARNING("Calling AnswerSetProperty with an invalidated object!"); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aSuccess = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aSuccess = false; + return true; + } + + NPVariant converted; + if (!ConvertToVariant(aValue, converted, instance)) { + *aSuccess = false; + return true; + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aSuccess = false; + return true; + } + + if ((*aSuccess = npn->setproperty(instance->GetNPP(), mObject, + stackID.ToNPIdentifier(), &converted))) { + ReleaseVariant(converted, instance); + } + return true; +} + +bool +PluginScriptableObjectParent::AnswerRemoveProperty(const PluginIdentifier& aId, + bool* aSuccess) +{ + if (!mObject) { + NS_WARNING("Calling AnswerRemoveProperty with an invalidated object!"); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aSuccess = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aSuccess = false; + return true; + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aSuccess = false; + return true; + } + + *aSuccess = npn->removeproperty(instance->GetNPP(), mObject, + stackID.ToNPIdentifier()); + return true; +} + +bool +PluginScriptableObjectParent::AnswerEnumerate(InfallibleTArray<PluginIdentifier>* aProperties, + bool* aSuccess) +{ + if (!mObject) { + NS_WARNING("Calling AnswerEnumerate with an invalidated object!"); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aSuccess = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_WARNING("No netscape funcs?!"); + *aSuccess = false; + return true; + } + + NPIdentifier* ids; + uint32_t idCount; + if (!npn->enumerate(instance->GetNPP(), mObject, &ids, &idCount)) { + *aSuccess = false; + return true; + } + + aProperties->SetCapacity(idCount); + + for (uint32_t index = 0; index < idCount; index++) { + PluginIdentifier id; + if (!FromNPIdentifier(ids[index], &id)) { + return false; + } + aProperties->AppendElement(id); + } + + npn->memfree(ids); + *aSuccess = true; + return true; +} + +bool +PluginScriptableObjectParent::AnswerConstruct(InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) +{ + if (!mObject) { + NS_WARNING("Calling AnswerConstruct with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, fallible)) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + for (uint32_t index = 0; index < argCount; index++) { + if (!ConvertToVariant(aArgs[index], convertedArgs[index], instance)) { + // Don't leak things we've already converted! + while (index-- > 0) { + ReleaseVariant(convertedArgs[index], instance); + } + *aResult = void_t(); + *aSuccess = false; + return true; + } + } + + NPVariant result; + bool success = npn->construct(instance->GetNPP(), mObject, + convertedArgs.Elements(), argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + ReleaseVariant(convertedArgs[index], instance); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, instance); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + *aSuccess = true; + *aResult = convertedResult; + return true; +} + +bool +PluginScriptableObjectParent::RecvProtect() +{ + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Protect(); + return true; +} + +bool +PluginScriptableObjectParent::RecvUnprotect() +{ + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Unprotect(); + return true; +} + +bool +PluginScriptableObjectParent::AnswerNPN_Evaluate(const nsCString& aScript, + Variant* aResult, + bool* aSuccess) +{ + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + PushSurrogateAcceptCalls acceptCalls(instance); + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return true; + } + + NPString script = { aScript.get(), aScript.Length() }; + + NPVariant result; + bool success = npn->evaluate(instance->GetNPP(), mObject, &script, &result); + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, instance); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return true; + } + + *aSuccess = true; + *aResult = convertedResult; + return true; +} + +bool +PluginScriptableObjectParent::GetPropertyHelper(NPIdentifier aName, + bool* aHasProperty, + bool* aHasMethod, + NPVariant* aResult) +{ + NS_ASSERTION(Type() == Proxy, "Bad type!"); + + ParentNPObject* object = static_cast<ParentNPObject*>(mObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + bool hasProperty, hasMethod, success; + Variant result; + if (!CallGetChildProperty(identifier, &hasProperty, &hasMethod, &result, + &success)) { + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(result, *aResult, GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + + *aHasProperty = hasProperty; + *aHasMethod = hasMethod; + return true; +} diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.h b/dom/plugins/ipc/PluginScriptableObjectParent.h new file mode 100644 index 000000000..8a1cdc028 --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectParent.h @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 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 dom_plugins_PluginScriptableObjectParent_h +#define dom_plugins_PluginScriptableObjectParent_h 1 + +#include "mozilla/plugins/PPluginScriptableObjectParent.h" +#include "mozilla/plugins/PluginMessageUtils.h" + +#include "npfunctions.h" +#include "npruntime.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceParent; +class PluginScriptableObjectParent; + +struct ParentNPObject : NPObject +{ + ParentNPObject() + : NPObject() + , parent(nullptr) + , invalidated(false) + , asyncWrapperCount(0) + {} + + // |parent| is always valid as long as the actor is alive. Once the actor is + // destroyed this will be set to null. + PluginScriptableObjectParent* parent; + bool invalidated; + int32_t asyncWrapperCount; +}; + +class PluginScriptableObjectParent : public PPluginScriptableObjectParent +{ + friend class PluginInstanceParent; + +public: + explicit PluginScriptableObjectParent(ScriptableObjectType aType); + virtual ~PluginScriptableObjectParent(); + + void + InitializeProxy(); + + void + InitializeLocal(NPObject* aObject); + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + AnswerHasMethod(const PluginIdentifier& aId, + bool* aHasMethod) override; + + virtual bool + AnswerInvoke(const PluginIdentifier& aId, + InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + AnswerInvokeDefault(InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + AnswerHasProperty(const PluginIdentifier& aId, + bool* aHasProperty) override; + + virtual bool + AnswerGetParentProperty(const PluginIdentifier& aId, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + AnswerSetProperty(const PluginIdentifier& aId, + const Variant& aValue, + bool* aSuccess) override; + + virtual bool + AnswerRemoveProperty(const PluginIdentifier& aId, + bool* aSuccess) override; + + virtual bool + AnswerEnumerate(InfallibleTArray<PluginIdentifier>* aProperties, + bool* aSuccess) override; + + virtual bool + AnswerConstruct(InfallibleTArray<Variant>&& aArgs, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + AnswerNPN_Evaluate(const nsCString& aScript, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + RecvProtect() override; + + virtual bool + RecvUnprotect() override; + + static const NPClass* + GetClass() + { + return &sNPClass; + } + + PluginInstanceParent* + GetInstance() const + { + return mInstance; + } + + NPObject* + GetObject(bool aCanResurrect); + + // Protect only affects LocalObject actors. It is called by the + // ProtectedVariant/Actor helper classes before the actor is used as an + // argument to an IPC call and when the child process resurrects a + // proxy object to the NPObject associated with this actor. + void Protect(); + + // Unprotect only affects LocalObject actors. It is called by the + // ProtectedVariant/Actor helper classes after the actor is used as an + // argument to an IPC call and when the child process is no longer using this + // actor. + void Unprotect(); + + // DropNPObject is only used for Proxy actors and is called when the parent + // process is no longer using the NPObject associated with this actor. The + // child process may subsequently use this actor again in which case a new + // NPObject will be created and associated with this actor (see + // ResurrectProxyObject). + void DropNPObject(); + + ScriptableObjectType + Type() const { + return mType; + } + + bool GetPropertyHelper(NPIdentifier aName, + bool* aHasProperty, + bool* aHasMethod, + NPVariant* aResult); + +private: + static NPObject* + ScriptableAllocate(NPP aInstance, + NPClass* aClass); + + static void + ScriptableInvalidate(NPObject* aObject); + + static void + ScriptableDeallocate(NPObject* aObject); + + static bool + ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName); + + static bool + ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult); + + static bool + ScriptableInvokeDefault(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult); + + static bool + ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName); + + static bool + ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult); + + static bool + ScriptableSetProperty(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aValue); + + static bool + ScriptableRemoveProperty(NPObject* aObject, + NPIdentifier aName); + + static bool + ScriptableEnumerate(NPObject* aObject, + NPIdentifier** aIdentifiers, + uint32_t* aCount); + + static bool + ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult); + + NPObject* + CreateProxyObject(); + + // ResurrectProxyObject is only used with Proxy actors. It is called when the + // child process uses an actor whose NPObject was deleted by the parent + // process. + bool ResurrectProxyObject(); + +private: + PluginInstanceParent* mInstance; + + // This may be a ParentNPObject or some other kind depending on who created + // it. Have to check its class to find out. + NPObject* mObject; + int mProtectCount; + + ScriptableObjectType mType; + + static const NPClass sNPClass; +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif /* dom_plugins_PluginScriptableObjectParent_h */ diff --git a/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h b/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h new file mode 100644 index 000000000..fef663f4c --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 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 "PluginScriptableObjectUtils.h" + +namespace { + +template<class InstanceType> +class VariantTraits; + +template<> +class VariantTraits<mozilla::plugins::PluginInstanceParent> +{ +public: + typedef mozilla::plugins::PluginScriptableObjectParent ScriptableObjectType; +}; + +template<> +class VariantTraits<mozilla::plugins::PluginInstanceChild> +{ +public: + typedef mozilla::plugins::PluginScriptableObjectChild ScriptableObjectType; +}; + +} /* anonymous namespace */ + +inline bool +mozilla::plugins::ConvertToVariant(const Variant& aRemoteVariant, + NPVariant& aVariant, + PluginInstanceParent* aInstance) +{ + switch (aRemoteVariant.type()) { + case Variant::Tvoid_t: { + VOID_TO_NPVARIANT(aVariant); + break; + } + + case Variant::Tnull_t: { + NULL_TO_NPVARIANT(aVariant); + break; + } + + case Variant::Tbool: { + BOOLEAN_TO_NPVARIANT(aRemoteVariant.get_bool(), aVariant); + break; + } + + case Variant::Tint: { + INT32_TO_NPVARIANT(aRemoteVariant.get_int(), aVariant); + break; + } + + case Variant::Tdouble: { + DOUBLE_TO_NPVARIANT(aRemoteVariant.get_double(), aVariant); + break; + } + + case Variant::TnsCString: { + const nsCString& string = aRemoteVariant.get_nsCString(); + const size_t length = string.Length(); + NPUTF8* buffer = static_cast<NPUTF8*>(::malloc(sizeof(NPUTF8) * (length + 1))); + if (!buffer) { + NS_ERROR("Out of memory!"); + return false; + } + + std::copy(string.get(), string.get() + length, buffer); + buffer[length] = '\0'; + STRINGN_TO_NPVARIANT(buffer, length, aVariant); + break; + } + + case Variant::TPPluginScriptableObjectParent: { + NS_ASSERTION(aInstance, "Must have an instance!"); + NPObject* object = NPObjectFromVariant(aRemoteVariant); + if (!object) { + NS_ERROR("Er, this shouldn't fail!"); + return false; + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(aInstance); + if (!npn) { + NS_ERROR("Null netscape funcs!"); + return false; + } + + npn->retainobject(object); + OBJECT_TO_NPVARIANT(object, aVariant); + break; + } + + case Variant::TPPluginScriptableObjectChild: { + NS_ASSERTION(!aInstance, "No instance should be given!"); + NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Plugin, + "Should be running on child only!"); + + NPObject* object = NPObjectFromVariant(aRemoteVariant); + NS_ASSERTION(object, "Null object?!"); + + PluginModuleChild::sBrowserFuncs.retainobject(object); + OBJECT_TO_NPVARIANT(object, aVariant); + break; + } + + default: + NS_NOTREACHED("Shouldn't get here!"); + return false; + } + + return true; +} + +template <class InstanceType> +bool +mozilla::plugins::ConvertToRemoteVariant(const NPVariant& aVariant, + Variant& aRemoteVariant, + InstanceType* aInstance, + bool aProtectActors) +{ + if (NPVARIANT_IS_VOID(aVariant)) { + aRemoteVariant = mozilla::void_t(); + } + else if (NPVARIANT_IS_NULL(aVariant)) { + aRemoteVariant = mozilla::null_t(); + } + else if (NPVARIANT_IS_BOOLEAN(aVariant)) { + aRemoteVariant = NPVARIANT_TO_BOOLEAN(aVariant); + } + else if (NPVARIANT_IS_INT32(aVariant)) { + aRemoteVariant = NPVARIANT_TO_INT32(aVariant); + } + else if (NPVARIANT_IS_DOUBLE(aVariant)) { + aRemoteVariant = NPVARIANT_TO_DOUBLE(aVariant); + } + else if (NPVARIANT_IS_STRING(aVariant)) { + NPString str = NPVARIANT_TO_STRING(aVariant); + nsCString string(str.UTF8Characters, str.UTF8Length); + aRemoteVariant = string; + } + else if (NPVARIANT_IS_OBJECT(aVariant)) { + NPObject* object = NPVARIANT_TO_OBJECT(aVariant); + + typename VariantTraits<InstanceType>::ScriptableObjectType* actor = + aInstance->GetActorForNPObject(object); + + if (!actor) { + NS_ERROR("Null actor!"); + return false; + } + + if (aProtectActors) { + actor->Protect(); + } + + aRemoteVariant = actor; + } + else { + NS_NOTREACHED("Shouldn't get here!"); + return false; + } + + return true; +} diff --git a/dom/plugins/ipc/PluginScriptableObjectUtils.h b/dom/plugins/ipc/PluginScriptableObjectUtils.h new file mode 100644 index 000000000..bef2113c7 --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectUtils.h @@ -0,0 +1,306 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 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 dom_plugins_PluginScriptableObjectUtils_h +#define dom_plugins_PluginScriptableObjectUtils_h + +#include "PluginModuleParent.h" +#include "PluginModuleChild.h" +#include "PluginInstanceParent.h" +#include "PluginInstanceChild.h" +#include "PluginScriptableObjectParent.h" +#include "PluginScriptableObjectChild.h" + +#include "npapi.h" +#include "npfunctions.h" +#include "npruntime.h" + +#include "nsDebug.h" + +namespace mozilla { +namespace plugins { + +inline PluginInstanceParent* +GetInstance(NPObject* aObject) +{ + NS_ASSERTION(aObject->_class == PluginScriptableObjectParent::GetClass(), + "Bad class!"); + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return nullptr; + } + if (!object->parent) { + return nullptr; + } + return object->parent->GetInstance(); +} + +inline NPObject* +NPObjectFromVariant(const Variant& aRemoteVariant) +{ + switch (aRemoteVariant.type()) { + case Variant::TPPluginScriptableObjectParent: { + PluginScriptableObjectParent* actor = + const_cast<PluginScriptableObjectParent*>( + reinterpret_cast<const PluginScriptableObjectParent*>( + aRemoteVariant.get_PPluginScriptableObjectParent())); + return actor->GetObject(true); + } + + case Variant::TPPluginScriptableObjectChild: { + PluginScriptableObjectChild* actor = + const_cast<PluginScriptableObjectChild*>( + reinterpret_cast<const PluginScriptableObjectChild*>( + aRemoteVariant.get_PPluginScriptableObjectChild())); + return actor->GetObject(true); + } + + default: + NS_NOTREACHED("Shouldn't get here!"); + return nullptr; + } +} + +inline NPObject* +NPObjectFromVariant(const NPVariant& aVariant) +{ + NS_ASSERTION(NPVARIANT_IS_OBJECT(aVariant), "Wrong variant type!"); + return NPVARIANT_TO_OBJECT(aVariant); +} + +inline const NPNetscapeFuncs* +GetNetscapeFuncs(PluginInstanceParent* aInstance) +{ + PluginModuleParent* module = aInstance->Module(); + if (!module) { + NS_WARNING("Null module?!"); + return nullptr; + } + return module->GetNetscapeFuncs(); +} + +inline const NPNetscapeFuncs* +GetNetscapeFuncs(NPObject* aObject) +{ + NS_ASSERTION(aObject->_class == PluginScriptableObjectParent::GetClass(), + "Bad class!"); + + PluginInstanceParent* instance = GetInstance(aObject); + if (!instance) { + return nullptr; + } + + return GetNetscapeFuncs(instance); +} + +inline void +ReleaseRemoteVariant(Variant& aVariant) +{ + switch (aVariant.type()) { + case Variant::TPPluginScriptableObjectParent: { + PluginScriptableObjectParent* actor = + const_cast<PluginScriptableObjectParent*>( + reinterpret_cast<const PluginScriptableObjectParent*>( + aVariant.get_PPluginScriptableObjectParent())); + actor->Unprotect(); + break; + } + + case Variant::TPPluginScriptableObjectChild: { + NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Plugin, + "Should only be running in the child!"); + PluginScriptableObjectChild* actor = + const_cast<PluginScriptableObjectChild*>( + reinterpret_cast<const PluginScriptableObjectChild*>( + aVariant.get_PPluginScriptableObjectChild())); + actor->Unprotect(); + break; + } + + default: + break; // Intentional fall-through for other variant types. + } + + aVariant = mozilla::void_t(); +} + +bool +ConvertToVariant(const Variant& aRemoteVariant, + NPVariant& aVariant, + PluginInstanceParent* aInstance = nullptr); + +template <class InstanceType> +bool +ConvertToRemoteVariant(const NPVariant& aVariant, + Variant& aRemoteVariant, + InstanceType* aInstance, + bool aProtectActors = false); + +class ProtectedVariant +{ +public: + ProtectedVariant(const NPVariant& aVariant, + PluginInstanceParent* aInstance) + { + mOk = ConvertToRemoteVariant(aVariant, mVariant, aInstance, true); + } + + ProtectedVariant(const NPVariant& aVariant, + PluginInstanceChild* aInstance) + { + mOk = ConvertToRemoteVariant(aVariant, mVariant, aInstance, true); + } + + ~ProtectedVariant() { + ReleaseRemoteVariant(mVariant); + } + + bool IsOk() { + return mOk; + } + + operator const Variant&() { + return mVariant; + } + +private: + Variant mVariant; + bool mOk; +}; + +class ProtectedVariantArray +{ +public: + ProtectedVariantArray(const NPVariant* aArgs, + uint32_t aCount, + PluginInstanceParent* aInstance) + : mUsingShadowArray(false) + { + for (uint32_t index = 0; index < aCount; index++) { + Variant* remoteVariant = mArray.AppendElement(); + if (!(remoteVariant && + ConvertToRemoteVariant(aArgs[index], *remoteVariant, aInstance, + true))) { + mOk = false; + return; + } + } + mOk = true; + } + + ProtectedVariantArray(const NPVariant* aArgs, + uint32_t aCount, + PluginInstanceChild* aInstance) + : mUsingShadowArray(false) + { + for (uint32_t index = 0; index < aCount; index++) { + Variant* remoteVariant = mArray.AppendElement(); + if (!(remoteVariant && + ConvertToRemoteVariant(aArgs[index], *remoteVariant, aInstance, + true))) { + mOk = false; + return; + } + } + mOk = true; + } + + ~ProtectedVariantArray() + { + InfallibleTArray<Variant>& vars = EnsureAndGetShadowArray(); + uint32_t count = vars.Length(); + for (uint32_t index = 0; index < count; index++) { + ReleaseRemoteVariant(vars[index]); + } + } + + operator const InfallibleTArray<Variant>&() + { + return EnsureAndGetShadowArray(); + } + + bool IsOk() + { + return mOk; + } + +private: + InfallibleTArray<Variant>& + EnsureAndGetShadowArray() + { + if (!mUsingShadowArray) { + mShadowArray.SwapElements(mArray); + mUsingShadowArray = true; + } + return mShadowArray; + } + + // We convert the variants fallibly, but pass them to Call*() + // methods as an infallible array + nsTArray<Variant> mArray; + InfallibleTArray<Variant> mShadowArray; + bool mOk; + bool mUsingShadowArray; +}; + +template<class ActorType> +struct ProtectedActorTraits +{ + static bool Nullable(); +}; + +template<class ActorType, class Traits=ProtectedActorTraits<ActorType> > +class ProtectedActor +{ +public: + explicit ProtectedActor(ActorType* aActor) : mActor(aActor) + { + if (!Traits::Nullable()) { + NS_ASSERTION(mActor, "This should never be null!"); + } + } + + ~ProtectedActor() + { + if (Traits::Nullable() && !mActor) + return; + mActor->Unprotect(); + } + + ActorType* operator->() + { + return mActor; + } + + explicit operator bool() + { + return !!mActor; + } + +private: + ActorType* mActor; +}; + +template<> +struct ProtectedActorTraits<PluginScriptableObjectParent> +{ + static bool Nullable() { return true; } +}; + +template<> +struct ProtectedActorTraits<PluginScriptableObjectChild> +{ + static bool Nullable() { return false; } +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#include "PluginScriptableObjectUtils-inl.h" + +#endif /* dom_plugins_PluginScriptableObjectUtils_h */ diff --git a/dom/plugins/ipc/PluginStreamChild.cpp b/dom/plugins/ipc/PluginStreamChild.cpp new file mode 100644 index 000000000..7fbcd9f33 --- /dev/null +++ b/dom/plugins/ipc/PluginStreamChild.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "PluginStreamChild.h" +#include "mozilla/plugins/PluginInstanceChild.h" + +namespace mozilla { +namespace plugins { + +PluginStreamChild::PluginStreamChild() + : mClosed(false) +{ + memset(&mStream, 0, sizeof(mStream)); + mStream.ndata = static_cast<AStream*>(this); +} + +bool +PluginStreamChild::Answer__delete__(const NPReason& reason, + const bool& artificial) +{ + AssertPluginThread(); + if (!artificial) + NPP_DestroyStream(reason); + return true; +} + +int32_t +PluginStreamChild::NPN_Write(int32_t length, void* buffer) +{ + AssertPluginThread(); + + int32_t written = 0; + CallNPN_Write(nsCString(static_cast<char*>(buffer), length), + &written); + if (written < 0) + PPluginStreamChild::Call__delete__(this, NPERR_GENERIC_ERROR, true); + // careful after here! |this| just got deleted + + return written; +} + +void +PluginStreamChild::NPP_DestroyStream(NPError reason) +{ + AssertPluginThread(); + + if (mClosed) + return; + + mClosed = true; + Instance()->mPluginIface->destroystream( + &Instance()->mData, &mStream, reason); +} + +PluginInstanceChild* +PluginStreamChild::Instance() +{ + return static_cast<PluginInstanceChild*>(Manager()); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginStreamChild.h b/dom/plugins/ipc/PluginStreamChild.h new file mode 100644 index 000000000..b133f754e --- /dev/null +++ b/dom/plugins/ipc/PluginStreamChild.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 mozilla_plugins_PluginStreamChild_h +#define mozilla_plugins_PluginStreamChild_h + +#include "mozilla/plugins/PPluginStreamChild.h" +#include "mozilla/plugins/AStream.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceChild; + +class PluginStreamChild : public PPluginStreamChild, public AStream +{ + friend class PluginInstanceChild; + +public: + PluginStreamChild(); + virtual ~PluginStreamChild() { } + + virtual bool IsBrowserStream() override { return false; } + + virtual bool Answer__delete__(const NPReason& reason, + const bool& artificial) override; + + int32_t NPN_Write(int32_t length, void* buffer); + void NPP_DestroyStream(NPError reason); + + void EnsureCorrectInstance(PluginInstanceChild* i) + { + if (i != Instance()) + NS_RUNTIMEABORT("Incorrect stream instance"); + } + void EnsureCorrectStream(NPStream* s) + { + if (s != &mStream) + NS_RUNTIMEABORT("Incorrect stream data"); + } + +private: + PluginInstanceChild* Instance(); + + NPStream mStream; + bool mClosed; +}; + + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/PluginStreamParent.cpp b/dom/plugins/ipc/PluginStreamParent.cpp new file mode 100644 index 000000000..1b4cfeb02 --- /dev/null +++ b/dom/plugins/ipc/PluginStreamParent.cpp @@ -0,0 +1,72 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "PluginStreamParent.h" +#include "PluginInstanceParent.h" + +namespace mozilla { +namespace plugins { + +PluginStreamParent::PluginStreamParent(PluginInstanceParent* npp, + const nsCString& mimeType, + const nsCString& target, + NPError* result) + : mInstance(npp) + , mClosed(false) +{ + *result = mInstance->mNPNIface->newstream(mInstance->mNPP, + const_cast<char*>(mimeType.get()), + NullableStringGet(target), + &mStream); + if (*result == NPERR_NO_ERROR) + mStream->pdata = static_cast<AStream*>(this); + else + mStream = nullptr; +} + +void +PluginStreamParent::ActorDestroy(ActorDestroyReason aWhy) +{ + // Implement me! Bug 1005166 +} + +bool +PluginStreamParent::AnswerNPN_Write(const Buffer& data, int32_t* written) +{ + if (mClosed) { + *written = -1; + return true; + } + + *written = mInstance->mNPNIface->write(mInstance->mNPP, mStream, + data.Length(), + const_cast<char*>(data.get())); + if (*written < 0) + mClosed = true; + + return true; +} + +bool +PluginStreamParent::Answer__delete__(const NPError& reason, + const bool& artificial) +{ + if (!artificial) + this->NPN_DestroyStream(reason); + return true; +} + +void +PluginStreamParent::NPN_DestroyStream(NPReason reason) +{ + if (mClosed) + return; + + mInstance->mNPNIface->destroystream(mInstance->mNPP, mStream, reason); + mClosed = true; +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginStreamParent.h b/dom/plugins/ipc/PluginStreamParent.h new file mode 100644 index 000000000..a361b72af --- /dev/null +++ b/dom/plugins/ipc/PluginStreamParent.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 mozilla_plugins_PluginStreamParent_h +#define mozilla_plugins_PluginStreamParent_h + +#include "mozilla/plugins/PPluginStreamParent.h" +#include "mozilla/plugins/AStream.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceParent; + +class PluginStreamParent : public PPluginStreamParent, public AStream +{ + friend class PluginModuleParent; + friend class PluginInstanceParent; + +public: + PluginStreamParent(PluginInstanceParent* npp, const nsCString& mimeType, + const nsCString& target, NPError* result); + virtual ~PluginStreamParent() { } + + virtual bool IsBrowserStream() override { return false; } + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool AnswerNPN_Write(const Buffer& data, int32_t* written) override; + + virtual bool Answer__delete__(const NPError& reason, const bool& artificial) override; + +private: + void NPN_DestroyStream(NPReason reason); + + PluginInstanceParent* mInstance; + NPStream* mStream; + bool mClosed; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/PluginSurfaceParent.cpp b/dom/plugins/ipc/PluginSurfaceParent.cpp new file mode 100644 index 000000000..1d5e19ad6 --- /dev/null +++ b/dom/plugins/ipc/PluginSurfaceParent.cpp @@ -0,0 +1,34 @@ +/* -*- 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/. */ + +#include "mozilla/plugins/PluginSurfaceParent.h" +#include "mozilla/gfx/SharedDIBSurface.h" + +using mozilla::gfx::SharedDIBSurface; + +namespace mozilla { +namespace plugins { + +PluginSurfaceParent::PluginSurfaceParent(const WindowsSharedMemoryHandle& handle, + const gfx::IntSize& size, + bool transparent) +{ + SharedDIBSurface* dibsurf = new SharedDIBSurface(); + if (dibsurf->Attach(handle, size.width, size.height, transparent)) + mSurface = dibsurf; +} + +PluginSurfaceParent::~PluginSurfaceParent() +{ +} + +void +PluginSurfaceParent::ActorDestroy(ActorDestroyReason aWhy) +{ + // Implement me! Bug 1005167 +} + +} +} diff --git a/dom/plugins/ipc/PluginSurfaceParent.h b/dom/plugins/ipc/PluginSurfaceParent.h new file mode 100644 index 000000000..037547d66 --- /dev/null +++ b/dom/plugins/ipc/PluginSurfaceParent.h @@ -0,0 +1,40 @@ +/* -*- 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/. */ + +#ifndef dom_plugins_PluginSurfaceParent_h +#define dom_plugins_PluginSurfaceParent_h + +#include "mozilla/plugins/PPluginSurfaceParent.h" +#include "mozilla/plugins/PluginMessageUtils.h" + +#ifndef XP_WIN +#error "This header is for Windows only." +#endif + +class gfxASurface; + +namespace mozilla { +namespace plugins { + +class PluginSurfaceParent : public PPluginSurfaceParent +{ +public: + PluginSurfaceParent(const WindowsSharedMemoryHandle& handle, + const gfx::IntSize& size, + const bool transparent); + ~PluginSurfaceParent(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + gfxASurface* Surface() { return mSurface; } + +private: + RefPtr<gfxASurface> mSurface; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugin_PluginSurfaceParent_h diff --git a/dom/plugins/ipc/PluginTypes.ipdlh b/dom/plugins/ipc/PluginTypes.ipdlh new file mode 100644 index 000000000..ed5ae0c71 --- /dev/null +++ b/dom/plugins/ipc/PluginTypes.ipdlh @@ -0,0 +1,35 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +namespace mozilla { +namespace plugins { + +struct PluginTag +{ + uint32_t id; + nsCString name; + nsCString description; + nsCString[] mimeTypes; + nsCString[] mimeDescriptions; + nsCString[] extensions; + bool isJavaPlugin; + bool isFlashPlugin; + bool supportsAsyncInit; + bool supportsAsyncRender; // flash specific + nsCString filename; + nsCString version; + int64_t lastModifiedTime; + bool isFromExtension; + int32_t sandboxLevel; +}; + +union PluginIdentifier +{ + nsCString; + int32_t; +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginUtilsOSX.h b/dom/plugins/ipc/PluginUtilsOSX.h new file mode 100644 index 000000000..c201782c0 --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsOSX.h @@ -0,0 +1,92 @@ +/* -*- 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 dom_plugins_PluginUtilsOSX_h +#define dom_plugins_PluginUtilsOSX_h 1 + +#include "npapi.h" +#include "mozilla/gfx/QuartzSupport.h" +#include "nsRect.h" + +namespace mozilla { +namespace plugins { +namespace PluginUtilsOSX { + +// Need to call back into the browser's message loop to process event. +typedef void (*RemoteProcessEvents) (void*); + +NPError ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent); + +void InvokeNativeEventLoop(); + +// Need to call back and send a cocoa draw event to the plugin. +typedef void (*DrawPluginFunc) (CGContextRef, void*, nsIntRect aUpdateRect); + +void* GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, double aContentsScaleFactor); +void ReleaseCGLayer(void* cgLayer); +void Repaint(void* cgLayer, nsIntRect aRect); + +bool SetProcessName(const char* aProcessName); + +/* + * Provides a wrapper around nsCARenderer to manage double buffering + * without having to unbind nsCARenderer on every surface swaps. + * + * The double buffer renderer begins with no initialize surfaces. + * The buffers can be initialized and cleared individually. + * Swapping still occurs regardless if the buffers are initialized. + */ +class nsDoubleBufferCARenderer { +public: + nsDoubleBufferCARenderer() : mCALayer(nullptr), mContentsScaleFactor(1.0) {} + // Returns width in "display pixels". A "display pixel" is the smallest + // fully addressable part of a display. But in HiDPI modes each "display + // pixel" corresponds to more than one device pixel. Multiply display pixels + // by mContentsScaleFactor to get device pixels. + size_t GetFrontSurfaceWidth(); + // Returns height in "display pixels". Multiply by + // mContentsScaleFactor to get device pixels. + size_t GetFrontSurfaceHeight(); + double GetFrontSurfaceContentsScaleFactor(); + // Returns width in "display pixels". Multiply by + // mContentsScaleFactor to get device pixels. + size_t GetBackSurfaceWidth(); + // Returns height in "display pixels". Multiply by + // mContentsScaleFactor to get device pixels. + size_t GetBackSurfaceHeight(); + double GetBackSurfaceContentsScaleFactor(); + IOSurfaceID GetFrontSurfaceID(); + + bool HasBackSurface(); + bool HasFrontSurface(); + bool HasCALayer(); + + void SetCALayer(void *aCALayer); + // aWidth and aHeight are in "display pixels". Multiply by + // aContentsScaleFactor to get device pixels. + bool InitFrontSurface(size_t aWidth, size_t aHeight, + double aContentsScaleFactor, + AllowOfflineRendererEnum aAllowOfflineRenderer); + void Render(); + void SwapSurfaces(); + void ClearFrontSurface(); + void ClearBackSurface(); + + double GetContentsScaleFactor() { return mContentsScaleFactor; } + +private: + void *mCALayer; + RefPtr<nsCARenderer> mCARenderer; + RefPtr<MacIOSurface> mFrontSurface; + RefPtr<MacIOSurface> mBackSurface; + double mContentsScaleFactor; +}; + +} // namespace PluginUtilsOSX +} // namespace plugins +} // namespace mozilla + +#endif //dom_plugins_PluginUtilsOSX_h diff --git a/dom/plugins/ipc/PluginUtilsOSX.mm b/dom/plugins/ipc/PluginUtilsOSX.mm new file mode 100644 index 000000000..2a920f7f6 --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsOSX.mm @@ -0,0 +1,462 @@ +/* -*- 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/. */ + +#include <dlfcn.h> +#import <AppKit/AppKit.h> +#import <QuartzCore/QuartzCore.h> +#include "PluginUtilsOSX.h" + +// Remove definitions for try/catch interfering with ObjCException macros. +#include "nsObjCExceptions.h" +#include "nsCocoaUtils.h" + +#include "nsDebug.h" + +#include "mozilla/Sprintf.h" + +@interface CALayer (ContentsScale) +- (double)contentsScale; +- (void)setContentsScale:(double)scale; +@end + +using namespace mozilla::plugins::PluginUtilsOSX; + +@interface CGBridgeLayer : CALayer { + DrawPluginFunc mDrawFunc; + void* mPluginInstance; + nsIntRect mUpdateRect; +} +- (void)setDrawFunc:(DrawPluginFunc)aFunc + pluginInstance:(void*)aPluginInstance; +- (void)updateRect:(nsIntRect)aRect; + +@end + +// CGBitmapContextSetData() is an undocumented function present (with +// the same signature) since at least OS X 10.5. As the name suggests, +// it's used to replace the "data" in a bitmap context that was +// originally specified in a call to CGBitmapContextCreate() or +// CGBitmapContextCreateWithData(). +typedef void (*CGBitmapContextSetDataFunc) (CGContextRef c, + size_t x, + size_t y, + size_t width, + size_t height, + void* data, + size_t bitsPerComponent, + size_t bitsPerPixel, + size_t bytesPerRow); +CGBitmapContextSetDataFunc CGBitmapContextSetDataPtr = NULL; + +@implementation CGBridgeLayer +- (void) updateRect:(nsIntRect)aRect +{ + mUpdateRect.UnionRect(mUpdateRect, aRect); +} + +- (void) setDrawFunc:(DrawPluginFunc)aFunc + pluginInstance:(void*)aPluginInstance +{ + mDrawFunc = aFunc; + mPluginInstance = aPluginInstance; +} + +- (void)drawInContext:(CGContextRef)aCGContext +{ + ::CGContextSaveGState(aCGContext); + ::CGContextTranslateCTM(aCGContext, 0, self.bounds.size.height); + ::CGContextScaleCTM(aCGContext, (CGFloat) 1, (CGFloat) -1); + + mUpdateRect = nsIntRect::Truncate(0, 0, self.bounds.size.width, self.bounds.size.height); + + mDrawFunc(aCGContext, mPluginInstance, mUpdateRect); + + ::CGContextRestoreGState(aCGContext); + + mUpdateRect.SetEmpty(); +} + +@end + +void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, + void* aPluginInstance, + double aContentsScaleFactor) { + CGBridgeLayer *bridgeLayer = [[CGBridgeLayer alloc] init]; + + // We need to make bridgeLayer behave properly when its superlayer changes + // size (in nsCARenderer::SetBounds()). + bridgeLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; + bridgeLayer.needsDisplayOnBoundsChange = YES; + NSNull *nullValue = [NSNull null]; + NSDictionary *actions = [NSDictionary dictionaryWithObjectsAndKeys: + nullValue, @"bounds", + nullValue, @"contents", + nullValue, @"contentsRect", + nullValue, @"position", + nil]; + [bridgeLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]]; + + // For reasons that aren't clear (perhaps one or more OS bugs), we can only + // use full HiDPI resolution here if the tree is built with the 10.7 SDK or + // up. If we build with the 10.6 SDK, changing the contentsScale property + // of bridgeLayer (even to the same value) causes it to stop working (go + // blank). This doesn't happen with objects that are members of the CALayer + // class (as opposed to one of its subclasses). +#if defined(MAC_OS_X_VERSION_10_7) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if ([bridgeLayer respondsToSelector:@selector(setContentsScale:)]) { + bridgeLayer.contentsScale = aContentsScaleFactor; + } +#endif + + [bridgeLayer setDrawFunc:aFunc + pluginInstance:aPluginInstance]; + return bridgeLayer; +} + +void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void *cgLayer) { + CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)cgLayer; + [bridgeLayer release]; +} + +void mozilla::plugins::PluginUtilsOSX::Repaint(void *caLayer, nsIntRect aRect) { + CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)caLayer; + [CATransaction begin]; + [bridgeLayer updateRect:aRect]; + [bridgeLayer setNeedsDisplay]; + [bridgeLayer displayIfNeeded]; + [CATransaction commit]; +} + +@interface EventProcessor : NSObject { + RemoteProcessEvents aRemoteEvents; + void *aPluginModule; +} +- (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule; +- (void)onTick; +@end + +@implementation EventProcessor +- (void) onTick +{ + aRemoteEvents(aPluginModule); +} + +- (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule +{ + aRemoteEvents = remoteEvents; + aPluginModule = pluginModule; +} +@end + +#define EVENT_PROCESS_DELAY 0.05 // 50 ms + +NPError mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + // Set the native cursor to the OS default (an arrow) before displaying the + // context menu. Otherwise (if the plugin has changed the cursor) it may + // stay as the plugin has set it -- which means it may be invisible. We + // need to do this because we display the context menu without making the + // plugin process the foreground process. If we did, the cursor would + // change to an arrow cursor automatically -- as it does in Chrome. + [[NSCursor arrowCursor] set]; + + EventProcessor* eventProcessor = nullptr; + NSTimer *eventTimer = nullptr; + if (pluginModule) { + // Create a timer to process browser events while waiting + // on the menu. This prevents the browser from hanging + // during the lifetime of the menu. + eventProcessor = [[EventProcessor alloc] init]; + [eventProcessor setRemoteEvents:remoteEvent pluginModule:pluginModule]; + eventTimer = [NSTimer timerWithTimeInterval:EVENT_PROCESS_DELAY + target:eventProcessor selector:@selector(onTick) + userInfo:nil repeats:TRUE]; + // Use NSEventTrackingRunLoopMode otherwise the timer will + // not fire during the right click menu. + [[NSRunLoop currentRunLoop] addTimer:eventTimer + forMode:NSEventTrackingRunLoopMode]; + } + + NSMenu* nsmenu = reinterpret_cast<NSMenu*>(aMenu); + NSPoint screen_point = ::NSMakePoint(aX, aY); + + [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil]; + + if (pluginModule) { + [eventTimer invalidate]; + [eventProcessor release]; + } + + return NPERR_NO_ERROR; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NPERR_GENERIC_ERROR); +} + +void mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + ::CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true); + NS_OBJC_END_TRY_ABORT_BLOCK; +} + + +#define UNDOCUMENTED_SESSION_CONSTANT ((int)-2) +namespace mozilla { +namespace plugins { +namespace PluginUtilsOSX { + static void *sApplicationASN = NULL; + static void *sApplicationInfoItem = NULL; +} // namespace PluginUtilsOSX +} // namespace plugins +} // namespace mozilla + +bool mozilla::plugins::PluginUtilsOSX::SetProcessName(const char* aProcessName) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + nsAutoreleasePool localPool; + + if (!aProcessName || strcmp(aProcessName, "") == 0) { + return false; + } + + NSString *currentName = [[[NSBundle mainBundle] localizedInfoDictionary] + objectForKey:(NSString *)kCFBundleNameKey]; + + char formattedName[1024]; + SprintfLiteral(formattedName, "%s %s", [currentName UTF8String], aProcessName); + + aProcessName = formattedName; + + // This function is based on Chrome/Webkit's and relies on potentially dangerous SPI. + typedef CFTypeRef (*LSGetASNType)(); + typedef OSStatus (*LSSetInformationItemType)(int, CFTypeRef, + CFStringRef, + CFStringRef, + CFDictionaryRef*); + + CFBundleRef launchServices = ::CFBundleGetBundleWithIdentifier( + CFSTR("com.apple.LaunchServices")); + if (!launchServices) { + NS_WARNING("Failed to set process name: Could not open LaunchServices bundle"); + return false; + } + + if (!sApplicationASN) { + sApplicationASN = ::CFBundleGetFunctionPointerForName(launchServices, + CFSTR("_LSGetCurrentApplicationASN")); + } + + LSGetASNType getASNFunc = reinterpret_cast<LSGetASNType> + (sApplicationASN); + + if (!sApplicationInfoItem) { + sApplicationInfoItem = ::CFBundleGetFunctionPointerForName(launchServices, + CFSTR("_LSSetApplicationInformationItem")); + } + + LSSetInformationItemType setInformationItemFunc + = reinterpret_cast<LSSetInformationItemType> + (sApplicationInfoItem); + + void * displayNameKeyAddr = ::CFBundleGetDataPointerForName(launchServices, + CFSTR("_kLSDisplayNameKey")); + + CFStringRef displayNameKey = nil; + if (displayNameKeyAddr) { + displayNameKey = reinterpret_cast<CFStringRef>(*(CFStringRef*)displayNameKeyAddr); + } + + // Rename will fail without this + ProcessSerialNumber psn; + if (::GetCurrentProcess(&psn) != noErr) { + return false; + } + + CFTypeRef currentAsn = getASNFunc(); + + if (!getASNFunc || !setInformationItemFunc || + !displayNameKey || !currentAsn) { + NS_WARNING("Failed to set process name: Accessing launchServices failed"); + return false; + } + + CFStringRef processName = ::CFStringCreateWithCString(nil, + aProcessName, + kCFStringEncodingASCII); + if (!processName) { + NS_WARNING("Failed to set process name: Could not create CFStringRef"); + return false; + } + + OSErr err = setInformationItemFunc(UNDOCUMENTED_SESSION_CONSTANT, currentAsn, + displayNameKey, processName, + nil); // Optional out param + ::CFRelease(processName); + if (err != noErr) { + NS_WARNING("Failed to set process name: LSSetInformationItemType err"); + return false; + } + + return true; + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); +} + +namespace mozilla { +namespace plugins { +namespace PluginUtilsOSX { + +size_t nsDoubleBufferCARenderer::GetFrontSurfaceWidth() { + if (!HasFrontSurface()) { + return 0; + } + + return mFrontSurface->GetWidth(); +} + +size_t nsDoubleBufferCARenderer::GetFrontSurfaceHeight() { + if (!HasFrontSurface()) { + return 0; + } + + return mFrontSurface->GetHeight(); +} + +double nsDoubleBufferCARenderer::GetFrontSurfaceContentsScaleFactor() { + if (!HasFrontSurface()) { + return 1.0; + } + + return mFrontSurface->GetContentsScaleFactor(); +} + +size_t nsDoubleBufferCARenderer::GetBackSurfaceWidth() { + if (!HasBackSurface()) { + return 0; + } + + return mBackSurface->GetWidth(); +} + +size_t nsDoubleBufferCARenderer::GetBackSurfaceHeight() { + if (!HasBackSurface()) { + return 0; + } + + return mBackSurface->GetHeight(); +} + +double nsDoubleBufferCARenderer::GetBackSurfaceContentsScaleFactor() { + if (!HasBackSurface()) { + return 1.0; + } + + return mBackSurface->GetContentsScaleFactor(); +} + +IOSurfaceID nsDoubleBufferCARenderer::GetFrontSurfaceID() { + if (!HasFrontSurface()) { + return 0; + } + + return mFrontSurface->GetIOSurfaceID(); +} + +bool nsDoubleBufferCARenderer::HasBackSurface() { + return !!mBackSurface; +} + +bool nsDoubleBufferCARenderer::HasFrontSurface() { + return !!mFrontSurface; +} + +bool nsDoubleBufferCARenderer::HasCALayer() { + return !!mCALayer; +} + +void nsDoubleBufferCARenderer::SetCALayer(void *aCALayer) { + mCALayer = aCALayer; +} + +bool nsDoubleBufferCARenderer::InitFrontSurface(size_t aWidth, size_t aHeight, + double aContentsScaleFactor, + AllowOfflineRendererEnum aAllowOfflineRenderer) { + if (!mCALayer) { + return false; + } + + mContentsScaleFactor = aContentsScaleFactor; + mFrontSurface = MacIOSurface::CreateIOSurface(aWidth, aHeight, mContentsScaleFactor); + if (!mFrontSurface) { + mCARenderer = nullptr; + return false; + } + + if (!mCARenderer) { + mCARenderer = new nsCARenderer(); + if (!mCARenderer) { + mFrontSurface = nullptr; + return false; + } + + mCARenderer->AttachIOSurface(mFrontSurface); + + nsresult result = mCARenderer->SetupRenderer(mCALayer, + mFrontSurface->GetWidth(), + mFrontSurface->GetHeight(), + mContentsScaleFactor, + aAllowOfflineRenderer); + + if (result != NS_OK) { + mCARenderer = nullptr; + mFrontSurface = nullptr; + return false; + } + } else { + mCARenderer->AttachIOSurface(mFrontSurface); + } + + return true; +} + +void nsDoubleBufferCARenderer::Render() { + if (!HasFrontSurface() || !mCARenderer) { + return; + } + + mCARenderer->Render(GetFrontSurfaceWidth(), GetFrontSurfaceHeight(), + mContentsScaleFactor, nullptr); +} + +void nsDoubleBufferCARenderer::SwapSurfaces() { + RefPtr<MacIOSurface> prevFrontSurface = mFrontSurface; + mFrontSurface = mBackSurface; + mBackSurface = prevFrontSurface; + + if (mFrontSurface) { + mCARenderer->AttachIOSurface(mFrontSurface); + } +} + +void nsDoubleBufferCARenderer::ClearFrontSurface() { + mFrontSurface = nullptr; + if (!mFrontSurface && !mBackSurface) { + mCARenderer = nullptr; + } +} + +void nsDoubleBufferCARenderer::ClearBackSurface() { + mBackSurface = nullptr; + if (!mFrontSurface && !mBackSurface) { + mCARenderer = nullptr; + } +} + +} // namespace PluginUtilsOSX +} // namespace plugins +} // namespace mozilla + diff --git a/dom/plugins/ipc/PluginUtilsWin.cpp b/dom/plugins/ipc/PluginUtilsWin.cpp new file mode 100644 index 000000000..db6387a51 --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsWin.cpp @@ -0,0 +1,237 @@ +/* -*- 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/. */
+
+/* PluginUtilsWin.cpp - top-level Windows plugin management code */
+
+#include <mmdeviceapi.h>
+#include "PluginUtilsWin.h"
+#include "PluginModuleParent.h"
+#include "mozilla/StaticMutex.h"
+
+namespace mozilla {
+namespace plugins {
+namespace PluginUtilsWin {
+
+typedef nsTHashtable<nsPtrHashKey<PluginModuleParent>> PluginModuleSet;
+StaticMutex sMutex;
+
+class AudioDeviceChangedRunnable : public Runnable
+{
+public:
+ explicit AudioDeviceChangedRunnable(const PluginModuleSet* aAudioNotificationSet,
+ NPAudioDeviceChangeDetailsIPC aChangeDetails) :
+ mChangeDetails(aChangeDetails)
+ , mAudioNotificationSet(aAudioNotificationSet)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ StaticMutexAutoLock lock(sMutex);
+ PLUGIN_LOG_DEBUG(("Notifying %d plugins of audio device change.",
+ mAudioNotificationSet->Count()));
+
+ for (auto iter = mAudioNotificationSet->ConstIter(); !iter.Done(); iter.Next()) {
+ PluginModuleParent* pluginModule = iter.Get()->GetKey();
+ pluginModule->SendNPP_SetValue_NPNVaudioDeviceChangeDetails(mChangeDetails);
+ }
+ return NS_OK;
+ }
+
+protected:
+ NPAudioDeviceChangeDetailsIPC mChangeDetails;
+ const PluginModuleSet* mAudioNotificationSet;
+};
+
+class AudioNotification : public IMMNotificationClient
+{
+public:
+ AudioNotification() :
+ mRefCt(1)
+ , mIsRegistered(false)
+ {
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
+ NULL, CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&mDeviceEnum));
+ if (FAILED(hr)) {
+ mDeviceEnum = nullptr;
+ return;
+ }
+
+ hr = mDeviceEnum->RegisterEndpointNotificationCallback(this);
+ if (FAILED(hr)) {
+ mDeviceEnum->Release();
+ mDeviceEnum = nullptr;
+ return;
+ }
+
+ mIsRegistered = true;
+ }
+
+ ~AudioNotification()
+ {
+ MOZ_ASSERT(!mIsRegistered,
+ "Destroying AudioNotification without first calling Unregister");
+ if (mDeviceEnum) {
+ mDeviceEnum->Release();
+ }
+ }
+
+ // IMMNotificationClient Implementation
+ HRESULT STDMETHODCALLTYPE
+ OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) override
+ {
+ NPAudioDeviceChangeDetailsIPC changeDetails;
+ changeDetails.flow = (int32_t)flow;
+ changeDetails.role = (int32_t)role;
+ changeDetails.defaultDevice = device_id ? std::wstring(device_id) : L"";
+
+ // Make sure that plugin is notified on the main thread.
+ RefPtr<AudioDeviceChangedRunnable> runnable =
+ new AudioDeviceChangedRunnable(&mAudioNotificationSet, changeDetails);
+ NS_DispatchToMainThread(runnable);
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE
+ OnDeviceAdded(LPCWSTR device_id) override
+ {
+ return S_OK;
+ };
+
+ HRESULT STDMETHODCALLTYPE
+ OnDeviceRemoved(LPCWSTR device_id) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE
+ OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE
+ OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) override
+ {
+ return S_OK;
+ }
+
+ // IUnknown Implementation
+ ULONG STDMETHODCALLTYPE
+ AddRef() override
+ {
+ return InterlockedIncrement(&mRefCt);
+ }
+
+ ULONG STDMETHODCALLTYPE
+ Release() override
+ {
+ ULONG ulRef = InterlockedDecrement(&mRefCt);
+ if (0 == ulRef) {
+ delete this;
+ }
+ return ulRef;
+ }
+
+ HRESULT STDMETHODCALLTYPE
+ QueryInterface(REFIID riid, VOID **ppvInterface) override
+ {
+ if (__uuidof(IUnknown) == riid) {
+ AddRef();
+ *ppvInterface = (IUnknown*)this;
+ } else if (__uuidof(IMMNotificationClient) == riid) {
+ AddRef();
+ *ppvInterface = (IMMNotificationClient*)this;
+ } else {
+ *ppvInterface = NULL;
+ return E_NOINTERFACE;
+ }
+ return S_OK;
+ }
+
+ /*
+ * A Valid instance must be Unregistered before Releasing it.
+ */
+ void Unregister()
+ {
+ if (mDeviceEnum) {
+ mDeviceEnum->UnregisterEndpointNotificationCallback(this);
+ }
+ mIsRegistered = false;
+ }
+
+ /*
+ * True whenever the notification server is set to report events to this object.
+ */
+ bool IsRegistered() {
+ return mIsRegistered;
+ }
+
+ void AddModule(PluginModuleParent* aModule) {
+ StaticMutexAutoLock lock(sMutex);
+ mAudioNotificationSet.PutEntry(aModule);
+ }
+
+ void RemoveModule(PluginModuleParent* aModule) {
+ StaticMutexAutoLock lock(sMutex);
+ mAudioNotificationSet.RemoveEntry(aModule);
+ }
+
+ /*
+ * Are any modules registered for audio notifications?
+ */
+ bool HasModules() {
+ return !mAudioNotificationSet.IsEmpty();
+ }
+
+private:
+ bool mIsRegistered; // only used to make sure that Unregister is called before destroying a Valid instance.
+ LONG mRefCt;
+ IMMDeviceEnumerator* mDeviceEnum;
+
+ // Set of plugin modules that have registered to be notified when the audio device
+ // changes.
+ PluginModuleSet mAudioNotificationSet;
+}; // class AudioNotification
+
+// callback that gets notified of audio device events, or NULL
+AudioNotification* sAudioNotification = nullptr;
+
+nsresult
+RegisterForAudioDeviceChanges(PluginModuleParent* aModuleParent, bool aShouldRegister)
+{
+ // Hold the AudioNotification singleton iff there are PluginModuleParents
+ // that are subscribed to it.
+ if (aShouldRegister) {
+ if (!sAudioNotification) {
+ // We are registering the first module. Create the singleton.
+ sAudioNotification = new AudioNotification();
+ if (!sAudioNotification->IsRegistered()) {
+ PLUGIN_LOG_DEBUG(("Registered for plugin audio device notification failed."));
+ sAudioNotification->Release();
+ sAudioNotification = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ PLUGIN_LOG_DEBUG(("Registered for plugin audio device notification."));
+ }
+ sAudioNotification->AddModule(aModuleParent);
+ }
+ else if (!aShouldRegister && sAudioNotification) {
+ sAudioNotification->RemoveModule(aModuleParent);
+ if (!sAudioNotification->HasModules()) {
+ // We have removed the last module from the notification mechanism
+ // so we can destroy the singleton.
+ PLUGIN_LOG_DEBUG(("Unregistering for plugin audio device notification."));
+ sAudioNotification->Unregister();
+ sAudioNotification->Release();
+ sAudioNotification = nullptr;
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace PluginUtilsWin
+} // namespace plugins
+} // namespace mozilla
diff --git a/dom/plugins/ipc/PluginUtilsWin.h b/dom/plugins/ipc/PluginUtilsWin.h new file mode 100644 index 000000000..097ae5262 --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsWin.h @@ -0,0 +1,23 @@ +/* -*- 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 dom_plugins_PluginUtilsWin_h
+#define dom_plugins_PluginUtilsWin_h 1
+
+#include "npapi.h"
+
+namespace mozilla {
+namespace plugins {
+namespace PluginUtilsWin {
+
+nsresult RegisterForAudioDeviceChanges(PluginModuleParent* aModuleParent,
+ bool aShouldRegister);
+
+} // namespace PluginUtilsWin
+} // namespace plugins
+} // namespace mozilla
+
+#endif //dom_plugins_PluginUtilsWin_h
diff --git a/dom/plugins/ipc/PluginWidgetChild.cpp b/dom/plugins/ipc/PluginWidgetChild.cpp new file mode 100644 index 000000000..263082981 --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetChild.cpp @@ -0,0 +1,71 @@ +/* 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/plugins/PluginWidgetChild.h" + +#include "mozilla/dom/TabChild.h" +#include "mozilla/plugins/PluginWidgetParent.h" +#include "PluginWidgetProxy.h" + +#include "mozilla/Unused.h" +#include "mozilla/DebugOnly.h" +#include "nsDebug.h" + +#if defined(XP_WIN) +#include "mozilla/plugins/PluginInstanceParent.h" +#endif + +#define PWLOG(...) +//#define PWLOG(...) printf_stderr(__VA_ARGS__) + +namespace mozilla { +namespace plugins { + +PluginWidgetChild::PluginWidgetChild() : + mWidget(nullptr) +{ + PWLOG("PluginWidgetChild::PluginWidgetChild()\n"); + MOZ_COUNT_CTOR(PluginWidgetChild); +} + +PluginWidgetChild::~PluginWidgetChild() +{ + PWLOG("PluginWidgetChild::~PluginWidgetChild()\n"); + MOZ_COUNT_DTOR(PluginWidgetChild); +} + +// Called by the proxy widget when it is destroyed by layout. Only gets +// called once. +void +PluginWidgetChild::ProxyShutdown() +{ + PWLOG("PluginWidgetChild::ProxyShutdown()\n"); + if (mWidget) { + mWidget = nullptr; + auto tab = static_cast<mozilla::dom::TabChild*>(Manager()); + if (!tab->IsDestroyed()) { + Unused << Send__delete__(this); + } + } +} + +void +PluginWidgetChild::KillWidget() +{ + PWLOG("PluginWidgetChild::KillWidget()\n"); + if (mWidget) { + mWidget->ChannelDestroyed(); + } + mWidget = nullptr; +} + +void +PluginWidgetChild::ActorDestroy(ActorDestroyReason aWhy) +{ + PWLOG("PluginWidgetChild::ActorDestroy(%d)\n", aWhy); + KillWidget(); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginWidgetChild.h b/dom/plugins/ipc/PluginWidgetChild.h new file mode 100644 index 000000000..cfb5a4327 --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetChild.h @@ -0,0 +1,39 @@ +/* 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 mozilla_plugins_PluginWidgetChild_h +#define mozilla_plugins_PluginWidgetChild_h + +#include "mozilla/plugins/PPluginWidgetChild.h" + +namespace mozilla { +namespace widget { +class PluginWidgetProxy; +} // namespace widget +namespace plugins { + +class PluginWidgetChild : public PPluginWidgetChild +{ +public: + PluginWidgetChild(); + virtual ~PluginWidgetChild(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + void SetWidget(mozilla::widget::PluginWidgetProxy* aWidget) { + mWidget = aWidget; + } + void ProxyShutdown(); + +private: + void KillWidget(); + + mozilla::widget::PluginWidgetProxy* mWidget; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginWidgetChild_h + diff --git a/dom/plugins/ipc/PluginWidgetParent.cpp b/dom/plugins/ipc/PluginWidgetParent.cpp new file mode 100644 index 000000000..3c9a95b52 --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetParent.cpp @@ -0,0 +1,237 @@ +/* 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 "PluginWidgetParent.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/dom/ContentParent.h" +#include "nsComponentManagerUtils.h" +#include "nsWidgetsCID.h" + +#include "mozilla/Unused.h" +#include "mozilla/DebugOnly.h" +#include "nsDebug.h" + +#if defined(MOZ_WIDGET_GTK) +#include "nsPluginNativeWindowGtk.h" +#endif + +using namespace mozilla; +using namespace mozilla::widget; + +#define PWLOG(...) +//#define PWLOG(...) printf_stderr(__VA_ARGS__) + +#if defined(XP_WIN) +namespace mozilla { +namespace dom { +// For nsWindow +const wchar_t* kPluginWidgetContentParentProperty = + L"kPluginWidgetParentProperty"; +} } +#endif + +namespace mozilla { +namespace plugins { + +static NS_DEFINE_CID(kWidgetCID, NS_CHILD_CID); + +// This macro returns true to prevent an abort in the child process when +// ipc message delivery fails. +#define ENSURE_CHANNEL { \ + if (!mWidget) { \ + NS_WARNING("called on an invalid remote widget."); \ + return true; \ + } \ +} + +PluginWidgetParent::PluginWidgetParent() +{ + PWLOG("PluginWidgetParent::PluginWidgetParent()\n"); + MOZ_COUNT_CTOR(PluginWidgetParent); +} + +PluginWidgetParent::~PluginWidgetParent() +{ + PWLOG("PluginWidgetParent::~PluginWidgetParent()\n"); + MOZ_COUNT_DTOR(PluginWidgetParent); + // A destroy call can actually get skipped if a widget is associated + // with the last out-of-process page, make sure and cleanup any left + // over widgets if we have them. + KillWidget(); +} + +mozilla::dom::TabParent* +PluginWidgetParent::GetTabParent() +{ + return static_cast<mozilla::dom::TabParent*>(Manager()); +} + +void +PluginWidgetParent::SetParent(nsIWidget* aParent) +{ + // This will trigger sync send messages to the plugin process window + // procedure and a cascade of events to that window related to focus + // and activation. + if (mWidget && aParent) { + mWidget->SetParent(aParent); + } +} + +// When plugins run in chrome, nsPluginNativeWindow(Plat) implements platform +// specific functionality that wraps plugin widgets. With e10s we currently +// bypass this code on Window, and reuse a bit of it on Linux. Content still +// makes use of some of the utility functions as well. + +bool +PluginWidgetParent::RecvCreate(nsresult* aResult, uint64_t* aScrollCaptureId, + uintptr_t* aPluginInstanceId) +{ + PWLOG("PluginWidgetParent::RecvCreate()\n"); + + *aScrollCaptureId = 0; + *aPluginInstanceId = 0; + + mWidget = do_CreateInstance(kWidgetCID, aResult); + NS_ASSERTION(NS_SUCCEEDED(*aResult), "widget create failure"); + +#if defined(MOZ_WIDGET_GTK) + // We need this currently just for GTK in setting up a socket widget + // we can send over to content -> plugin. + PLUG_NewPluginNativeWindow((nsPluginNativeWindow**)&mWrapper); + if (!mWrapper) { + KillWidget(); + return false; + } + // Give a copy of this to the widget, which handles some update + // work for us. + mWidget->SetNativeData(NS_NATIVE_PLUGIN_OBJECT_PTR, (uintptr_t)mWrapper.get()); +#endif + + // This returns the top level window widget + nsCOMPtr<nsIWidget> parentWidget = GetTabParent()->GetWidget(); + // If this fails, bail. + if (!parentWidget) { + *aResult = NS_ERROR_NOT_AVAILABLE; + KillWidget(); + return true; + } + + nsWidgetInitData initData; + initData.mWindowType = eWindowType_plugin_ipc_chrome; + initData.mUnicode = false; + initData.clipChildren = true; + initData.clipSiblings = true; + *aResult = mWidget->Create(parentWidget.get(), nullptr, + LayoutDeviceIntRect(0, 0, 0, 0), &initData); + if (NS_FAILED(*aResult)) { + KillWidget(); + // This should never fail, abort. + return false; + } + + mWidget->EnableDragDrop(true); + +#if defined(MOZ_WIDGET_GTK) + // For setup, initially GTK code expects 'window' to hold the parent. + mWrapper->window = mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT); + DebugOnly<nsresult> drv = mWrapper->CreateXEmbedWindow(false); + NS_ASSERTION(NS_SUCCEEDED(drv), "widget call failure"); + mWrapper->SetAllocation(); + PWLOG("Plugin XID=%p\n", (void*)mWrapper->window); +#elif defined(XP_WIN) + DebugOnly<DWORD> winres = + ::SetPropW((HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW), + mozilla::dom::kPluginWidgetContentParentProperty, + GetTabParent()->Manager()->AsContentParent()); + NS_ASSERTION(winres, "SetPropW call failure"); + + *aScrollCaptureId = mWidget->CreateScrollCaptureContainer(); + *aPluginInstanceId = + reinterpret_cast<uintptr_t>(mWidget->GetNativeData(NS_NATIVE_PLUGIN_ID)); +#endif + + // This is a special call we make to nsBaseWidget to register this + // window as a remote plugin window which is expected to receive + // visibility updates from the compositor, which ships this data + // over with corresponding layer updates. + mWidget->RegisterPluginWindowForRemoteUpdates(); + + return true; +} + +void +PluginWidgetParent::KillWidget() +{ + PWLOG("PluginWidgetParent::KillWidget() widget=%p\n", (void*)mWidget.get()); + if (mWidget) { + mWidget->UnregisterPluginWindowForRemoteUpdates(); + mWidget->Destroy(); +#if defined(MOZ_WIDGET_GTK) + mWidget->SetNativeData(NS_NATIVE_PLUGIN_OBJECT_PTR, (uintptr_t)0); + mWrapper = nullptr; +#elif defined(XP_WIN) + ::RemovePropW((HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW), + mozilla::dom::kPluginWidgetContentParentProperty); +#endif + mWidget = nullptr; + } +} + +void +PluginWidgetParent::ActorDestroy(ActorDestroyReason aWhy) +{ + PWLOG("PluginWidgetParent::ActorDestroy(%d)\n", aWhy); + KillWidget(); +} + +// Called by TabParent's Destroy() in response to an early tear down (Early +// in that this is happening before layout in the child has had a chance +// to destroy the child widget.) when the tab is closing. +void +PluginWidgetParent::ParentDestroy() +{ + PWLOG("PluginWidgetParent::ParentDestroy()\n"); +} + +bool +PluginWidgetParent::RecvSetFocus(const bool& aRaise) +{ + ENSURE_CHANNEL; + PWLOG("PluginWidgetParent::RecvSetFocus(%d)\n", aRaise); + mWidget->SetFocus(aRaise); + return true; +} + +bool +PluginWidgetParent::RecvGetNativePluginPort(uintptr_t* value) +{ + ENSURE_CHANNEL; +#if defined(MOZ_WIDGET_GTK) + *value = (uintptr_t)mWrapper->window; + NS_ASSERTION(*value, "no xid??"); +#else + *value = (uintptr_t)mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT); + NS_ASSERTION(*value, "no native port??"); +#endif + PWLOG("PluginWidgetParent::RecvGetNativeData() %p\n", (void*)*value); + return true; +} + +bool +PluginWidgetParent::RecvSetNativeChildWindow(const uintptr_t& aChildWindow) +{ +#if defined(XP_WIN) + ENSURE_CHANNEL; + PWLOG("PluginWidgetParent::RecvSetNativeChildWindow(%p)\n", + (void*)aChildWindow); + mWidget->SetNativeData(NS_NATIVE_CHILD_WINDOW, aChildWindow); + return true; +#else + NS_NOTREACHED("PluginWidgetParent::RecvSetNativeChildWindow not implemented!"); + return false; +#endif +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginWidgetParent.h b/dom/plugins/ipc/PluginWidgetParent.h new file mode 100644 index 000000000..17778002b --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetParent.h @@ -0,0 +1,65 @@ +/* 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 mozilla_plugins_PluginWidgetParent_h +#define mozilla_plugins_PluginWidgetParent_h + +#include "mozilla/plugins/PPluginWidgetParent.h" +#include "nsAutoPtr.h" +#include "nsIWidget.h" +#include "nsCOMPtr.h" + +#if defined(MOZ_WIDGET_GTK) +class nsPluginNativeWindowGtk; +#endif + +namespace mozilla { + +namespace dom { +class TabParent; +} // namespace dom + +namespace plugins { + +class PluginWidgetParent : public PPluginWidgetParent +{ +public: + PluginWidgetParent(); + virtual ~PluginWidgetParent(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + virtual bool RecvCreate(nsresult* aResult, uint64_t* aScrollCaptureId, + uintptr_t* aPluginInstanceId) override; + virtual bool RecvSetFocus(const bool& aRaise) override; + virtual bool RecvGetNativePluginPort(uintptr_t* value) override; + bool RecvSetNativeChildWindow(const uintptr_t& aChildWindow) override; + + // Helper for compositor checks on the channel + bool ActorDestroyed() { return !mWidget; } + + // Called by PBrowser when it receives a Destroy() call from the child. + void ParentDestroy(); + + // Sets mWidget's parent + void SetParent(nsIWidget* aParent); + +private: + // The tab our connection is associated with. + mozilla::dom::TabParent* GetTabParent(); + +private: + void KillWidget(); + + // The chrome side native widget. + nsCOMPtr<nsIWidget> mWidget; +#if defined(MOZ_WIDGET_GTK) + nsAutoPtr<nsPluginNativeWindowGtk> mWrapper; +#endif +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginWidgetParent_h + diff --git a/dom/plugins/ipc/StreamNotifyChild.h b/dom/plugins/ipc/StreamNotifyChild.h new file mode 100644 index 000000000..b43a76111 --- /dev/null +++ b/dom/plugins/ipc/StreamNotifyChild.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=2 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 mozilla_plugins_StreamNotifyChild_h +#define mozilla_plugins_StreamNotifyChild_h + +#include "mozilla/plugins/PStreamNotifyChild.h" + +namespace mozilla { +namespace plugins { + +class BrowserStreamChild; + +class StreamNotifyChild : public PStreamNotifyChild +{ + friend class PluginInstanceChild; + friend class BrowserStreamChild; + +public: + explicit StreamNotifyChild(const nsCString& aURL) + : mURL(aURL) + , mClosure(nullptr) + , mBrowserStream(nullptr) + { } + + virtual void ActorDestroy(ActorDestroyReason why) override; + + void SetValid(void* aClosure) { + mClosure = aClosure; + } + + void NPP_URLNotify(NPReason reason); + +private: + virtual bool Recv__delete__(const NPReason& reason) override; + + bool RecvRedirectNotify(const nsCString& url, const int32_t& status) override; + + /** + * If a stream is created for this this URLNotify, we associate the objects + * so that the NPP_URLNotify call is not fired before the stream data is + * completely delivered. The BrowserStreamChild takes responsibility for + * calling NPP_URLNotify and deleting this object. + */ + void SetAssociatedStream(BrowserStreamChild* bs); + + nsCString mURL; + void* mClosure; + + /** + * If mBrowserStream is true, it is responsible for deleting this C++ object + * and DeallocPStreamNotify is not, so that the delayed delivery of + * NPP_URLNotify is possible. + */ + BrowserStreamChild* mBrowserStream; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/StreamNotifyParent.h b/dom/plugins/ipc/StreamNotifyParent.h new file mode 100644 index 000000000..dac5f1c28 --- /dev/null +++ b/dom/plugins/ipc/StreamNotifyParent.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=2 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 mozilla_plugins_StreamNotifyParent_h +#define mozilla_plugins_StreamNotifyParent_h + +#include "mozilla/plugins/PStreamNotifyParent.h" + +namespace mozilla { +namespace plugins { + +class StreamNotifyParent : public PStreamNotifyParent +{ + friend class PluginInstanceParent; + + StreamNotifyParent() + : mDestructionFlag(nullptr) + { } + ~StreamNotifyParent() { + if (mDestructionFlag) + *mDestructionFlag = true; + } + +public: + // If we are destroyed within the call to NPN_GetURLNotify, notify the caller + // so that we aren't destroyed again. see bug 536437. + void SetDestructionFlag(bool* flag) { + mDestructionFlag = flag; + } + void ClearDestructionFlag() { + mDestructionFlag = nullptr; + } + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + +private: + bool RecvRedirectNotifyResponse(const bool& allow) override; + + bool* mDestructionFlag; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/hangui/HangUIDlg.h b/dom/plugins/ipc/hangui/HangUIDlg.h new file mode 100644 index 000000000..47339acc2 --- /dev/null +++ b/dom/plugins/ipc/hangui/HangUIDlg.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 mozilla_plugins_HangUIDlg_h +#define mozilla_plugins_HangUIDlg_h + +#define IDD_HANGUIDLG 102 +#define IDC_MSG 1000 +#define IDC_CONTINUE 1001 +#define IDC_STOP 1002 +#define IDC_NOFUTURE 1003 +#define IDC_DLGICON 1004 + +#endif // mozilla_plugins_HangUIDlg_h + diff --git a/dom/plugins/ipc/hangui/HangUIDlg.rc b/dom/plugins/ipc/hangui/HangUIDlg.rc new file mode 100644 index 000000000..62e98ca24 --- /dev/null +++ b/dom/plugins/ipc/hangui/HangUIDlg.rc @@ -0,0 +1,26 @@ +/* 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 "HangUIDlg.h" +#include <windows.h> + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_HANGUIDLG DIALOGEX 0, 0, 400, 75 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Continue",IDC_CONTINUE,283,51,50,18 + PUSHBUTTON "Stop",IDC_STOP,341,51,50,18 + CONTROL "Check1",IDC_NOFUTURE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,37,32,354,10 + LTEXT "Static",IDC_MSG,37,7,353,24 + ICON "",IDC_DLGICON,7,7,20,20 +END + diff --git a/dom/plugins/ipc/hangui/MiniShmBase.h b/dom/plugins/ipc/hangui/MiniShmBase.h new file mode 100644 index 000000000..0ac8840dd --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmBase.h @@ -0,0 +1,334 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 mozilla_plugins_MiniShmBase_h +#define mozilla_plugins_MiniShmBase_h + +#include "base/basictypes.h" + +#include "nsDebug.h" + +#include <windows.h> + +namespace mozilla { +namespace plugins { + +/** + * This class is used to provide RAII semantics for mapped views. + * @see ScopedHandle + */ +class ScopedMappedFileView +{ +public: + explicit + ScopedMappedFileView(LPVOID aView) + : mView(aView) + { + } + + ~ScopedMappedFileView() + { + Close(); + } + + void + Close() + { + if (mView) { + ::UnmapViewOfFile(mView); + mView = nullptr; + } + } + + void + Set(LPVOID aView) + { + Close(); + mView = aView; + } + + LPVOID + Get() const + { + return mView; + } + + LPVOID + Take() + { + LPVOID result = mView; + mView = nullptr; + return result; + } + + operator LPVOID() + { + return mView; + } + + bool + IsValid() const + { + return (mView); + } + +private: + DISALLOW_COPY_AND_ASSIGN(ScopedMappedFileView); + + LPVOID mView; +}; + +class MiniShmBase; + +class MiniShmObserver +{ +public: + /** + * This function is called whenever there is a new shared memory request. + * @param aMiniShmObj MiniShmBase object that may be used to read and + * write from shared memory. + */ + virtual void OnMiniShmEvent(MiniShmBase *aMiniShmObj) = 0; + /** + * This function is called once when a MiniShmParent and a MiniShmChild + * object have successfully negotiated a connection. + * + * @param aMiniShmObj MiniShmBase object that may be used to read and + * write from shared memory. + */ + virtual void OnMiniShmConnect(MiniShmBase *aMiniShmObj) { } +}; + +/** + * Base class for MiniShm connections. This class defines the common + * interfaces and code between parent and child. + */ +class MiniShmBase +{ +public: + /** + * Obtains a writable pointer into shared memory of type T. + * typename T must be plain-old-data and contain an unsigned integral + * member T::identifier that uniquely identifies T with respect to + * other types used by the protocol being implemented. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T is not valid for MiniShm. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + * NS_ERROR_NOT_AVAILABLE if the memory is not safe to write. + */ + template<typename T> nsresult + GetWritePtr(T*& aPtr) + { + if (!mWriteHeader || !mGuard) { + return NS_ERROR_NOT_INITIALIZED; + } + if (sizeof(T) > mPayloadMaxLen || + T::identifier <= RESERVED_CODE_LAST) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (::WaitForSingleObject(mGuard, mTimeout) != WAIT_OBJECT_0) { + return NS_ERROR_NOT_AVAILABLE; + } + mWriteHeader->mId = T::identifier; + mWriteHeader->mPayloadLen = sizeof(T); + aPtr = reinterpret_cast<T*>(mWriteHeader + 1); + return NS_OK; + } + + /** + * Obtains a readable pointer into shared memory of type T. + * typename T must be plain-old-data and contain an unsigned integral + * member T::identifier that uniquely identifies T with respect to + * other types used by the protocol being implemented. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T is not valid for MiniShm or if + * type T does not match the type of the data + * stored in shared memory. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + */ + template<typename T> nsresult + GetReadPtr(const T*& aPtr) + { + if (!mReadHeader) { + return NS_ERROR_NOT_INITIALIZED; + } + if (mReadHeader->mId != T::identifier || + sizeof(T) != mReadHeader->mPayloadLen) { + return NS_ERROR_ILLEGAL_VALUE; + } + aPtr = reinterpret_cast<const T*>(mReadHeader + 1); + return NS_OK; + } + + /** + * Fires the peer's event causing its request handler to execute. + * + * @return Should return NS_OK if the send was successful. + */ + virtual nsresult + Send() = 0; + +protected: + /** + * MiniShm reserves some identifier codes for its own use. Any + * identifiers used by MiniShm protocol implementations must be + * greater than RESERVED_CODE_LAST. + */ + enum ReservedCodes + { + RESERVED_CODE_INIT = 0, + RESERVED_CODE_INIT_COMPLETE = 1, + RESERVED_CODE_LAST = RESERVED_CODE_INIT_COMPLETE + }; + + struct MiniShmHeader + { + unsigned int mId; + unsigned int mPayloadLen; + }; + + struct MiniShmInit + { + enum identifier_t + { + identifier = RESERVED_CODE_INIT + }; + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + }; + + struct MiniShmInitComplete + { + enum identifier_t + { + identifier = RESERVED_CODE_INIT_COMPLETE + }; + bool mSucceeded; + }; + + MiniShmBase() + : mObserver(nullptr), + mWriteHeader(nullptr), + mReadHeader(nullptr), + mPayloadMaxLen(0), + mGuard(nullptr), + mTimeout(INFINITE) + { + } + virtual ~MiniShmBase() + { } + + virtual void + OnEvent() + { + if (mObserver) { + mObserver->OnMiniShmEvent(this); + } + } + + virtual void + OnConnect() + { + if (mObserver) { + mObserver->OnMiniShmConnect(this); + } + } + + nsresult + SetView(LPVOID aView, const unsigned int aSize, bool aIsChild) + { + if (!aView || aSize <= 2 * sizeof(MiniShmHeader)) { + return NS_ERROR_ILLEGAL_VALUE; + } + // Divide the region into halves for parent and child + if (aIsChild) { + mReadHeader = static_cast<MiniShmHeader*>(aView); + mWriteHeader = reinterpret_cast<MiniShmHeader*>(static_cast<char*>(aView) + + aSize / 2U); + } else { + mWriteHeader = static_cast<MiniShmHeader*>(aView); + mReadHeader = reinterpret_cast<MiniShmHeader*>(static_cast<char*>(aView) + + aSize / 2U); + } + mPayloadMaxLen = aSize / 2U - sizeof(MiniShmHeader); + return NS_OK; + } + + nsresult + SetGuard(HANDLE aGuard, DWORD aTimeout) + { + if (!aGuard || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + mGuard = aGuard; + mTimeout = aTimeout; + return NS_OK; + } + + inline void + SetObserver(MiniShmObserver *aObserver) { mObserver = aObserver; } + + /** + * Obtains a writable pointer into shared memory of type T. This version + * differs from GetWritePtr in that it allows typename T to be one of + * the private data structures declared in MiniShmBase. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T not an internal MiniShm struct. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + */ + template<typename T> nsresult + GetWritePtrInternal(T*& aPtr) + { + if (!mWriteHeader) { + return NS_ERROR_NOT_INITIALIZED; + } + if (sizeof(T) > mPayloadMaxLen || + T::identifier > RESERVED_CODE_LAST) { + return NS_ERROR_ILLEGAL_VALUE; + } + mWriteHeader->mId = T::identifier; + mWriteHeader->mPayloadLen = sizeof(T); + aPtr = reinterpret_cast<T*>(mWriteHeader + 1); + return NS_OK; + } + + static VOID CALLBACK + SOnEvent(PVOID aContext, BOOLEAN aIsTimer) + { + MiniShmBase* object = static_cast<MiniShmBase*>(aContext); + object->OnEvent(); + } + +private: + MiniShmObserver* mObserver; + MiniShmHeader* mWriteHeader; + MiniShmHeader* mReadHeader; + unsigned int mPayloadMaxLen; + HANDLE mGuard; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmBase); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmBase_h + diff --git a/dom/plugins/ipc/hangui/MiniShmChild.cpp b/dom/plugins/ipc/hangui/MiniShmChild.cpp new file mode 100644 index 000000000..0683340a1 --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmChild.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "MiniShmChild.h" + +#include <limits> +#include <sstream> + +namespace mozilla { +namespace plugins { + +MiniShmChild::MiniShmChild() + : mParentEvent(nullptr), + mParentGuard(nullptr), + mChildEvent(nullptr), + mChildGuard(nullptr), + mFileMapping(nullptr), + mRegWait(nullptr), + mView(nullptr), + mTimeout(INFINITE) +{} + +MiniShmChild::~MiniShmChild() +{ + if (mRegWait) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + } + if (mParentGuard) { + // Try to avoid shutting down while the parent's event handler is running. + ::WaitForSingleObject(mParentGuard, mTimeout); + ::CloseHandle(mParentGuard); + } + if (mParentEvent) { + ::CloseHandle(mParentEvent); + } + if (mChildEvent) { + ::CloseHandle(mChildEvent); + } + if (mChildGuard) { + ::CloseHandle(mChildGuard); + } + if (mView) { + ::UnmapViewOfFile(mView); + } + if (mFileMapping) { + ::CloseHandle(mFileMapping); + } +} + +nsresult +MiniShmChild::Init(MiniShmObserver* aObserver, const std::wstring& aCookie, + const DWORD aTimeout) +{ + if (aCookie.empty() || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (mFileMapping) { + return NS_ERROR_ALREADY_INITIALIZED; + } + std::wistringstream iss(aCookie); + HANDLE mapHandle = nullptr; + iss >> mapHandle; + if (!iss) { + return NS_ERROR_ILLEGAL_VALUE; + } + ScopedMappedFileView view(::MapViewOfFile(mapHandle, + FILE_MAP_WRITE, + 0, 0, 0)); + if (!view.IsValid()) { + return NS_ERROR_FAILURE; + } + MEMORY_BASIC_INFORMATION memInfo = {0}; + SIZE_T querySize = ::VirtualQuery(view, &memInfo, sizeof(memInfo)); + unsigned int mappingSize = 0; + if (querySize) { + if (memInfo.RegionSize <= std::numeric_limits<unsigned int>::max()) { + mappingSize = static_cast<unsigned int>(memInfo.RegionSize); + } + } + if (!querySize || !mappingSize) { + return NS_ERROR_FAILURE; + } + nsresult rv = SetView(view, mappingSize, true); + if (NS_FAILED(rv)) { + return rv; + } + + const MiniShmInit* initStruct = nullptr; + rv = GetReadPtr(initStruct); + if (NS_FAILED(rv)) { + return rv; + } + if (!initStruct->mParentEvent || !initStruct->mParentGuard || + !initStruct->mChildEvent || !initStruct->mChildGuard) { + return NS_ERROR_FAILURE; + } + rv = SetGuard(initStruct->mParentGuard, aTimeout); + if (NS_FAILED(rv)) { + return rv; + } + if (!::RegisterWaitForSingleObject(&mRegWait, + initStruct->mChildEvent, + &SOnEvent, + this, + INFINITE, + WT_EXECUTEDEFAULT)) { + return NS_ERROR_FAILURE; + } + + MiniShmInitComplete* initCompleteStruct = nullptr; + rv = GetWritePtrInternal(initCompleteStruct); + if (NS_FAILED(rv)) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = nullptr; + return NS_ERROR_FAILURE; + } + + initCompleteStruct->mSucceeded = true; + + // We must set the member variables before we signal the event + mFileMapping = mapHandle; + mView = view.Take(); + mParentEvent = initStruct->mParentEvent; + mParentGuard = initStruct->mParentGuard; + mChildEvent = initStruct->mChildEvent; + mChildGuard = initStruct->mChildGuard; + SetObserver(aObserver); + mTimeout = aTimeout; + + rv = Send(); + if (NS_FAILED(rv)) { + initCompleteStruct->mSucceeded = false; + mFileMapping = nullptr; + view.Set(mView); + mView = nullptr; + mParentEvent = nullptr; + mParentGuard = nullptr; + mChildEvent = nullptr; + mChildGuard = nullptr; + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = nullptr; + return rv; + } + + OnConnect(); + return NS_OK; +} + +nsresult +MiniShmChild::Send() +{ + if (!mParentEvent) { + return NS_ERROR_NOT_INITIALIZED; + } + if (!::SetEvent(mParentEvent)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void +MiniShmChild::OnEvent() +{ + MiniShmBase::OnEvent(); + ::SetEvent(mChildGuard); +} + +} // namespace plugins +} // namespace mozilla + diff --git a/dom/plugins/ipc/hangui/MiniShmChild.h b/dom/plugins/ipc/hangui/MiniShmChild.h new file mode 100644 index 000000000..19c9deea7 --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmChild.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 mozilla_plugins_MiniShmChild_h +#define mozilla_plugins_MiniShmChild_h + +#include "MiniShmBase.h" + +#include <string> + +namespace mozilla { +namespace plugins { + +/** + * This class provides a lightweight shared memory interface for a child + * process in Win32. + * This code assumes that there is a parent-child relationship between + * processes, as it inherits handles from the parent process. + * Note that this class is *not* an IPDL actor. + * + * @see MiniShmParent + */ +class MiniShmChild : public MiniShmBase +{ +public: + MiniShmChild(); + virtual ~MiniShmChild(); + + /** + * Initialize shared memory on the child side. + * + * @param aObserver A MiniShmObserver object to receive event notifications. + * @param aCookie Cookie obtained from MiniShmParent::GetCookie + * @param aTimeout Timeout in milliseconds. + * @return nsresult error code + */ + nsresult + Init(MiniShmObserver* aObserver, const std::wstring& aCookie, + const DWORD aTimeout); + + virtual nsresult + Send() override; + +protected: + void + OnEvent() override; + +private: + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + HANDLE mFileMapping; + HANDLE mRegWait; + LPVOID mView; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmChild_h + diff --git a/dom/plugins/ipc/hangui/PluginHangUI.h b/dom/plugins/ipc/hangui/PluginHangUI.h new file mode 100644 index 000000000..2c6df78bb --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUI.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 mozilla_plugins_PluginHangUI_h +#define mozilla_plugins_PluginHangUI_h + +namespace mozilla { +namespace plugins { + +enum HangUIUserResponse +{ + HANGUI_USER_RESPONSE_CANCEL = 1, + HANGUI_USER_RESPONSE_CONTINUE = 2, + HANGUI_USER_RESPONSE_STOP = 4, + HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN = 8 +}; + +enum PluginHangUIStructID +{ + PLUGIN_HANGUI_COMMAND = 0x10, + PLUGIN_HANGUI_RESULT +}; + +struct PluginHangUICommand +{ + enum + { + identifier = PLUGIN_HANGUI_COMMAND + }; + enum CmdCode + { + HANGUI_CMD_SHOW = 1, + HANGUI_CMD_CANCEL = 2 + }; + CmdCode mCode; +}; + +struct PluginHangUIResponse +{ + enum + { + identifier = PLUGIN_HANGUI_RESULT + }; + unsigned int mResponseBits; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUI_h + diff --git a/dom/plugins/ipc/hangui/PluginHangUIChild.cpp b/dom/plugins/ipc/hangui/PluginHangUIChild.cpp new file mode 100644 index 000000000..c1730a207 --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUIChild.cpp @@ -0,0 +1,425 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "PluginHangUI.h" + +#include "PluginHangUIChild.h" +#include "HangUIDlg.h" + +#include <assert.h> +#include <commctrl.h> +#include <windowsx.h> +#include <algorithm> +#include <sstream> +#include <vector> + +namespace mozilla { +namespace plugins { + +struct WinInfo +{ + WinInfo(HWND aHwnd, POINT& aPos, SIZE& aSize) + :hwnd(aHwnd) + { + pos.x = aPos.x; + pos.y = aPos.y; + size.cx = aSize.cx; + size.cy = aSize.cy; + } + HWND hwnd; + POINT pos; + SIZE size; +}; +typedef std::vector<WinInfo> WinInfoVec; + +PluginHangUIChild* PluginHangUIChild::sSelf = nullptr; +const int PluginHangUIChild::kExpectedMinimumArgc = 10; + +PluginHangUIChild::PluginHangUIChild() + : mResponseBits(0), + mParentWindow(nullptr), + mDlgHandle(nullptr), + mMainThread(nullptr), + mParentProcess(nullptr), + mRegWaitProcess(nullptr), + mIPCTimeoutMs(0) +{ +} + +PluginHangUIChild::~PluginHangUIChild() +{ + if (mMainThread) { + CloseHandle(mMainThread); + } + if (mRegWaitProcess) { + UnregisterWaitEx(mRegWaitProcess, INVALID_HANDLE_VALUE); + } + if (mParentProcess) { + CloseHandle(mParentProcess); + } + sSelf = nullptr; +} + +bool +PluginHangUIChild::Init(int aArgc, wchar_t* aArgv[]) +{ + if (aArgc < kExpectedMinimumArgc) { + return false; + } + unsigned int i = 1; + mMessageText = aArgv[i]; + mWindowTitle = aArgv[++i]; + mWaitBtnText = aArgv[++i]; + mKillBtnText = aArgv[++i]; + mNoFutureText = aArgv[++i]; + std::wistringstream issHwnd(aArgv[++i]); + issHwnd >> reinterpret_cast<HANDLE&>(mParentWindow); + if (!issHwnd) { + return false; + } + std::wistringstream issProc(aArgv[++i]); + issProc >> mParentProcess; + if (!issProc) { + return false; + } + // Only set the App User Model ID if it's present in the args + if (wcscmp(aArgv[++i], L"-")) { + HMODULE shell32 = LoadLibrary(L"shell32.dll"); + if (shell32) { + SETAPPUSERMODELID fSetAppUserModelID = (SETAPPUSERMODELID) + GetProcAddress(shell32, "SetCurrentProcessExplicitAppUserModelID"); + if (fSetAppUserModelID) { + fSetAppUserModelID(aArgv[i]); + } + FreeLibrary(shell32); + } + } + std::wistringstream issTimeout(aArgv[++i]); + issTimeout >> mIPCTimeoutMs; + if (!issTimeout) { + return false; + } + + nsresult rv = mMiniShm.Init(this, + std::wstring(aArgv[++i]), + IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); + if (NS_FAILED(rv)) { + return false; + } + sSelf = this; + return true; +} + +void +PluginHangUIChild::OnMiniShmEvent(MiniShmBase* aMiniShmObj) +{ + const PluginHangUICommand* cmd = nullptr; + nsresult rv = aMiniShmObj->GetReadPtr(cmd); + assert(NS_SUCCEEDED(rv)); + bool returnStatus = false; + if (NS_SUCCEEDED(rv)) { + switch (cmd->mCode) { + case PluginHangUICommand::HANGUI_CMD_SHOW: + returnStatus = RecvShow(); + break; + case PluginHangUICommand::HANGUI_CMD_CANCEL: + returnStatus = RecvCancel(); + break; + default: + break; + } + } +} + +// static +INT_PTR CALLBACK +PluginHangUIChild::SHangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, + WPARAM aWParam, LPARAM aLParam) +{ + PluginHangUIChild *self = PluginHangUIChild::sSelf; + if (self) { + return self->HangUIDlgProc(aDlgHandle, aMsgCode, aWParam, aLParam); + } + return FALSE; +} + +void +PluginHangUIChild::ResizeButtons() +{ + // Control IDs are specified right-to-left as they appear in the dialog + UINT ids[] = { IDC_STOP, IDC_CONTINUE }; + UINT numIds = sizeof(ids)/sizeof(ids[0]); + + // Pass 1: Compute the ideal size + bool needResizing = false; + SIZE idealSize = {0}; + WinInfoVec winInfo; + for (UINT i = 0; i < numIds; ++i) { + HWND wnd = GetDlgItem(mDlgHandle, ids[i]); + if (!wnd) { + return; + } + + // Get the button's dimensions in screen coordinates + RECT curRect; + if (!GetWindowRect(wnd, &curRect)) { + return; + } + + // Get (x,y) position of the button in client coordinates + POINT pt; + pt.x = curRect.left; + pt.y = curRect.top; + if (!ScreenToClient(mDlgHandle, &pt)) { + return; + } + + // Request the button's text margins + RECT margins; + if (!Button_GetTextMargin(wnd, &margins)) { + return; + } + + // Compute the button's width and height + SIZE curSize; + curSize.cx = curRect.right - curRect.left; + curSize.cy = curRect.bottom - curRect.top; + + // Request the button's ideal width and height and add in the margins + SIZE size = {0}; + if (!Button_GetIdealSize(wnd, &size)) { + return; + } + size.cx += margins.left + margins.right; + size.cy += margins.top + margins.bottom; + + // Size all buttons to be the same width as the longest button encountered + idealSize.cx = std::max(idealSize.cx, size.cx); + idealSize.cy = std::max(idealSize.cy, size.cy); + + // We won't bother resizing unless we need extra space + if (idealSize.cx > curSize.cx) { + needResizing = true; + } + + // Save the relevant info for the resize, if any. We do this even if + // needResizing is false because another button may trigger a resize later. + winInfo.push_back(WinInfo(wnd, pt, curSize)); + } + + if (!needResizing) { + return; + } + + // Pass 2: Resize the windows + int deltaX = 0; + HDWP hwp = BeginDeferWindowPos((int) winInfo.size()); + if (!hwp) { + return; + } + for (WinInfoVec::const_iterator itr = winInfo.begin(); + itr != winInfo.end(); ++itr) { + // deltaX accumulates the size changes so that each button's x coordinate + // can compensate for the width increases + deltaX += idealSize.cx - itr->size.cx; + hwp = DeferWindowPos(hwp, itr->hwnd, nullptr, + itr->pos.x - deltaX, itr->pos.y, + idealSize.cx, itr->size.cy, + SWP_NOZORDER | SWP_NOACTIVATE); + if (!hwp) { + return; + } + } + EndDeferWindowPos(hwp); +} + +INT_PTR +PluginHangUIChild::HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, + LPARAM aLParam) +{ + mDlgHandle = aDlgHandle; + switch (aMsgCode) { + case WM_INITDIALOG: { + // Register a wait on the Firefox process so that we will be informed + // if it dies while the dialog is showing + RegisterWaitForSingleObject(&mRegWaitProcess, + mParentProcess, + &SOnParentProcessExit, + this, + INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + SetWindowText(aDlgHandle, mWindowTitle); + SetDlgItemText(aDlgHandle, IDC_MSG, mMessageText); + SetDlgItemText(aDlgHandle, IDC_NOFUTURE, mNoFutureText); + SetDlgItemText(aDlgHandle, IDC_CONTINUE, mWaitBtnText); + SetDlgItemText(aDlgHandle, IDC_STOP, mKillBtnText); + ResizeButtons(); + HANDLE icon = LoadImage(nullptr, IDI_QUESTION, IMAGE_ICON, 0, 0, + LR_DEFAULTSIZE | LR_SHARED); + if (icon) { + SendDlgItemMessage(aDlgHandle, IDC_DLGICON, STM_SETICON, (WPARAM)icon, 0); + } + EnableWindow(mParentWindow, FALSE); + return TRUE; + } + case WM_CLOSE: { + mResponseBits |= HANGUI_USER_RESPONSE_CANCEL; + EndDialog(aDlgHandle, 0); + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + case WM_COMMAND: { + switch (LOWORD(aWParam)) { + case IDC_CONTINUE: + if (HIWORD(aWParam) == BN_CLICKED) { + mResponseBits |= HANGUI_USER_RESPONSE_CONTINUE; + EndDialog(aDlgHandle, 1); + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + break; + case IDC_STOP: + if (HIWORD(aWParam) == BN_CLICKED) { + mResponseBits |= HANGUI_USER_RESPONSE_STOP; + EndDialog(aDlgHandle, 1); + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + break; + case IDC_NOFUTURE: + if (HIWORD(aWParam) == BN_CLICKED) { + if (Button_GetCheck(GetDlgItem(aDlgHandle, + IDC_NOFUTURE)) == BST_CHECKED) { + mResponseBits |= HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN; + } else { + mResponseBits &= + ~static_cast<DWORD>(HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); + } + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + break; + default: + break; + } + break; + } + case WM_DESTROY: { + EnableWindow(mParentWindow, TRUE); + SetForegroundWindow(mParentWindow); + break; + } + default: + break; + } + return FALSE; +} + +// static +VOID CALLBACK +PluginHangUIChild::SOnParentProcessExit(PVOID aObject, BOOLEAN aIsTimer) +{ + // Simulate a cancel if the parent process died + PluginHangUIChild* object = static_cast<PluginHangUIChild*>(aObject); + object->RecvCancel(); +} + +bool +PluginHangUIChild::RecvShow() +{ + return (QueueUserAPC(&ShowAPC, + mMainThread, + reinterpret_cast<ULONG_PTR>(this))); +} + +bool +PluginHangUIChild::Show() +{ + INT_PTR dlgResult = DialogBox(GetModuleHandle(nullptr), + MAKEINTRESOURCE(IDD_HANGUIDLG), + nullptr, + &SHangUIDlgProc); + mDlgHandle = nullptr; + assert(dlgResult != -1); + bool result = false; + if (dlgResult != -1) { + PluginHangUIResponse* response = nullptr; + nsresult rv = mMiniShm.GetWritePtr(response); + if (NS_SUCCEEDED(rv)) { + response->mResponseBits = mResponseBits; + result = NS_SUCCEEDED(mMiniShm.Send()); + } + } + return result; +} + +// static +VOID CALLBACK +PluginHangUIChild::ShowAPC(ULONG_PTR aContext) +{ + PluginHangUIChild* object = reinterpret_cast<PluginHangUIChild*>(aContext); + object->Show(); +} + +bool +PluginHangUIChild::RecvCancel() +{ + if (mDlgHandle) { + PostMessage(mDlgHandle, WM_CLOSE, 0, 0); + } + return true; +} + +bool +PluginHangUIChild::WaitForDismissal() +{ + if (!SetMainThread()) { + return false; + } + DWORD waitResult = WaitForSingleObjectEx(mParentProcess, + mIPCTimeoutMs, + TRUE); + return waitResult == WAIT_OBJECT_0 || + waitResult == WAIT_IO_COMPLETION; +} + +bool +PluginHangUIChild::SetMainThread() +{ + if (mMainThread) { + CloseHandle(mMainThread); + mMainThread = nullptr; + } + mMainThread = OpenThread(THREAD_SET_CONTEXT, + FALSE, + GetCurrentThreadId()); + return !(!mMainThread); +} + +} // namespace plugins +} // namespace mozilla + +#ifdef __MINGW32__ +extern "C" +#endif +int +wmain(int argc, wchar_t *argv[]) +{ + INITCOMMONCONTROLSEX icc = { sizeof(INITCOMMONCONTROLSEX), + ICC_STANDARD_CLASSES }; + if (!InitCommonControlsEx(&icc)) { + return 1; + } + mozilla::plugins::PluginHangUIChild hangui; + if (!hangui.Init(argc, argv)) { + return 1; + } + if (!hangui.WaitForDismissal()) { + return 1; + } + return 0; +} + diff --git a/dom/plugins/ipc/hangui/PluginHangUIChild.h b/dom/plugins/ipc/hangui/PluginHangUIChild.h new file mode 100644 index 000000000..000e003ec --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUIChild.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 mozilla_plugins_PluginHangUIChild_h +#define mozilla_plugins_PluginHangUIChild_h + +#include "MiniShmChild.h" + +#include <string> + +#include <windows.h> + +namespace mozilla { +namespace plugins { + +/** + * This class implements the plugin-hang-ui. + * + * NOTE: PluginHangUIChild is *not* an IPDL actor! In this case, "Child" + * is describing the fact that plugin-hang-ui is a child process to the + * firefox process, which is the PluginHangUIParent. + * PluginHangUIParent and PluginHangUIChild are a matched pair. + * @see PluginHangUIParent + */ +class PluginHangUIChild : public MiniShmObserver +{ +public: + PluginHangUIChild(); + virtual ~PluginHangUIChild(); + + bool + Init(int aArgc, wchar_t* aArgv[]); + + /** + * Displays the Plugin Hang UI and does not return until the UI has + * been dismissed. + * + * @return true if the UI was displayed and the user response was + * successfully sent back to the parent. Otherwise false. + */ + bool + Show(); + + /** + * Causes the calling thread to wait either for the Hang UI to be + * dismissed or for the parent process to terminate. This should + * be called by the main thread. + * + * @return true unless there was an error initiating the wait + */ + bool + WaitForDismissal(); + + virtual void + OnMiniShmEvent(MiniShmBase* aMiniShmObj) override; + +private: + bool + RecvShow(); + + bool + RecvCancel(); + + bool + SetMainThread(); + + void + ResizeButtons(); + + INT_PTR + HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, LPARAM aLParam); + + static VOID CALLBACK + ShowAPC(ULONG_PTR aContext); + + static INT_PTR CALLBACK + SHangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, + LPARAM aLParam); + + static VOID CALLBACK + SOnParentProcessExit(PVOID aObject, BOOLEAN aIsTimer); + + static PluginHangUIChild *sSelf; + + const wchar_t* mMessageText; + const wchar_t* mWindowTitle; + const wchar_t* mWaitBtnText; + const wchar_t* mKillBtnText; + const wchar_t* mNoFutureText; + unsigned int mResponseBits; + HWND mParentWindow; + HWND mDlgHandle; + HANDLE mMainThread; + HANDLE mParentProcess; + HANDLE mRegWaitProcess; + DWORD mIPCTimeoutMs; + MiniShmChild mMiniShm; + + static const int kExpectedMinimumArgc; + + typedef HRESULT (WINAPI *SETAPPUSERMODELID)(PCWSTR); + + DISALLOW_COPY_AND_ASSIGN(PluginHangUIChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUIChild_h + diff --git a/dom/plugins/ipc/hangui/module.ver b/dom/plugins/ipc/hangui/module.ver new file mode 100644 index 000000000..d11506f4a --- /dev/null +++ b/dom/plugins/ipc/hangui/module.ver @@ -0,0 +1,6 @@ +WIN32_MODULE_COMPANYNAME=Mozilla Corporation +WIN32_MODULE_PRODUCTVERSION=@MOZ_APP_WINVERSION@ +WIN32_MODULE_PRODUCTVERSION_STRING=@MOZ_APP_VERSION@ +WIN32_MODULE_DESCRIPTION=Plugin Hang UI for @MOZ_APP_DISPLAYNAME@ +WIN32_MODULE_PRODUCTNAME=@MOZ_APP_DISPLAYNAME@ +WIN32_MODULE_NAME=@MOZ_APP_DISPLAYNAME@ diff --git a/dom/plugins/ipc/hangui/moz.build b/dom/plugins/ipc/hangui/moz.build new file mode 100644 index 000000000..0b84cfb8f --- /dev/null +++ b/dom/plugins/ipc/hangui/moz.build @@ -0,0 +1,27 @@ +# -*- 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/. + +Program('plugin-hang-ui') + +UNIFIED_SOURCES += [ + 'MiniShmChild.cpp', + 'PluginHangUIChild.cpp', +] +include('/ipc/chromium/chromium-config.mozbuild') + +DEFINES['NS_NO_XPCOM'] = True +DEFINES['_HAS_EXCEPTIONS'] = 0 + +DISABLE_STL_WRAPPING = True + +if CONFIG['GNU_CC']: + WIN32_EXE_LDFLAGS += ['-municode'] + +RCINCLUDE = 'HangUIDlg.rc' + +OS_LIBS += [ + 'comctl32', +] diff --git a/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest b/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest new file mode 100644 index 000000000..8d6149c58 --- /dev/null +++ b/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="plugin-hang-ui" + type="win32" +/> +<description>Firefox Plugin Hang User Interface</description> +<dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> +</dependency> + <ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> + </ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> + </application> + </compatibility> +</assembly> diff --git a/dom/plugins/ipc/interpose/moz.build b/dom/plugins/ipc/interpose/moz.build new file mode 100644 index 000000000..8bd8ee651 --- /dev/null +++ b/dom/plugins/ipc/interpose/moz.build @@ -0,0 +1,13 @@ +# -*- 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/. + +SharedLibrary('plugin_child_interpose') + +UNIFIED_SOURCES += [ "%s.mm" % (LIBRARY_NAME) ] + +OS_LIBS += ['-framework Carbon'] + +DIST_INSTALL = True diff --git a/dom/plugins/ipc/interpose/plugin_child_interpose.mm b/dom/plugins/ipc/interpose/plugin_child_interpose.mm new file mode 100644 index 000000000..58c39b0a1 --- /dev/null +++ b/dom/plugins/ipc/interpose/plugin_child_interpose.mm @@ -0,0 +1,134 @@ +/* -*- 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/. */ + +// Use "dyld interposing" to hook methods imported from other libraries in the +// plugin child process. The basic technique is described at +// http://books.google.com/books?id=K8vUkpOXhN4C&pg=PA73&lpg=PA73&dq=__interpose&source=bl&ots=OJnnXZYpZC&sig=o7I3lXvoduUi13SrPfOON7o3do4&hl=en&ei=AoehS9brCYGQNrvsmeUM&sa=X&oi=book_result&ct=result&resnum=6&ved=0CBsQ6AEwBQ#v=onepage&q=__interpose&f=false. +// The idea of doing it for the plugin child process comes from Chromium code, +// particularly from plugin_carbon_interpose_mac.cc +// (http://codesearch.google.com/codesearch/p?hl=en#OAMlx_jo-ck/src/chrome/browser/plugin_carbon_interpose_mac.cc&q=nscursor&exact_package=chromium&d=1&l=168) +// and from PluginProcessHost::Init() in plugin_process_host.cc +// (http://codesearch.google.com/codesearch/p?hl=en#OAMlx_jo-ck/src/content/browser/plugin_process_host.cc&q=nscursor&exact_package=chromium&d=1&l=222). + +// These hooks are needed to make certain OS calls work from the child process +// (a background process) that would normally only work when called in the +// parent process (the foreground process). They allow us to serialize +// information from the child process to the parent process, so that the same +// (or equivalent) calls can be made from the parent process. + +// This file lives in a seperate module (libplugin_child_interpose.dylib), +// which will get loaded by the OS before any other modules when the plugin +// child process is launched (from GeckoChildProcessHost:: +// PerformAsyncLaunchInternal()). For this reason it shouldn't link in other +// browser modules when loaded. Instead it should use dlsym() to load +// pointers to the methods it wants to call in other modules. + +#if !defined(__LP64__) + +#include <dlfcn.h> +#import <Carbon/Carbon.h> + +// The header file QuickdrawAPI.h is missing on OS X 10.7 and up (though the +// QuickDraw APIs defined in it are still present) -- so we need to supply the +// relevant parts of its contents here. It's likely that Apple will eventually +// remove the APIs themselves (probably in OS X 10.8), so we need to make them +// weak imports, and test for their presence before using them. +#if !defined(__QUICKDRAWAPI__) + +struct Cursor; +extern "C" void SetCursor(const Cursor * crsr) __attribute__((weak_import)); + +#endif /* __QUICKDRAWAPI__ */ + +BOOL (*OnSetThemeCursorPtr) (ThemeCursor) = NULL; +BOOL (*OnSetCursorPtr) (const Cursor*) = NULL; +BOOL (*OnHideCursorPtr) () = NULL; +BOOL (*OnShowCursorPtr) () = NULL; + +static BOOL loadXULPtrs() +{ + if (!OnSetThemeCursorPtr) { + // mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor) is in + // PluginInterposeOSX.mm + OnSetThemeCursorPtr = (BOOL(*)(ThemeCursor)) + dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnSetThemeCursor"); + } + if (!OnSetCursorPtr) { + // mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor) is in + // PluginInterposeOSX.mm + OnSetCursorPtr = (BOOL(*)(const Cursor*)) + dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnSetCursor"); + } + if (!OnHideCursorPtr) { + // mac_plugin_interposing_child_OnHideCursor() is in PluginInterposeOSX.mm + OnHideCursorPtr = (BOOL(*)()) + dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnHideCursor"); + } + if (!OnShowCursorPtr) { + // mac_plugin_interposing_child_OnShowCursor() is in PluginInterposeOSX.mm + OnShowCursorPtr = (BOOL(*)()) + dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnShowCursor"); + } + return (OnSetCursorPtr && OnSetThemeCursorPtr && OnHideCursorPtr && OnShowCursorPtr); +} + +static OSStatus MacPluginChildSetThemeCursor(ThemeCursor cursor) +{ + if (loadXULPtrs()) { + OnSetThemeCursorPtr(cursor); + } + return ::SetThemeCursor(cursor); +} + +static void MacPluginChildSetCursor(const Cursor* cursor) +{ + if (::SetCursor) { + if (loadXULPtrs()) { + OnSetCursorPtr(cursor); + } + ::SetCursor(cursor); + } +} + +static CGError MacPluginChildCGDisplayHideCursor(CGDirectDisplayID display) +{ + if (loadXULPtrs()) { + OnHideCursorPtr(); + } + return ::CGDisplayHideCursor(display); +} + +static CGError MacPluginChildCGDisplayShowCursor(CGDirectDisplayID display) +{ + if (loadXULPtrs()) { + OnShowCursorPtr(); + } + return ::CGDisplayShowCursor(display); +} + +#pragma mark - + +struct interpose_substitution { + const void* replacement; + const void* original; +}; + +#define INTERPOSE_FUNCTION(function) \ + { reinterpret_cast<const void*>(MacPluginChild##function), \ + reinterpret_cast<const void*>(function) } + +__attribute__((used)) static const interpose_substitution substitutions[] + __attribute__((section("__DATA, __interpose"))) = { + INTERPOSE_FUNCTION(SetThemeCursor), + INTERPOSE_FUNCTION(CGDisplayHideCursor), + INTERPOSE_FUNCTION(CGDisplayShowCursor), + // SetCursor() and other QuickDraw APIs will probably be removed in OS X + // 10.8. But this will make 'SetCursor' NULL, which will just stop the OS + // from interposing it (tested using an INTERPOSE_FUNCTION_BROKEN macro + // that just sets the second address of each tuple to NULL). + INTERPOSE_FUNCTION(SetCursor), +}; + +#endif // !__LP64__ diff --git a/dom/plugins/ipc/moz.build b/dom/plugins/ipc/moz.build new file mode 100644 index 000000000..b569aeb4c --- /dev/null +++ b/dom/plugins/ipc/moz.build @@ -0,0 +1,153 @@ +# -*- 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'] == 'cocoa': + DIRS += ['interpose'] + +EXPORTS.mozilla += [ + 'PluginLibrary.h', +] + +EXPORTS.mozilla.plugins += [ + 'AStream.h', + 'BrowserStreamChild.h', + 'BrowserStreamParent.h', + 'ChildAsyncCall.h', + 'ChildTimer.h', + 'NPEventAndroid.h', + 'NPEventOSX.h', + 'NPEventUnix.h', + 'NPEventWindows.h', + 'PluginAsyncSurrogate.h', + 'PluginBridge.h', + 'PluginDataResolver.h', + 'PluginInstanceChild.h', + 'PluginInstanceParent.h', + 'PluginMessageUtils.h', + 'PluginModuleChild.h', + 'PluginModuleParent.h', + 'PluginProcessChild.h', + 'PluginProcessParent.h', + 'PluginQuirks.h', + 'PluginScriptableObjectChild.h', + 'PluginScriptableObjectParent.h', + 'PluginScriptableObjectUtils-inl.h', + 'PluginScriptableObjectUtils.h', + 'PluginStreamChild.h', + 'PluginStreamParent.h', + 'PluginUtilsOSX.h', + 'PluginWidgetChild.h', + 'PluginWidgetParent.h', + 'StreamNotifyChild.h', + 'StreamNotifyParent.h', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + EXPORTS.mozilla.plugins += [ + 'PluginSurfaceParent.h', + ] + UNIFIED_SOURCES += [ + 'PluginHangUIParent.cpp', + 'PluginSurfaceParent.cpp', + ] + SOURCES += [ + 'MiniShmParent.cpp', # Issues with CreateEvent + ] + DEFINES['MOZ_HANGUI_PROCESS_NAME'] = '"plugin-hang-ui%s"' % CONFIG['BIN_SUFFIX'] + LOCAL_INCLUDES += [ + '/widget', + 'hangui', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + EXPORTS.mozilla.plugins += [ + 'PluginInterposeOSX.h', + ] + +UNIFIED_SOURCES += [ + 'BrowserStreamChild.cpp', + 'BrowserStreamParent.cpp', + 'ChildAsyncCall.cpp', + 'ChildTimer.cpp', + 'PluginAsyncSurrogate.cpp', + 'PluginBackgroundDestroyer.cpp', + 'PluginInstanceParent.cpp', + 'PluginMessageUtils.cpp', + 'PluginModuleParent.cpp', + 'PluginProcessChild.cpp', + 'PluginProcessParent.cpp', + 'PluginQuirks.cpp', + 'PluginScriptableObjectChild.cpp', + 'PluginScriptableObjectParent.cpp', + 'PluginStreamChild.cpp', + 'PluginStreamParent.cpp', +] + +SOURCES += [ + 'PluginInstanceChild.cpp', # 'PluginThreadCallback' : ambiguous symbol + 'PluginModuleChild.cpp', # Redefinition of mozilla::WindowsDllInterceptor sUser32Intercept + 'PluginWidgetChild.cpp', + 'PluginWidgetParent.cpp', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + UNIFIED_SOURCES += [ + 'PluginInterposeOSX.mm', + 'PluginUtilsOSX.mm', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + UNIFIED_SOURCES += [ + 'D3D11SurfaceHolder.cpp', + 'PluginUtilsWin.cpp' + ] + +IPDL_SOURCES += [ + 'PBrowserStream.ipdl', + 'PluginTypes.ipdlh', + 'PPluginBackgroundDestroyer.ipdl', + 'PPluginInstance.ipdl', + 'PPluginModule.ipdl', + 'PPluginScriptableObject.ipdl', + 'PPluginStream.ipdl', + 'PPluginSurface.ipdl', + 'PStreamNotify.ipdl', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '../base', + '/xpcom/base/', +] + +if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT': + LOCAL_INCLUDES += [ + '/security/sandbox/chromium', + '/security/sandbox/chromium-shim', + ] + +DEFINES['FORCE_PR_LOG'] = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gtk3': + CXXFLAGS += CONFIG['TK_CFLAGS'] +else: + # Force build against gtk+2 for struct offsets and such. + CXXFLAGS += CONFIG['MOZ_GTK2_CFLAGS'] + +CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] + +if CONFIG['_MSC_VER']: + # This is intended as a temporary hack to support building with VS2015. + # conversion from 'X' to 'Y' requires a narrowing conversion + CXXFLAGS += ['-wd4838'] + + # 'type cast': conversion from 'unsigned int' to 'HIMC' of greater size + CXXFLAGS += ['-wd4312'] diff --git a/dom/plugins/test/crashtests/110650-1.html b/dom/plugins/test/crashtests/110650-1.html new file mode 100644 index 000000000..9826227b0 --- /dev/null +++ b/dom/plugins/test/crashtests/110650-1.html @@ -0,0 +1,11 @@ +<HTML> +<HEAD> +<TITLE>123246 testcase</TITLE> +</HEAD> +<BODY> +<object align="right" width=100> +</object> +</BODY > +</HTML> + + diff --git a/dom/plugins/test/crashtests/41276-1.html b/dom/plugins/test/crashtests/41276-1.html new file mode 100644 index 000000000..ba35c34fa --- /dev/null +++ b/dom/plugins/test/crashtests/41276-1.html @@ -0,0 +1,28 @@ +<HTML><HEAD><TITLE>Plugin Limit</TITLE></HEAD>
+<BODY>
+
+Mozilla has a hardcoded limit of 10 simultaneously embedded plugins.<br>
+If that limit is exceeded, plugin instances are prematurely destroyed (see the empty boxes below).<br>
+Leave or reload a page that has a prematurely destroyed plugin and mozilla will crash.<br>
+Sometimes, just loading this page will cause mozilla to crash.<br>
+
+
+<br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<br>
+</BODY></HTML>
diff --git a/dom/plugins/test/crashtests/48856-1.html b/dom/plugins/test/crashtests/48856-1.html new file mode 100644 index 000000000..cd0de2ab9 --- /dev/null +++ b/dom/plugins/test/crashtests/48856-1.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/REC-html40/loose.dtd"> +<HTML> + <HEAD> + <TITLE>Mozilla Bug 48856</TITLE> + </HEAD> + <BODY> + <EMBED></EMBED> + </BODY> +</HTML> diff --git a/dom/plugins/test/crashtests/539897-1.html b/dom/plugins/test/crashtests/539897-1.html new file mode 100644 index 000000000..f280e62e6 --- /dev/null +++ b/dom/plugins/test/crashtests/539897-1.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> +<script type="text/javascript"> +function crashplugin() { + var plugin = document.getElementById('p'); + plugin.reinitWidget(); + plugin.reinitWidget(); +} + +function getTestCases() { + return [ + { testPassed: + (function () { + var plugin = document.getElementById('p'); + try { + plugin.getPaintCount(); + return true; + } catch (e) { + return false; + } + }), + testDescription: + (function () { + return "plugin should not crash"; + }) + } + ]; +} +</script> +</head> +<body onload="crashplugin();"> +<embed id="p" type="application/x-test" wmode="window"/> +</body> +</html> diff --git a/dom/plugins/test/crashtests/540114-1.html b/dom/plugins/test/crashtests/540114-1.html new file mode 100644 index 000000000..8243649aa --- /dev/null +++ b/dom/plugins/test/crashtests/540114-1.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function crashplugin() { + var plugin = document.getElementById('removeme'); + var flush_reflow = plugin.offsetHeight; // this may not be necessary + document.body.removeChild(plugin); + // Give the plugin time to crash + setTimeout(function() { document.documentElement.removeAttribute('class') }, + 1000); +} + +function getTestCases() { + return [ + { testPassed: + (function () { + // Assuming the same process is used for removeme and checkme + var plugin = document.getElementById('checkme'); + try { + plugin.getPaintCount(); + return true; + } catch (e) { + return false; + } + }), + testDescription: + (function () { + return "plugin should not crash"; + }) + } + ]; +} +</script> +</head> +<body onload="crashplugin();"> +<embed id="checkme" type="application/x-test"/> +<embed id="removeme" type="application/x-test" wmode="window" cleanupwidget="false"/> +</body> +</html> diff --git a/dom/plugins/test/crashtests/570884.html b/dom/plugins/test/crashtests/570884.html new file mode 100644 index 000000000..7af5cdba5 --- /dev/null +++ b/dom/plugins/test/crashtests/570884.html @@ -0,0 +1,8 @@ +<body> +<embed type="application/x-test" width="20"></embed> +<embed type="application/x-test" width="10"></embed> +<embed type="application/x-test" width="0"></embed> +<embed type="application/x-test" width="20" height="20"></embed> +<embed type="application/x-test" width="10" height="10"></embed> +<embed type="application/x-test" width="0" height="0"></embed> +</body> diff --git a/dom/plugins/test/crashtests/598862.html b/dom/plugins/test/crashtests/598862.html new file mode 100644 index 000000000..9b1ad38d7 --- /dev/null +++ b/dom/plugins/test/crashtests/598862.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> + <head> + <script> +var unusedScreenX; +function XSync() { + unusedScreenX = window.screenX; +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +function cleanup() { + try { + document.getElementById("plugin").crash(); + } catch (dontcare) { + } + window.setTimeout(finish, 100); +} + +function scrollOfDeath() { + // Add a listener for the MozAfterPaint after the scrollTo below. + // If we don't crash during the scroll, we'll get the + // MozAfterPaint. + window.addEventListener("MozAfterPaint", cleanup, false); + window.scrollTo(0, 50); +} + +// +// The sequence of events we expect is +// +// load (including initial paints of plugin that are cached) +// destroy X resources +// [X server has time to observe resource destruction] +// scrollTo +// [repaint using cached plugin surface: BOOM if buggy] +// MozAfterPaint +// cleanup +// +// However, this test is fundamentally nondeterministic. There are +// two main "failure" modes +// (1) X server doesn't have time to observer resource destruction +// before paint-after-scroll +// (2) plugin's crash notification arrives before +// paint-after-scroll +// Both result in spurious passes. +// +// This test is anal about cleanup because it must be pretty sure that +// the plugin subprocess is gone before starting the next test. We +// double-check that the plugin is gone by the time we're done by +// trying to crash it again, after we expect it to have crashed already. +// +function runTest() { + // Have the plugin throw away its X resources, one of which is + // probably a drawable to which we hold a reference + document.getElementById("plugin").destroySharedGfxStuff(); + + // Do something that's (hopefully) equivalent to an XSync() to allow + // the resource destruction to propagate to the server + XSync(); + + // Set up a scroll to happen soon + window.setTimeout(scrollOfDeath, 100); +} + +window.addEventListener("MozReftestInvalidate", runTest, false); + </script> + </head> + + <body style="width: 400px; height: 10000px;"> + <embed id="plugin" type="application/x-test" + style="position:absolute; + top:100px; left:50px; width:200px; height:200px;"> + </embed> + </body> +</html> diff --git a/dom/plugins/test/crashtests/626602-1.html b/dom/plugins/test/crashtests/626602-1.html new file mode 100644 index 000000000..0a878bbd1 --- /dev/null +++ b/dom/plugins/test/crashtests/626602-1.html @@ -0,0 +1,109 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:4; +} +#two { + position:absolute; + top:100px; left:100px; + background-color:rgb(0,0,0,0); + z-index:3; +} +#three { + position:absolute; + left:100px; top:100px; + width:200px; height:200px; + background-color: rgb(255,0,0); + opacity:0.6; + z-index:2; +} +#four { + position:absolute; + top:100px; left:100px; + z-index:1; +} + </style> + <script type="text/javascript"> +var plugin, div, canvas; +function start() { + plugin = document.getElementById("four"); + div = document.getElementById("three"); + canvas = document.getElementById("two"); + paintCanvas(); + + requestAnimationFrame(moveSomething); +} + +function paintCanvas() { + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgba(255,0,0, 0.6)"; + ctx.fillRect(0,0, 200,200); +} + +var i = 0, numLoops = 20; +var pluginIn = true, divIn = true, canvasIn = true; +function moveSomething() { + var didSomething = (0 === (i % 2)) ? moveSomethingOut() : moveSomethingIn(); + if (!didSomething && ++i >= numLoops) { + return finish(); + } + + requestAnimationFrame(moveSomething); +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +function moveSomethingOut() { + if (pluginIn) { + plugin.style.left = "400px"; + pluginIn = false; + } else if (divIn) { + div.style.left = "400px"; + divIn = false; + } else if (canvasIn) { + canvas.style.left = "400px"; + canvasIn = false; + } else { + return false; + } + return true; +} + +function moveSomethingIn() { + if (!pluginIn) { + plugin.style.left = "100px"; + pluginIn = true; + } else if (!divIn) { + div.style.left = "100px"; + divIn = true; + } else if (!canvasIn) { + canvas.style.left = "100px"; + canvasIn = true; + } else { + return false; + } + return true; +} + +function reset() { + +} + </script> +</style> +</head> +<body onload="start();"> + <embed id="four" type="application/x-test" width="200" height="200" + drawmode="solid" color="FFFF0000"></embed> + <div id="three"></div> + <canvas id="two" width="200" height="200"></canvas> + <embed id="one" type="application/x-test" width="400" height="400" + drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/crashtests/752340.html b/dom/plugins/test/crashtests/752340.html new file mode 100644 index 000000000..c4c8c464f --- /dev/null +++ b/dom/plugins/test/crashtests/752340.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> +<head> + <script type="text/javascript"> + // Failures in this file can manifest as ###!!! ASSERTION: scope has non-empty map: '0 == mWrappedNativeMap->Count()' + // followed by an Assertion failure: allocated() crash during the next GC. + // It can also manifest as a leak. + function breakthings() { + var e = document.createElement("embed"); + var i = document.getElementById("i"); + i.contentDocument.body.appendChild(e); + i.src = "about:blank"; + } + </script> +</head> +<body onload="javascript:breakthings();"> +<iframe id="i" /> +</body> +</html> diff --git a/dom/plugins/test/crashtests/843086.xhtml b/dom/plugins/test/crashtests/843086.xhtml new file mode 100644 index 000000000..a88e2193f --- /dev/null +++ b/dom/plugins/test/crashtests/843086.xhtml @@ -0,0 +1 @@ +<applet xmlns="http://www.w3.org/1999/xhtml" /> diff --git a/dom/plugins/test/crashtests/crashtests.list b/dom/plugins/test/crashtests/crashtests.list new file mode 100644 index 000000000..aec2195f2 --- /dev/null +++ b/dom/plugins/test/crashtests/crashtests.list @@ -0,0 +1,14 @@ +load 41276-1.html +load 48856-1.html +load 110650-1.html +skip-if(!haveTestPlugin) script 539897-1.html +asserts-if(winWidget&&browserIsRemote,0-1) skip-if(!haveTestPlugin) script 540114-1.html +load 570884.html +# This test relies on the reading of screenX/Y forcing a round trip to +# the X server, which is a bad assumption for <browser remote>. +# Plugin arch is going to change anyway with OOP content so skipping +# this test for now is OK. +skip-if(!haveTestPlugin||http.platform!="X11") load 598862.html +skip-if(Android) load 626602-1.html # bug 908363 +load 752340.html +load 843086.xhtml diff --git a/dom/plugins/test/mochitest/307-xo-redirect.sjs b/dom/plugins/test/mochitest/307-xo-redirect.sjs new file mode 100644 index 000000000..b880978ce --- /dev/null +++ b/dom/plugins/test/mochitest/307-xo-redirect.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) +{ + response.setStatusLine(request.httpVersion, 307, "Moved temporarily"); + response.setHeader("Location", "http://example.org/tests/dom/plugins/test/mochitest/loremipsum.txt"); + response.setHeader("Content-Type", "text/html"); +} diff --git a/dom/plugins/test/mochitest/browser.ini b/dom/plugins/test/mochitest/browser.ini new file mode 100644 index 000000000..a28a22f10 --- /dev/null +++ b/dom/plugins/test/mochitest/browser.ini @@ -0,0 +1,15 @@ +[DEFAULT] +support-files = + head.js + plugin_test.html + plugin_subframe_test.html + plugin_no_scroll_div.html + +[browser_bug1163570.js] +skip-if = true # Bug 1249878 +[browser_bug1196539.js] +skip-if = (!e10s || os != "win") +[browser_tabswitchbetweenplugins.js] +skip-if = (!e10s || os != "win") +[browser_pluginscroll.js] +skip-if = (true || !e10s || os != "win") # Bug 1213631 diff --git a/dom/plugins/test/mochitest/browser_bug1163570.js b/dom/plugins/test/mochitest/browser_bug1163570.js new file mode 100644 index 000000000..c6a75d2ec --- /dev/null +++ b/dom/plugins/test/mochitest/browser_bug1163570.js @@ -0,0 +1,96 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + +// simple tab load helper, pilfered from browser plugin tests +function promiseTabLoad(tab, url, eventType="load") { + return new Promise((resolve) => { + function handle(event) { + if (event.originalTarget != tab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank" || + (url && event.target.location.href != url)) { + return; + } + tab.linkedBrowser.removeEventListener(eventType, handle, true); + resolve(event); + } + + tab.linkedBrowser.addEventListener(eventType, handle, true, true); + if (url) { + tab.linkedBrowser.loadURI(url); + } + }); +} + +// dom event listener helper +function promiseWaitForEvent(object, eventName, capturing = false, chrome = false) { + return new Promise((resolve) => { + function listener(event) { + object.removeEventListener(eventName, listener, capturing, chrome); + resolve(event); + } + object.addEventListener(eventName, listener, capturing, chrome); + }); +} + +add_task(function* () { + registerCleanupFunction(function () { + window.focus(); + }); +}); + +add_task(function* () { + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = gBrowser.selectedTab = gBrowser.addTab(); + let prefTab = gBrowser.addTab(); + + yield promiseTabLoad(pluginTab, gTestRoot + "plugin_test.html"); + yield promiseTabLoad(prefTab, "about:preferences"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + Assert.ok(!!plugin, "plugin is loaded"); + }); + + let ppromise = promiseWaitForEvent(window, "MozAfterPaint"); + gBrowser.selectedTab = prefTab; + yield ppromise; + + // We're going to switch tabs using actual mouse clicks, which helps + // reproduce this bug. + let tabStripContainer = document.getElementById("tabbrowser-tabs"); + + // diagnosis if front end layout changes + info("-> " + tabStripContainer.tagName); // tabs + info("-> " + tabStripContainer.firstChild.tagName); // tab + info("-> " + tabStripContainer.childNodes[0].label); // test harness tab + info("-> " + tabStripContainer.childNodes[1].label); // plugin tab + info("-> " + tabStripContainer.childNodes[2].label); // preferences tab + + for (let iteration = 0; iteration < 5; iteration++) { + ppromise = promiseWaitForEvent(window, "MozAfterPaint"); + EventUtils.synthesizeMouseAtCenter(tabStripContainer.childNodes[1], {}, window); + yield ppromise; + + yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + Assert.ok(XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(), + "plugin is visible"); + }); + + ppromise = promiseWaitForEvent(window, "MozAfterPaint"); + EventUtils.synthesizeMouseAtCenter(tabStripContainer.childNodes[2], {}, window); + yield ppromise; + + yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + Assert.ok(!XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(), + "plugin is hidden"); + }); + } + + gBrowser.removeTab(prefTab); + gBrowser.removeTab(pluginTab); +}); diff --git a/dom/plugins/test/mochitest/browser_bug1196539.js b/dom/plugins/test/mochitest/browser_bug1196539.js new file mode 100644 index 000000000..8b2210ccd --- /dev/null +++ b/dom/plugins/test/mochitest/browser_bug1196539.js @@ -0,0 +1,119 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + +function checkPaintCount(aCount) { + ok(aCount != 0, "paint count can't be greater than zero, count was " + aCount); + ok(aCount < kMaxPaints, "paint count should be within limits, count was " + aCount); +} + +// maximum number of paints we allow before failing. The test plugin doesn't +// animate so this should really be just 1, but operating systems can +// occasionally fire a few of these so we give these tests a fudge factor. +// A bad regression would either be 0, or 100+. +const kMaxPaints = 10; + +add_task(function* () { + let result, tabSwitchedPromise; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let testTab = gBrowser.selectedTab; + let pluginTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gTestRoot + "plugin_test.html"); + let homeTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + }); + is(result, true, "plugin is loaded"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is hidden"); + + // reset plugin paint count + yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + XPCNativeWrapper.unwrap(plugin).resetPaintCount(); + }); + + // select plugin tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = pluginTab; + yield tabSwitchedPromise; + + // wait a bit for spurious paints + yield waitForMs(100); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is visible"); + + // check for good paint count + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).getPaintCount(); + }); + checkPaintCount(result); + + // select home tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = homeTab; + yield tabSwitchedPromise; + + // reset paint count + yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + XPCNativeWrapper.unwrap(plugin).resetPaintCount(); + }); + + // wait a bit for spurious paints + yield waitForMs(100); + + // check for no paint count + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).getPaintCount(); + }); + is(result, 0, "no paints, this is correct."); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is hidden"); + + // reset paint count + yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + XPCNativeWrapper.unwrap(plugin).resetPaintCount(); + }); + + // select plugin tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = pluginTab; + yield tabSwitchedPromise; + + // check paint count + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).getPaintCount(); + }); + checkPaintCount(result); + + gBrowser.removeTab(homeTab); + gBrowser.removeTab(pluginTab); +}); diff --git a/dom/plugins/test/mochitest/browser_pluginscroll.js b/dom/plugins/test/mochitest/browser_pluginscroll.js new file mode 100644 index 000000000..7314b8410 --- /dev/null +++ b/dom/plugins/test/mochitest/browser_pluginscroll.js @@ -0,0 +1,302 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + +/** + * tests for plugin windows and scroll + */ + +function coordinatesRelativeToWindow(aX, aY, aElement) { + var targetWindow = aElement.ownerDocument.defaultView; + var scale = targetWindow.devicePixelRatio; + var rect = aElement.getBoundingClientRect(); + return { + x: targetWindow.mozInnerScreenX + ((rect.left + aX) * scale), + y: targetWindow.mozInnerScreenY + ((rect.top + aY) * scale) + }; +} + +var apzEnabled = Preferences.get("layers.async-pan-zoom.enabled", false); +var pluginHideEnabled = Preferences.get("gfx.e10s.hide-plugins-for-scroll", true); + + +add_task(function* () { + registerCleanupFunction(function () { + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in"); + }); +}); + +add_task(function*() { + yield new Promise((resolve) => { + SpecialPowers.pushPrefEnv({ + "set": [ + ["general.smoothScroll", true], + ["general.smoothScroll.other", true], + ["general.smoothScroll.mouseWheel", true], + ["general.smoothScroll.other.durationMaxMS", 2000], + ["general.smoothScroll.other.durationMinMS", 1999], + ["general.smoothScroll.mouseWheel.durationMaxMS", 2000], + ["general.smoothScroll.mouseWheel.durationMinMS", 1999], + ]}, resolve); + }); +}); + +/* + * test plugin visibility when scrolling with scroll wheel and apz in a top level document. + */ + +add_task(function* () { + let result; + + if (!apzEnabled) { + ok(true, "nothing to test, need apz"); + return; + } + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let testTab = gBrowser.selectedTab; + let pluginTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gTestRoot + "plugin_test.html"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + }); + is(result, true, "plugin is loaded"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is visible"); + + let nativeId = nativeVerticalWheelEventMsg(); + let utils = SpecialPowers.getDOMWindowUtils(window); + let screenCoords = coordinatesRelativeToWindow(10, 10, + gBrowser.selectedBrowser); + utils.sendNativeMouseScrollEvent(screenCoords.x, screenCoords.y, + nativeId, 0, -50, 0, 0, 0, + gBrowser.selectedBrowser); + + yield waitScrollStart(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin is hidden"); + + yield waitScrollFinish(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); + +/* + * test plugin visibility when scrolling with scroll wheel and apz in a sub document. + */ + +add_task(function* () { + let result; + + if (!apzEnabled) { + ok(true, "nothing to test, need apz"); + return; + } + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let testTab = gBrowser.selectedTab; + let pluginTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gTestRoot + "plugin_subframe_test.html"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + }); + is(result, true, "plugin is loaded"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is visible"); + + let nativeId = nativeVerticalWheelEventMsg(); + let utils = SpecialPowers.getDOMWindowUtils(window); + let screenCoords = coordinatesRelativeToWindow(10, 10, + gBrowser.selectedBrowser); + utils.sendNativeMouseScrollEvent(screenCoords.x, screenCoords.y, + nativeId, 0, -50, 0, 0, 0, + gBrowser.selectedBrowser); + + yield waitScrollStart(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin is hidden"); + + yield waitScrollFinish(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); + +/* + * test visibility when scrolling with keyboard shortcuts for a top level document. + * This circumvents apz and relies on dom scroll, which is what we want to target + * for this test. + */ + +add_task(function* () { + let result; + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let testTab = gBrowser.selectedTab; + let pluginTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gTestRoot + "plugin_test.html"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + }); + is(result, true, "plugin is loaded"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is visible"); + + EventUtils.synthesizeKey("VK_END", {}); + + yield waitScrollStart(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin is hidden"); + + yield waitScrollFinish(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin is hidden"); + + EventUtils.synthesizeKey("VK_HOME", {}); + + yield waitScrollFinish(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); + +/* + * test visibility when scrolling with keyboard shortcuts for a sub document. + */ + +add_task(function* () { + let result; + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let testTab = gBrowser.selectedTab; + let pluginTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gTestRoot + "plugin_subframe_test.html"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + }); + is(result, true, "plugin is loaded"); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is visible"); + + EventUtils.synthesizeKey("VK_END", {}); + + yield waitScrollStart(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin is hidden"); + + yield waitScrollFinish(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin is hidden"); + + EventUtils.synthesizeKey("VK_HOME", {}); + + yield waitScrollFinish(gBrowser.selectedBrowser); + + result = yield ContentTask.spawn(pluginTab.linkedBrowser, null, function*() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); diff --git a/dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js b/dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js new file mode 100644 index 000000000..d1994c209 --- /dev/null +++ b/dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js @@ -0,0 +1,105 @@ +var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/"); + +// tests that we get plugin updates when we flip between tabs that +// have the same plugin in the same position in the page. + +add_task(function* () { + let result, tabSwitchedPromise; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let testTab = gBrowser.selectedTab; + let pluginTab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gTestRoot + "plugin_test.html"); + let pluginTab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gTestRoot + "plugin_test.html"); + + result = yield ContentTask.spawn(pluginTab1.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + }); + is(result, true, "plugin1 is loaded"); + + result = yield ContentTask.spawn(pluginTab2.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + }); + is(result, true, "plugin2 is loaded"); + + // plugin tab 2 should be selected + is(gBrowser.selectedTab == pluginTab2, true, "plugin2 is selected"); + + result = yield ContentTask.spawn(pluginTab1.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin1 is hidden"); + + result = yield ContentTask.spawn(pluginTab2.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin2 is visible"); + + // select plugin1 tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = pluginTab1; + yield tabSwitchedPromise; + + result = yield ContentTask.spawn(pluginTab1.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin1 is visible"); + + result = yield ContentTask.spawn(pluginTab2.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin2 is hidden"); + + // select plugin2 tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = pluginTab2; + yield tabSwitchedPromise; + + result = yield ContentTask.spawn(pluginTab1.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin1 is hidden"); + + result = yield ContentTask.spawn(pluginTab2.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, true, "plugin2 is visible"); + + // select test tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = testTab; + yield tabSwitchedPromise; + + result = yield ContentTask.spawn(pluginTab1.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin1 is hidden"); + + result = yield ContentTask.spawn(pluginTab2.linkedBrowser, null, function*() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + is(result, false, "plugin2 is hidden"); + + gBrowser.removeTab(pluginTab1); + gBrowser.removeTab(pluginTab2); +}); diff --git a/dom/plugins/test/mochitest/chrome.ini b/dom/plugins/test/mochitest/chrome.ini new file mode 100644 index 000000000..35a1c7ddc --- /dev/null +++ b/dom/plugins/test/mochitest/chrome.ini @@ -0,0 +1,34 @@ +[DEFAULT] +support-files = + hang_test.js + privatemode_perwindowpb.xul + plugin-utils.js + +[test_bug479979.xul] +[test_bug751809.html] +[test_busy_hang.xul] +skip-if = (!crashreporter) || (os != "win") +[test_clear_site_data.html] +[test_convertpoint.xul] +skip-if = toolkit != "cocoa" +[test_crash_notify.xul] +skip-if = !crashreporter +[test_crash_notify_no_report.xul] +skip-if = !crashreporter +[test_crash_submit.xul] +skip-if = !crashreporter +[test_hang_submit.xul] +skip-if = !crashreporter +[test_hangui.xul] +skip-if = (!crashreporter) || (os != "win") +support-files = hangui_subpage.html hangui_common.js hangui_iface.js dialog_watcher.js +[test_idle_hang.xul] +skip-if = (!crashreporter) || (os != "win") +[test_npruntime.xul] +[test_plugin_tag_clicktoplay.html] +[test_privatemode_perwindowpb.xul] +[test_refresh_navigator_plugins.html] +[test_xulbrowser_plugin_visibility.xul] +skip-if = (toolkit == "cocoa") || (os == "win") +support-files = xulbrowser_plugin_visibility.xul plugin_visibility_loader.html +[test_wmode.xul] diff --git a/dom/plugins/test/mochitest/cocoa_focus.html b/dom/plugins/test/mochitest/cocoa_focus.html new file mode 100644 index 000000000..ca35fd7a1 --- /dev/null +++ b/dom/plugins/test/mochitest/cocoa_focus.html @@ -0,0 +1,142 @@ +<html> +<head> + <title>NPCocoaEventFocusChanged Tests</title> +</head> +<body> + <embed id="plugin1" type="application/x-test" width="100" height="100"></embed> + <embed id="plugin2" type="application/x-test" width="100" height="100"></embed> + <script type="application/javascript"> + function is(aLeft, aRight, aMessage) { + window.opener.SimpleTest.is(aLeft, aRight, aMessage); + } + + function ok(aValue, aMessage) { + window.opener.SimpleTest.ok(aValue, aMessage); + } + + function synthesizeNativeMouseEvent(aX, aY, aNativeMsg, aModifiers, aElement, aCallback) { + var observer = { + observe: function(aSubject, aTopic, aData) { + if (aCallback && aTopic == "mouseevent") { + aCallback(aData); + } + } + }; + SpecialPowers.DOMWindowUtils.sendNativeMouseEvent(aX, aY, aNativeMsg, aModifiers, aElement, observer); + return true; + } + + function* runTests() { + var utils = SpecialPowers.DOMWindowUtils; + var scale = utils.screenPixelsPerCSSPixel; + + var plugin1 = document.getElementById("plugin1"); // What we're testing. + var plugin2 = document.getElementById("plugin2"); // Dummy. + + var plugin1Bounds = plugin1.getBoundingClientRect(); + var plugin2Bounds = plugin2.getBoundingClientRect(); + + var plugin1X = (window.mozInnerScreenX + plugin1Bounds.left + 10); + var plugin1Y = (window.mozInnerScreenY + plugin1Bounds.top + 10); + var plugin2X = (window.mozInnerScreenX + plugin2Bounds.left + 10); + var plugin2Y = (window.mozInnerScreenY + plugin2Bounds.top + 10); + + const NSLeftMouseDown = 1, + NSLeftMouseUp = 2; + + if (plugin1.getEventModel() != 1) { + window.opener.todo(false, "Skipping this test when not testing the Cocoa event model"); + return; + } + + // Initialize to 0 since there is no initial state event, + // plugins should assume they do not initially have focus. + var expectedEventCount = 0; + + // Make sure initial event count is correct. + is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount); + + // Make sure initial focus state is unknown (assumed false). + var initialStateUnknown = false; + try { + plugin1.getFocusState(); + } catch (e) { + initialStateUnknown = true; + } + is(initialStateUnknown, true, "Initial state should be unknown, assumed false."); + + // Give the plugin focus (the window is already focused). + yield synthesizeNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseDown, 0, plugin1, continueTest); + yield synthesizeNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseUp, 0, plugin1, continueTest); + expectedEventCount++; + + is(plugin1.getFocusState(), true, "(1) Plugin should have focus."); + is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount); + + // Make sure window activation state changes don't spontaneously + // change plugin focus. + + // Blur the window. + SpecialPowers.focus(opener); + + is(plugin1.getFocusState(), true, "(2) Plugin should still have focus."); + is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount); + + // Focus the window. + SpecialPowers.focus(window); + + is(plugin1.getFocusState(), true, "(3) Plugin should still have focus."); + is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount); + + // Take focus from the plugin. + yield synthesizeNativeMouseEvent(plugin2X * scale, plugin2Y * scale, NSLeftMouseDown, 0, plugin2, continueTest); + yield synthesizeNativeMouseEvent(plugin2X * scale, plugin2Y * scale, NSLeftMouseUp, 0, plugin2, continueTest); + expectedEventCount++; + + is(plugin1.getFocusState(), false, "(4) Plugin should not have focus."); + is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount); + + // Make sure window activation causes the plugin to be informed of focus + // changes that took place while the window was inactive. + + // Give the plugin focus (the window is already focused). + yield synthesizeNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseDown, 0, plugin1, continueTest); + yield synthesizeNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseUp, 0, plugin1, continueTest); + expectedEventCount++; + + // Blur the window. + SpecialPowers.focus(opener); + + // Take focus from the plugin while the window is blurred. + plugin2.focus(); + + is(plugin1.getFocusState(), true, "(5) Plugin should still have focus."); + is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount); + + // Focus the window. + SpecialPowers.focus(window); + expectedEventCount++; + + is(plugin1.getFocusState(), false, "(6) Plugin should not have focus."); + is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount); + } + + var gTestContinuation = null; + function continueTest() { + if (!gTestContinuation) { + gTestContinuation = runTests(); + } + var ret = gTestContinuation.next(); + if (ret.done) { + window.opener.testsFinished(); + } else { + is(ret.value, true, "Mouse event successfully synthesized"); + } + } + + // Onload hander doesn't work for these tests -- no events arrive at the plugin. + window.opener.SimpleTest.waitForFocus(continueTest, window); + + </script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/cocoa_window_focus.html b/dom/plugins/test/mochitest/cocoa_window_focus.html new file mode 100644 index 000000000..8305b19a4 --- /dev/null +++ b/dom/plugins/test/mochitest/cocoa_window_focus.html @@ -0,0 +1,95 @@ +<html> +<head> + <title>NPCocoaEventWindowFocusChanged Tests</title> +</head> +<body onload="runTests()"> + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> + <embed id="plugin2" type="application/x-test" width="400" height="400"></embed> + <script type="application/javascript"> + function is(aLeft, aRight, aMessage) { + window.opener.SimpleTest.is(aLeft, aRight, aMessage); + } + + function ok(aValue, aMessage) { + window.opener.SimpleTest.ok(aValue, aMessage); + } + + function executeSoon(func) { + window.opener.SimpleTest.executeSoon(func); + } + + function waitForFocus(aCb, aTarget, aBlank) { + window.opener.SimpleTest.waitForFocus(aCb, aTarget, aBlank); + } + + function runTests() { + var plugin1 = document.getElementById("plugin1"); + var plugin2 = document.getElementById("plugin2"); + + if (plugin1.getEventModel() != 1) { + window.opener.todo(false, "Skipping this test when not testing the Cocoa event model"); + window.opener.testsFinished(); + return; + } + + // The first plugin will have in-page focus for these tests. + plugin1.focus(); + + // The expected event count which applies to all instances. + // Initialize to 1 to account for the initial state event. + var expectedEventCount = 1; + + // First make sure the plugins got an initial window focus event. + // Since this window was just opened it should be in the front. If + // the plugin has not been sent an initial window state then it will + // be in an unknown state and it will throw an exception. + try { + is(plugin1.getTopLevelWindowActivationState(), true, "Activation state should be: activated"); + is(plugin1.getTopLevelWindowActivationEventCount(), expectedEventCount, "Window focus event count should be " + expectedEventCount); + + is(plugin2.getTopLevelWindowActivationState(), true, "Activation state should be: activated"); + is(plugin2.getTopLevelWindowActivationEventCount(), expectedEventCount, "Window focus event count should be " + expectedEventCount); + } catch (e) { + ok(false, "Plugin does not know its initial top-level window activation state!"); + } + + var fm = SpecialPowers.Services.focus; + + waitForFocus(function() { + // Make sure the plugin handled the focus event before checking. + executeSoon(function() { + expectedEventCount++; + + is(plugin1.getTopLevelWindowActivationState(), false, "Activation state should be: deactivated"); + is(plugin1.getTopLevelWindowActivationEventCount(), expectedEventCount, "Window focus event count should be " + expectedEventCount); + + is(plugin2.getTopLevelWindowActivationState(), false, "Activation state should be: deactivated"); + is(plugin2.getTopLevelWindowActivationEventCount(), expectedEventCount, "Window focus event count should be " + expectedEventCount); + + // Bring our window back to the front and make sure plugins were properly notified. + fm.focusedWindow = window; + + waitForFocus(function() { + // Make sure the plugin handled the focus event before checking. + executeSoon(function() { + expectedEventCount++; + + is(plugin1.getTopLevelWindowActivationState(), true, "Activation state should be: activated"); + is(plugin1.getTopLevelWindowActivationEventCount(), expectedEventCount, "Window focus event count should be " + expectedEventCount); + + is(plugin2.getTopLevelWindowActivationState(), true, "Activation state should be: activated"); + is(plugin2.getTopLevelWindowActivationEventCount(), expectedEventCount, "Window focus event count should be " + expectedEventCount); + + window.opener.testsFinished(); + }); + }, window); + }); + }, window.opener); + + // Send our window to the back and make sure plugins were properly notified. + // Calling window.blur() is not allowed. + fm.focusedWindow = window.opener; + } + </script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/crashing_subpage.html b/dom/plugins/test/mochitest/crashing_subpage.html new file mode 100644 index 000000000..7c00f25c4 --- /dev/null +++ b/dom/plugins/test/mochitest/crashing_subpage.html @@ -0,0 +1,4 @@ +<html> +<body onload="window.parent.frameLoaded()"> + <h1>Crashing subpage</h1> + <embed id="plugin1" type="application/x-test" width="400" height="400" drawmode="solid" color="FF00FFFF"></embed> diff --git a/dom/plugins/test/mochitest/dialog_watcher.js b/dom/plugins/test/mochitest/dialog_watcher.js new file mode 100644 index 000000000..8a8c82997 --- /dev/null +++ b/dom/plugins/test/mochitest/dialog_watcher.js @@ -0,0 +1,183 @@ +const EVENT_OBJECT_SHOW = 0x8002; +const EVENT_OBJECT_HIDE = 0x8003; +const WINEVENT_OUTOFCONTEXT = 0; +const WINEVENT_SKIPOWNPROCESS = 2; +const QS_ALLINPUT = 0x04FF; +const INFINITE = 0xFFFFFFFF; +const WAIT_OBJECT_0 = 0; +const WAIT_TIMEOUT = 258; +const PM_NOREMOVE = 0; + +function DialogWatcher(titleText, onDialogStart, onDialogEnd) { + this.titleText = titleText; + this.onDialogStart = onDialogStart; + this.onDialogEnd = onDialogEnd; +} + +DialogWatcher.prototype.init = function() { + this.hwnd = undefined; + if (!this.user32) { + this.user32 = ctypes.open("user32.dll"); + } + if (!this.findWindow) { + this.findWindow = user32.declare("FindWindowW", + ctypes.winapi_abi, + ctypes.uintptr_t, + ctypes.char16_t.ptr, + ctypes.char16_t.ptr); + } + if (!this.winEventProcType) { + this.winEventProcType = ctypes.FunctionType(ctypes.stdcall_abi, + ctypes.void_t, + [ctypes.uintptr_t, + ctypes.uint32_t, + ctypes.uintptr_t, + ctypes.long, + ctypes.long, + ctypes.uint32_t, + ctypes.uint32_t]).ptr; + } + if (!this.setWinEventHook) { + this.setWinEventHook = user32.declare("SetWinEventHook", + ctypes.winapi_abi, + ctypes.uintptr_t, + ctypes.uint32_t, + ctypes.uint32_t, + ctypes.uintptr_t, + this.winEventProcType, + ctypes.uint32_t, + ctypes.uint32_t, + ctypes.uint32_t); + } + if (!this.unhookWinEvent) { + this.unhookWinEvent = user32.declare("UnhookWinEvent", + ctypes.winapi_abi, + ctypes.int, + ctypes.uintptr_t); + } + if (!this.pointType) { + this.pointType = ctypes.StructType("tagPOINT", + [ { "x": ctypes.long }, + { "y": ctypes.long } ] ); + } + if (!this.msgType) { + this.msgType = ctypes.StructType("tagMSG", + [ { "hwnd": ctypes.uintptr_t }, + { "message": ctypes.uint32_t }, + { "wParam": ctypes.uintptr_t }, + { "lParam": ctypes.intptr_t }, + { "time": ctypes.uint32_t }, + { "pt": this.pointType } ] ); + } + if (!this.peekMessage) { + this.peekMessage = user32.declare("PeekMessageW", + ctypes.winapi_abi, + ctypes.int, + this.msgType.ptr, + ctypes.uintptr_t, + ctypes.uint32_t, + ctypes.uint32_t, + ctypes.uint32_t); + } + if (!this.msgWaitForMultipleObjects) { + this.msgWaitForMultipleObjects = user32.declare("MsgWaitForMultipleObjects", + ctypes.winapi_abi, + ctypes.uint32_t, + ctypes.uint32_t, + ctypes.uintptr_t.ptr, + ctypes.int, + ctypes.uint32_t, + ctypes.uint32_t); + } + if (!this.getWindowTextW) { + this.getWindowTextW = user32.declare("GetWindowTextW", + ctypes.winapi_abi, + ctypes.int, + ctypes.uintptr_t, + ctypes.char16_t.ptr, + ctypes.int); + } + if (!this.messageBox) { + // Handy for debugging this code + this.messageBox = user32.declare("MessageBoxW", + ctypes.winapi_abi, + ctypes.int, + ctypes.uintptr_t, + ctypes.char16_t.ptr, + ctypes.char16_t.ptr, + ctypes.uint32_t); + } +}; + +DialogWatcher.prototype.getWindowText = function(hwnd) { + var bufType = ctypes.ArrayType(ctypes.char16_t); + var buffer = new bufType(256); + + if (this.getWindowTextW(hwnd, buffer, buffer.length)) { + return buffer.readString(); + } +}; + +DialogWatcher.prototype.processWindowEvents = function(timeout) { + var onWinEvent = function(self, hook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) { + var nhwnd = Number(hwnd) + if (event == EVENT_OBJECT_SHOW) { + if (nhwnd == self.hwnd) { + // We've already picked up this event via FindWindow + return; + } + var windowText = self.getWindowText(hwnd); + if (windowText == self.titleText && self.onDialogStart) { + self.hwnd = nhwnd; + self.onDialogStart(nhwnd); + } + } else if (event == EVENT_OBJECT_HIDE && nhwnd == self.hwnd && self.onDialogEnd) { + self.onDialogEnd(); + self.hwnd = null; + } + }; + var self = this; + var callback = this.winEventProcType(function(hook, event, hwnd, idObject, + idChild, dwEventThread, + dwmsEventTime) { + onWinEvent(self, hook, event, hwnd, idObject, idChild, dwEventThread, + dwmsEventTime); + } ); + var hook = this.setWinEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE, + 0, callback, 0, 0, + WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + if (!hook) { + return; + } + // Check if the window is already showing + var hwnd = this.findWindow(null, this.titleText); + if (hwnd && hwnd > 0) { + this.hwnd = Number(hwnd); + if (this.onDialogStart) { + this.onDialogStart(this.hwnd); + } + } + + if (!timeout) { + timeout = INFINITE; + } + + var waitStatus = WAIT_OBJECT_0; + var expectingStart = this.onDialogStart && this.hwnd === undefined; + var startWaitTime = Date.now(); + while (this.hwnd === undefined || this.onDialogEnd && this.hwnd) { + waitStatus = this.msgWaitForMultipleObjects(0, null, 0, expectingStart ? + INFINITE : timeout, 0); + if (waitStatus == WAIT_OBJECT_0) { + var msg = new this.msgType; + this.peekMessage(msg.address(), 0, 0, 0, PM_NOREMOVE); + } + if (waitStatus == WAIT_TIMEOUT || (Date.now() - startWaitTime) >= timeout) { + break; + } + } + + this.unhookWinEvent(hook); + // Returns true if the hook was successful, something was found, and we never timed out + return this.hwnd !== undefined && waitStatus == WAIT_OBJECT_0; +}; diff --git a/dom/plugins/test/mochitest/file_authident.js b/dom/plugins/test/mochitest/file_authident.js new file mode 100644 index 000000000..f255fc30c --- /dev/null +++ b/dom/plugins/test/mochitest/file_authident.js @@ -0,0 +1,6 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; +var am = Cc["@mozilla.org/network/http-auth-manager;1"]. + getService(Ci.nsIHttpAuthManager); +am.setAuthIdentity("http", "mochi.test", 8888, "basic", "testrealm", "", + "mochi.test", "user1", "password1"); diff --git a/dom/plugins/test/mochitest/file_bug1245545.js b/dom/plugins/test/mochitest/file_bug1245545.js new file mode 100644 index 000000000..47cb618eb --- /dev/null +++ b/dom/plugins/test/mochitest/file_bug1245545.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; +const { Services } = Cu.import('resource://gre/modules/Services.jsm'); + +function getTestPlugin(pluginName) { + var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + var tags = ph.getPluginTags(); + var name = pluginName || "Test Plug-in"; + for (var tag of tags) { + if (tag.name == name) { + return tag; + } + } + return null; +} + +addMessageListener('check-plugin-unload', function(message) { + var tag = getTestPlugin(); + var results = sendAsyncMessage("check-plugin-unload", tag.loaded); +}); diff --git a/dom/plugins/test/mochitest/file_bug738396.html b/dom/plugins/test/mochitest/file_bug738396.html new file mode 100644 index 000000000..f91f8f3d9 --- /dev/null +++ b/dom/plugins/test/mochitest/file_bug738396.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> +<head> + <title>Helper for test_bug738396.html</title> + <meta charset="utf-8"> +</head> +<body> + <!-- Test that the plugin sees "good" in each of these cases --> + <div id="codebasevis"> + <applet codebase="good" codebase="bad" ></applet> + + <applet codebase="bad"> + <param name="codebase" value="good"> + </applet> + + <applet codebase="bad"> + <param name="codebase" value="stillbad"> + <param name="codebase" value="good"> + </applet> + + <applet> + <param name="codebase" value="good"> + </applet> + + <object type="application/x-java-test" codebase="good" codebase="bad"></object> + + <object type="application/x-java-test" codebase="bad"> + <param name="codebase" value="good"> + </object> + + <object type="application/x-java-test" codebase="bad"> + <param name="codebase" value="stillbad"> + <param name="codebase" value="good"> + </object> + + <object type="application/x-java-test"> + <param name="codebase" value="good"> + </object> + + <embed type="application/x-java-test" codebase="good" codebase="bad"> + </div> + <div id="blockedcodebase"> + <!-- Test that none of these are allowed to load --> + <applet codebase="file:///" codebase="notused"></applet> + + <applet codebase="notused"> + <param name="codebase" value="file:///"> + </applet> + + <applet codebase="notused"> + <param name="codebase" value="notused"> + <param name="codebase" value="file:///"> + </applet> + + <applet> + <param name="codebase" value="file:///"> + </applet> + + <object type="application/x-java-test" codebase="file:///" codebase="notused"></object> + + <object type="application/x-java-test" codebase="notused"> + <param name="codebase" value="file:///"> + </object> + + <object type="application/x-java-test" codebase="notused"> + <param name="codebase" value="notused"> + <param name="codebase" value="file:///"> + </object> + + <object type="application/x-java-test"> + <param name="codebase" value="file:///"> + </object> + + <embed type="application/x-java-test" codebase="file:///" codebase="notused"> + </div> + <div id="nocodebase"> + <applet></applet> + <object type="application/x-java-test"></object> + <embed type="application/x-java-test"> + </div> + <div id="emptycodebase"> + <applet codebase=""></applet> + <object type="application/x-java-test" codebase=""></object> + <embed type="application/x-java-test" codebase=""> + </div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/file_bug771202.html b/dom/plugins/test/mochitest/file_bug771202.html new file mode 100644 index 000000000..935be65b2 --- /dev/null +++ b/dom/plugins/test/mochitest/file_bug771202.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> + <embed id="pluginElement" type="application/x-test" width="200" height="200"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/file_bug863792.html b/dom/plugins/test/mochitest/file_bug863792.html new file mode 100644 index 000000000..b4d3dc2e0 --- /dev/null +++ b/dom/plugins/test/mochitest/file_bug863792.html @@ -0,0 +1,45 @@ +<!doctype html> +<html> +<head> + <title>File for Bug 863792</title> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <base href="chrome://browser/content/"> +</head> +<body> +<script type="application/javascript"> + +// A plugin that removes itself from the document and inactivates said document +// inside NPP_New. We should not leak the instance. See also test_bug854082 + +var outerwindow = window; +var i = document.createElement("iframe"); +i.width = 500; +i.height = 500; +var ob = document.body; +document.body.appendChild(i); +i.addEventListener("load", function loaded() { + var id = i.contentDocument; + var e = id.createElement("embed"); + var callbackrun = false; + e.width = 200; + e.height = 200; + e.type = "application/x-test"; + e.__defineSetter__("pluginFoundElement", function() { + window.console.log("pluginFoundElement"); + e.style.display = "none"; + e.clientTop; + i.removeEventListener("load", loaded); + ob.removeChild(i); + id.body.clientTop; + id.body.removeChild(e); + callbackrun = true; + }); + id.body.appendChild(e); + e.clientTop; + e = id = i = ob = null; + SpecialPowers.forceCC(); SpecialPowers.forceGC(); +}, false); +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/file_checkcookie.sjs b/dom/plugins/test/mochitest/file_checkcookie.sjs new file mode 100644 index 000000000..9bfa04f59 --- /dev/null +++ b/dom/plugins/test/mochitest/file_checkcookie.sjs @@ -0,0 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +function handleRequest(request, response) { + try { + var cookie = request.getHeader("Cookie"); + } catch (e) { + cookie = "EMPTY_COOKIE"; + } + + // avoid confusing cache behaviors. + response.setHeader("Cache-Control", "no-cache", false); + // allow XHR requests accross origin. + response.setHeader("Access-control-allow-origin", "*"); + response.setHeader("Content-type", "text/plain", false); + response.setStatusLine(request.httpVersion, "200", "OK"); + response.write(cookie); +} diff --git a/dom/plugins/test/mochitest/file_setcookie.html b/dom/plugins/test/mochitest/file_setcookie.html new file mode 100644 index 000000000..3838db7fe --- /dev/null +++ b/dom/plugins/test/mochitest/file_setcookie.html @@ -0,0 +1,9 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> + <head> + <meta charset=UTF-8> + <body> + <script>document.cookie = "found=another_cookie";</script> + </body> +</html> diff --git a/dom/plugins/test/mochitest/hang_test.js b/dom/plugins/test/mochitest/hang_test.js new file mode 100644 index 000000000..796093fa3 --- /dev/null +++ b/dom/plugins/test/mochitest/hang_test.js @@ -0,0 +1,114 @@ + +Components.utils.import("resource://gre/modules/KeyValueParser.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +var success = false; +var observerFired = false; + +var testObserver = { + idleHang: true, + + observe: function(subject, topic, data) { + observerFired = true; + ok(true, "Observer fired"); + is(topic, "plugin-crashed", "Checking correct topic"); + is(data, null, "Checking null data"); + ok((subject instanceof Ci.nsIPropertyBag2), "got Propbag"); + ok((subject instanceof Ci.nsIWritablePropertyBag2), "got writable Propbag"); + + var pluginId = subject.getPropertyAsAString("pluginDumpID"); + isnot(pluginId, "", "got a non-empty plugin crash id"); + + // check plugin dump and extra files + let directoryService = + Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + let profD = directoryService.get("ProfD", Ci.nsIFile); + profD.append("minidumps"); + let pluginDumpFile = profD.clone(); + pluginDumpFile.append(pluginId + ".dmp"); + ok(pluginDumpFile.exists(), "plugin minidump exists"); + + let pluginExtraFile = profD.clone(); + pluginExtraFile.append(pluginId + ".extra"); + ok(pluginExtraFile.exists(), "plugin extra file exists"); + + let extraData = parseKeyValuePairsFromFile(pluginExtraFile); + + // check additional dumps + + ok("additional_minidumps" in extraData, "got field for additional minidumps"); + let additionalDumps = extraData.additional_minidumps.split(','); + ok(additionalDumps.indexOf('browser') >= 0, "browser in additional_minidumps"); + + let additionalDumpFiles = []; + for (let name of additionalDumps) { + let file = profD.clone(); + file.append(pluginId + "-" + name + ".dmp"); + ok(file.exists(), "additional dump '"+name+"' exists"); + if (file.exists()) { + additionalDumpFiles.push(file); + } + } + + // check cpu usage field + + ok("PluginCpuUsage" in extraData, "got extra field for plugin cpu usage"); + let cpuUsage = parseFloat(extraData["PluginCpuUsage"]); + if (this.idleHang) { + ok(cpuUsage == 0, "plugin cpu usage is 0%"); + } else { + ok(cpuUsage > 0, "plugin cpu usage is >0%"); + } + + // check processor count field + ok("NumberOfProcessors" in extraData, "got extra field for processor count"); + ok(parseInt(extraData["NumberOfProcessors"]) > 0, "number of processors is >0"); + + // cleanup, to be nice + pluginDumpFile.remove(false); + pluginExtraFile.remove(false); + for (let file of additionalDumpFiles) { + file.remove(false); + } + }, + + QueryInterface: function(iid) { + if (iid.equals(Ci.nsIObserver) || + iid.equals(Ci.nsISupportsWeakReference) || + iid.equals(Ci.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + } +}; + + +function onPluginCrashed(aEvent) { + ok(true, "Plugin crashed notification received"); + ok(observerFired, "Observer should have fired first"); + is(aEvent.type, "PluginCrashed", "event is correct type"); + + var pluginElement = document.getElementById("plugin1"); + is (pluginElement, aEvent.target, "Plugin crashed event target is plugin element"); + + ok(aEvent instanceof PluginCrashedEvent, + "plugin crashed event has the right interface"); + + is(typeof aEvent.pluginDumpID, "string", "pluginDumpID is correct type"); + isnot(aEvent.pluginDumpID, "", "got a non-empty dump ID"); + is(typeof aEvent.pluginName, "string", "pluginName is correct type"); + is(aEvent.pluginName, "Test Plug-in", "got correct plugin name"); + is(typeof aEvent.pluginFilename, "string", "pluginFilename is correct type"); + isnot(aEvent.pluginFilename, "", "got a non-empty filename"); + // The app itself may or may not have decided to submit the report, so + // allow either true or false here. + ok("submittedCrashReport" in aEvent, "submittedCrashReport is a property of event"); + is(typeof aEvent.submittedCrashReport, "boolean", "submittedCrashReport is correct type"); + + var os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + os.removeObserver(testObserver, "plugin-crashed"); + + SimpleTest.finish(); +} diff --git a/dom/plugins/test/mochitest/hangui_common.js b/dom/plugins/test/mochitest/hangui_common.js new file mode 100644 index 000000000..cf0d049f2 --- /dev/null +++ b/dom/plugins/test/mochitest/hangui_common.js @@ -0,0 +1,20 @@ +// Plugin Hang UI constants +const HANGUIOP_NOTHING = 0; +const HANGUIOP_CANCEL = 1; +const HANGUIOP_COMMAND = 2; +const IDC_CONTINUE = 1001; +const IDC_STOP = 1002; +const IDC_NOFUTURE = 1003; + +// Windows constants +const WM_CLOSE = 0x0010; +const WM_COMMAND = 0x0111; +const BM_GETCHECK = 0x00F0; +const BM_SETCHECK = 0x00F1; +const BN_CLICKED = 0; +const BST_CHECKED = 1; + +// Test-specific constants +const EPSILON_MS = 1000; +const STALL_DURATION = 2; + diff --git a/dom/plugins/test/mochitest/hangui_iface.js b/dom/plugins/test/mochitest/hangui_iface.js new file mode 100644 index 000000000..853d136bb --- /dev/null +++ b/dom/plugins/test/mochitest/hangui_iface.js @@ -0,0 +1,121 @@ +var user32; +var sendMessage; +var getDlgItem; +var messageBox; +var watcher; + +importScripts("hangui_common.js"); +importScripts("dialog_watcher.js"); + +function initCTypes() { + if (!user32) { + user32 = ctypes.open("user32.dll"); + } + if (!getDlgItem) { + getDlgItem = user32.declare("GetDlgItem", + ctypes.winapi_abi, + ctypes.uintptr_t, + ctypes.uintptr_t, + ctypes.int); + } + if (!sendMessage) { + sendMessage = user32.declare("SendMessageW", + ctypes.winapi_abi, + ctypes.intptr_t, + ctypes.uintptr_t, + ctypes.uint32_t, + ctypes.uintptr_t, + ctypes.intptr_t); + } + if (!messageBox) { + // Handy for debugging the test itself + messageBox = user32.declare("MessageBoxW", + ctypes.winapi_abi, + ctypes.int, + ctypes.uintptr_t, + ctypes.char16_t.ptr, + ctypes.char16_t.ptr, + ctypes.uint32_t); + } + if (!watcher) { + watcher = new DialogWatcher("Warning: Unresponsive plugin"); + } +} + +function postSuccess(params) { + self.postMessage({"status": true, "params": params}); +} + +function postFail(params, msg) { + self.postMessage({"status": false, "params": params, "msg": msg}); +} + +function onDialogStart(inparams, hwnd) { + var params = Object.create(inparams); + params.testName += " (Start)"; + params.callback = null; + if (!params.expectToFind) { + postFail(params, "Dialog showed when we weren't expecting it to!"); + return; + } + if (params.opCode == HANGUIOP_CANCEL) { + sendMessage(hwnd, WM_CLOSE, 0, 0); + } else if (params.opCode == HANGUIOP_COMMAND) { + if (params.check) { + var checkbox = getDlgItem(hwnd, IDC_NOFUTURE); + if (!checkbox) { + postFail(params, "Couldn't find checkbox"); + return; + } + sendMessage(checkbox, BM_SETCHECK, BST_CHECKED, 0); + sendMessage(hwnd, WM_COMMAND, (BN_CLICKED << 16) | IDC_NOFUTURE, checkbox); + } + var button = getDlgItem(hwnd, params.commandId); + if (!button) { + postFail(params, + "GetDlgItem failed to find button with ID " + params.commandId); + return; + } + sendMessage(hwnd, WM_COMMAND, (BN_CLICKED << 16) | params.commandId, button); + } + postSuccess(params); +} + +function onDialogEnd(inparams) { + var params = Object.create(inparams); + params.testName += " (End)"; + params.callback = inparams.callback; + postSuccess(params); +} + +self.onmessage = function(event) { + initCTypes(); + watcher.init(); + var params = event.data; + var timeout = params.timeoutMs; + if (params.expectToFind) { + watcher.onDialogStart = function(hwnd) { onDialogStart(params, hwnd); }; + if (params.expectToClose) { + watcher.onDialogEnd = function() { onDialogEnd(params); }; + } + } else { + watcher.onDialogStart = null; + watcher.onDialogEnd = null; + } + var result = watcher.processWindowEvents(timeout); + if (result === null) { + postFail(params, "Hook failed"); + } else if (!result) { + if (params.expectToFind) { + postFail(params, "The dialog didn't show but we were expecting it to"); + } else { + postSuccess(params); + } + } +} + +self.onerror = function(event) { + var msg = "Error: " + event.message + " at " + event.filename + ":" + event.lineno; + postFail(null, msg); +}; + diff --git a/dom/plugins/test/mochitest/hangui_subpage.html b/dom/plugins/test/mochitest/hangui_subpage.html new file mode 100644 index 000000000..401912f68 --- /dev/null +++ b/dom/plugins/test/mochitest/hangui_subpage.html @@ -0,0 +1,4 @@ +<html> +<body onload="window.parent.frameLoaded()"> + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> + diff --git a/dom/plugins/test/mochitest/head.js b/dom/plugins/test/mochitest/head.js new file mode 100644 index 000000000..42143b84b --- /dev/null +++ b/dom/plugins/test/mochitest/head.js @@ -0,0 +1,133 @@ + +/** + * Waits for a tab switch. + */ +function waitTabSwitched() { + return new Promise(resolve => { + gBrowser.addEventListener("TabSwitchDone", function onSwitch() { + gBrowser.removeEventListener("TabSwitchDone", onSwitch); + executeSoon(resolve); + }); + }); +} + +/** + * Waits a specified number of miliseconds. + * + * Usage: + * let wait = yield waitForMs(2000); + * ok(wait, "2 seconds should now have elapsed"); + * + * @param aMs the number of miliseconds to wait for + * @returns a Promise that resolves to true after the time has elapsed + */ +function waitForMs(aMs) { + return new Promise((resolve) => { + setTimeout(done, aMs); + function done() { + resolve(true); + } + }); +} + +/** + * Platform string helper for nativeVerticalWheelEventMsg + */ +function getPlatform() { + if (navigator.platform.indexOf("Win") == 0) { + return "windows"; + } + if (navigator.platform.indexOf("Mac") == 0) { + return "mac"; + } + if (navigator.platform.indexOf("Linux") == 0) { + return "linux"; + } + return "unknown"; +} + +/** + * Returns a native wheel scroll event id for dom window + * uitls sendNativeMouseScrollEvent. + */ +function nativeVerticalWheelEventMsg() { + switch (getPlatform()) { + case "windows": return 0x020A; // WM_MOUSEWHEEL + case "mac": return 0; // value is unused, can be anything + case "linux": return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway + } + throw "Native wheel events not supported on platform " + getPlatform(); +} + +/** + * Waits for the first dom "scroll" event. + */ +function waitScrollStart(aTarget) { + return new Promise((resolve, reject) => { + aTarget.addEventListener("scroll", function listener(event) { + aTarget.removeEventListener("scroll", listener, true); + resolve(event); + }, true); + }); +} + +/** + * Waits for the last dom "scroll" event which generally indicates + * a scroll operation is complete. To detect this the helper waits + * 1 second intervals checking for scroll events from aTarget. If + * a scroll event is not received during that time, it considers + * the scroll operation complete. Not super accurate, be careful. + */ +function waitScrollFinish(aTarget) { + return new Promise((resolve, reject) => { + let recent = false; + let count = 0; + function listener(event) { + recent = true; + } + aTarget.addEventListener("scroll", listener, true); + setInterval(function () { + // one second passed and we didn't receive a scroll event. + if (!recent) { + aTarget.removeEventListener("scroll", listener, true); + resolve(); + return; + } + recent = false; + // ten seconds + if (count > 10) { + aTarget.removeEventListener("scroll", listener, true); + reject(); + } + }, 1000); + }); +} + +/** + * Set a plugin activation state. See nsIPluginTag for + * supported states. Affected plugin default to the first + * test plugin. + */ +function setTestPluginEnabledState(aState, aPluginName) { + let name = aPluginName || "Test Plug-in"; + SpecialPowers.setTestPluginEnabledState(aState, name); +} + +/** + * Returns the chrome side nsIPluginTag for this plugin, helper for + * setTestPluginEnabledState. + */ +function getTestPlugin(aName) { + let pluginName = aName || "Test Plug-in"; + let ph = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost); + let tags = ph.getPluginTags(); + + // Find the test plugin + for (let i = 0; i < tags.length; i++) { + if (tags[i].name == pluginName) + return tags[i]; + } + ok(false, "Unable to find plugin"); + return null; +} + diff --git a/dom/plugins/test/mochitest/large-pic.jpg b/dom/plugins/test/mochitest/large-pic.jpg Binary files differnew file mode 100644 index 000000000..b167f6b9b --- /dev/null +++ b/dom/plugins/test/mochitest/large-pic.jpg diff --git a/dom/plugins/test/mochitest/loremipsum.txt b/dom/plugins/test/mochitest/loremipsum.txt new file mode 100644 index 000000000..c5becca59 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum.xtest b/dom/plugins/test/mochitest/loremipsum.xtest new file mode 100644 index 000000000..c5becca59 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum.xtest @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum.xtest^headers^ b/dom/plugins/test/mochitest/loremipsum.xtest^headers^ new file mode 100644 index 000000000..8dd7784af --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum.xtest^headers^ @@ -0,0 +1 @@ +Content-Type: application/x-test diff --git a/dom/plugins/test/mochitest/loremipsum_file.txt b/dom/plugins/test/mochitest/loremipsum_file.txt new file mode 100644 index 000000000..c5becca59 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum_file.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum_nocache.txt b/dom/plugins/test/mochitest/loremipsum_nocache.txt new file mode 100644 index 000000000..c5becca59 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum_nocache.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ b/dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ new file mode 100644 index 000000000..12a01c4a2 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store
diff --git a/dom/plugins/test/mochitest/mixed_case_mime.sjs b/dom/plugins/test/mochitest/mixed_case_mime.sjs new file mode 100644 index 000000000..3c29b8289 --- /dev/null +++ b/dom/plugins/test/mochitest/mixed_case_mime.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) +{ + response.processAsync(); + response.setHeader("Content-Type", "application/x-Second-Test", false); + + response.write("Hello world.\n"); + response.finish(); +} diff --git a/dom/plugins/test/mochitest/mochitest.ini b/dom/plugins/test/mochitest/mochitest.ini new file mode 100644 index 000000000..0e8587f34 --- /dev/null +++ b/dom/plugins/test/mochitest/mochitest.ini @@ -0,0 +1,150 @@ +[DEFAULT] +subsuite = clipboard +support-files = + 307-xo-redirect.sjs + crashing_subpage.html + file_authident.js + file_bug738396.html + file_bug771202.html + file_bug863792.html + file_bug1245545.js + large-pic.jpg + loremipsum.txt + loremipsum.xtest + loremipsum.xtest^headers^ + loremipsum_file.txt + loremipsum_nocache.txt + loremipsum_nocache.txt^headers^ + mixed_case_mime.sjs + neverending.sjs + npruntime_identifiers_subpage.html + plugin-stream-referer.sjs + plugin_window.html + pluginstream.js + post.sjs + plugin-utils.js + !/toolkit/components/passwordmgr/test/authenticate.sjs + +[test_bug406541.html] +[test_bug532208.html] +[test_bug539565-1.html] +[test_bug539565-2.html] +[test_bug738396.html] +[test_bug771202.html] +[test_bug777098.html] +[test_bug784131.html] +[test_bug813906.html] +[test_bug827160.html] +skip-if = toolkit == 'android' # needs plugin support +[test_bug852315.html] +[test_bug854082.html] +[test_bug863792.html] +[test_bug967694.html] +[test_bug985859.html] +[test_bug986930.html] +[test_bug1092842.html] +[test_bug1165981.html] +skip-if = !(os == "win" && processor == "x86_64") +[test_bug1245545.html] +[test_bug1307694.html] +[test_cocoa_focus.html] +skip-if = toolkit != "cocoa" || e10s # Bug 1194534 +support-files = cocoa_focus.html +[test_cocoa_window_focus.html] +skip-if = toolkit != "cocoa" # Bug 1194534 +support-files = cocoa_window_focus.html +[test_cookies.html] +[test_copyText.html] +skip-if = (toolkit != "gtk2") && (toolkit != "gtk3") +[test_crash_nested_loop.html] +skip-if = (toolkit != "gtk2") && (toolkit != "gtk3") +[test_crashing.html] +skip-if = !crashreporter +[test_crashing2.html] +skip-if = (!crashreporter) || true # Bug 566049 +[test_CrashService_crash.html] +skip-if = !crashreporter || e10s +[test_CrashService_hang.html] +skip-if = !crashreporter || e10s +[test_defaultValue.html] +[test_enumerate.html] +[test_fullpage.html] +[test_GCrace.html] +[test_getauthenticationinfo.html] +[test_hanging.html] +skip-if = !crashreporter || e10s +[test_hidden_plugin.html] +[test_instance_re-parent.html] +skip-if = release_or_beta # Bug 1172627 +[test_instance_unparent1.html] +[test_instance_unparent2.html] +[test_instance_unparent3.html] +[test_instantiation.html] +[test_mixed_case_mime.html] +[test_multipleinstanceobjects.html] +[test_newstreamondestroy.html] +[test_npn_asynccall.html] +[test_npn_timers.html] +[test_npobject_getters.html] +[test_NPNVdocumentOrigin.html] +[test_NPPVpluginWantsAllNetworkStreams.html] +[test_npruntime_construct.html] +[test_npruntime_identifiers.html] +[test_npruntime_npnevaluate.html] +[test_npruntime_npninvoke.html] +[test_npruntime_npninvokedefault.html] +[test_object.html] +skip-if = toolkit == 'android' # needs plugin support +[test_painting.html] +skip-if = (toolkit == "cocoa" && e10s) # bug 1252230 +[test_plugin_scroll_invalidation.html] +skip-if = toolkit != "gtk2" +support-files = plugin_scroll_invalidation.html +[test_plugin_scroll_painting.html] +skip-if = true # Bug 596491 +[test_pluginstream_3rdparty.html] +support-files = + file_checkcookie.sjs + file_setcookie.html +[test_pluginstream_asfile.html] +[test_pluginstream_asfileonly.html] +[test_pluginstream_err.html] +[test_pluginstream_geturl.html] +skip-if = true # Bug 1267432 +[test_pluginstream_geturlnotify.html] +skip-if = true # Bug 1267432 +[test_pluginstream_newstream.html] +[test_pluginstream_post.html] +[test_pluginstream_poststream.html] +[test_pluginstream_referer.html] +[test_pluginstream_seek.html] +[test_pluginstream_seek_close.html] +[test_pluginstream_src.html] +[test_pluginstream_src_dynamic.html] +[test_pluginstream_src_referer.html] +[test_positioning.html] +skip-if = true # disabled due to oddness, perhaps scrolling of the mochitest window? +[test_propertyAndMethod.html] +[test_queryCSSZoomFactor.html] +[test_queryContentsScaleFactor.html] +skip-if = (toolkit != "cocoa") || (os != "win") +[test_queryContentsScaleFactorWindowed.html] +skip-if = (toolkit != "cocoa") || (os != "win") +[test_redirect_handling.html] +[test_secondPlugin.html] +[test_src_url_change.html] +[test_streamatclose.html] +[test_streamNotify.html] +[test_stringHandling.html] +[test_twostreams.html] +[test_visibility.html] +skip-if = toolkit == "cocoa" +[test_windowed_invalidate.html] +skip-if = os != "win" +[test_windowless_flash.html] +skip-if = !(os == "win" && processor == "x86_64") +[test_windowless_ime.html] +skip-if = os != "win" +[test_x11_error_crash.html] +skip-if = !crashreporter || e10s || ((toolkit != "gtk2") && (toolkit != "gtk3")) +[test_zero_opacity.html] diff --git a/dom/plugins/test/mochitest/neverending.sjs b/dom/plugins/test/mochitest/neverending.sjs new file mode 100644 index 000000000..1576ce344 --- /dev/null +++ b/dom/plugins/test/mochitest/neverending.sjs @@ -0,0 +1,16 @@ +var timer = null; // declare timer outside to prevent premature GC
+function handleRequest(request, response)
+{
+ response.processAsync();
+ response.setHeader("Content-Type", "text/plain", false);
+
+ for (var i = 0; i < 1000; ++i)
+ response.write("Hello... ");
+
+ timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(function() {
+ response.write("world.\n");
+ response.finish();
+ }, 10 * 1000 /* 10 secs */, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/dom/plugins/test/mochitest/npruntime_identifiers_subpage.html b/dom/plugins/test/mochitest/npruntime_identifiers_subpage.html new file mode 100644 index 000000000..38c62e017 --- /dev/null +++ b/dom/plugins/test/mochitest/npruntime_identifiers_subpage.html @@ -0,0 +1,4 @@ +<html> + <body> + <embed id="plugin1" type="application/x-test" width="400" height="100"> + </embed> diff --git a/dom/plugins/test/mochitest/plugin-stream-referer.sjs b/dom/plugins/test/mochitest/plugin-stream-referer.sjs new file mode 100644 index 000000000..a1c9692c9 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin-stream-referer.sjs @@ -0,0 +1,12 @@ +function handleRequest(request, response) +{ + response.setHeader('Content-Type', 'text/plain', false); + response.setHeader('Cache-Control', 'no-cache', false); + response.setHeader('Content-Type', 'application/x-test', false); + if (request.hasHeader('Referer')) { + response.write('Referer found: ' + request.getHeader('Referer')); + } + else { + response.write('No Referer found'); + } +} diff --git a/dom/plugins/test/mochitest/plugin-utils.js b/dom/plugins/test/mochitest/plugin-utils.js new file mode 100644 index 000000000..90d3c285d --- /dev/null +++ b/dom/plugins/test/mochitest/plugin-utils.js @@ -0,0 +1,95 @@ +function paintCountIs(plugin, expected, msg) {
+ var count = plugin.getPaintCount();
+ var realExpected = expected;
+ ++realExpected; // extra paint at startup for all async-rendering plugins
+ ok(realExpected == count, msg + " (expected " + expected +
+ " independent paints, expected " + realExpected + " logged paints, got " +
+ count + " actual paints)");
+}
+
+function getTestPlugin(pluginName) {
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+ .getService(SpecialPowers.Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+ var name = pluginName || "Test Plug-in";
+ for (var tag of tags) {
+ if (tag.name == name) {
+ return tag;
+ }
+ }
+
+ ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+ return null;
+}
+
+// call this to set the test plugin(s) initially expected enabled state.
+// it will automatically be reset to it's previous value after the test
+// ends
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
+ var plugin = getTestPlugin(pluginName);
+ while (plugin.enabledState != newEnabledState) {
+ // Run a nested event loop to wait for the preference change to
+ // propagate to the child. Yuck!
+ SpecialPowers.Services.tm.currentThread.processNextEvent(true);
+ }
+ SimpleTest.registerCleanupFunction(function() {
+ SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
+ });
+}
+
+function crashAndGetCrashServiceRecord(crashMethodName, callback) {
+ var crashMan =
+ SpecialPowers.Cu.import("resource://gre/modules/Services.jsm").
+ Services.crashmanager;
+
+ // First, clear the crash record store.
+ info("Waiting for pruneOldCrashes");
+ var future = new Date(Date.now() + 1000 * 60 * 60 * 24);
+ crashMan.pruneOldCrashes(future).then(function () {
+
+ var iframe = document.getElementById("iframe1");
+ var p = iframe.contentDocument.getElementById("plugin1");
+
+ var crashDateMS = Date.now();
+ try {
+ p[crashMethodName]();
+ ok(false, "p." + crashMethodName + "() should throw an exception");
+ }
+ catch (e) {
+ ok(true, "p." + crashMethodName + "() should throw an exception");
+ }
+
+ // The crash record store is written and read back asyncly, so poll for
+ // the new record.
+ function tryGetCrash() {
+ info("Waiting for getCrashes");
+ crashMan.getCrashes().then(SpecialPowers.wrapCallback(function (crashes) {
+ if (crashes.length) {
+ is(crashes.length, 1, "There should be only one record");
+ var crash = SpecialPowers.wrap(crashes[0]);
+ ok(!!crash.id, "Record should have an ID");
+ ok(!!crash.crashDate, "Record should have a crash date");
+ var dateMS = crash.crashDate.valueOf();
+ var twoMin = 1000 * 60 * 2;
+ ok(crashDateMS - twoMin <= dateMS &&
+ dateMS <= crashDateMS + twoMin,
+ "Record's crash date should be nowish: " +
+ "now=" + crashDateMS + " recordDate=" + dateMS);
+ callback(crashMan, crash);
+ }
+ else {
+ setTimeout(tryGetCrash, 1000);
+ }
+ }), function (err) {
+ ok(false, "Error getting crashes: " + err);
+ SimpleTest.finish();
+ });
+ }
+ setTimeout(tryGetCrash, 1000);
+
+ }, function () {
+ ok(false, "pruneOldCrashes error");
+ SimpleTest.finish();
+ });
+}
diff --git a/dom/plugins/test/mochitest/plugin_no_scroll_div.html b/dom/plugins/test/mochitest/plugin_no_scroll_div.html new file mode 100644 index 000000000..b28f6d6ff --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_no_scroll_div.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <embed id="testplugin" type="application/x-test" drawmode="solid" color="ff00ff00" wmode="window" + style="position:absolute; top:5px; left:5px; width:500px; height:250px"> +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_scroll_invalidation.html b/dom/plugins/test/mochitest/plugin_scroll_invalidation.html new file mode 100644 index 000000000..74a68ff4a --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_scroll_invalidation.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test helper for plugin child widgets not being invalidated by scrolling</title> + <style> + body { + background: linear-gradient(red, black); + width: 200px; + height: 200px; + } + embed { + position:absolute; + } + embed#paint-waiter { + top: 0px; + left: 0px; + width: 0px; + height: 0px; + } + embed#e0 { + top: 70px; + left: 70px; + width: 10px; + height: 10px; + } + embed#e1 { + top: 60px; + left: 60px; + width: 10px; + height: 20px; + } + embed#e2 { + top: 60px; + left: 70px; + width: 20px; + height: 10px; + } + embed#e3 { + top: 70px; + left: 80px; + width: 10px; + height: 20px; + } + embed#e4 { + top: 80px; + left: 60px; + width: 20px; + height: 10px; + } + </style> +</head> +<body> + <embed id="paint-waiter" type="application/x-test" wmode="window"> + <embed id="e0" type="application/x-test" wmode="window" class="scrolling"/> + <embed id="e1" type="application/x-test" wmode="window" class="scrolling"/> + <embed id="e2" type="application/x-test" wmode="window" class="scrolling"/> + <embed id="e3" type="application/x-test" wmode="window" class="scrolling"/> + <embed id="e4" type="application/x-test" wmode="window" class="scrolling"/> +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_subframe_test.html b/dom/plugins/test/mochitest/plugin_subframe_test.html new file mode 100644 index 000000000..598521d57 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_subframe_test.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <iframe id="subframe" style="width:510px; height:260px;" src="plugin_no_scroll_div.html"></iframe> + <div style="display:block; height:3000px;"></div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_test.html b/dom/plugins/test/mochitest/plugin_test.html new file mode 100644 index 000000000..c7eb376cd --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_test.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <embed id="testplugin" type="application/x-test" drawmode="solid" color="ff00ff00" wmode="window" + style="position:absolute; top:50px; left:50px; width:500px; height:250px"> +<div style="display:block; height:3000px;"></div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_visibility_loader.html b/dom/plugins/test/mochitest/plugin_visibility_loader.html new file mode 100644 index 000000000..22802d9a5 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_visibility_loader.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body style="background-color: #88AAFF;"> + <h1>This Page Has a Solid Plugin</h1> + + <p><embed id="p" type="application/x-test" drawmode="solid" color="FFFF0000" width="200" height="200"></embed> diff --git a/dom/plugins/test/mochitest/plugin_window.html b/dom/plugins/test/mochitest/plugin_window.html new file mode 100644 index 000000000..d3a298e89 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_window.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>NPAPI Stream Tests</title> +</head> +<body onload="doTest()"> +<p id="display"></p> +<div id="content" style="display: none"> +This is a plugin test window. +</div> +<div id="test"> +<script class="testbody" type="text/javascript"> + +function doTest() { + window.opener.continueTest(); +} + +</script> +</div> +</body> + +</html> + diff --git a/dom/plugins/test/mochitest/pluginstream.js b/dom/plugins/test/mochitest/pluginstream.js new file mode 100644 index 000000000..9c47cd5f8 --- /dev/null +++ b/dom/plugins/test/mochitest/pluginstream.js @@ -0,0 +1,39 @@ + SimpleTest.waitForExplicitFinish(); + + function frameLoaded(finishWhenCalled = true, lastObject = false) { + var testframe = document.getElementById('testframe'); + function getNode(list) { + if (list.length === 0) + return undefined; + return lastObject ? list[list.length - 1] : list[0]; + } + var embed = getNode(document.getElementsByTagName('embed')); + if (undefined === embed) + embed = getNode(document.getElementsByTagName('object')); + + // In the file:// URI case, this ends up being cross-origin. + // Skip these checks in that case. + if (testframe.contentDocument) { + var content = testframe.contentDocument.body.innerHTML; + if (!content.length) + return; + + var filename = embed.getAttribute("src") || + embed.getAttribute("geturl") || + embed.getAttribute("geturlnotify") || + embed.getAttribute("data"); + + var req = new XMLHttpRequest(); + req.open('GET', filename, false); + req.overrideMimeType('text/plain; charset=x-user-defined'); + req.send(null); + is(req.status, 200, "bad XMLHttpRequest status"); + is(content, req.responseText.replace(/\r\n/g, "\n"), + "content doesn't match"); + } + + is(embed.getError(), "pass", "plugin reported error"); + if (finishWhenCalled) { + SimpleTest.finish(); + } + } diff --git a/dom/plugins/test/mochitest/post.sjs b/dom/plugins/test/mochitest/post.sjs new file mode 100644 index 000000000..b391dbdd8 --- /dev/null +++ b/dom/plugins/test/mochitest/post.sjs @@ -0,0 +1,17 @@ +const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ var body = "";
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var bytes = [], avail = 0;
+ while ((avail = bodyStream.available()) > 0)
+ body += String.fromCharCode.apply(String, bodyStream.readByteArray(avail));
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(body);
+}
+
diff --git a/dom/plugins/test/mochitest/privatemode_perwindowpb.xul b/dom/plugins/test/mochitest/privatemode_perwindowpb.xul new file mode 100644 index 000000000..ab7f06395 --- /dev/null +++ b/dom/plugins/test/mochitest/privatemode_perwindowpb.xul @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="NPAPI Private Mode Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<body xmlns="http://www.w3.org/1999/xhtml"> +<embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +<embed id="plugin2" type="application/x-test" width="200" height="200"></embed> +</body> +</window> diff --git a/dom/plugins/test/mochitest/test_CrashService_crash.html b/dom/plugins/test/mochitest/test_CrashService_crash.html new file mode 100644 index 000000000..84508a6dc --- /dev/null +++ b/dom/plugins/test/mochitest/test_CrashService_crash.html @@ -0,0 +1,28 @@ +<head> + <title>nsICrashService plugin crash</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout( + "crashAndGetCrashServiceRecord() polls for async crash recording"); + SimpleTest.requestCompleteLog(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + window.frameLoaded = function frameLoaded_toCrash() { + SimpleTest.expectChildProcessCrash(); + + crashAndGetCrashServiceRecord("crash", function (cm, crash) { + var isPluginCrash = crash.isOfType(cm.PROCESS_TYPE_PLUGIN, cm.CRASH_TYPE_CRASH); + ok(isPluginCrash, "Record should be a plugin crash"); + if (!isPluginCrash) { + dump("Crash type: " + crash.type + "\n"); + } + SimpleTest.finish(); + }); + + } + </script> + <iframe id="iframe1" src="crashing_subpage.html" width="600" height="600"></iframe> diff --git a/dom/plugins/test/mochitest/test_CrashService_hang.html b/dom/plugins/test/mochitest/test_CrashService_hang.html new file mode 100644 index 000000000..6ecf7d419 --- /dev/null +++ b/dom/plugins/test/mochitest/test_CrashService_hang.html @@ -0,0 +1,28 @@ +<head> + <title>nsICrashService plugin hang</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout( + "crashAndGetCrashServiceRecord() polls for async crash recording"); + SimpleTest.requestCompleteLog(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + window.frameLoaded = function frameLoaded_toCrash() { + SimpleTest.expectChildProcessCrash(); + + // the default timeout is annoying high for mochitest runs + var timeoutPref = "dom.ipc.plugins.timeoutSecs"; + SpecialPowers.setIntPref(timeoutPref, 5); + + crashAndGetCrashServiceRecord("hang", function (cm, crash) { + ok(crash.isOfType(cm.PROCESS_TYPE_PLUGIN, cm.CRASH_TYPE_HANG), + "Record should be a plugin hang"); + SimpleTest.finish(); + }); + } + </script> + <iframe id="iframe1" src="crashing_subpage.html" width="600" height="600"></iframe> diff --git a/dom/plugins/test/mochitest/test_GCrace.html b/dom/plugins/test/mochitest/test_GCrace.html new file mode 100644 index 000000000..50b598ef1 --- /dev/null +++ b/dom/plugins/test/mochitest/test_GCrace.html @@ -0,0 +1,62 @@ +<head> + <title>GC race with actors on the parent</title> + + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +<body onload="start()"> + <p id="display"></p> + + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function start() { + setTimeout(checkGCRace, 1000); + } + + var nested = false; + + function cb(f) { + ok(!nested, "Callback shouldn't occur in a nested stack frame"); + try { + f(35); + ok(true, "Callback was called, no crash"); + } + catch (e) { + ok(false, "Exception calling callback object: " + e); + } + SimpleTest.executeSoon(removePlugin); + } + + function removePlugin() { + var p = document.getElementById('p'); + p.parentNode.removeChild(p); + p = null; + SpecialPowers.Cu.forceGC(); + SimpleTest.finish(); + } + + function checkGCRace() { + nested = true; + + // The plugin will hand back a function and immediately sleep. + // We will lose our only reference to the function and force GC, followed + // by calling us with that function object again. We should be able to + // call the function and not crash. + var p = document.getElementById('p'); + var f = p.checkGCRace(cb); + f = null; // 'f' should be collected next GC + + nested = false; + + setTimeout(function() { + SpecialPowers.Cu.forceGC(); + }, 2000); + } + </script> + + <embed id="p" type="application/x-test" wmode="window"></embed> diff --git a/dom/plugins/test/mochitest/test_NPNVdocumentOrigin.html b/dom/plugins/test/mochitest/test_NPNVdocumentOrigin.html new file mode 100644 index 000000000..75ec66e13 --- /dev/null +++ b/dom/plugins/test/mochitest/test_NPNVdocumentOrigin.html @@ -0,0 +1,47 @@ +<html> +<head> + <title>Test NPNVdocumentOrigin</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> +</head> +<body onload="runTest()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTest() { + "use strict"; + var p1 = document.getElementById("plugin1"); + var realOrigin = "http://mochi.test:8888"; + + // Test with no modifications + is(p1.getNPNVdocumentOrigin(), realOrigin, "Checking for expected origin."); + + // This used to test that shadowing window.location.toString didn't confuse + // getNPNVdocumentOrigin. But now we explicitly throw when that happens. So + // just verify that we throw. There's no reason why getNPNVdocumentOrigin _would_ + // be confused in this case anyway. + try { + window.location.toString = function() { return 'http://victim.rckc.at/'; } + ok(false, "Should throw when shadowing window.location.toString"); + } + catch (e) { + ok(true, "Should throw when shadowing window.location.toString"); + } + + // Create a plugin in a new window with about:blank + var newWindow = window.open("about:blank"); + newWindow.onload = function() { + newWindow.document.writeln('<embed id="plugin2" type="application/x-test" width="200" height="200"></embed>'); + var p2 = newWindow.document.getElementById("plugin2"); + is(p2.getNPNVdocumentOrigin(), realOrigin, "Checking for expected origin of plugin in new about:blank window."); + newWindow.close(); + + SimpleTest.finish(); + }; + } + </script> + + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_NPPVpluginWantsAllNetworkStreams.html b/dom/plugins/test/mochitest/test_NPPVpluginWantsAllNetworkStreams.html new file mode 100644 index 000000000..588e785d3 --- /dev/null +++ b/dom/plugins/test/mochitest/test_NPPVpluginWantsAllNetworkStreams.html @@ -0,0 +1,77 @@ +<html> +<head> + <title>Test NPPVpluginWantsAllNetworkStreams</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> +</head> +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var p = null; + + var missingDoc = "not-found.html"; + + var expectedWriteURL = ""; + var expectedNotifyStatus = -1; + + var writeHappened = false; + var expectedWrite = false; + + function writeCallback(url) { + writeHappened = true; + } + + function notifyCallback(status, data) { + is(writeHappened, expectedWrite, "Test for expected write."); + is(status, expectedNotifyStatus, "Test for expected stream notification status."); + runNextTest(); + } + + function test1() { + // In this test we do not expect a stream for the missing document. + p.setPluginWantsAllStreams(false); + + expectedWriteURL = missingDoc; + expectedNotifyStatus = 1; + + writeHappened = false; + expectedWrite = false; + + p.streamTest(missingDoc, false, null, writeCallback, notifyCallback, null, false); + } + + function test2() { + // In this test we expect a stream for the missing document. + p.setPluginWantsAllStreams(true); + + expectedWriteURL = missingDoc; + expectedNotifyStatus = 0; + + writeHappened = false; + expectedWrite = true; + + p.streamTest(missingDoc, false, null, writeCallback, notifyCallback, null, false); + } + + var tests = [test1, test2]; + var currentTest = -1; + function runNextTest() { + currentTest++; + if (currentTest < tests.length) { + tests[currentTest](); + } else { + SimpleTest.finish(); + } + } + + function runTests() { + p = document.getElementById("plugin1"); + runNextTest(); + } + </script> + + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug1092842.html b/dom/plugins/test/mochitest/test_bug1092842.html new file mode 100644 index 000000000..e22d0a3b9 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug1092842.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> +<head> + <title>Bug 1092842</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + +<body onload="startTest()"> + <script type="application/javascript;version=1.8"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var p = null; + + function startTest() { + p = document.getElementById('theplugin'); + if (!p.hasWidget()) { + todo(false, "This test is only relevant for windowed plugins"); + SimpleTest.finish(); + return; + } + + // Wait for the plugin to have painted once. + var interval = setInterval(function() { + if (!p.getPaintCount()) + return; + + clearInterval(interval); + doTest(); + SimpleTest.finish(); + }, 100); + } + + function doTest() { + is(p.getClipRegionRectCount(), 1, "getClipRegionRectCount should be a single rect"); + is(p.getClipRegionRectEdge(0,2) - p.getClipRegionRectEdge(0,0), 100, "width of clip region rect"); + is(p.getClipRegionRectEdge(0,3) - p.getClipRegionRectEdge(0,1), 26, "height of clip region rect"); + } + </script> + + <div style="position:fixed; z-index:1; left:0; right:0; top:0; height:100px; border-bottom:24px solid blue; background:pink; transform:translateZ(0)"></div> + <object id="theplugin" type="application/x-test" drawmode="solid" color="ff00ff00" wmode="window" + style="position:absolute; top:50px; left:0; width:100px; height:100px"></object> + + <p id="display"></p> diff --git a/dom/plugins/test/mochitest/test_bug1165981.html b/dom/plugins/test/mochitest/test_bug1165981.html new file mode 100644 index 000000000..360d9312a --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug1165981.html @@ -0,0 +1,81 @@ +<html> + <head> + <title>Bug 1165981 Test</title> + + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + </head> + <script class="testbody" type="application/javascript"> + "use strict"; + + SimpleTest.waitForExplicitFinish(); + ok(SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Shockwave Flash"), "Should find allowed test flash plugin"); + ok(SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Silverlight Test Plug-in"), "Should find allowed test silverlight plugin"); + ok(!SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Third Test Plug-in"), "Should not find disallowed plugin"); + + function findPlugin(pluginName) { + for (var i = 0; i < navigator.plugins.length; i++) { + var plugin = navigator.plugins[i]; + if (plugin.name === pluginName) { + return plugin; + } + } + return null; + } + + function findMimeType(mimeTypeType) { + for (var i = 0; i < navigator.mimeTypes.length; i++) { + var mimeType = navigator.mimeTypes[i]; + if (mimeType.type === mimeTypeType) { + return mimeType; + } + } + return null; + } + + function createNode(id, type) { + let obj = document.createElement("object"); + obj.type = type; + obj.id = id; + obj.width = 200; + obj.height = 200; + document.body.appendChild(obj); + } + + function run() { + createNode("plugin-flash", "application/x-shockwave-flash-test"); + createNode("plugin-silverlight", "application/x-silverlight-test"); + createNode("disallowedPlugin", "application/x-third-test"); + var pluginElement = document.getElementById("plugin-flash"); + is(pluginElement.identifierToStringTest("foo"), "foo", "Should be able to call a function provided by the plugin"); + + pluginElement = document.getElementById("plugin-silverlight"); + is(pluginElement.identifierToStringTest("foo"), "foo", "Should be able to call a function provided by the plugin"); + + pluginElement = document.getElementById("disallowedPlugin"); + is(typeof pluginElement.identifierToStringTest, "undefined", "Should NOT be able to call a function on a disallowed plugin"); + + ok(navigator.plugins["Shockwave Flash"], "Should have queried a plugin named 'Shockwave Flash'"); + ok(navigator.plugins["Silverlight Test Plug-in"], "Should have queried a plugin named 'Silverlight Test Plug-in'"); + ok(!navigator.plugins["Third Test Plug-in"], "Should NOT have queried a disallowed plugin named 'Third Test Plug-in'"); + + ok(findPlugin("Shockwave Flash"), "Should have found a plugin named 'Shockwave Flash'"); + ok(findPlugin("Silverlight Test Plug-in"), "Should have found a plugin named 'Silverlight Test Plug-in'"); + ok(!findPlugin("Third Test Plug-in"), "Should NOT found a disallowed plugin named 'Third Test Plug-in'"); + + ok(navigator.mimeTypes["application/x-shockwave-flash-test"], "Should have queried a MIME type named 'application/x-shockwave-flash-test'"); + ok(navigator.mimeTypes["application/x-silverlight-test"], "Should have queried a MIME type named 'application/x-silverlight-test'"); + ok(!navigator.mimeTypes["application/x-third-test"], "Should NOT have queried a disallowed type named 'application/x-third-test'"); + + ok(findMimeType("application/x-shockwave-flash-test"), "Should have found a MIME type named 'application/x-shockwave-flash-test'"); + ok(findMimeType("application/x-silverlight-test"), "Should have found a MIME type named 'application/x-silverlight-test'"); + ok(!findMimeType("application/x-third-test"), "Should NOT have found a disallowed MIME type named 'application/x-third-test'"); + + SimpleTest.finish(); + } + </script> + + <body onload="run()"> + </body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug1245545.html b/dom/plugins/test/mochitest/test_bug1245545.html new file mode 100644 index 000000000..23e3b21e0 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug1245545.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> + <head> + <meta><charset="utf-8"/> + <title>Test Modifying Plugin click-to-play Flag</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + </head> + <body onload="startTest()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + SpecialPowers.pushPrefEnv({ "set": [ + ['dom.ipc.plugins.unloadTimeoutSecs', 0] + ] }); + + function startTest() { + let url = SimpleTest.getTestFileURL("file_bug1245545.js"); + let script = SpecialPowers.loadChromeScript(url); + script.addMessageListener("check-plugin-unload", testChromeUnload); + var testPlugin = getTestPlugin(); + ok(testPlugin, "Should have Test Plug-in"); + is(testPlugin.loaded, true, "Test plugin should be loaded"); + var pluginNode = document.getElementById("theplugin"); + pluginNode.parentNode.removeChild(pluginNode); + // Poll for plugin to unload. + function testContentUnload() { + if (!testPlugin.loaded) { + ok(true, "Test plugin unloaded in client process"); + // Start the chrome unload test + testChromeUnload(true); + } else { + setTimeout(testContentUnload, 0); + } + } + + function testChromeUnload(isLoaded) { + if (!isLoaded) { + ok(true, "Test plugin unloaded in chrome process"); + SimpleTest.finish(); + } else { + var results = script.sendAsyncMessage("check-plugin-unload"); + } + } + testContentUnload(); + } + </script> + <object id="theplugin" type="application/x-test"></object> + + </body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug1307694.html b/dom/plugins/test/mochitest/test_bug1307694.html new file mode 100644 index 000000000..97ee1b6ef --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug1307694.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onLoad="addPluginElement()"> + + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function addPluginElement() { + var p = document.createElement('embed'); + p.setAttribute('id', 'plugin2'); + p.setAttribute('type', 'application/x-shockwave-flash-test'); + p.setAttribute('scale', 'noscale'); + p.setAttribute('salign', 'lt'); + document.body.appendChild(p); + SimpleTest.executeSoon(function() { + runTests(); + }); + } + + function runTests() { + p = document.getElementById('plugin1'); + ok(p.setColor != undefined, "Static plugin parameter (salign/scale) ordering were correct"); + p2 = document.getElementById('plugin2'); + ok(p2.setColor != undefined, "Dynamic plugin parameter (salign/scale) ordering were correct"); + SimpleTest.finish(); + } + + </script> + <p id="display"></p> + + <div id="div1"> + <embed id="plugin1" type="application/x-shockwave-flash-test" width="200" height="200" scale="noscale" salign="lt"></embed> + </div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug406541.html b/dom/plugins/test/mochitest/test_bug406541.html new file mode 100644 index 000000000..6ce9d4554 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug406541.html @@ -0,0 +1,100 @@ +<!doctype html> +<html> +<head> + <title>Test for Bug 406541</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> +</head> +<body> +<script type="application/x-child-payload" id="child-payload"> + // This is injected into the file:/// origin iframe, see below. + + // appletA should spawn, appletB, with a codebase outside the temp directory, + // should not. + var appletA = document.createElement("applet"); + var appletB = document.createElement("applet"); + var appletC = document.createElement("applet"); + appletA.type = appletB.type = appletC.type = "application/x-java-test"; + appletB.setAttribute("codebase", "file:///"); + appletC.setAttribute("codebase", "./subdir_bug406541/"); + document.body.appendChild(appletA); + document.body.appendChild(appletB); + document.body.appendChild(appletC); + function isSpawned(plugin) { + try { + var x = plugin.getJavaCodebase(); + return true; + } catch (e) {} + return false; + } + window.parent.postMessage({ "A": isSpawned(appletA), + "B": isSpawned(appletB), + "C": isSpawned(appletC) }, "*"); +</script> +<script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, + "Java Test Plug-in"); + SpecialPowers.pushPrefEnv({ "set": [ + ['plugin.java.mime', 'application/x-java-test'] + ] }, runTest); + + function runTest() { + // Create a empty file and point an iframe at it + var Cc = SpecialPowers.Cc; + var Ci = SpecialPowers.Ci; + var file = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + var subdir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + file.append("test_bug406541.html"); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); + subdir.append("subdir_bug406541"); + subdir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0600); + + var i = document.createElement("iframe"); + var loaded = false; + i.addEventListener("load", function initialLoad() { + if (!loaded) { + // Once loaded, use special powers to point it at the file + SpecialPowers.wrap(i.contentWindow).location.href = "file://" + file.path; + loaded = true; + } else { + // Inject the child-payload script to the file:/// origin. Let it test + // applet spawning and send the results in a postMessage. (Because I + // couldn't get SpecialPowers to let me touch applets cross-origin, then + // gave up.) + var innerdoc = SpecialPowers.wrap(i.contentWindow).document; + var s = innerdoc.createElement("script"); + s.type = "text/javascript"; + s.textContent = document.getElementById("child-payload").textContent; + var finished = false; + window.onmessage = function(message) { + ok(message.data.A, "Plugin A should spawn"); + ok(!message.data.B, "Plugin B should NOT spawn"); + ok(message.data.C, "Plugin C should spawn"); + file.remove(false); + subdir.remove(false); + finished = true; + SimpleTest.finish(); + }; + innerdoc.body.appendChild(s); + + SimpleTest.executeSoon(function() { + if (!finished) { + ok(finished, "Should have received callback by now"); + SimpleTest.finish(); + } + }); + } + }, false); + document.body.appendChild(i); + } +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug479979.xul b/dom/plugins/test/mochitest/test_bug479979.xul new file mode 100644 index 000000000..1f295cbeb --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug479979.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="NPAPI Set Undefined Value Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +function runTests() { + var pluginElement1 = document.getElementById("plugin1"); + + var rv = true; // we want !true from the test plugin + var exceptionThrown = false; + try { + rv = pluginElement1.setUndefinedValueTest(); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Exception thrown setting undefined variable."); + is(rv, false, "Setting undefined variable succeeded."); + + // give the UI a chance to settle with the current enabled plugin state + // before we finish the test and reset the state to disabled. Otherwise + // the UI shows the plugin infobar! + SimpleTest.executeSoon(SimpleTest.finish); +} +]]> +</script> +<embed id="plugin1" type="application/x-test" width="300" height="300"></embed> +</body> +</window> diff --git a/dom/plugins/test/mochitest/test_bug532208.html b/dom/plugins/test/mochitest/test_bug532208.html new file mode 100644 index 000000000..0a4cdb985 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug532208.html @@ -0,0 +1,29 @@ +<head> +<title>Test for correct async delivery of large streams, bug +532208</title> + +<script type="application/javascript" +src="/tests/SimpleTest/SimpleTest.js"></script> +<script type="application/javascript" src="plugin-utils.js"></script> + +<body onload="setTimeout(runTests, 2000)"> + +<script class="testbody" type="application/javascript"> +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +function runTests() { + try { + document.getElementById('plugin1').getPaintCount(); + ok(true, "Data delivery didn't crash"); + } + catch (e) { + ok(false, "Data delivery crashed"); + } + SimpleTest.finish(); +} +</script> + +<embed id="plugin1" type="application/x-test" width="400" + height="400" src="large-pic.jpg" functiontofail="npp_write_rpc" streammode="normal"></embed> diff --git a/dom/plugins/test/mochitest/test_bug539565-1.html b/dom/plugins/test/mochitest/test_bug539565-1.html new file mode 100644 index 000000000..022b8cfc6 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug539565-1.html @@ -0,0 +1,91 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=539565 +--> +<head> + <title>Test #1 for Bug 539565</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script class="testbody" type="text/javascript"> +function runTests() { + var moveBy = 17; + var waitedForPaint = 0; + function waitForPaint(func) { + waitedForPaint = 0; + var testplugin = $("plugin1"); + testplugin.last_paint_count = testplugin.getPaintCount ? testplugin.getPaintCount() : -2; + function waitForPaintCountIncrement() { + waitedForPaint++; + moveBy = -moveBy; + $("abs").style.left = ($("abs").offsetLeft + moveBy) + 'px'; + var x = document.documentElement.offsetHeight; + var pc = testplugin.getPaintCount ? testplugin.getPaintCount() : -2; + if (waitedForPaint == 20 || (pc != testplugin.last_paint_count && pc >= 0)) { + setTimeout(func,0); + } else + setTimeout(waitForPaintCountIncrement, 50); + } + waitForPaintCountIncrement(); + } + + function doClick(x,y,func) { + synthesizeMouse($("plugin1"), x, y, {}, window); + setTimeout(func,0); + } + + function verify(test,x,y,next) { + var p = $("plugin1").getLastMouseX(); + const delta = 2; + ok(p-delta <= x && x <= p+delta, "test"+test+" LastMouseX got " + p + " expected " + x + + " with fullZoom="+SpecialPowers.getFullZoom(window)+" MozTransform='"+$("container").style.MozTransform+"'"); + p = $("plugin1").getLastMouseY(); + ok(p-delta <= y && y <= p+delta, "test"+test+" LastMouseY got " + p + " expected " + y + + " with fullZoom="+SpecialPowers.getFullZoom(window)+" MozTransform='"+$("container").style.MozTransform+"'"); + if (next) next(); + } + + function click(x,y,next) { + waitForPaint(function(){doClick(x,y,next);}) + } + function zoom(factor) { + SpecialPowers.setFullZoom(window, factor); + } + + function test1() { // fullZoom=1 (sanity check) + zoom(1); + click(55,136, function(){verify("1",55,136,test2)}); + } + function test2() { // fullZoom=2 + zoom(2); + click(40,108, function(){verify("2",80,216,test2b)}) + } + function test2b() { + click(108,112, function(){verify("2c",216,224,endTest)}) + } + + function endTest() { + zoom(1); + SimpleTest.finish(); + } + + setTimeout(function(){waitForPaint(test1)},1000); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + </script> +</head> + +<body onload="runTests()"> + <div id="container" style="position:relative;top: 0px; left: 0px; width: 640px; height: 480px;"> + <div id="abs" style="position:absolute; left:90px; top:90px; width:20px; height:20px; background:blue; pointer-events:none;"></div> + <embed id="plugin1" type="application/x-test" wmode="transparent" width="200" height="200"></embed> + </div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug539565-2.html b/dom/plugins/test/mochitest/test_bug539565-2.html new file mode 100644 index 000000000..7d8d02a42 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug539565-2.html @@ -0,0 +1,111 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=539565 +--> +<head> + <title>Test #2 for Bug 539565</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script class="testbody" type="text/javascript"> +function runTests() { + var moveBy = 17; + var waitedForPaint = 0; + function waitForPaint(func) { + waitedForPaint = 0; + var testplugin = $("plugin1"); + testplugin.last_paint_count = testplugin.getPaintCount ? testplugin.getPaintCount() : -2; + function waitForPaintCountIncrement() { + waitedForPaint++; + moveBy = -moveBy; + $("abs").style.left = ($("abs").offsetLeft + moveBy) + 'px'; + var x = document.documentElement.offsetHeight; + var pc = testplugin.getPaintCount ? testplugin.getPaintCount() : -2; + if (waitedForPaint == 20 || (pc != testplugin.last_paint_count && pc >= 0)) { + setTimeout(func,0); + } else + setTimeout(waitForPaintCountIncrement, 50); + } + waitForPaintCountIncrement(); + } + + function doClick(x,y,func) { + synthesizeMouse($("plugin1"), x, y, {}, window); + setTimeout(func,0); + } + + function verify(test,x,y,next) { + var p = $("plugin1").getLastMouseX(); + const delta = 2; + ok(p-delta <= x && x <= p+delta, "test"+test+" LastMouseX got " + p + " expected " + x + + " with fullZoom="+SpecialPowers.getFullZoom(window)+" MozTransform='"+$("container").style.MozTransform+"'"); + p = $("plugin1").getLastMouseY(); + ok(p-delta <= y && y <= p+delta, "test"+test+" LastMouseY got " + p + " expected " + y + + " with fullZoom="+SpecialPowers.getFullZoom(window)+" MozTransform='"+$("container").style.MozTransform+"'"); + if (next) next(); + } + + function click(x,y,next) { + waitForPaint(function(){doClick(x,y,next);}) + } + function zoom(factor) { + SpecialPowers.setFullZoom(window, factor); + } + + function test3() { // fullZoom=1 + scale(2) + zoom(1); + // + // ======================== BUG WARNING ========================================= + // 'container' already has -moz-transform:scale(2) in its style attribute. + // Removing that and setting MozTransform dynamically here (as in test4) + // makes the test fail ("getLastMouseX is not a function" in verify() above) + // Looks like the plugin instance got destroyed and we never recover from that... + // ============================================================================== + // + click(50,136, function(){verify("3",25,68,test3b)}); + } + function test3b() { + click(208,212, function(){verify("3b",104,106,test4)}); + } + function test4() { // fullZoom=2 + scale(0.5) + zoom(2); + var container = $("container"); + container.style.MozTransformOrigin = "0px 0px"; + container.style.MozTransform = "scale(0.5)"; + var x = document.documentElement.offsetHeight; + click(60,52, function(){verify("4",240,208,test5)}); + } + function test5() { // fullZoom=2 + scale(2) + zoom(2); + var container = $("container"); + container.style.MozTransformOrigin = "0px 0px"; + container.style.MozTransform = "scale(2)"; + var x = document.documentElement.offsetHeight; + click(108,112, function(){verify("5",108,112,endTest)}); + } + + function endTest() { + zoom(1); + SimpleTest.finish(); + } + + setTimeout(function(){waitForPaint(test3)},1000); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + </script> +</head> + +<body onload="runTests()"> + <div id="container" style="position:relative;top: 0px; left: 0px; width: 640px; height: 480px; -moz-transform:scale(2); -moz-transform-origin:0px 0px;"> + <div id="abs" style="position:absolute; left:90px; top:90px; width:20px; height:20px; background:blue; pointer-events:none;"></div> + <embed id="plugin1" type="application/x-test" wmode="transparent" width="200" height="200"></embed> + </div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug738396.html b/dom/plugins/test/mochitest/test_bug738396.html new file mode 100644 index 000000000..ead7f5c1a --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug738396.html @@ -0,0 +1,88 @@ +<!doctype html> +<html> +<head> + <title>Test for Bug 738396</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> +</head> +<body> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, + "Java Test Plug-in"); + + SpecialPowers.pushPrefEnv({ "set": [ + ['plugin.java.mime', 'application/x-java-test'] + ] }, loadFrame); + SimpleTest.waitForExplicitFinish(); + + function loadFrame() { + var iframe = document.createElement("iframe"); + iframe.src = "./file_bug738396.html"; + iframe.addEventListener("load", function() { + runTest(iframe.contentDocument); + }); + document.body.appendChild(iframe); + } + + function runTest(doc) { + // Check that the canonicalized version of the codebase 'good' was passed + // to the plugin in all cases + var a = doc.createElement('a'); + a.href = "good"; + var goodCodebase = a.href; + var codebasevis = doc.getElementById("codebasevis") + .querySelectorAll("applet, object, embed"); + for (var elem of codebasevis) { + var codebase = null; + try { + codebase = elem.getJavaCodebase(); + } catch (e) {} + is(codebase, goodCodebase, + "Check that the test plugin sees the proper codebase"); + } + // Check that none of the applets in blockedcodebase were allowed to spawn + var blockedcodebase = doc.getElementById("blockedcodebase") + .querySelectorAll("applet, object, embed"); + for (var elem of blockedcodebase) { + var spawned = false; + try { + elem.getObjectValue(); + spawned = true; + } catch (e) {} + ok(!spawned, "Plugin should not be allowed to spawn"); + } + + // With no codebase, the codebase should resolve to "." + a.href = "."; + goodCodebase = a.href; + var nocodebase = doc.getElementById("nocodebase") + .querySelectorAll("applet, object, embed"); + for (var elem of nocodebase) { + var codebase = null; + try { + codebase = elem.getJavaCodebase(); + } catch (e) {} + is(codebase, goodCodebase, "Codebase should resolve to '.'"); + } + + // With empty codebase, the codebase should resolve to "/" + a.href = "/"; + goodCodebase = a.href; + var nocodebase = doc.getElementById("emptycodebase") + .querySelectorAll("applet, object, embed"); + for (var elem of nocodebase) { + var codebase = null; + try { + codebase = elem.getJavaCodebase(); + } catch (e) {} + is(codebase, goodCodebase, "Codebase should resolve to '/'"); + } + + SimpleTest.finish(); + } +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug751809.html b/dom/plugins/test/mochitest/test_bug751809.html new file mode 100644 index 000000000..8f4987a8a --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug751809.html @@ -0,0 +1,84 @@ +<html> +<head> + <title>Bug 751809</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript;version=1.7"> + Components.utils.import("resource://gre/modules/Services.jsm"); + Services.prefs.setBoolPref("plugins.click_to_play", true); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_CLICKTOPLAY); + </script> +</head> + +<body onload="go();"> + <embed id="plugin" type="application/x-test" width="400" height="400" drawmode="solid" color="FF00FFFF"></embed> + + <script type="application/javascript;version=1.7"> + + SimpleTest.waitForExplicitFinish(); + + const Ci = Components.interfaces; + const utils = window.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils); + + function go() { + var plugin = document.getElementById('plugin'); + var objLoadingContent = SpecialPowers.wrap(plugin); + ok(!objLoadingContent.activated, "plugin should not be activated"); + + SimpleTest.waitForFocus(afterWindowFocus); + } + + function afterWindowFocus() { + var plugin = document.getElementById('plugin'); + var objLoadingContent = SpecialPowers.wrap(plugin); + + objLoadingContent.playPlugin(); + var condition = () => plugin.setColor !== undefined; + SimpleTest.waitForCondition(condition, afterPluginActivation, + "Waited too long for plugin to activate"); + } + + function afterPluginActivation() { + var plugin = document.getElementById('plugin'); + var objLoadingContent = SpecialPowers.wrap(plugin); + ok(objLoadingContent.activated, "plugin should be activated now"); + + // Triggering a paint and waiting for it to be flushed makes sure + // that both plugin and platform see the plugin element as visible. + // See bug 805330 for details. + plugin.setColor("FF000088"); + waitForAllPaintsFlushed(afterPaintsFlushed); + } + + function afterPaintsFlushed() { + var plugin = document.getElementById('plugin'); + try { + is(plugin.getMouseUpEventCount(), 0, "Plugin should not have received mouse events yet."); + } catch(e) { + ok(false, "plugin.getMouseUpEventCount() shouldn't throw"); + } + + synthesizeMouseAtCenter(plugin, {}); + var condition = () => plugin.getMouseUpEventCount() > 0; + SimpleTest.waitForCondition(condition, afterFirstClick, + "Waited too long for plugin to receive the mouse click"); + } + + function afterFirstClick() { + var plugin = document.getElementById('plugin'); + try { + is(plugin.getMouseUpEventCount(), 1, "Plugin should have received 1 mouse up event."); + } catch(e) { + ok(false, "plugin.getMouseUpEventCount() shouldn't throw"); + } + + Services.prefs.clearUserPref("plugins.click_to_play"); + SimpleTest.finish(); + } + + </script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug771202.html b/dom/plugins/test/mochitest/test_bug771202.html new file mode 100644 index 000000000..712c11de7 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug771202.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=771202 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 771202</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=771202">Mozilla Bug 771202</a> +<pre id="test"> +<script type="application/javascript"> + +/** Test for recreating spliced plugin prototype chains after tranplant. **/ +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +function go() { + // Set things up. + var win = document.getElementById('ifr').contentWindow; + var plugin = win.document.getElementById('pluginElement'); + var testValue = plugin.getObjectValue(); + + function checkPlugin() { + dump("About to call checkObjectValue\n"); + ok(plugin.checkObjectValue(testValue), 'Plugin proto should work correctly'); + } + // First, check things before transplanting. + checkPlugin(); + + // Adopt the plugin and retest. + document.body.appendChild(plugin); + checkPlugin(); + + // All done. + SimpleTest.finish(); +} + +</script> +</pre> + +<iframe id="ifr" onload="go();" src="file_bug771202.html"> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug777098.html b/dom/plugins/test/mochitest/test_bug777098.html new file mode 100644 index 000000000..77ba9ed31 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug777098.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=777098 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 777098</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="go();"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=777098">Mozilla Bug 777098</a> +<pre id="test"> +<script type="application/javascript"> + +/** Test for passing dead NPObjects back into plugins. **/ +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +function go() { + var plugin = document.getElementById('plugin'); + + // Get wrapped npobject from plugin (plugin.__proto__) + var val = plugin.getObjectValue(); + + // Force a re-instantiate by re-setting dummy uri, + // making val a wrapper for a dead plugin + plugin.data = plugin.data; + + // The correct behavior is an exception, if plugin.checkObjectValue succeeds + // the plugin wasn't torn down for some reason, and if we crash... that's bad + function pluginCheck() { + try { + plugin.checkObjectValue(val); + } catch (e) { + return true; + } + return false; + } + + // Spin the event loop so the instantiation can complete + window.setTimeout(function () { + ok(pluginCheck(), "Shouldn't explode"); + + // All done. + SimpleTest.finish(); + }, 0); +} + +</script> +</pre> + +<object data="data:text/plain,a" width=200 height=200 type="application/x-test" id="plugin"></object> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug784131.html b/dom/plugins/test/mochitest/test_bug784131.html new file mode 100644 index 000000000..ff2dd19c1 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug784131.html @@ -0,0 +1,85 @@ +<!doctype html> +<html> +<head> + <title>Test for Bug 784131</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <base href="chrome://browser/content/"> +</head> +<body> + +<script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); +</script> + +<embed id="body" type="application/x-test"> +<div> + <embed id="nested" type="application/x-test"> +</div> + +<script type="application/javascript"> + function getObjectValue(obj) { + try { + return obj.getObjectValue(); + } catch (e) { + return null; + } + } + SimpleTest.waitForExplicitFinish(); + + var body_embed = document.querySelector("embed#body"); + var nested_embed = document.querySelector("embed#nested"); + var nested_parent = nested_embed.parentNode; + // Ensure plugins are spawned + var body_obj = getObjectValue(body_embed); + var nested_obj = getObjectValue(nested_embed); + isnot(body_obj, null, "body plugin spawned"); + isnot(nested_obj, null, "nested plugin spawned"); + // Display:none the plugin and the nested plugin's parent + body_embed.style.display = "none"; + nested_parent.style.display = "none"; + body_embed.clientTop; + nested_embed.clientTop; + + // Plugins should still be running the same instance + ok(body_embed.checkObjectValue(body_obj), "body plugin still running"); + ok(nested_embed.checkObjectValue(nested_obj), "nested plugin still running"); + // Spin event loop + SimpleTest.executeSoon(function() { + // Plugins should be stopped + is(getObjectValue(body_embed), null, "body plugin gone"); + is(getObjectValue(nested_embed), null, "nested plugin gone"); + + // Restart plugins... + body_embed.style.display = "inherit"; + nested_parent.style.display = "inherit"; + + // Ensure plugins are spawned + var body_obj = getObjectValue(body_embed); + var nested_obj = getObjectValue(nested_embed); + isnot(body_obj, null, "body plugin spawned"); + isnot(nested_obj, null, "nested plugin spawned"); + + // Take away frames again, flush layout, restore frames + body_embed.style.display = "none"; + nested_parent.style.display = "none"; + body_embed.clientTop; + nested_embed.clientTop; + body_embed.style.display = "inherit"; + nested_parent.style.display = "inherit"; + body_embed.clientTop; + nested_embed.clientTop; + + // Spin event loop, ensure plugin remains running + SimpleTest.executeSoon(function() { + ok(body_embed.checkObjectValue(body_obj), "body plugin still running"); + ok(nested_embed.checkObjectValue(nested_obj), "nested plugin still running"); + SimpleTest.finish(); + }); + }); +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug813906.html b/dom/plugins/test/mochitest/test_bug813906.html new file mode 100644 index 000000000..04c34daaf --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug813906.html @@ -0,0 +1,54 @@ +<!doctype html> +<html> +<head> + <title>Test for Bug 813906</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <base href="chrome://browser/content/"> +</head> +<body> + +<script type="application/javascript"> +function f() { + document.getElementsByTagName("base")[0].href = "http://www.safe.com/"; +} +</script> + +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +var frameLoadCount = 0; +function frameLoaded() { + frameLoadCount++; + if (frameLoadCount == 1) { + document.getElementsByTagName("object")[0].type = "application/x-test"; + document.getElementsByTagName("use")[0].setAttributeNS("http://www.w3.org/1999/xlink", "href", location.href + "#a"); + } else if (frameLoadCount == 2) { + isnot(SpecialPowers.wrap(window.frame1).location.href.indexOf('chrome://'), + 0, 'plugin shouldnt be able to cause navigation to chrome URLs'); + SimpleTest.finish(); + } +} +</script> + +<!-- Note that <svg:use> ends up creating an anonymous subtree, which means that the plugin + reflector gets hoisted into the XBL scope, and isn't accessible to content. We pass + the 'donttouchelement' parameter to the plugin to prevent it from trying to define the + 'pluginFoundElement' property on the plugin reflector, since doing so would throw a + security exception. --> +<svg> + <symbol id="a"> + <foreignObject> + <object bugmode="813906" frame="frame1"><param name="donttouchelement"></param></object> + </foreignObject> + </symbol> + <use /> +</svg> + +<iframe name="frame1" onload="frameLoaded()"></iframe> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug827160.html b/dom/plugins/test/mochitest/test_bug827160.html new file mode 100644 index 000000000..13cedd62a --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug827160.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=827160 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 827160</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SpecialPowers.js"></script> + <script type="application/javascript" src="utils.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=827160">Mozilla Bug 827160</a> + +<script type="application/javascript"> + +// Make sure the test plugin is not click-to-play +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + +</script> + +<!-- Should load test plugin application/x-test --> +<object id="shouldLoad" data="data:application/x-test,foo"></object> +<!-- Should load test plugin application/x-test2, ignoring type="" --> +<object id="shouldIgnoreType" type="application/x-test" data="data:application/x-test2,foo"></object> +<!-- Should load nothing, channel type does not match type and typeMustMatch is present --> +<object id="shouldNotLoad" type="application/x-test" data="data:application/x-test2,foo" typemustmatch></object> +<!-- Should not load test plugin application/x-test2, no type field is present --> +<object id="shouldNotLoadMissingType" data="data:application/x-test2,foo" typemustmatch></object> +<!-- Should load, no data field is present --> +<object id="shouldLoadMissingData" type="application/x-test" typemustmatch></object> +<pre id="test"> + +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +window.addEventListener("load", function () { + const OBJLC = SpecialPowers.Ci.nsIObjectLoadingContent; + is(SpecialPowers.wrap(document.getElementById("shouldLoad")).displayedType, OBJLC.TYPE_PLUGIN, "Testing object load without type, failed expected load"); + is(SpecialPowers.wrap(document.getElementById("shouldIgnoreType")).displayedType, OBJLC.TYPE_PLUGIN, "Testing object load with type, failed expected load"); + is(SpecialPowers.wrap(document.getElementById("shouldNotLoad")).displayedType, OBJLC.TYPE_NULL, "Testing object load with typemustmatch, load success even though failure expected"); + is(SpecialPowers.wrap(document.getElementById("shouldNotLoadMissingType")).displayedType, OBJLC.TYPE_NULL, "Testing object load with typemustmatch and with type, load success even though failure expected"); + is(SpecialPowers.wrap(document.getElementById("shouldLoadMissingData")).displayedType, OBJLC.TYPE_PLUGIN, "Testing object load with typemustmatch and without data, failed expected load"); + SimpleTest.finish(); +}, false); + +</script> +</pre> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug852315.html b/dom/plugins/test/mochitest/test_bug852315.html new file mode 100644 index 000000000..cbbc3d34a --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug852315.html @@ -0,0 +1,62 @@ +<!doctype html> +<html> +<head> + <title>Test for Bug 852315</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <base href="chrome://browser/content/"> +</head> +<body> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +// Tests that the document-inactive notification stopping plugins does not +// fatally re-enter when adding other plugins to the document. + +var i = document.createElement("iframe"); +var ob = document.body; +i.addEventListener("load", function loadfunc() { + var d = i.contentWindow.document; + var e = i.contentDocument.createElement("embed"); + var destroyran = false; + e.type = "application/x-test"; + i.contentDocument.body.appendChild(e); + + // On despawn, append an embed tag to document. + e.callOnDestroy(function() { + var e2 = d.createElement("embed"); + d.body.appendChild(e2); + destroyran = true; + }); + + // Navigate the frame to cause the document with the plugin to go inactive + i.removeEventListener("load", loadfunc); + i.src = "about:blank"; + + const MAX_ATTEMPTS = 50; + var attempts = 0; + function checkPluginDestroyRan() { + // We may need to retry a few times until the plugin stop event makes + // its way through the event queue. + if (attempts < MAX_ATTEMPTS && !destroyran) { + ++attempts; + SimpleTest.executeSoon(checkPluginDestroyRan); + } else { + info("Number of retry attempts: " + attempts); + ok(destroyran, "OnDestroy callback ran and did not crash"); + SimpleTest.finish(); + } + } + + SimpleTest.executeSoon(checkPluginDestroyRan); +}); +document.body.appendChild(i); + +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug854082.html b/dom/plugins/test/mochitest/test_bug854082.html new file mode 100644 index 000000000..ff3d1e1e8 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug854082.html @@ -0,0 +1,38 @@ +<!doctype html> +<html> +<head> + <title>Test for Bug 854082</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <base href="chrome://browser/content/"> +</head> +<body> +<script type="application/javascript"> + // Tests that destroying the plugin's frame inside NPP_New does not cause a + // crash + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + // Create an object that will spawn asynchronously + var o = document.createElement("object"); + o.type = "application/x-test"; + + // The test plugin sets pluginFoundElement on its element inside NPP_New, + // abuse this to run arbitrary script. + var setterCalled; + o.__defineSetter__("pluginFoundElement", function() { + o.style.display = "none"; + // Access clientTop to force layout flush + o.clientTop; + ok(true, "Setter called and did not crash"); + SimpleTest.finish(); + setterCalled = true; + }); + document.body.appendChild(o); +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug863792.html b/dom/plugins/test/mochitest/test_bug863792.html new file mode 100644 index 000000000..ccd0fc83c --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug863792.html @@ -0,0 +1,40 @@ +<!doctype html> +<html> +<head> + <title>Test for Bug 863792</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> +</head> +<body> +<script type="application/javascript"> + +// A plugin that removes itself from the document and inactivates said document +// inside NPP_New. We should not leak the instance. See also test_bug854082 + +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +var i = document.createElement("iframe"); +i.src = "file_bug863792.html"; +i.width = 500; +i.height = 500; +document.body.appendChild(i); + +i.addEventListener("load", function() { + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + SimpleTest.executeSoon(function() { + SpecialPowers.forceGC(); + SpecialPowers.forceCC(); + SimpleTest.executeSoon(function() { + ok(true, "Didn't crash"); + SimpleTest.finish(); + }); + }); +}, false); +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug967694.html b/dom/plugins/test/mochitest/test_bug967694.html new file mode 100644 index 000000000..8a7602134 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug967694.html @@ -0,0 +1,78 @@ +<!doctype html> +<html> +<head> + <title>Test for Bug 967694</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> +</head> +<body> +<script type="application/javascript"> + +// Touching a plugin from chrome scope should not spawn it, bug should +// synchronously spawn it from content scope + +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +var plugin; +var spPlugin; + +function recreatePlugin() { + if (plugin) { + document.body.removeChild(plugin); + } + plugin = document.createElement("embed"); + plugin.type = "application/x-test"; + + document.body.appendChild(plugin); + // Plugin should now be queued for async spawning. + spPlugin = SpecialPowers.wrap(plugin); + + is(spPlugin.displayedType, spPlugin.TYPE_PLUGIN, "Should be configured as plugin"); + ok(!spPlugin.hasRunningPlugin, "Should not be spawned yet"); +} + +recreatePlugin(); + +// Try various JS operations with chrome context +var thrown = false; +// Get and set non-existent Property +var hi = spPlugin._testShouldntExist; +spPlugin._testShouldntExist = 5; +// Call some test-plugin function +try { + var val = spPlugin.getObjectValue(); +} catch (e) { + thrown = true; +} + +ok(thrown, "Function call should have thrown"); +ok(!spPlugin.hasRunningPlugin, "Plugin should not have spawned"); + +// Try property access from content +var hi = plugin._testShouldntExistContent; +ok(spPlugin.hasRunningPlugin, "Should've caused plugin to spawn"); + +// Property set +recreatePlugin(); +plugin._testShouldntExistContent = 5; +ok(spPlugin.hasRunningPlugin, "Should've caused plugin to spawn"); + +// Call test plugin function. Should succeed. +recreatePlugin(); +thrown = false; +try { + var value = plugin.getObjectValue(); +} catch (e) { + thrown = true; +} + +ok(!thrown, "Call should have succeeded"); +ok(spPlugin.hasRunningPlugin, "Call should have synchronously spawned plugin"); +ok(plugin.checkObjectValue(value), "Plugin should recognize self"); + +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug985859.html b/dom/plugins/test/mochitest/test_bug985859.html new file mode 100644 index 000000000..1a4329ab4 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug985859.html @@ -0,0 +1,27 @@ +<!doctype html> +<html> +<head> + <title>Test for Bug 985859</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <base href="chrome://browser/content/"> +</head> +<body> +<script type="application/javascript"> + +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +var testType = navigator.mimeTypes["application/x-test"]; +var testTypeCap = navigator.mimeTypes["Application/x-Test"]; +var testTypeCap2 = navigator.mimeTypes["APPLICATION/X-TEST"]; + +ok(testType, "Test plugin should be found"); +is(testType, testTypeCap, "navigator.mimeTypes should be case insensitive"); +is(testType, testTypeCap2, "navigator.mimeTypes should be case insensitive"); + +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_bug986930.html b/dom/plugins/test/mochitest/test_bug986930.html new file mode 100644 index 000000000..f86539e58 --- /dev/null +++ b/dom/plugins/test/mochitest/test_bug986930.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"/> + <title>Test for Bug 986930</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript" src="plugin-utils.js"></script> +</head> +<body> + <script class="testbody" type="application/javascript"> + var testPlugin = getTestPlugin("Test Plug-in"); + + var mimeDescriptions = testPlugin.getMimeDescriptions({}); + + is(mimeDescriptions[0], "Test \u2122 mimetype", + "Plugin should handle non-ascii mime description"); + </script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_busy_hang.xul b/dom/plugins/test/mochitest/test_busy_hang.xul new file mode 100644 index 000000000..824dcbb4d --- /dev/null +++ b/dom/plugins/test/mochitest/test_busy_hang.xul @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Basic Plugin Tests" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <title>Plugin Busy Hang Test</title> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hang_test.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> + </body> + <script class="testbody" type="application/javascript"> + <![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function runTests() { + // Default plugin hang timeout is too high for mochitests + var prefs = Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefBranch); + var timeoutPref = "dom.ipc.plugins.timeoutSecs"; + prefs.setIntPref(timeoutPref, 5); + + var os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + os.addObserver(testObserver, "plugin-crashed", true); + + testObserver.idleHang = false; + document.addEventListener("PluginCrashed", onPluginCrashed, false); + + var pluginElement = document.getElementById("plugin1"); + try { + pluginElement.hang(true); + } catch (e) { + } +} +]]> + </script> +</window> + diff --git a/dom/plugins/test/mochitest/test_clear_site_data.html b/dom/plugins/test/mochitest/test_clear_site_data.html new file mode 100644 index 000000000..88ba937d5 --- /dev/null +++ b/dom/plugins/test/mochitest/test_clear_site_data.html @@ -0,0 +1,242 @@ +<html> +<head> + <title>NPAPI ClearSiteData/GetSitesWithData Functionality</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> +</head> +<body> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> + + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + const pluginHostIface = Components.interfaces.nsIPluginHost; + var pluginHost = Components.classes["@mozilla.org/plugin/host;1"]. + getService(pluginHostIface); + const FLAG_CLEAR_ALL = pluginHostIface.FLAG_CLEAR_ALL; + const FLAG_CLEAR_CACHE = pluginHostIface.FLAG_CLEAR_CACHE; + + var p = document.getElementById("plugin1"); + + // Since we're running with chrome permissions, accessing the plugin wont + // synchronously spawn it -- wait for the async spawning to finish. + SimpleTest.executeSoon(function() { + // Make sure clearing by timerange is supported. + p.setSitesWithDataCapabilities(true); + ok(PluginUtils.withTestPlugin(runTest), "Test plugin found"); + }); + + function stored(needles) { + var something = pluginHost.siteHasData(this.pluginTag, null); + if (!needles) + return something; + + if (!something) + return false; + + for (var i = 0; i < needles.length; ++i) { + if (!pluginHost.siteHasData(this.pluginTag, needles[i])) + return false; + } + return true; + } + + function checkThrows(fn, result) { + try { + fn(); + throw new Error("bad exception"); + } catch (e) { + is(e.result, result, "Correct exception thrown"); + } + } + + function runTest(pluginTag) { + this.pluginTag = pluginTag; + p.setSitesWithData( + "foo.com:0:5," + + "foo.com:0:7," + + "bar.com:0:10," + + "baz.com:0:10," + + "foo.com:1:7," + + "qux.com:1:5," + + "quz.com:1:8" + ); + ok(stored(["foo.com","bar.com","baz.com","qux.com","quz.com"]), + "Data stored for sites"); + + // Clear nothing. + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_ALL, 4, {callback: function() { test1(); }}); + } + function test1() { + ok(stored(["foo.com","bar.com","baz.com","qux.com","quz.com"]), + "Data stored for sites"); + + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 4, {callback: function() { test2(); }}); + } + function test2() { + ok(stored(["foo.com","bar.com","baz.com","qux.com","quz.com"]), + "Data stored for sites"); + + // Clear cache data 5 seconds or older. + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 5, {callback: function() { test3(); }}); + } + function test3() { + ok(stored(["foo.com","bar.com","baz.com","quz.com"]), + "Data stored for sites"); + ok(!stored(["qux.com"]), "Data cleared for qux.com"); + // Clear cache data for foo.com, but leave non-cache data. + pluginHost.clearSiteData(pluginTag, "foo.com", FLAG_CLEAR_CACHE, 20, {callback: function() { test4(); }}); + } + function test4() { + ok(stored(["foo.com","bar.com","baz.com","quz.com"]), + "Data stored for sites"); + + // Clear all data 7 seconds or older. + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_ALL, 7, {callback: function() { test5(); }}); + } + function test5() { + ok(stored(["bar.com","baz.com","quz.com"]), "Data stored for sites"); + ok(!stored(["foo.com"]), "Data cleared for foo.com"); + ok(!stored(["qux.com"]), "Data cleared for qux.com"); + + // Clear all cache data. + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 20, {callback: function() { test6(); }}); + } + function test6() { + ok(stored(["bar.com","baz.com"]), "Data stored for sites"); + ok(!stored(["quz.com"]), "Data cleared for quz.com"); + + // Clear all data for bar.com. + pluginHost.clearSiteData(pluginTag, "bar.com", FLAG_CLEAR_ALL, 20, {callback: function(rv) { test7(rv); }}); + } + function test7(rv) { + ok(stored(["baz.com"]), "Data stored for baz.com"); + ok(!stored(["bar.com"]), "Data cleared for bar.com"); + + // Disable clearing by age. + p.setSitesWithDataCapabilities(false); + + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_ALL, 20, {callback: function(rv) { + is(rv, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); + test8(rv); + }}); + } + function test8(rv) { + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, 20, {callback: function(rv) { + is(rv, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); + test9(rv); + }}); + } + function test9(rv) { + pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_ALL, 20, {callback: function(rv) { + is(rv, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); + test10(rv); + }}); + } + function test10(rv) { + pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_CACHE, 20, {callback: function(rv) { + is(rv, Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED); + test11(); + }}); + } + function test11() { + // Clear cache for baz.com and globally for all ages. + pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_CACHE, -1, {callback: function(rv) { test12()}}); + } + function test12() { + pluginHost.clearSiteData(pluginTag, null, FLAG_CLEAR_CACHE, -1, {callback: function(rv) { test13()}}); + } + function test13() { + // Check that all of the above were no-ops. + ok(stored(["baz.com"]), "Data stored for baz.com"); + + // Clear everything for baz.com. + pluginHost.clearSiteData(pluginTag, "baz.com", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test14()}}); + } + function test14() { + ok(!stored(["baz.com"]), "Data cleared for baz.com"); + ok(!stored(null), "All data cleared"); + + // Set data to test subdomains, IP literals, and 'localhost'-like hosts. + p.setSitesWithData( + "foo.com:0:0," + + "bar.foo.com:0:0," + + "baz.foo.com:0:0," + + "bar.com:0:0," + + "[192.168.1.1]:0:0," + + "localhost:0:0" + ); + ok(stored(["foo.com","nonexistent.foo.com","bar.com","192.168.1.1","localhost"]), + "Data stored for sites"); + + // Clear data for "foo.com" and its subdomains. + pluginHost.clearSiteData(pluginTag, "foo.com", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test15()}}); + } + function test15() { + ok(stored(["bar.com","192.168.1.1","localhost"]), "Data stored for sites"); + ok(!stored(["foo.com"]), "Data cleared for foo.com"); + ok(!stored(["bar.foo.com"]), "Data cleared for subdomains of foo.com"); + + // Clear data for "bar.com" using a subdomain. + pluginHost.clearSiteData(pluginTag, "foo.bar.com", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test16()}}); + } + function test16() { + ok(!stored(["bar.com"]), "Data cleared for bar.com"); + + // Clear data for "192.168.1.1". + pluginHost.clearSiteData(pluginTag, "192.168.1.1", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test17()}}); + } + function test17() { + ok(!stored(["192.168.1.1"]), "Data cleared for 192.168.1.1"); + + // Clear data for "localhost". + pluginHost.clearSiteData(pluginTag, "localhost", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test18()}}); + } + function test18() { + ok(!stored(null), "All data cleared"); + + // Set data to test international domains. + p.setSitesWithData( + "b\u00FCcher.es:0:0," + + "b\u00FCcher.uk:0:0," + + "xn--bcher-kva.NZ:0:0" + ); + // Check that both the ACE and UTF-8 representations register. + ok(stored(["b\u00FCcher.es","xn--bcher-kva.es","b\u00FCcher.uk","xn--bcher-kva.uk"]), + "Data stored for sites"); + + // Clear data for the UTF-8 version. + pluginHost.clearSiteData(pluginTag, "b\u00FCcher.es", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test19()}}); + } + function test19() { + ok(!stored(["b\u00FCcher.es"]), "Data cleared for UTF-8 representation"); + ok(!stored(["xn--bcher-kva.es"]), "Data cleared for ACE representation"); + + // Clear data for the ACE version. + pluginHost.clearSiteData(pluginTag, "xn--bcher-kva.uk", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test20()}}); + } + function test20() { + ok(!stored(["b\u00FCcher.uk"]), "Data cleared for UTF-8 representation"); + ok(!stored(["xn--bcher-kva.uk"]), "Data cleared for ACE representation"); + + // The NPAPI spec requires that the plugin report sites in normalized + // UTF-8. We do happen to normalize the result anyway, so while that's not + // strictly required, we test it here. + ok(stored(["b\u00FCcher.nz","xn--bcher-kva.nz"]), + "Data stored for sites"); + pluginHost.clearSiteData(pluginTag, "b\u00FCcher.nz", FLAG_CLEAR_ALL, -1, {callback: function(rv) { test21()}}); + } + function test21() { + ok(!stored(["b\u00FCcher.nz"]), "Data cleared for UTF-8 representation"); + ok(!stored(["xn--bcher-kva.nz"]), "Data cleared for ACE representation"); + ok(!stored(null), "All data cleared"); + SimpleTest.finish(); + } + </script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_cocoa_focus.html b/dom/plugins/test/mochitest/test_cocoa_focus.html new file mode 100644 index 000000000..17043ebae --- /dev/null +++ b/dom/plugins/test/mochitest/test_cocoa_focus.html @@ -0,0 +1,28 @@ +<html> +<head> + <title>NPCocoaEventFocusChanged Tests</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var gOtherWindow; + + function runTests() { + // We have to have two top-level windows in play in order to run these tests. + gOtherWindow = window.open("cocoa_focus.html", "", "width=250,height=250"); + } + + function testsFinished() { + // Tests have finished running, close the new window and end tests. + gOtherWindow.close(); + SimpleTest.finish(); + } + </script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_cocoa_window_focus.html b/dom/plugins/test/mochitest/test_cocoa_window_focus.html new file mode 100644 index 000000000..3458c24eb --- /dev/null +++ b/dom/plugins/test/mochitest/test_cocoa_window_focus.html @@ -0,0 +1,28 @@ +<html> +<head> + <title>NPCocoaEventWindowFocusChanged Tests</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var gOtherWindow; + + function runTests() { + // We have to have two top-level windows in play in order to run these tests. + gOtherWindow = window.open("cocoa_window_focus.html", "", "width=200,height=200"); + } + + function testsFinished() { + // Tests have finished running, close the new window and end tests. + gOtherWindow.close(); + SimpleTest.finish(); + } + </script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_convertpoint.xul b/dom/plugins/test/mochitest/test_convertpoint.xul new file mode 100644 index 000000000..5db01a253 --- /dev/null +++ b/dom/plugins/test/mochitest/test_convertpoint.xul @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Basic Plugin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function runTests() { + var pluginElement = document.getElementById("plugin1"); + // Poll to see if the plugin is in the right place yet. + // Check if x-coordinate 0 in plugin space is 0 in window space. If it is, + // the plugin hasn't been placed yet. + if (pluginElement.convertPointX(1, 0, 0, 2) == 0) { + setTimeout(runTests, 0); + return; + } + + var domWindowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + + var pluginRect = pluginElement.getBoundingClientRect(); + var pluginX = pluginRect.left + (window.mozInnerScreenX - window.screenX); + var pluginY = pluginRect.top + (window.mozInnerScreenY - window.screenY); + + var windowX = window.screenX; + var windowY = window.screenY; + var windowHeight = window.outerHeight; + + var screenHeight = window.screen.height; + + // arbitrary coordinates of test point in plugin top-left origin terms + var xOffset = 5; + var yOffset = 5; + + var NPCoordinateSpacePluginX = 0 + xOffset; + var NPCoordinateSpacePluginY = 0 + yOffset; + + var NPCoordinateSpaceWindowX = pluginX + xOffset; + var NPCoordinateSpaceWindowY = (windowHeight - pluginY) - yOffset; + + var NPCoordinateSpaceFlippedWindowX = pluginX + xOffset; + var NPCoordinateSpaceFlippedWindowY = pluginY + yOffset; + + var NPCoordinateSpaceScreenX = windowX + pluginX + xOffset; + var NPCoordinateSpaceScreenY = ((screenHeight - windowY) - pluginY) - yOffset; + + var NPCoordinateSpaceFlippedScreenX = windowX + pluginX + xOffset; + var NPCoordinateSpaceFlippedScreenY = windowY + pluginY + yOffset; + + // these are in coordinate space enumeration order + var xValues = new Array(NPCoordinateSpacePluginX, NPCoordinateSpaceWindowX, NPCoordinateSpaceFlippedWindowX, NPCoordinateSpaceScreenX, NPCoordinateSpaceFlippedScreenX); + var yValues = new Array(NPCoordinateSpacePluginY, NPCoordinateSpaceWindowY, NPCoordinateSpaceFlippedWindowY, NPCoordinateSpaceScreenY, NPCoordinateSpaceFlippedScreenY); + + var i; + for (i = 0; i < 5; i = i + 1) { + var sourceCoordSpaceValue = i + 1; + var j; + for (j = 0; j < 5; j = j + 1) { + var destCoordSpaceValue = j + 1; + xResult = pluginElement.convertPointX(sourceCoordSpaceValue, xValues[i], yValues[i], destCoordSpaceValue); + yResult = pluginElement.convertPointY(sourceCoordSpaceValue, xValues[i], yValues[i], destCoordSpaceValue); + is(xResult, xValues[j], "convertPointX: space " + sourceCoordSpaceValue + " to space " + destCoordSpaceValue + " mismatch"); + is(yResult, yValues[j], "convertPointY: space " + sourceCoordSpaceValue + " to space " + destCoordSpaceValue + " mismatch"); + } + } + + SimpleTest.finish(); +} +]]> +</script> +</window> diff --git a/dom/plugins/test/mochitest/test_cookies.html b/dom/plugins/test/mochitest/test_cookies.html new file mode 100644 index 000000000..f52796770 --- /dev/null +++ b/dom/plugins/test/mochitest/test_cookies.html @@ -0,0 +1,23 @@ +<html> +<head> + <title>NPAPI Cookie Tests</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var pluginElement = document.getElementById("plugin1"); + pluginElement.setCookie("foo"); + is(pluginElement.getCookie(), "foo", "Cookie was set and retrieved correctly via NPAPI."); + SimpleTest.finish(); + } + </script> + + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_copyText.html b/dom/plugins/test/mochitest/test_copyText.html new file mode 100644 index 000000000..553207e25 --- /dev/null +++ b/dom/plugins/test/mochitest/test_copyText.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test copying text from browser to plugin</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + + <script class="testbody" type="text/javascript"> +function runTests() { + var text = " some text \n to copy 'n paste " + var textElt = document.getElementById("input"); + var plugin = document.getElementById("plugin1"); + + // Make sure we wait for the clipboard + SimpleTest.waitForClipboard(text, () => { + textElt.focus(); + textElt.value = text; + textElt.select(); + SpecialPowers.wrap(textElt).editor.copy(); + }, () => { + is(plugin.getClipboardText(), text); + SimpleTest.finish(); + }, () => { + ok(false, "Failed to set the clipboard text!"); + SimpleTest.finish(); + }); +} + +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + </script> +</head> + +<body onload="runTests()"> + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> + <textarea id="input"></textarea> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_crash_nested_loop.html b/dom/plugins/test/mochitest/test_crash_nested_loop.html new file mode 100644 index 000000000..379b693e7 --- /dev/null +++ b/dom/plugins/test/mochitest/test_crash_nested_loop.html @@ -0,0 +1,46 @@ +<head> + <title>Plugin crashing in nested loop</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + window.frameLoaded = function frameLoaded_toCrash() { + var iframe = document.getElementById('iframe1'); + var p = iframe.contentDocument.getElementById('plugin1'); + + // This test is for bug 550026, which is inherently nondeterministic. + // If we hit that bug, the browser process would crash when the plugin + // crashes in crashInNestedLoop. If not, we'll pass "spuriously". + try { + p.crashInNestedLoop(); + // The plugin didn't crash when expected. This happens sometimes. Give + // it longer to crash. If it crashes (but not at the apropriate time), + // soft fail with a todo; if it doesn't crash then something went wrong, + // so fail. + SimpleTest.requestFlakyTimeout("sometimes the plugin takes a little longer to crash"); + setTimeout( + function() { + try { + p.getPaintCount(); + ok(false, "plugin should crash"); + } catch (e) { + todo(false, "p.crashInNestedLoop() should throw an exception"); + } + + SimpleTest.finish(); + }, + 1000); + } + catch (e) { + ok(true, "p.crashInNestedLoop() should throw an exception"); + SimpleTest.finish(); + } + + } + + </script> + <iframe id="iframe1" src="crashing_subpage.html" width="600" height="600"></iframe> diff --git a/dom/plugins/test/mochitest/test_crash_notify.xul b/dom/plugins/test/mochitest/test_crash_notify.xul new file mode 100644 index 000000000..fac95b07d --- /dev/null +++ b/dom/plugins/test/mochitest/test_crash_notify.xul @@ -0,0 +1,106 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Basic Plugin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +var success = false; + +var observerFired = false; + +var testObserver = { + observe: function(subject, topic, data) { + observerFired = true; + ok(true, "Observer fired"); + is(topic, "plugin-crashed", "Checking correct topic"); + is(data, null, "Checking null data"); + ok((subject instanceof Components.interfaces.nsIPropertyBag2), "got Propbag"); + ok((subject instanceof Components.interfaces.nsIWritablePropertyBag2), "got writable Propbag"); + + var id = subject.getPropertyAsAString("pluginDumpID"); + isnot(id, "", "got a non-empty crash id"); + let directoryService = + Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties); + let profD = directoryService.get("ProfD", Components.interfaces.nsIFile); + profD.append("minidumps"); + let dumpFile = profD.clone(); + dumpFile.append(id + ".dmp"); + ok(dumpFile.exists(), "minidump exists"); + let extraFile = profD.clone(); + extraFile.append(id + ".extra"); + ok(extraFile.exists(), "extra file exists"); + // cleanup, to be nice + dumpFile.remove(false); + extraFile.remove(false); + }, + + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIObserver) || + iid.equals(Components.interfaces.nsISupportsWeakReference) || + iid.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + } +}; + + +function onPluginCrashed(aEvent) { + ok(true, "Plugin crashed notification received"); + ok(observerFired, "Observer should have fired first"); + is(aEvent.type, "PluginCrashed", "event is correct type"); + + var pluginElement = document.getElementById("plugin1"); + is (pluginElement, aEvent.target, "Plugin crashed event target is plugin element"); + + ok(aEvent instanceof PluginCrashedEvent, + "plugin crashed event has the right interface"); + + is(typeof aEvent.pluginDumpID, "string", "pluginDumpID is correct type"); + isnot(aEvent.pluginDumpID, "", "got a non-empty dump ID"); + is(typeof aEvent.pluginName, "string", "pluginName is correct type"); + is(aEvent.pluginName, "Test Plug-in", "got correct plugin name"); + is(typeof aEvent.pluginFilename, "string", "pluginFilename is correct type"); + isnot(aEvent.pluginFilename, "", "got a non-empty filename"); + // The app itself may or may not have decided to submit the report, so + // allow either true or false here. + ok("submittedCrashReport" in aEvent, "submittedCrashReport is a property of event"); + is(typeof aEvent.submittedCrashReport, "boolean", "submittedCrashReport is correct type"); + + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + os.removeObserver(testObserver, "plugin-crashed"); + + SimpleTest.finish(); +} + +function runTests() { + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + os.addObserver(testObserver, "plugin-crashed", true); + + document.addEventListener("PluginCrashed", onPluginCrashed, false); + + var pluginElement = document.getElementById("plugin1"); + try { + pluginElement.crash(); + } catch (e) { + } +} +]]> +</script> +</window> + diff --git a/dom/plugins/test/mochitest/test_crash_notify_no_report.xul b/dom/plugins/test/mochitest/test_crash_notify_no_report.xul new file mode 100644 index 000000000..a1344bf0f --- /dev/null +++ b/dom/plugins/test/mochitest/test_crash_notify_no_report.xul @@ -0,0 +1,116 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Basic Plugin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +var success = false; + +var observerFired = false; + +var testObserver = { + observe: function(subject, topic, data) { + observerFired = true; + ok(true, "Observer fired"); + is(topic, "plugin-crashed", "Checking correct topic"); + is(data, null, "Checking null data"); + ok((subject instanceof Components.interfaces.nsIPropertyBag2), "got Propbag"); + ok((subject instanceof Components.interfaces.nsIWritablePropertyBag2), +"got writable Propbag"); + + var id = subject.getPropertyAsAString("pluginDumpID"); + isnot(id, "", "got a non-empty crash id"); + let directoryService = + Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties); + let pendingD = directoryService.get("UAppData", + Components.interfaces.nsIFile); + pendingD.append("Crash Reports"); + pendingD.append("pending"); + let dumpFile = pendingD.clone(); + dumpFile.append(id + ".dmp"); + ok(dumpFile.exists(), "minidump exists"); + let extraFile = pendingD.clone(); + extraFile.append(id + ".extra"); + ok(extraFile.exists(), "extra file exists"); + // cleanup, to be nice + dumpFile.remove(false); + extraFile.remove(false); + }, + + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIObserver) || + iid.equals(Components.interfaces.nsISupportsWeakReference) || + iid.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + } +}; + + +function onPluginCrashed(aEvent) { + ok(true, "Plugin crashed notification received"); + ok(observerFired, "Observer should have fired first"); + is(aEvent.type, "PluginCrashed", "event is correct type"); + + var pluginElement = document.getElementById("plugin1"); + is (pluginElement, aEvent.target, "Plugin crashed event target is plugin element"); + + ok(aEvent instanceof PluginCrashedEvent, + "plugin crashed event has the right interface"); + + is(typeof aEvent.pluginName, "string", "pluginName is correct type"); + is(aEvent.pluginName, "Test Plug-in"); + // The app itself may or may not have decided to submit the report, so + // allow either true or false here. + ok("submittedCrashReport" in aEvent, "submittedCrashReport is a property of event"); + is(typeof aEvent.submittedCrashReport, "boolean", "submittedCrashReport is correct type"); + + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + os.removeObserver(testObserver, "plugin-crashed"); + + // re-set MOZ_CRASHREPORTER_NO_REPORT + let env = Components.classes["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + env.set("MOZ_CRASHREPORTER_NO_REPORT", "1"); + SimpleTest.finish(); +} + +function runTests() { + // the test harness will have set MOZ_CRASHREPORTER_NO_REPORT, + // ensure that we can change the setting and have our minidumps + // wind up in Crash Reports/pending + let env = Components.classes["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + env.set("MOZ_CRASHREPORTER_NO_REPORT", ""); + + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + os.addObserver(testObserver, "plugin-crashed", true); + + document.addEventListener("PluginCrashed", onPluginCrashed, false); + + var pluginElement = document.getElementById("plugin1"); + try { + pluginElement.crash(); + } catch (e) { + } +} +]]> +</script> +</window> + diff --git a/dom/plugins/test/mochitest/test_crash_submit.xul b/dom/plugins/test/mochitest/test_crash_submit.xul new file mode 100644 index 000000000..22f39384b --- /dev/null +++ b/dom/plugins/test/mochitest/test_crash_submit.xul @@ -0,0 +1,157 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Basic Plugin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); +SimpleTest.ignoreAllUncaughtExceptions(); + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +var crashReporter = + Components.classes["@mozilla.org/toolkit/crash-reporter;1"] + .getService(Components.interfaces.nsICrashReporter); +var oldServerURL = crashReporter.serverURL; + +const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs"; + +var testObserver = { + observe: function(subject, topic, data) { + if (data == "submitting") // not done yet + return; + is(data, "success", "report should have been submitted successfully"); + is(topic, "crash-report-status", "Checking correct topic"); + ok(subject instanceof Components.interfaces.nsIPropertyBag2, + "Subject should be a property bag"); + + ok(subject.hasKey("minidumpID"), "Should have a local crash ID"); + let crashID = subject.getPropertyAsAString("minidumpID"); + isnot(crashID, "", "Local crash ID should not be an empty string"); + + ok(subject.hasKey("serverCrashID"), "Should have a server crash ID"); + let remoteID = subject.getPropertyAsAString("serverCrashID"); + isnot(remoteID, "", "Server crash ID should not be an empty string"); + + // Verify the data. The SJS script will return the data that was POSTed + let req = new XMLHttpRequest(); + req.open("GET", SERVER_URL + "?id=" + remoteID, false); + req.send(null); + is(req.status, 200, "Server response should be 200 OK"); + let submitted = JSON.parse(req.responseText); + ok(!("Throttleable" in submitted), "Submit request should not be Throttleable"); + is(submitted.ProcessType, "plugin", "Should specify ProcessType=plugin"); + + // Cleanup + // First remove our fake submitted report + let file = Services.dirsvc.get("UAppData", Components.interfaces.nsILocalFile); + file.append("Crash Reports"); + file.append("submitted"); + file.append(remoteID + ".txt"); + file.remove(false); + + // Next unregister our observer + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + os.removeObserver(testObserver, "crash-report-status"); + + // Then re-set MOZ_CRASHREPORTER_NO_REPORT + let env = Components.classes["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + env.set("MOZ_CRASHREPORTER_NO_REPORT", "1"); + + // Finally re-set crashreporter URL + crashReporter.serverURL = oldServerURL; + + // Check and cleanup CrashManager. + Task.spawn(function* () { + let cm = Services.crashmanager; + let store = yield cm._getStore(); + is(store.crashesCount, 1, "Store should have only 1 item"); + + let crash = store.getCrash(crashID); + ok(!!crash, "Store should have the crash record"); + ok(crash.isOfType(cm.PROCESS_TYPE_PLUGIN, cm.CRASH_TYPE_CRASH), + "Crash type should be plugin-crash"); + is(crash.remoteID, remoteID, "Crash remoteID should match"); + + is(crash.submissions.size, 1, "Crash should have a submission"); + let submission = crash.submissions.values().next().value; + is(submission.result, cm.SUBMISSION_RESULT_OK, + "Submission should be successful"); + + store.reset(); + + SimpleTest.finish(); + }); + }, + + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIObserver) || + iid.equals(Components.interfaces.nsISupportsWeakReference) || + iid.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + } +}; + + +function onPluginCrashed(aEvent) { + ok(true, "Plugin crashed notification received"); + is(aEvent.type, "PluginCrashed", "event is correct type"); + + let submitButton = document.getAnonymousElementByAttribute(aEvent.target, + "class", + "submitButton"); + // try to submit this report + sendMouseEvent({type:'click'}, submitButton, window); +} + +function runTests() { + // the test harness will have set MOZ_CRASHREPORTER_NO_REPORT, + // ensure that we can change the setting and have our minidumps + // wind up in Crash Reports/pending + let env = Components.classes["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + env.set("MOZ_CRASHREPORTER_NO_REPORT", ""); + + // Override the crash reporter URL to send to our fake server + crashReporter.serverURL = NetUtil.newURI(SERVER_URL); + + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + os.addObserver(testObserver, "crash-report-status", true); + + document.addEventListener("PluginCrashed", onPluginCrashed, false); + + var pluginElement = document.getElementById("plugin1"); + try { + Task.spawn(function* () { + // Clear data in CrashManager in case previous tests caused something + // to be added. + let store = yield Services.crashmanager._getStore(); + store.reset(); + + pluginElement.crash(); + }); + } catch (e) { + } +} +]]> +</script> +</window> diff --git a/dom/plugins/test/mochitest/test_crashing.html b/dom/plugins/test/mochitest/test_crashing.html new file mode 100644 index 000000000..eac6e42be --- /dev/null +++ b/dom/plugins/test/mochitest/test_crashing.html @@ -0,0 +1,62 @@ +<head> + <title>Plugin crashing</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + window.frameLoaded = function frameLoaded_toCrash() { + SimpleTest.expectChildProcessCrash(); + + var iframe = document.getElementById('iframe1'); + var p = iframe.contentDocument.getElementById('plugin1'); + + p.setColor("FFFF00FF"); + + try { + p.crash(); + ok(false, "p.crash() should throw an exception"); + } + catch (e) { + ok(true, "p.crash() should throw an exception"); + } + + // Create random identifiers to test bug 560213 + for (var i = 0; i < 5; ++i) { + var r = 'rid_' + Math.floor(Math.random() * 10000 + 1); + try { + ok(!(r in p), "unknown identifier in crashed plugin should fail silently"); + } + catch (e) { + ok(false, "unknown identifier check threw"); + } + } + + try { + p.setColor("FFFF0000"); + ok(false, "p.setColor should throw after the plugin crashes"); + } + catch (e) { + ok(true, "p.setColor should throw after the plugin crashes"); + } + + window.frameLoaded = function reloaded() { + var p = iframe.contentDocument.getElementById('plugin1'); + try { + p.setColor('FF00FF00'); + ok(true, "Reloading worked"); + } + catch (e) { + ok(false, "Reloading didn't give us a usable plugin"); + } + SimpleTest.finish(); + } + + iframe.contentWindow.location.reload(); + } + + </script> + <iframe id="iframe1" src="crashing_subpage.html" width="600" height="600"></iframe> diff --git a/dom/plugins/test/mochitest/test_crashing2.html b/dom/plugins/test/mochitest/test_crashing2.html new file mode 100644 index 000000000..be61f3d50 --- /dev/null +++ b/dom/plugins/test/mochitest/test_crashing2.html @@ -0,0 +1,74 @@ +<head> + <title>Plugin crashing</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body onload="mainLoaded()"> + <iframe id="iframe1" src="about:blank" width="600" height="600"></iframe> + + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var iframe = document.getElementById('iframe1'); + + function mainLoaded() { + SimpleTest.expectChildProcessCrash(); + + var p = iframe.contentDocument.createElement('embed'); + p.setAttribute('id', 'plugin1'); + p.setAttribute('type', 'application/x-test'); + p.setAttribute('width', '400'); + p.setAttribute('height', '400'); + p.setAttribute('drawmode', 'solid'); + p.setAttribute('color', 'FF00FFFF'); + p.setAttribute('newCrash', 'true'); + iframe.contentDocument.body.appendChild(p); + + // The plugin will now crash when it is instantiated, but + // that happens asynchronously. HACK: this should use an + // event instead of nested pending runnables, but I don't + // know of any DOM event that's fired when a plugin is + // instantiated. + SimpleTest.executeSoon(function() { + SimpleTest.executeSoon(function() { + ok(p.setColor === undefined, + "Plugin should have crashed on creation"); + + window.frameLoaded = reloaded1; + iframe.contentWindow.location.replace('crashing_subpage.html'); + }) + }); + } + + function reloaded1() { + var p = iframe.contentDocument.getElementById('plugin1'); + try { + p.setColor('FF00FF00'); + ok(true, "Reloading after crash-on-new worked"); + } + catch (e) { + ok(false, "Reloading after crash-on-new didn't give us a usable plugin"); + } + p.crashOnDestroy(); + // the child crash should happen here + p.parentNode.removeChild(p); + + window.frameLoaded = reloaded2; + SimpleTest.executeSoon(function() { + iframe.contentWindow.location.reload(); + }); + } + + function reloaded2() { + var p = iframe.contentDocument.getElementById('plugin1'); + try { + p.setColor('FF00FF00'); + ok(true, "Reloading after crash-on-destroy worked"); + } + catch (e) { + ok(false, "Reloading after crash-on-destroy didn't give us a usable plugin"); + } + SimpleTest.finish(); + } + </script> diff --git a/dom/plugins/test/mochitest/test_defaultValue.html b/dom/plugins/test/mochitest/test_defaultValue.html new file mode 100644 index 000000000..2cbe52efe --- /dev/null +++ b/dom/plugins/test/mochitest/test_defaultValue.html @@ -0,0 +1,38 @@ +<html> + <head> + <title>NPObject [[DefaultValue]] implementation</title> + + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + </head> + + <body onload="run()"> + + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function run() { + var plugin = document.getElementById("plugin"); + var pluginProto = Object.getPrototypeOf(plugin); + + plugin.propertyAndMethod = {}; + plugin.propertyAndMethod + "baz"; + ok(true, "|plugin.propertyAndMethod + \"baz\"| shouldn't assert"); + pluginProto.propertyAndMethod = {}; + pluginProto.propertyAndMethod + "quux"; + ok(true, "|pluginProto.propertyAndMethod + \"quux\"| shouldn't assert"); + + plugin + "foo"; + ok(true, "|plugin + \"foo\"| shouldn't assert"); + pluginProto + "bar"; + ok(true, "|pluginProto + \"bar\"| shouldn't assert"); + + SimpleTest.finish(); + } + </script> + + <embed id="plugin" type="application/x-test" wmode="window"></embed> + </body> +</html> diff --git a/dom/plugins/test/mochitest/test_enumerate.html b/dom/plugins/test/mochitest/test_enumerate.html new file mode 100644 index 000000000..af8431c7c --- /dev/null +++ b/dom/plugins/test/mochitest/test_enumerate.html @@ -0,0 +1,36 @@ +<html> +<head> + <title>NPAPI Cookie Tests</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var pluginElement = document.getElementById("plugin1"); + var c = 0; + var foundSetColor = false; + for (var n in pluginElement) { + ++c; + ok(n in pluginElement, "Enumerated property which doesn't exist?"); + if (n == 'setColor') + foundSetColor = true; + } + ok(c > 0, "Should have enumerated some properties"); + ok(foundSetColor, "Should have enumerated .setColor"); + SimpleTest.finish(); + } + </script> + + <p id="display"></p> + + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_fullpage.html b/dom/plugins/test/mochitest/test_fullpage.html new file mode 100644 index 000000000..680eb73a0 --- /dev/null +++ b/dom/plugins/test/mochitest/test_fullpage.html @@ -0,0 +1,35 @@ +<head> + <title>Full-page seekable stream</title> + + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css"> + +<body> + <p id="display"></p> + + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function frameLoaded() { + var testframe = document.getElementById('testframe'); + var content = testframe.contentDocument.body.innerHTML; + if (!content.length) + return; + + var req = new XMLHttpRequest(); + req.open('GET', 'loremipsum.xtest', false); + req.overrideMimeType('text/plain; charset=x-user-defined'); + req.send(null); + is(req.status, 200, "bad XMLHttpRequest"); + is(content, req.responseText.replace(/\r\n/g, "\n"), + "content doesn't match"); + SimpleTest.finish(); + } + </script> + + <iframe src="loremipsum.xtest" streamtype="seek"></iframe> + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> diff --git a/dom/plugins/test/mochitest/test_getauthenticationinfo.html b/dom/plugins/test/mochitest/test_getauthenticationinfo.html new file mode 100644 index 000000000..f249386c7 --- /dev/null +++ b/dom/plugins/test/mochitest/test_getauthenticationinfo.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Login Manager</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Test for NPN_GetAuthenticationInfo +<p id="display"></p> + +<div id="content"> + <iframe id="iframe"></iframe> +</div> + +<script class="testbody" type="text/javascript"> + +const Ci = SpecialPowers.Ci; +const Cc = SpecialPowers.Cc; + +function iframeLoad() { + var plugin = iframe.contentDocument.getElementById("embedtest"); + // valid request + is(plugin.getAuthInfo("http", "mochi.test", 8888, "basic", "testrealm"), + "user1|password1", + "correct user/pass retrieved"); + try { + // invalid request -- wrong host + is(plugin.getAuthInfo("http", "example.com", 8888, "basic", "testrealm"), + "user1|password1", + "correct user/pass retrieved"); + ok(false, "no exception was thrown"); + } + catch (err) { + ok(true, "expected exception caught"); + } + try { + // invalid request -- wrong port + is(plugin.getAuthInfo("http", "mochi.test", 90, "basic", "testrealm"), + "user1|password1", + "correct user/pass retrieved"); + ok(false, "no exception was thrown"); + } + catch (err) { + ok(true, "expected exception caught"); + } + try { + // invalid request -- wrong realm + is(plugin.getAuthInfo("http", "mochi.test", 8888, "basic", "wrongrealm"), + "user1|password1", + "correct user/pass retrieved"); + ok(false, "no exception was thrown"); + } + catch (err) { + ok(true, "expected exception caught"); + } + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +// Authentication info is added twice here. In the non-e10s case, this does +// nothing. In the e10s case, we need to add auth info in both the child process, +// which the plugin checks for auth validity, and the parent process, which the +// http network objects use. +// TODO: Clean this up once HTTPAuthManager is made e10s compliant in bug 1249172 +var iframe = document.getElementById("iframe"); +var am = Cc["@mozilla.org/network/http-auth-manager;1"]. + getService(Ci.nsIHttpAuthManager); +am.setAuthIdentity("http", "mochi.test", 8888, "basic", "testrealm", "", + "mochi.test", "user1", "password1"); +SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("file_authident.js")); +iframe.onload = iframeLoad; +iframe.src = "http://mochi.test:8888/tests/toolkit/components/passwordmgr/" + + "test/authenticate.sjs?user=user1&pass=password1&realm=testrealm&plugin=1"; + +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_hang_submit.xul b/dom/plugins/test/mochitest/test_hang_submit.xul new file mode 100644 index 000000000..6c037ecd4 --- /dev/null +++ b/dom/plugins/test/mochitest/test_hang_submit.xul @@ -0,0 +1,165 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Basic Plugin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); +SimpleTest.ignoreAllUncaughtExceptions(); + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +const crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService(Ci.nsICrashReporter); +const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs"; + +var oldServerURL = crashReporter.serverURL; + +const oldTimeoutPref = Services.prefs.getIntPref("dom.ipc.plugins.timeoutSecs"); + +var testObserver = { + observe: function(subject, topic, data) { + if (data == "submitting") // not done yet + return; + is(data, "success", "report should have been submitted successfully"); + is(topic, "crash-report-status", "Checking correct topic"); + ok(subject instanceof Ci.nsIPropertyBag2, "Subject should be a property bag"); + + ok(subject.hasKey("minidumpID"), "Should have a local crash ID"); + let crashID = subject.getPropertyAsAString("minidumpID"); + isnot(crashID, "", "Local crash ID should not be an empty string"); + + ok(subject.hasKey("serverCrashID"), "Should have a server crash ID"); + let remoteID = subject.getPropertyAsAString("serverCrashID"); + isnot(remoteID, "", "Server crash ID should not be an empty string"); + + // Verify the data. The SJS script will return the data that was POSTed + let req = new XMLHttpRequest(); + req.open("GET", SERVER_URL + "?id=" + remoteID, false); + req.send(null); + is(req.status, 200, "Server response should be 200 OK"); + let submitted = JSON.parse(req.responseText); + + ok(!("Throttleable" in submitted), "Submit request should not be Throttleable"); + is(submitted.ProcessType, "plugin", "Should specify ProcessType=plugin"); + ok("PluginHang" in submitted, "Request should contain PluginHang field"); + ok("additional_minidumps" in submitted, "Request should contain additional_minidumps field"); + let dumpNames = submitted.additional_minidumps.split(','); + ok(dumpNames.indexOf("browser") != -1, "additional_minidumps should contain browser"); + info("additional_minidumps="+submitted.additional_minidumps); + ok("upload_file_minidump" in submitted, "Request should contain upload_file_minidump field"); + ok("upload_file_minidump_browser" in submitted, "Request should contain upload_file_minidump_browser field"); + + // Cleanup + // First remove our fake submitted report + let file = Services.dirsvc.get("UAppData", Ci.nsILocalFile); + file.append("Crash Reports"); + file.append("submitted"); + file.append(remoteID + ".txt"); + file.remove(false); + + // Next unregister our observer + Services.obs.removeObserver(testObserver, "crash-report-status"); + + // Then re-set MOZ_CRASHREPORTER_NO_REPORT + let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); + env.set("MOZ_CRASHREPORTER_NO_REPORT", "1"); + + // Finally re-set prefs + crashReporter.serverURL = oldServerURL; + Services.prefs.setIntPref("dom.ipc.plugins.timeoutSecs", oldTimeoutPref); + + // Check and cleanup CrashManager. + Task.spawn(function* () { + let cm = Services.crashmanager; + let store = yield cm._getStore(); + is(store.crashesCount, 1, "Store should have only 1 item"); + + let crash = store.getCrash(crashID); + ok(!!crash, "Store should have the crash record"); + ok(crash.isOfType(cm.PROCESS_TYPE_PLUGIN, cm.CRASH_TYPE_HANG), + "Crash type should be plugin-hang"); + is(crash.remoteID, remoteID, "Crash remoteID should match"); + + is(crash.submissions.size, 1, "Crash should have a submission"); + let submission = crash.submissions.values().next().value; + is(submission.result, cm.SUBMISSION_RESULT_OK, + "Submission should be successful"); + + store.reset(); + + SimpleTest.finish(); + }); + }, + + QueryInterface: function(iid) { + if (iid.equals(Ci.nsIObserver) || + iid.equals(Ci.nsISupportsWeakReference) || + iid.equals(Ci.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + } +}; + +function onPluginCrashed(aEvent) { + ok(true, "Plugin crashed notification received"); + is(aEvent.type, "PluginCrashed", "event is correct type"); + + let submitButton = document.getAnonymousElementByAttribute(aEvent.target, + "class", + "submitButton"); + // try to submit this report + sendMouseEvent({type:'click'}, submitButton, window); +} + +function runTests() { + // Default plugin hang timeout is too high for mochitests + Services.prefs.setIntPref("dom.ipc.plugins.timeoutSecs", 1); + + // the test harness will have set MOZ_CRASHREPORTER_NO_REPORT, + // ensure that we can change the setting and have our minidumps + // wind up in Crash Reports/pending + let env = Cc["@mozilla.org/process/environment;1"] + .getService(Ci.nsIEnvironment); + env.set("MOZ_CRASHREPORTER_NO_REPORT", ""); + + // Override the crash reporter URL to send to our fake server + crashReporter.serverURL = NetUtil.newURI(SERVER_URL); + + // Hook into plugin crash events + Services.obs.addObserver(testObserver, "crash-report-status", true); + document.addEventListener("PluginCrashed", onPluginCrashed, false); + + var pluginElement = document.getElementById("plugin1"); + try { + Task.spawn(function* () { + // Clear data in CrashManager in case previous tests caused something + // to be added. + let store = yield Services.crashmanager._getStore(); + store.reset(); + + pluginElement.hang(); + }); + } catch (e) { + } +} +]]> +</script> +</window> diff --git a/dom/plugins/test/mochitest/test_hanging.html b/dom/plugins/test/mochitest/test_hanging.html new file mode 100644 index 000000000..c8ea936ce --- /dev/null +++ b/dom/plugins/test/mochitest/test_hanging.html @@ -0,0 +1,63 @@ +<head> + <title>Plugin hanging</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + window.frameLoaded = function frameLoaded_toCrash() { + SimpleTest.expectChildProcessCrash(); + + // the default timeout is annoying high for mochitest runs + var timeoutPref = "dom.ipc.plugins.timeoutSecs"; + SpecialPowers.setIntPref(timeoutPref, 5); + + var iframe = document.getElementById('iframe1'); + var p = iframe.contentDocument.getElementById('plugin1'); + + p.setColor("FFFF00FF"); + + try { + p.hang(); + ok(false, "p.hang() should throw an exception"); + } + catch (e) { + ok(true, "p.hang() should throw an exception"); + } + + try { + p.setColor("FFFF0000"); + ok(false, "p.setColor should throw after the plugin crashes"); + } + catch (e) { + ok(true, "p.setColor should throw after the plugin crashes"); + } + + window.frameLoaded = function reloaded() { + var p = iframe.contentDocument.getElementById('plugin1'); + try { + p.setColor('FF00FF00'); + ok(true, "Reloading worked"); + } + catch (e) { + ok(false, "Reloading didn't give us a usable plugin"); + } + + try { + SpecialPowers.clearUserPref(timeoutPref); + } + catch(e) { + ok(false, "Couldn't reset timeout pref"); + } + + SimpleTest.finish(); + } + + iframe.contentWindow.location.reload(); + } + + </script> + <iframe id="iframe1" src="crashing_subpage.html" width="600" height="600"></iframe> diff --git a/dom/plugins/test/mochitest/test_hangui.xul b/dom/plugins/test/mochitest/test_hangui.xul new file mode 100644 index 000000000..edb90db00 --- /dev/null +++ b/dom/plugins/test/mochitest/test_hangui.xul @@ -0,0 +1,262 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Basic Plugin Tests" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <title>Plugin Hang UI Test</title> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="plugin-utils.js" /> + <script type="application/javascript" + src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hang_test.js" /> + <script type="application/javascript" + src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hangui_common.js" /> + +<body xmlns="http://www.w3.org/1999/xhtml"> + <iframe id="iframe1" src="hangui_subpage.html" width="400" height="400"></iframe> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +Components.utils.import("resource://gre/modules/Services.jsm"); + +const hangUITimeoutPref = "dom.ipc.plugins.hangUITimeoutSecs"; +const hangUIMinDisplayPref = "dom.ipc.plugins.hangUIMinDisplaySecs"; +const timeoutPref = "dom.ipc.plugins.timeoutSecs"; + +var worker = new ChromeWorker("hangui_iface.js"); +worker.onmessage = function(event) { + var result = event.data; + var params = result.params; + var output = params.testName; + if (result.msg) { + output += ": " + result.msg; + } + ok(result.status, output); + if (params.callback) { + var cb = eval(params.callback); + var timeout = setTimeout(function() { clearTimeout(timeout); cb(); }, 100); + } +}; +worker.onerror = function(event) { + var output = "Error: " + event.message + " at " + event.filename + ":" + event.lineno; + ok(false, output); +}; + +var iframe; +var p; +var os = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService); + +function hanguiOperation(testName, timeoutSec, expectFind, expectClose, opCode, + commandId, check, cb) { + var timeoutMs = timeoutSec * 1000 + EPSILON_MS; + worker.postMessage({ "timeoutMs": timeoutMs, "expectToFind": expectFind, + "expectToClose": expectClose, "opCode": opCode, + "commandId": commandId, "check": check, + "testName": testName, "callback": cb }); +} + +function hanguiExpect(testName, shouldBeShowing, shouldClose, cb) { + var timeoutSec = Services.prefs.getIntPref(hangUITimeoutPref); + if (!shouldBeShowing && !timeoutSec) { + timeoutSec = Services.prefs.getIntPref(timeoutPref); + } + hanguiOperation(testName, timeoutSec, shouldBeShowing, shouldClose, HANGUIOP_NOTHING, 0, false, cb); +} + +function hanguiContinue(testName, check, cb) { + var timeoutSec = Services.prefs.getIntPref(hangUITimeoutPref); + hanguiOperation(testName, timeoutSec, true, true, HANGUIOP_COMMAND, IDC_CONTINUE, check, cb); +} + +function hanguiStop(testName, check, cb) { + var timeoutSec = Services.prefs.getIntPref(hangUITimeoutPref); + hanguiOperation(testName, timeoutSec, true, true, HANGUIOP_COMMAND, IDC_STOP, check, cb); +} + +function hanguiCancel(testName, cb) { + var timeoutSec = Services.prefs.getIntPref(hangUITimeoutPref); + hanguiOperation(testName, timeoutSec, true, true, HANGUIOP_CANCEL, 0, false, cb); +} + +function finishTest() { + if (obsCount > 0) { + os.removeObserver(testObserver, "plugin-crashed"); + --obsCount; + } + SpecialPowers.clearUserPref(hangUITimeoutPref); + SpecialPowers.clearUserPref(hangUIMinDisplayPref); + SpecialPowers.clearUserPref(timeoutPref); + SimpleTest.finish(); +} + +function runTests() { + resetVars(); + + hanguiOperation("Prime ChromeWorker", 0, false, false, HANGUIOP_NOTHING, 0, + false, "test1"); +} + +window.frameLoaded = runTests; + +var obsCount = 0; + +function onPluginCrashedHangUI(aEvent) { + ok(true, "Plugin crashed notification received"); + is(aEvent.type, "PluginCrashed", "event is correct type"); + + is(p, aEvent.target, "Plugin crashed event target is plugin element"); + + ok(aEvent instanceof PluginCrashedEvent, + "plugin crashed event has the right interface"); + + is(typeof aEvent.pluginDumpID, "string", "pluginDumpID is correct type"); + isnot(aEvent.pluginDumpID, "", "got a non-empty dump ID"); + is(typeof aEvent.pluginName, "string", "pluginName is correct type"); + is(aEvent.pluginName, "Test Plug-in", "got correct plugin name"); + is(typeof aEvent.pluginFilename, "string", "pluginFilename is correct type"); + isnot(aEvent.pluginFilename, "", "got a non-empty filename"); + // The app itself may or may not have decided to submit the report, so + // allow either true or false here. + ok("submittedCrashReport" in aEvent, "submittedCrashReport is a property of event"); + is(typeof aEvent.submittedCrashReport, "boolean", "submittedCrashReport is correct type"); + + os.removeObserver(testObserver, "plugin-crashed"); + --obsCount; +} + +function resetVars() { + iframe = document.getElementById('iframe1'); + p = iframe.contentDocument.getElementById("plugin1"); + if (obsCount == 0) { + os.addObserver(testObserver, "plugin-crashed", true); + ++obsCount; + } + iframe.contentDocument.addEventListener("PluginCrashed", + onPluginCrashedHangUI, + false); +} + +function test9b() { + hanguiExpect("test9b: Plugin Hang UI is not showing (checkbox)", false); + p.stall(STALL_DURATION); + hanguiExpect("test9b: Plugin Hang UI is still not showing (checkbox)", false, false, "finishTest"); + p.stall(STALL_DURATION); +} + +function test9a() { + resetVars(); + SpecialPowers.setIntPref(hangUITimeoutPref, 1); + SpecialPowers.setIntPref(hangUIMinDisplayPref, 1); + SpecialPowers.setIntPref(timeoutPref, 45); + hanguiContinue("test9a: Continue button works with checkbox", true, "test9b"); + p.stall(STALL_DURATION); +} + +function test9() { + window.frameLoaded = test9a; + iframe.contentWindow.location.reload(); +} + +function test8a() { + resetVars(); + SpecialPowers.setIntPref(hangUITimeoutPref, 1); + SpecialPowers.setIntPref(hangUIMinDisplayPref, 4); + hanguiExpect("test8a: Plugin Hang UI is not showing (disabled due to hangUIMinDisplaySecs)", false, false, "test9"); + var exceptionThrown = false; + try { + p.hang(); + } catch(e) { + exceptionThrown = true; + } + ok(exceptionThrown, "test8a: Exception thrown from hang() when plugin was terminated"); +} + +function test8() { + window.frameLoaded = test8a; + iframe.contentWindow.location.reload(); +} + +function test7a() { + resetVars(); + SpecialPowers.setIntPref(hangUITimeoutPref, 0); + hanguiExpect("test7a: Plugin Hang UI is not showing (disabled)", false, false, "test8"); + var exceptionThrown = false; + try { + p.hang(); + } catch(e) { + exceptionThrown = true; + } + ok(exceptionThrown, "test7a: Exception thrown from hang() when plugin was terminated"); +} + +function test7() { + window.frameLoaded = test7a; + iframe.contentWindow.location.reload(); +} + +function test6() { + SpecialPowers.setIntPref(hangUITimeoutPref, 1); + SpecialPowers.setIntPref(hangUIMinDisplayPref, 1); + SpecialPowers.setIntPref(timeoutPref, 3); + hanguiExpect("test6: Plugin Hang UI is showing", true, true, "test7"); + var exceptionThrown = false; + try { + p.hang(); + } catch(e) { + exceptionThrown = true; + } + ok(exceptionThrown, "test6: Exception thrown from hang() when plugin was terminated (child timeout)"); +} + +function test5a() { + resetVars(); + hanguiCancel("test5a: Close button works", "test6"); + p.stall(STALL_DURATION); +} + +function test5() { + window.frameLoaded = test5a; + iframe.contentWindow.location.reload(); +} + +function test4() { + hanguiStop("test4: Stop button works", false, "test5"); + // We'll get an exception here because the plugin was terminated + var exceptionThrown = false; + try { + p.hang(); + } catch(e) { + exceptionThrown = true; + } + ok(exceptionThrown, "test4: Exception thrown from hang() when plugin was terminated"); +} + +function test3() { + hanguiContinue("test3: Continue button works", false, "test4"); + p.stall(STALL_DURATION); +} + +function test2() { + // This test is identical to test1 because there were some bugs where the + // Hang UI would show on the first hang but not on subsequent hangs + hanguiExpect("test2: Plugin Hang UI is showing", true, true, "test3"); + p.stall(STALL_DURATION); +} + +function test1() { + SpecialPowers.setIntPref(hangUITimeoutPref, 1); + SpecialPowers.setIntPref(hangUIMinDisplayPref, 1); + SpecialPowers.setIntPref(timeoutPref, 45); + hanguiExpect("test1: Plugin Hang UI is showing", true, true, "test2"); + p.stall(STALL_DURATION); +} + +]]> +</script> +</window> diff --git a/dom/plugins/test/mochitest/test_hidden_plugin.html b/dom/plugins/test/mochitest/test_hidden_plugin.html new file mode 100644 index 000000000..446d8cbbd --- /dev/null +++ b/dom/plugins/test/mochitest/test_hidden_plugin.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test whether we are adding the dummy plugin correctly when there is only 1 plugin and its hidden</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + +<body> + <script type="application/javascript;version=1.7"> + "use strict" + { + SimpleTest.waitForExplicitFinish(); + let ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"].getService(SpecialPowers.Ci.nsIPluginHost); + let plugins = ph.getPluginTags(); + let testPluginName = plugins[0].name; + let oldPrefVal = null; + let prefName = "plugins.navigator.hidden_ctp_plugin"; + try { + oldPrefVal = SpecialPowers.getCharPref(prefName); + } catch (ex) {} + let promise = SpecialPowers.pushPrefEnv({ set: [[prefName, testPluginName]]}); + promise.then(function() { + for (let i = 0; i < plugins.length; i++) { + let plugin = plugins[i]; + let newState = (plugin.name == testPluginName ? SpecialPowers.Ci.nsIPluginTag.STATE_CLICKTOPLAY : + SpecialPowers.Ci.nsIPluginTag.STATE_DISABLED); + if (plugin.enabledState != newState) { + let oldState = plugin.enabledState; + setTestPluginEnabledState(newState, plugin.name); + SimpleTest.registerCleanupFunction(function() { + if (plugin.enabledState != oldState) + setTestPluginEnabledState(oldState, plugin.name); + }); + } + } + // we have disabled all the plugins except for 1 which is click to play and hidden. The + // navigator.plugins list should have only one entry and it should be the dummy plugin. + isnot(navigator.plugins.length, 0, "navigator.plugins should not be empty"); + SimpleTest.finish(); + }); + } +</script> +</body> diff --git a/dom/plugins/test/mochitest/test_idle_hang.xul b/dom/plugins/test/mochitest/test_idle_hang.xul new file mode 100644 index 000000000..83ff28f52 --- /dev/null +++ b/dom/plugins/test/mochitest/test_idle_hang.xul @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Basic Plugin Tests" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <title>Plugin Idle Hang Test</title> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hang_test.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED; + </script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function runTests() { + // Default plugin hang timeout is too high for mochitests + var prefs = Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefBranch); + var timeoutPref = "dom.ipc.plugins.timeoutSecs"; + prefs.setIntPref(timeoutPref, 5); + + var os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + os.addObserver(testObserver, "plugin-crashed", true); + + testObserver.idleHang = true; + document.addEventListener("PluginCrashed", onPluginCrashed, false); + + var pluginElement = document.getElementById("plugin1"); + try { + pluginElement.hang(false); + } catch (e) { + } +} +]]> +</script> +</window> + diff --git a/dom/plugins/test/mochitest/test_instance_re-parent.html b/dom/plugins/test/mochitest/test_instance_re-parent.html new file mode 100644 index 000000000..1ba87adb5 --- /dev/null +++ b/dom/plugins/test/mochitest/test_instance_re-parent.html @@ -0,0 +1,123 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test re-parentinging an instance's DOM node</title> + <script type="text/javascript" src="/MochiKit/packed.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="plugin-utils.js"></script> +</head> +<body onload="begin()"> + <script type="application/javascript;version=1.8"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + const MAX_CHECK_PLUGIN_STOPPED_ATTEMPTS = 50; + var numCheckPluginStoppedAttempts = 0; + var exceptionThrown = false; + var p = null; + var d1 = null; + var d2 = null; + + var destroyed = false; + + function begin() { + runTests(function() { + // Callback when finished - set plugin to windowed and repeat the tests + + info("Repeating tests with wmode=window"); + p.setAttribute("wmode", "window"); + d1.appendChild(p); + + // Forces the plugin to be respawned + p.src = p.src; + + destroyed = false; + exceptionThrown = false; + runTests(function () { + SimpleTest.finish(); + }); + }); + } + + function checkPluginStopped(callback, param) { + if (numCheckPluginStoppedAttempts < MAX_CHECK_PLUGIN_STOPPED_ATTEMPTS && + !destroyed) { + ++numCheckPluginStoppedAttempts; + SimpleTest.executeSoon(function() { + checkPluginStopped(callback, param); + }); + } else { + info("Number of check plugin stopped attempts: " + + numCheckPluginStoppedAttempts); + callback(param); + } + } + + function runTests(callback) { + p = document.getElementById('plugin1'); + d1 = document.getElementById('div1'); + d2 = document.getElementById('div2'); + + // First tests involve moving the instance from one div to another. + p.startWatchingInstanceCount(); + p.callOnDestroy(function() { + destroyed = true; + }); + + try { + d1.removeChild(p); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Testing for exception after removeChild."); + + try { + d2.appendChild(p); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Testing for exception after appendChild."); + + is(destroyed, false, "No instances should have been destroyed at this point."); + is(p.getInstanceCount(), 0, "No new instances should have been created at this point."); + + // Wait for the event loop to spin and ensure the plugin still wasn't touched + SimpleTest.executeSoon(function() { + is(destroyed, false, "No instances should have been destroyed at this point."); + is(p.getInstanceCount(), 0, "No new instances should have been created at this point."); + + d2.removeChild(p); + checkPluginStopped(continueTestsAfterPluginDestruction, callback); + }); + } + + function continueTestsAfterPluginDestruction(callback) { + d2.appendChild(p); + SimpleTest.executeSoon(function() { + try { + is(p.getInstanceCount(), 1, "One new instance should have been created at this point."); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Testing for exception getting instance count from plugin."); + + p.stopWatchingInstanceCount(); + callback.apply(null); + }); + } + </script> + + <p id="display"></p> + + <div id="div1"> + <!-- This embed has to have a "src" attribute. Part of this test involves seeing if we + properly restart plugins that have been added back to a document without a change + in URL. Not re-loading an object when the URL hasn't changed is a shortcut used for + some object types. Without a URL, this won't be tested. --> + <embed id="plugin1" src="loremipsum.txt" type="application/x-test" width="200" height="200"></embed> + </div> + <div id="div2"> + </div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_instance_unparent1.html b/dom/plugins/test/mochitest/test_instance_unparent1.html new file mode 100644 index 000000000..5a11dc588 --- /dev/null +++ b/dom/plugins/test/mochitest/test_instance_unparent1.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test removing an instance's DOM node</title> + <script type="text/javascript" src="/MochiKit/packed.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="startTest()"> + <script type="application/javascript;version=1.8"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + const MAX_ATTEMPTS = 50; + var attempts = 0; + var destroyed = false; + function onDestroy() { + destroyed = true; + } + + function checkPluginAlreadyDestroyed() { + // We may need to retry a few times until the plugin stop event makes + // its way through the event queue. + if (attempts < MAX_ATTEMPTS && !destroyed) { + ++attempts; + SimpleTest.executeSoon(checkPluginAlreadyDestroyed); + } else { + info("Number of retry attempts: " + attempts); + is(destroyed, true, "Plugin instance should have been destroyed."); + SimpleTest.finish(); + } + } + + function startTest() { + var p1 = document.getElementById('plugin1'); + var d1 = document.getElementById('div1'); + + p1.callOnDestroy(onDestroy); + d1.removeChild(p1); + SimpleTest.executeSoon(checkPluginAlreadyDestroyed); + } + </script> + + <p id="display"></p> + + <div id="div1"> + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> + </div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_instance_unparent2.html b/dom/plugins/test/mochitest/test_instance_unparent2.html new file mode 100644 index 000000000..eedbca8e4 --- /dev/null +++ b/dom/plugins/test/mochitest/test_instance_unparent2.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test removing an instance's DOM node</title> + <script type="text/javascript" src="/MochiKit/packed.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="startTest()"> + <script type="application/javascript;version=1.8"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + const MAX_ATTEMPTS = 50; + var attempts = 0; + var destroyed = false; + function onDestroy() { + destroyed = true; + } + + function checkPluginAlreadyDestroyed() { + // We may need to retry a few times until the plugin stop event makes + // its way through the event queue. + if (attempts < MAX_ATTEMPTS && !destroyed) { + ++attempts; + SimpleTest.executeSoon(checkPluginAlreadyDestroyed); + } else { + info("Number of retry attempts: " + attempts); + is(destroyed, true, "Plugin instance should have been destroyed."); + SimpleTest.finish(); + } + } + + function startTest() { + var p1 = document.getElementById('plugin1'); + var d1 = document.getElementById('div1'); + + p1.callOnDestroy(onDestroy); + + // Get two parent check events to run. + d1.removeChild(p1); + d1.appendChild(p1); + d1.removeChild(p1); + + SimpleTest.executeSoon(checkPluginAlreadyDestroyed); + } + </script> + + <p id="display"></p> + + <div id="div1"> + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> + </div> + + <div id="div2"> + <div id="div3"> + <embed id="plugin2" type="application/x-test" width="200" height="200"></embed> + </div> + </div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_instance_unparent3.html b/dom/plugins/test/mochitest/test_instance_unparent3.html new file mode 100644 index 000000000..9e55c7ed9 --- /dev/null +++ b/dom/plugins/test/mochitest/test_instance_unparent3.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test removing an instance's DOM node</title> + <script type="text/javascript" src="/MochiKit/packed.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="startTest()"> + <script type="application/javascript;version=1.8"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + const MAX_ATTEMPTS = 50; + var attempts = 0; + var destroyed = false; + function onDestroy() { + destroyed = true; + } + + function checkPluginAlreadyDestroyed() { + // We may need to retry a few times until the plugin stop event makes + // its way through the event queue. + if (attempts < MAX_ATTEMPTS && !destroyed) { + ++attempts; + SimpleTest.executeSoon(checkPluginAlreadyDestroyed); + } else { + info("Number of retry attempts: " + attempts); + is(destroyed, true, "Plugin instance should have been destroyed."); + SimpleTest.finish(); + } + } + + function startTest() { + var p1 = document.getElementById('plugin1'); + var d1 = document.getElementById('div1'); + var d2 = document.getElementById('div2'); + + p1.callOnDestroy(onDestroy); + d1.removeChild(d2); + SimpleTest.executeSoon(checkPluginAlreadyDestroyed); + } + </script> + + <p id="display"></p> + + <div id="div1"> + <div id="div2"> + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> + </div< + </div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_instantiation.html b/dom/plugins/test/mochitest/test_instantiation.html new file mode 100644 index 000000000..e8fc07745 --- /dev/null +++ b/dom/plugins/test/mochitest/test_instantiation.html @@ -0,0 +1,33 @@ +<head> + <title>Plugin instantiation</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body onload="mainLoaded()"> + <iframe id="iframe1" src="about:blank" width="600" height="600"></iframe> + + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var iframe = document.getElementById('iframe1'); + + function mainLoaded() { + var p = iframe.contentDocument.createElement('embed'); + p.setAttribute('id', 'plugin1'); + p.setAttribute('type', 'application/x-test'); + p.setAttribute('width', '400'); + p.setAttribute('height', '400'); + p.setAttribute('drawmode', 'solid'); + p.setAttribute('color', 'FF00FFFF'); + iframe.contentDocument.body.appendChild(p); + + // Plugin instantiation happens asynchronously + SimpleTest.executeSoon(function() { + SimpleTest.executeSoon(function() { + ok(p.setColor !== undefined, "Dynamic plugin instantiation."); + SimpleTest.finish(); + }) + }); + } + </script> diff --git a/dom/plugins/test/mochitest/test_mixed_case_mime.html b/dom/plugins/test/mochitest/test_mixed_case_mime.html new file mode 100644 index 000000000..9258541bb --- /dev/null +++ b/dom/plugins/test/mochitest/test_mixed_case_mime.html @@ -0,0 +1,29 @@ +<body> +<head> + <title>Test mixed case mimetype for plugins</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<script> + SimpleTest.expectAssertions(0, 1); + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in"); + + function frameLoaded() { + var contentDocument = document.getElementById('testframe').contentDocument; + ok(contentDocument.body.innerHTML.length > 0, "Frame content shouldn't be empty."); + ok(contentDocument.plugins.length > 0, "Frame content should have a plugin."); + var plugin = contentDocument.plugins[0]; + is(plugin.type.toLowerCase(), "application/x-second-test", "Should have loaded the second test plugin."); + SimpleTest.finish(); + } +</script> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()" src="mixed_case_mime.sjs"></iframe> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_multipleinstanceobjects.html b/dom/plugins/test/mochitest/test_multipleinstanceobjects.html new file mode 100644 index 000000000..74749e019 --- /dev/null +++ b/dom/plugins/test/mochitest/test_multipleinstanceobjects.html @@ -0,0 +1,24 @@ +<head> + <title>NPNV*NPObject accessibility tests</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var p1 = document.getElementById('plugin1'); + var p2 = document.getElementById('plugin2'); + + var o = p1.getObjectValue(); + ok(p1.checkObjectValue(o), "Plugin objects passed to the same instance are identical."); + ok(!p2.checkObjectValue(o), "Plugin objects passed to another instance are double-wrapped."); + + SimpleTest.finish(); + } + </script> + + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> + <embed id="plugin2" type="application/x-test" width="400" height="400"></embed> diff --git a/dom/plugins/test/mochitest/test_newstreamondestroy.html b/dom/plugins/test/mochitest/test_newstreamondestroy.html new file mode 100644 index 000000000..26c4709af --- /dev/null +++ b/dom/plugins/test/mochitest/test_newstreamondestroy.html @@ -0,0 +1,36 @@ +<head> + <title>NPN_GetURL called from NPP_Destroy</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css"> + +<body onload="runTest()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTest() { + var p = document.getElementById('plugin1'); + var destroyed = false; + p.callOnDestroy(function() { + destroyed = true; + ok(!p.streamTest('loremipsum.txt', false, null, null, + function(r, t) { + ok(false, "get-during-destroy should have failed"); + }, null, true), "NPN_GetURLNotify should fail during NPP_Destroy"); + }); + document.body.removeChild(p); + + setTimeout(function() { + ok(destroyed, "callback was fired as expected"); + SimpleTest.finish(); + }, 1000); + } + </script> + + <p id="display"></p> + + <embed id="plugin1" type="application/x-test"></embed> diff --git a/dom/plugins/test/mochitest/test_npn_asynccall.html b/dom/plugins/test/mochitest/test_npn_asynccall.html new file mode 100644 index 000000000..4f007aacb --- /dev/null +++ b/dom/plugins/test/mochitest/test_npn_asynccall.html @@ -0,0 +1,33 @@ +<html> +<head> + <title>NPN_AsyncCallback Tests</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function asyncTestsComplete(result) { + ok(result, "asyncCallback tests completed"); + SimpleTest.finish(); + } + + function runTests() { + var plugin = document.getElementById("plugin1"); + plugin.asyncCallbackTest("asyncTestsComplete"); + } + </script> + + <p id="display"></p> + + <embed id="plugin1" type="application/x-test" width="400" height="100"> + </embed> + + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_npn_timers.html b/dom/plugins/test/mochitest/test_npn_timers.html new file mode 100644 index 000000000..64d5aebbd --- /dev/null +++ b/dom/plugins/test/mochitest/test_npn_timers.html @@ -0,0 +1,33 @@ +<html> +<head> + <title>NPN_Timer Tests</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function pluginTimerTestFinish(result) { + ok(result, "timer tests completed"); + SimpleTest.finish(); + } + + function runTests() { + var plugin = document.getElementById("plugin1"); + plugin.timerTest("pluginTimerTestFinish"); + } + </script> + + <p id="display"></p> + + <embed id="plugin1" type="application/x-test" width="400" height="100"> + </embed> + + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_npobject_getters.html b/dom/plugins/test/mochitest/test_npobject_getters.html new file mode 100644 index 000000000..5a9a47081 --- /dev/null +++ b/dom/plugins/test/mochitest/test_npobject_getters.html @@ -0,0 +1,21 @@ +<head> + <title>NPNV*NPObject accessibility tests</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + dump('lastScript\n'); + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + ok(document.getElementById('plugin1').pluginFoundElement, "plugin1.pluginFoundElement (NPNVPluginElementNPObject)", document.getElementById('plugin1').pluginFoundElement); + ok(window.pluginFoundWindow, "window.pluginFoundWindow (NPNVWindowNPObject)", window.pluginFoundWindow); + + SimpleTest.finish(); + } + </script> + + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> diff --git a/dom/plugins/test/mochitest/test_npruntime.xul b/dom/plugins/test/mochitest/test_npruntime.xul new file mode 100644 index 000000000..7e5cc087f --- /dev/null +++ b/dom/plugins/test/mochitest/test_npruntime.xul @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Basic Plugin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<embed id="plugin1" type="application/x-test" width="400" height="400"></embed> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function runTests() { + var pluginElement = document.getElementById("plugin1"); + + ok(pluginElement.identifierToStringTest('foo') == "foo", "identifierToStringTest failed"); + + SimpleTest.finish(); +} +]]> +</script> +</window> diff --git a/dom/plugins/test/mochitest/test_npruntime_construct.html b/dom/plugins/test/mochitest/test_npruntime_construct.html new file mode 100644 index 000000000..bd85930a4 --- /dev/null +++ b/dom/plugins/test/mochitest/test_npruntime_construct.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test whether windowless plugins receive correct visible/invisible notifications.</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + +<body> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + + <p id="display"></p> + + <embed id="theplugin" type="application/x-test"></embed> + + <script type="application/javascript"> + function MyFunc(arg) { + is(arg, "hi", "Argument passed to constructor function"); + this.localProp = 'local'; + } + MyFunc.prototype.protoProp = 't'; + + var theplugin = document.getElementById('theplugin'); + + ok(theplugin.constructObject(Array) instanceof Array, "Constructed Array"); + var o = theplugin.constructObject(MyFunc, "hi"); + ok(o instanceof MyFunc, "Constructed MyFunc"); + is(o.localProp, 'local', "this property correct"); + is(o.protoProp, 't', "prototype property correct"); + </script> diff --git a/dom/plugins/test/mochitest/test_npruntime_identifiers.html b/dom/plugins/test/mochitest/test_npruntime_identifiers.html new file mode 100644 index 000000000..a19a59be5 --- /dev/null +++ b/dom/plugins/test/mochitest/test_npruntime_identifiers.html @@ -0,0 +1,67 @@ +<html> +<head> + <title>NPN_Invoke Tests</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <script class="testbody" type="application/javascript"> + //// + // This test exercises NP identifiers by querying the reflector to make sure + // that identifiers are translated to values correctly. + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var testsRun = 0; + + function doTest() { + SpecialPowers.gc(); + + var reflector = document.getElementById("subframe").contentDocument.getElementById("plugin1").getReflector(); + + var i, prop, randomnumber; + + for (i = 0; i < 20; ++i) { + randomnumber=Math.floor(Math.random()*1001); + prop = "prop" + randomnumber; + is(reflector[prop], prop, "Property " + prop); + } + + for (i = -10; i < 0; ++i) { + is(reflector[i], String(i), "Property " + i); + prop = "prop" + i; + is(reflector[prop], prop, "Property " + prop); + } + + for (i = 0; i < 10; ++i) { + is(reflector[i], i, "Property " + i); + prop = "prop" + i; + is(reflector[prop], prop, "Property " + prop); + } + + is(reflector.a, 'a', "Property .a"); + is(reflector['a'], 'a', "Property ['a']"); + reflector = null; + + SpecialPowers.gc(); + + ++testsRun; + if (testsRun == 3) { + SimpleTest.finish(); + } + else { + document.getElementById('subframe').contentWindow.location.reload(true); + } + } + </script> + + <iframe id="subframe" src="npruntime_identifiers_subpage.html" onload="doTest()"></iframe> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_npruntime_npnevaluate.html b/dom/plugins/test/mochitest/test_npruntime_npnevaluate.html new file mode 100644 index 000000000..4a1c22978 --- /dev/null +++ b/dom/plugins/test/mochitest/test_npruntime_npnevaluate.html @@ -0,0 +1,101 @@ +<html> +<head> + <title>NPN_Evaluate Tests</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + // global test function + function testMe(arg) { + var result = arg+arg; + for (var i = 1; i < arguments.length; i++) { + result += arguments[i] + arguments[i]; + } + return result; + } + + //// + // This test exercises NPN_Evaluate using the test plugin's + // npnEvaluateTest method. This method calls NPN_Evaluate on + // a string argument passed to it, and returns the eval result. + // The array below drives the tests; each array member has two + // members: the first is a string to eval, and the second is + // the expected result of the eval. + // + + function runTests() { + var tests = [ + ["3", 3], + ["3 + 3", 6], + ["'3'", "3"], + ["function test() { return 3; } test();", 3], + ["testMe(3)", 6], + ["testMe(new Object(3))", 6], + ["new Object(3)", new Object(3)], + ["new Array(1, 2, 3, 4)", [1, 2, 3, 4]], + ["document.getElementById('display')", + document.getElementById("display")], + ["encodeURI('a = b')", "a%20=%20b"], + ["document.getElementById('testdiv').innerHTML = 'Hello world!'", + "Hello world!"], + ["function test2() { var x = {a: '1', b: '2'}; return x; } test2();", + {a: '1', b: '2'}], + ["(function() { var ret; try { win = window.open(); win.document.writeln('wibble'); ret = 'no error' } catch(e) { ret = e.name; } win.close(); return ret; })()", "no error"], + ]; + + var plugin = document.getElementById("plugin1"); + + // Test calling NPN_Evaluate from within plugin code. + for (var test of tests) { + var expected = test[1]; + var result = plugin.npnEvaluateTest(test[0]); + // serialize the two values for easy comparison + var json_expected = JSON.stringify(expected); + var json_result = JSON.stringify(result); + if (typeof(result) == "function") + json_result = result.toString(); + if (typeof(expected) == "function") + json_expected = expected.toString(); + is(json_result, json_expected, + "npnEvaluateTest returned an unexpected value"); + is(typeof(result), typeof(expected), + "npnEvaluateTest return value was of unexpected type"); + var success = (json_result == json_expected && + typeof(result) == typeof(expected)); + $("verbose").appendChild( + createEl('span',null, (success ? "pass" : "fail") + ": eval(" + test[0] + ")")); + $("verbose").appendChild( + createEl('span', null," == " + json_result + "(" + + typeof(result) + "), expected " + json_expected + "(" + + typeof(expected) + ")")); + $("verbose").appendChild( + createEl('br') + ); + } + + is(document.getElementById('testdiv').innerHTML, "Hello world!", + "innerHTML not set correctly via NPN_Evaluate"); + + SimpleTest.finish(); + } + </script> + + <p id="display"></p> + + <embed id="plugin1" type="application/x-test" width="400" height="100"> + </embed> + + <div id="verbose"> + </div> + <div id="testdiv"> + </div> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_npruntime_npninvoke.html b/dom/plugins/test/mochitest/test_npruntime_npninvoke.html new file mode 100644 index 000000000..ff4c92a6d --- /dev/null +++ b/dom/plugins/test/mochitest/test_npruntime_npninvoke.html @@ -0,0 +1,162 @@ +<html> +<head> + <title>NPN_Invoke Tests</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTests()"> + + <script class="testbody" type="application/javascript"> + //// + // This test exercises NPN_Invoke by calling the plugin's npnInvokeTest + // method, which in turn invokes a script method with 1 or more parameters, + // and then compares the return vale with an expected value. This is good + // for verifying various NPVariant values and types moving between + // the browser and the plugin. + // + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + // This function returns all the arguments passed to it, either as a + // single variable (in the caes of 1 argument), or as an array. + function returnArgs() { + if (arguments.length == 1) + return arguments[0]; + var arr = new Array(); + for (i = 0; i < arguments.length; i++) { + arr.push(arguments[i]); + } + return arr; + } + + // Same as above but explicitly expects two arguments. + function returnTwoArgs(arg1, arg2) { + return [arg1, arg2]; + } + + // some objects and arrays used in the tests... + var obj = {"key1": "string", "key2": 0, "key3": true, "key4": undefined, + "key5": null, "key6": -551235.12552, "key7": false}; + var arr = ["string", 0, true, false, undefined, null, -1, 55512.1252]; + var obj2 = {"key1": "string", "key2": 0, "key3": true, "key4": undefined, + "key5": null, "key6": -551235.12552, "key7": false, "array": arr}; + var arr2 = ["string", false, undefined, null, -1, 55512.1252, + {"a": "apple", "b": true, "c": undefined}]; + + //// + // A list of tests to run. Each member of the main array contains + // two members: the first contains the arguments passed to npnInvokeTest, + // and the second is the expected result. + // + var tests = [ + // numeric values + [["returnArgs", 0, 0], true], + [["returnArgs", 1, 1], true], + [["returnArgs", 32768, 32768], true], + [["returnArgs", -32768, -32768], true], + [["returnArgs", 2147483648, 2147483648], true], + [["returnArgs", -2147483648, -2147483648], true], + [["returnArgs", 1.0, 1.0], true], + [["returnArgs", 1.592786, 1.592786], true], + [["returnArgs", 1.592786837, 1.592786837], true], + [["returnArgs", -1.592786, -1.592786], true], + [["returnArgs", -1.592786837, -1.592786837], true], + [["returnArgs", 15235.592786, 15235.592786], true], + // null, void, bool + [["returnArgs", null, null], true], + [["returnArgs", undefined, undefined], true], + [["returnArgs", undefined, null], false], + [["returnArgs", null, undefined], false], + [["returnArgs", 0, undefined], false], + [["returnArgs", 0, null], false], + [["returnArgs", 0, false], false], + [["returnArgs", 1, true], false], + [["returnArgs", true, true], true], + [["returnArgs", false, false], true], + // strings + [["returnArgs", "", ""], true], + [["returnArgs", "test", "test"], true], + [["returnArgs", "test", "testing"], false], + [["returnArgs", "test\n", "test\n"], true], + [["returnArgs", "test\nline2", "test\nline2"], true], + [["returnArgs", "test\nline2", "testline2"], false], + [["returnArgs", "test\u000aline2", "test\u000aline2"], true], + [["returnArgs", "test\u0020line2", "test line2"], true], + [["returnArgs", "test line2", "test\u0020line2"], true], + // objects and arrays + [["returnArgs", obj, obj], true], + [["returnArgs", arr, arr], true], + [["returnArgs", obj2, obj2], true], + [["returnArgs", arr2, arr2], true], + // multiple arguments + [["returnArgs", [0, 1, 2], 0, 1, 2], true], + [["returnArgs", [5, "a", true, false, null], + 5, "a", true, false, null], true], + [["returnArgs", [-1500.583, "test string\n", + [5, 10, 15, 20], {"a": 1, "b": 2}], + -1500.583, "test string\n", [5, 10, 15, 20], {"a": 1, "b": 2}], true], + [["returnArgs", [undefined, 0, null, "yes"], + undefined, 0, null, "yes"], true], + [["returnArgs", [0, undefined, null, "yes"], + 0, undefined, null, "yes"], true], + // too many/too few args + [["returnTwoArgs", ["a", "b"], "a", "b", "c"], true], + [["returnTwoArgs", ["a", undefined], "a"], true], + [["returnTwoArgs", [undefined, undefined]], true], + ]; + + function runTests() { + var plugin = document.getElementById("plugin1"); + + var result; + for (var test of tests) { + switch (test[0].length) { + case 2: + result = plugin.npnInvokeTest(test[0][0], test[0][1]); + break; + case 3: + result = plugin.npnInvokeTest(test[0][0], test[0][1], test[0][2]); + break; + case 4: + result = plugin.npnInvokeTest(test[0][0], test[0][1], test[0][2], + test[0][3]); + break; + case 5: + result = plugin.npnInvokeTest(test[0][0], test[0][1], test[0][2], + test[0][3], test[0][4]); + case 6: + result = plugin.npnInvokeTest(test[0][0], test[0][1], test[0][2], + test[0][3], test[0][4], test[0][5]); + break; + case 7: + result = plugin.npnInvokeTest(test[0][0], test[0][1], test[0][2], + test[0][3], test[0][4], test[0][5], test[0][6]); + break; + default: + is(false, "bad number of test arguments"); + } + is(result, test[1], "npnInvokeTestFailed: " + plugin.getError()); + $("verbose").appendChild( + createEl('span', null, ((result == test[1] ? "pass" : "fail") + ": " + test[0]))); + if (result != test[1]) + $("verbose").appendChild(createEl("span", null, (" " + plugin.getError()))); + $("verbose").appendChild(createEl('br')); + } + + SimpleTest.finish(); + } + </script> + + <p id="display"></p> + + <embed id="plugin1" type="application/x-test" width="400" height="100"> + </embed> + + <div id="verbose"> + </div> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_npruntime_npninvokedefault.html b/dom/plugins/test/mochitest/test_npruntime_npninvokedefault.html new file mode 100644 index 000000000..79a75e755 --- /dev/null +++ b/dom/plugins/test/mochitest/test_npruntime_npninvokedefault.html @@ -0,0 +1,153 @@ +<html> +<head> + <title>NPN_Invoke_Default Tests</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + // global test function + function testMe(arg) { + var result = arg+arg; + for (var i = 1; i < arguments.length; i++) { + result += arguments[i] + arguments[i]; + } + return result; + } + + //// + // This test exercises NPN_InvokeDefault using the test plugin's + // npnInvokeDefaultTest method. This method invokes an object + // with a single parameter, and returns the result of the invocation. + // The test list below is used to drive the tests. Each member of the + // array contains three members: the object to invoke, an argument to + // invoke it with, and the expected result of the invocation. + // + var tests = [ + // Number object + ["Number", 3, 3], + ["Number", "3", 3], + ["Number", "0x20", 32], + ["Number", "three", Number.NaN], + ["Number", "1e+3", 1000], + ["Number", 5.6612, 5.6612], + // Array object + ["Array", 3, Array(3)], + // Boolean object + ["Boolean", 0, false], + ["Boolean", null, false], + ["Boolean", "", false], + ["Boolean", false, false], + ["Boolean", true, true], + ["Boolean", "true", true], + ["Boolean", "false", true], + ["Boolean", new Boolean(false), true], + ["Boolean", { "value": false }, true], + // Function object + ["Function", "return 3", Function("return 3")], + ["Function", "window.alert('test')", Function("window.alert('test')")], + // Object object + ["Object", undefined, Object()], + ["Object", null, Object()], + ["Object", true, new Boolean(true)], + ["Object", Boolean(), new Boolean(false)], + ["Object", "a string", new String("a string")], + ["Object", 3.14, new Number(3.14)], + ["Object", { "key1": "test", "key2": 15 }, { "key1": "test", "key2": 15 }], + ["Object", [1, 3, 5, 7, 9, 11, 13, 17], [1, 3, 5, 7, 9, 11, 13, 17]], + // RegExp object + ["RegExp", "...", RegExp("...")], + // String object + ["String", "testing", "testing"], + ["String", "test\u0020me", "test me"], + ["String", "314", "314"], + ["String", "true", "true"], + ["String", "null", "null"], + ["String", "2 + 2", String("2 + 2")], + ["String", ""], + // global functions + ["testMe", 3, 6], + ["testMe", "string", [1,2], "stringstring1,21,2"], + ["testMe", "me", "meme"], + ["testMe", undefined, Number.NaN], + ["testMe", [1, 2], "1,21,2"], + ["testMe", 3, 4, 14], + ["isNaN", "junk", true], + ["parseInt", "156", 156], + ["encodeURI", "a = b", "a%20=%20b"], + ]; + + function runTests() { + var plugin = document.getElementById("plugin1"); + + // Test calling NPN_InvokeDefault from within plugin code. + for (var test of tests) { + var result; + var expected = test[test.length - 1]; + switch (test.length) { + case 2: + result = plugin.npnInvokeDefaultTest(test[0]); + break; + case 3: + result = plugin.npnInvokeDefaultTest(test[0], test[1]); + break; + case 4: + result = plugin.npnInvokeDefaultTest(test[0], test[1], test[2]); + break; + } + // serialize the two values for easy + var json_expected = JSON.stringify(expected); + var json_result = JSON.stringify(result); + if (typeof(result) == "function") + json_result = result.toString(); + if (typeof(test[2]) == "function") + json_expected = expected.toString(); + is(json_result, json_expected, + "npnInvokeDefault returned an unexpected value"); + is(typeof(result), typeof(expected), + "npnInvokeDefaultTest return value was of unexpected type"); + var success = (json_result == json_expected && + typeof(result) == typeof(expected)); + $("verbose").appendChild( + createEl('span', null, ((success ? "pass" : "fail") + ": " + test[0] + "("))); + for (var i = 1; i < test.length - 1; i++) { + $("verbose").appendChild( + createEl('span', null, (JSON.stringify(test[i]) + (i < test.length - 2 ? "," : "")))); + } + $("verbose").appendChild( + createEl('span', null, (") == " + json_result + "(" + + typeof(result) + "), expected " + json_expected + "(" + + typeof(expected) + ")"))); + $("verbose").appendChild(createEl('br')); + } + + // Test calling the invokedefault method of plugin-defined object + is(plugin(), "Test Plug-in", + "calling NPN_InvokeDefault on plugin-defined Object doesn't work"); + is(plugin(1), "Test Plug-in;1", + "calling NPN_InvokeDefault on plugin-defined Object doesn't work"); + is(plugin("test"), "Test Plug-in;test", + "calling NPN_InvokeDefault on plugin-defined Object doesn't work"); + is(plugin(undefined, -1, null), "Test Plug-in;undefined;-1;null", + "calling NPN_InvokeDefault on plugin-defined Object doesn't work"); + + SimpleTest.finish(); + } + </script> + + <p id="display"></p> + + <embed id="plugin1" type="application/x-test" width="400" height="100"> + </embed> + + <div id="verbose"> + </div> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_object.html b/dom/plugins/test/mochitest/test_object.html new file mode 100644 index 000000000..093333ea5 --- /dev/null +++ b/dom/plugins/test/mochitest/test_object.html @@ -0,0 +1,510 @@ +<!DOCTYPE html> +<html> + <head> + <title>Plugin instantiation</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SpecialPowers.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <meta charset="utf-8"> + <body onload="onLoad()"> + <script class="testbody" type="text/javascript;version=1.8"> + + "use strict"; + SimpleTest.waitForExplicitFinish(); + + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + // This can go away once embed also is on WebIDL + let OBJLC = SpecialPowers.Ci.nsIObjectLoadingContent; + + // Use string modes in this test to make the test easier to read/debug. + // nsIObjectLoadingContent refers to this as "type", but I am using "mode" + // in the test to avoid confusing with content-type. + let prettyModes = {}; + prettyModes[OBJLC.TYPE_LOADING] = "loading"; + prettyModes[OBJLC.TYPE_IMAGE] = "image"; + prettyModes[OBJLC.TYPE_PLUGIN] = "plugin"; + prettyModes[OBJLC.TYPE_DOCUMENT] = "document"; + prettyModes[OBJLC.TYPE_NULL] = "none"; + + let body = document.body; + // A single-pixel white png + let testPNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AoIFiETNqbNRQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAACklEQVQIHWP4DwABAQEANl9ngAAAAABJRU5ErkJggg=='; + // An empty, but valid, SVG + let testSVG = 'data:image/svg+xml,<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"></svg>'; + // executeSoon wrapper to count pending callbacks + let pendingCalls = 0; + let afterPendingCalls = false; + + function runWhenDone(func) { + if (!pendingCalls) + func(); + else + afterPendingCalls = func; + } + function runSoon(func) { + pendingCalls++; + SimpleTest.executeSoon(function() { + func(); + if (--pendingCalls < 1 && afterPendingCalls) + afterPendingCalls(); + }); + } + function src(obj, state, uri) { + // If we have a running plugin, src changing should always throw it out, + // even if it causes us to load the same plugin again. + if (uri && runningPlugin(obj, state)) { + if (!state.oldPlugins) + state.oldPlugins = []; + try { + state.oldPlugins.push(obj.getObjectValue()); + } catch (e) { + ok(false, "Running plugin but cannot call getObjectValue?"); + } + } + + var srcattr; + if (state.tagName == "object") + srcattr = "data"; + else if (state.tagName == "embed") + srcattr = "src"; + else + ok(false, "Internal test fail: Why are we setting the src of an applet"); + + // Plugins should always go away immediately on src/data change + state.initialPlugin = false; + if (uri === null) { + removeAttr(obj, srcattr); + // TODO Bug 767631 - we don't trigger loadObject on UnsetAttr :( + forceReload(obj, state); + } else { + setAttr(obj, srcattr, uri); + } + } + // We have to be careful not to reach past the nsObjectLoadingContent + // prototype to touch generic element attributes, as this will try to + // spawn the plugin, breaking our ability to test for that. + function getAttr(obj, attr) { + return document.body.constructor.prototype.getAttribute.call(obj, attr); + } + function setAttr(obj, attr, val) { + return document.body.constructor.prototype.setAttribute.call(obj, attr, val); + } + function hasAttr(obj, attr) { + return document.body.constructor.prototype.hasAttribute.call(obj, attr); + } + function removeAttr(obj, attr) { + return document.body.constructor.prototype.removeAttribute.call(obj, attr); + } + function setDisplayed(obj, display) { + if (display) + removeAttr(obj, 'style'); + else + setAttr(obj, 'style', "display: none;"); + } + function displayed(obj) { + // Hacky, but that's all we use style for. + return !hasAttr(obj, 'style'); + } + function actualType(obj, state) { + return state.getActualType.call(obj); + } + function getMode(obj, state) { + return prettyModes[state.getDisplayedType.call(obj)]; + } + function runningPlugin(obj, state) { + return state.getHasRunningPlugin.call(obj); + } + + // TODO this is hacky and might hide some failures, but is needed until + // Bug 767635 lands -- which itself will probably mean tweaking this test. + function forceReload(obj, state) { + let attr; + if (state.tagName == "object") + attr = "data"; + else if (state.tagName == "embed") + attr = "src"; + + if (attr && hasAttr(obj, attr)) { + src(obj, state, getAttr(obj, attr)); + } else if (body.contains(obj)) { + body.appendChild(obj); + } else { + // Out of document nodes without data attributes simply can't be + // reloaded currently. Bug 767635 + } + }; + + // Make a list of combinations of sub-lists, e.g.: + // [ [a, b], [c, d] ] + // -> + // [ [a, c], [a, d], [b, c], [b, d] ] + function eachList() { + let all = []; + if (!arguments.length) + return all; + let list = Array.prototype.slice.call(arguments, 0); + for (let c of list[0]) { + if (list.length > 1) { + for (let x of eachList.apply(this,list.slice(1))) { + all.push((c.length ? [c] : []).concat(x)); + } + } else if (c.length) { + all.push([c]); + } + } + return all; + } + + let states = { + svg: function(obj, state) { + removeAttr(obj, "type"); + src(obj, state, testSVG); + state.noChannel = false; + state.expectedType = "image/svg"; + // SVGs are actually image-like subdocuments + state.expectedMode = "document"; + }, + image: function(obj, state) { + removeAttr(obj, "type"); + src(obj, state, testPNG); + state.noChannel = false; + state.expectedMode = "image"; + state.expectedType = "image/png"; + }, + plugin: function(obj, state) { + removeAttr(obj, "type"); + src(obj, state, "data:application/x-test,foo"); + state.noChannel = false; + state.expectedType = "application/x-test"; + state.expectedMode = "plugin"; + }, + pluginExtension: function(obj, state) { + src(obj, state, "./fake_plugin.tst"); + state.expectedMode = "plugin"; + state.pluginExtension = true; + state.noChannel = false; + }, + document: function(obj, state) { + removeAttr(obj, "type"); + src(obj, state, "data:text/plain,I am a document"); + state.noChannel = false; + state.expectedType = "text/plain"; + state.expectedMode = "document"; + }, + fallback: function(obj, state) { + removeAttr(obj, "type"); + state.expectedType = "application/x-unknown"; + state.expectedMode = "none"; + state.noChannel = true; + src(obj, state, null); + }, + addToDoc: function(obj, state) { + body.appendChild(obj); + }, + removeFromDoc: function(obj, state) { + if (body.contains(obj)) + body.removeChild(obj); + }, + // Set the proper type + setType: function(obj, state) { + if (state.expectedType) { + state.badType = false; + setAttr(obj, 'type', state.expectedType); + forceReload(obj, state); + } + }, + // Set an improper type + setWrongType: function(obj, state) { + // This should break no-channel-plugins but nothing else + state.badType = true; + setAttr(obj, 'type', "application/x-unknown"); + forceReload(obj, state); + }, + // Set a plugin type + setPluginType: function(obj, state) { + // If an object/embed has a type set to a plugin type, it should not + // use the channel type. + state.badType = false; + setAttr(obj, 'type', 'application/x-test'); + state.expectedType = "application/x-test"; + state.expectedMode = "plugin"; + forceReload(obj, state); + }, + noChannel: function(obj, state) { + src(obj, state, null); + state.noChannel = true; + state.pluginExtension = false; + }, + displayNone: function(obj, state) { + setDisplayed(obj, false); + }, + displayInherit: function(obj, state) { + setDisplayed(obj, true); + } + }; + + + function testObject(obj, state) { + // If our test combination both sets noChannel but no explicit type + // it shouldn't load ever. + let expectedMode = state.expectedMode; + let actualMode = getMode(obj, state); + + if (state.noChannel && !getAttr(obj, 'type')) { + // Some combinations of test both set no type and no channel. This is + // worth testing with the various combinations, but shouldn't load. + expectedMode = "none"; + } + + // Embed tags should always try to load a plugin by type or extension + // before falling back to opening a channel. See bug 803159 + if (state.tagName == "embed" && + (getAttr(obj, 'type') == "application/x-test" || state.pluginExtension)) { + state.noChannel = true; + } + + // with state.loading, unless we're loading with no channel, these types + // should still be in loading state pending a channel. + if (state.loading && (expectedMode == "image" || expectedMode == "document" || + (expectedMode == "plugin" && !state.initialPlugin && !state.noChannel))) { + expectedMode = "loading"; + } + + // With the exception of plugins with a proper type, nothing should + // load without a channel + if (state.noChannel && (expectedMode != "plugin" || state.badType) && + body.contains(obj)) { + expectedMode = "none"; + } + + // embed tags should reject documents, except for SVG images which + // render as such + if (state.tagName == "embed" && expectedMode == "document" && + actualType(obj, state) != "image/svg+xml") { + expectedMode = "none"; + } + + // Embeds with a plugin type should skip opening a channel prior to + // loading, taking only type into account. + if (state.tagName == 'embed' && getAttr(obj, 'type') == 'application/x-test' && + body.contains(obj)) { + expectedMode = "plugin"; + } + + if (!body.contains(obj) + && (!state.loading || expectedMode != "image") + && (!state.initialPlugin || expectedMode != "plugin")) { + // Images are handled by nsIImageLoadingContent so we dont track + // their state change as they're detached and reattached. All other + // types switch to state "loading", and are completely unloaded + expectedMode = "loading"; + } + + is(actualMode, expectedMode, "check loaded mode"); + + // If we're a plugin, check that we spawned successfully. state.loading + // is set if we haven't had an event loop since applying state, in which + // case the plugin would not have stopped yet if it was initially a + // plugin. + let shouldBeSpawnable = expectedMode == "plugin" && displayed(obj); + let shouldSpawn = shouldBeSpawnable && (!state.loading || state.initialPlugin); + let didSpawn = runningPlugin(obj, state); + is(didSpawn, !!shouldSpawn, "check plugin spawned is " + !!shouldSpawn); + + // If we are a plugin, scripting should work. If we're not spawned we + // should spawn synchronously. + let scripted = false; + try { + let x = obj.getObjectValue(); + scripted = true; + } catch(e) {} + is(scripted, shouldBeSpawnable, "check plugin scriptability"); + + // If this tag previously had other spawned plugins, make sure it + // respawned between then and now + if (state.oldPlugins && didSpawn) { + let didRespawn = false; + for (let oldp of state.oldPlugins) { + // If this returns false or throws, it's not the same plugin + try { + didRespawn = !obj.checkObjectValue(oldp); + } catch (e) { + didRespawn = true; + } + } + is(didRespawn, true, "Plugin should have re-spawned since old state ("+state.oldPlugins.length+")"); + } + } + + let total = 0; + let test_modes = { + // Just apply from_state then to_state + "immediate": function(obj, from_state, to_state, state) { + for (let from of from_state) + states[from](obj, state); + for (let to of to_state) + states[to](obj, state); + + // We don't spin the event loop between applying to_state and + // running tests, so some types are still loading + state.loading = true; + info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / immediate"); + testObject(obj, state); + + if (body.contains(obj)) + body.removeChild(obj); + + }, + // Apply states, spin event loop, run tests. + "cycle": function(obj, from_state, to_state, state) { + for (let from of from_state) + states[from](obj, state); + for (let to of to_state) + states[to](obj, state); + // Because re-appending to the document creates a script blocker, but + // plugins spawn asynchronously, we need to return to the event loop + // twice to ensure the plugin has been given a chance to lazily spawn. + runSoon(function() { runSoon(function() { + info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / cycle"); + testObject(obj, state); + + if (body.contains(obj)) + body.removeChild(obj); + }); }); + }, + // Apply initial state, spin event loop, apply final state, spin event + // loop again. + "cycleboth": function(obj, from_state, to_state, state) { + for (let from of from_state) { + states[from](obj, state); + } + runSoon(function() { + for (let to of to_state) { + states[to](obj, state); + } + // Because re-appending to the document creates a script blocker, + // but plugins spawn asynchronously, we need to return to the event + // loop twice to ensure the plugin has been given a chance to lazily + // spawn. + runSoon(function() { runSoon(function() { + info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / cycleboth"); + testObject(obj, state); + + if (body.contains(obj)) + body.removeChild(obj); + }); }); + }); + }, + // Apply initial state, spin event loop, apply later state, test + // immediately + "cyclefirst": function(obj, from_state, to_state, state) { + for (let from of from_state) { + states[from](obj, state); + } + runSoon(function() { + state.initialPlugin = runningPlugin(obj, state); + for (let to of to_state) { + states[to](obj, state); + } + info("["+(++total)+"] Testing [ " + from_state + " ] -> [ " + to_state + " ] / " + state.tagName + " / cyclefirst"); + // We don't spin the event loop between applying to_state and + // running tests, so some types are still loading + state.loading = true; + testObject(obj, state); + + if (body.contains(obj)) + body.removeChild(obj); + }); + }, + }; + + function test(testdat) { + // FIXME bug 1291854: Change back to lets when the test is fixed. + for (var from_state of testdat['from_states']) { + for (var to_state of testdat['to_states']) { + for (var mode of testdat['test_modes']) { + for (var type of testdat['tag_types']) { + runSoon(function () { + let obj = document.createElement(type); + obj.width = 1; obj.height = 1; + let state = {}; + state.noChannel = true; + state.tagName = type; + // Part of the test checks whether a plugin spawned or not, + // but touching the object prototype will attempt to + // synchronously spawn a plugin! We use this terrible hack to + // get a privileged getter for the attributes we want to touch + // prior to applying any attributes. + // TODO when embed goes away we wont need to check for + // QueryInterface any longer. + var lookup_on = obj.QueryInterface ? obj.QueryInterface(OBJLC): obj; + state.getDisplayedType = SpecialPowers.do_lookupGetter(lookup_on, 'displayedType'); + state.getHasRunningPlugin = SpecialPowers.do_lookupGetter(lookup_on, 'hasRunningPlugin'); + state.getActualType = SpecialPowers.do_lookupGetter(lookup_on, 'actualType'); + test_modes[mode](obj, from_state, to_state, state); + }); + } + } + } + } + } + + function onLoad() { + // Generic tests + test({ + 'tag_types': [ 'embed', 'object' ], + // In all three modes + 'test_modes': [ 'immediate', 'cycle', 'cyclefirst', 'cycleboth' ], + // Starting from a blank tag in and out of the document, a loading + // plugin, and no-channel plugin (initial types only really have + // odd cases with plugins) + 'from_states': [ + [ 'addToDoc' ], + [ 'plugin' ], + [ 'plugin', 'addToDoc' ], + [ 'plugin', 'noChannel', 'setType', 'addToDoc' ], + [], + ], + // To various combinations of loaded objects + 'to_states': eachList( + [ 'svg', 'image', 'plugin', 'document', '' ], + [ 'setType', 'setWrongType', 'setPluginType', '' ], + [ 'noChannel', '' ], + [ 'displayNone', 'displayInherit', '' ] + )}); + // Special case test for embed tags with plugin-by-extension + // TODO object tags should be tested here too -- they have slightly + // different behavior, but waiting on a file load requires a loaded + // event handler and wont work with just our event loop spinning. + test({ + 'tag_types': [ 'embed' ], + 'test_modes': [ 'immediate', 'cyclefirst', 'cycle', 'cycleboth' ], + 'from_states': eachList( + [ 'svg', 'plugin', 'image', 'document' ], + [ 'addToDoc' ] + ), + // Set extension along with valid ty + 'to_states': [ + [ 'pluginExtension' ] + ]}); + // Test plugin add/remove from document with adding/removing frame, with + // and without a channel. + test({ + 'tag_types': [ 'embed', 'object' ], // Ideally we'd test object too, but this gets exponentially long. + 'test_modes': [ 'immediate', 'cyclefirst', 'cycle' ], + 'from_states': [ [ 'displayNone', 'plugin', 'addToDoc' ], + [ 'displayNone', 'plugin', 'noChannel', 'addToDoc' ], + [ 'plugin', 'noChannel', 'addToDoc' ], + [ 'plugin', 'noChannel' ] ], + 'to_states': eachList( + [ 'displayNone', '' ], + [ 'removeFromDoc' ], + [ 'image', 'displayNone', '' ], + [ 'image', 'displayNone', '' ], + [ 'addToDoc' ], + [ 'displayInherit' ] + )}); + runWhenDone(() => SimpleTest.finish()); + } + </script> diff --git a/dom/plugins/test/mochitest/test_painting.html b/dom/plugins/test/mochitest/test_painting.html new file mode 100644 index 000000000..08ebd4675 --- /dev/null +++ b/dom/plugins/test/mochitest/test_painting.html @@ -0,0 +1,121 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for windowless plugin invalidation and expose events in clips</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <style> + div#container { + position: relative; + height: 30px; + background: blue; + } + div#clip { + overflow:hidden; + position:absolute; + left: 10.3px; + top: 9.7px; + width: 10px; + height: 0px; + background: red; + } + embed { + position:absolute; + } + embed#paint-waiter { + top: 0px; + left: 0px; + width: 1px; + height: 0px; + } + embed#clipped { + left: -5.3px; + top: -4.7px; + width: 20px; + height: 20px; + } + </style> +</head> +<body onload="initialize()"> + +<script type="application/javascript" src="plugin-utils.js"></script> +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + +var paint_waiter; +var clip; +var clipped; + +function initialize() { + paint_waiter = document.getElementById("paint-waiter"); + clip = document.getElementById("clip"); + clipped = document.getElementById("clipped"); + + waitForPaint(show); +} + +function show() { + paintCountIs(clipped, 0, "fully clipped plugin not painted"); + + clip.style.height = "10px"; + + // Capturing an image (as in a reftest) would force a repaint and use + // different paths for the image surface, so instead check the plugin's + // paint count. + waitForPaint(invalidate); +} + +function invalidate() { + paintCountIs(clipped, 1, "partially clipped plugin painted once"); + + clipped.setColor("FF00FF00"); // plugin invalidates + + waitForPaint(done); +} + +function done() { + paintCountIs(clipped, 2, "painted after invalidate"); + + SimpleTest.finish(); +} + +function waitForPaint(func) { + paint_waiter.last_paint_count = paint_waiter.getPaintCount(); + // Ensure the waiter has had a style change, so that this will + // change its size and cause a paint. + paint_waiter.style.backgroundColor = paint_waiter.style.backgroundColor == "blue" ? "yellow" : "blue"; + var flush = paint_waiter.offsetHeight; + paint_waiter.style.height = "1px"; + waitForPaintHelper(func); +} + +function waitForPaintHelper(func) { + if (paint_waiter.getPaintCount() != paint_waiter.last_paint_count) { + // hide the paint waiter + paint_waiter.style.height = "0px"; + setTimeout(func, 0); + return; + } + setTimeout(function() { waitForPaintHelper(func); }, 1000); +} + +</script> + +<p id="display"></p> +<div id="container"> + <embed id="paint-waiter" type="application/x-test"/> + <div id="clip"> + <embed id="clipped" type="application/x-test" + drawmode="solid" color="FF808080"/> + </div> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html b/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html new file mode 100644 index 000000000..65ea3498a --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for plugin child widgets not being invalidated by scrolling</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="initialize()"> +<script type="application/javascript"> +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, + "Test Plug-in"); +</script> + +<p id="display"> + <iframe id="i" src="plugin_scroll_invalidation.html" + width="50" height="50" scrolling="no"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +var scrolling; +var scrolling_plugins = []; +var paint_waiter; +var last_paint_counts; + +function initialize() { + scrolling = document.getElementById("i").contentWindow; + scrolling_plugins = scrolling.document.querySelectorAll("embed.scrolling"); + paint_waiter = scrolling.document.getElementById("paint-waiter"); + + scrolling.scrollTo(50, 45); + + is(paint_waiter.getPaintCount(), 0, "zero-sized plugin not painted"); + + waitForPaint(scrollAround); +} + +function scrollAround() { + var paints = getPaintCounts(); + + for (var i = 0; i < paints.length; ++i) { + isnot(paints[i], 0, "embed " + scrolling_plugins[i].id + " is painted"); + } + + last_paint_counts = paints; + + scrolling.scrollBy(-5, 5); + scrolling.scrollBy(5, 5); + scrolling.scrollBy(5, -5); + scrolling.scrollBy(-5, -5); + + scrolling.scrollTo(45, 45); + scrolling.scrollBy(10, 0); + scrolling.scrollBy(0, 10); + scrolling.scrollBy(-10, 0); + scrolling.scrollBy(0, -10); + + waitForPaint(done); +} + +function done() { + var paints = getPaintCounts(); + for (var i = 0; i < paints.length; ++i) { + is(paints[i], last_paint_counts[i], "embed " + scrolling_plugins[i].id + " is not painted on scroll"); + } + SimpleTest.finish(); +} + +// Waits for the paint_waiter plugin to be repainted and then +// calls 'func' to continue. +function waitForPaint(func) { + paint_waiter.last_paint_count = paint_waiter.getPaintCount(); + + paint_waiter.style.left = scrolling.scrollX + "px"; + paint_waiter.style.top = scrolling.scrollY + "px"; + + // Fiddle with the style in a way that should force some repainting + paint_waiter.style.width = + (paint_waiter.getBoundingClientRect().width + 1) + "px"; + paint_waiter.style.height = "1px"; + + function waitForPaintHelper() { + if (paint_waiter.getPaintCount() != paint_waiter.last_paint_count) { + setTimeout(func, 0); + return; + } + setTimeout(waitForPaintHelper, 0); + } + waitForPaintHelper(); +} + +function getPaintCounts() { + var result = []; + for (var i = 0; i < scrolling_plugins.length; ++i) { + result[i] = scrolling_plugins[i].getPaintCount(); + } + return result; +} + +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_plugin_scroll_painting.html b/dom/plugins/test/mochitest/test_plugin_scroll_painting.html new file mode 100644 index 000000000..9626156b6 --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_scroll_painting.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that scrolling a windowless plugin doesn't force us to repaint it</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTest()"> +<p id="display"></p> + <embed id="plugin" type="application/x-test" style="width:50px; height:10px; margin-top:20px;"></embed> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +var container = document.documentElement; +container.scrollTop = 0; +var plugin = document.getElementById("plugin"); +var pluginTop; +var beforeScrollPaintCount; + +function waitForScroll() { + if (plugin.getEdge(1) >= pluginTop) { + setTimeout(waitForScroll, 0); + return; + } + + is(plugin.getPaintCount(), beforeScrollPaintCount, "plugin should not paint due to scrolling"); + SimpleTest.finish(); +} + +function waitForInitialScroll() { + if (plugin.getEdge(1) >= pluginTop) { + setTimeout(waitForInitialScroll, 0); + return; + } + + pluginTop = plugin.getEdge(1); + beforeScrollPaintCount = plugin.getPaintCount(); + container.scrollTop = 20; + waitForScroll(); +} + +function runTest() { + document.body.offsetTop; + pluginTop = plugin.getEdge(1); + container.scrollTop = 10; + waitForInitialScroll(); +} +</script> + +<div style="height:4000px;"></div> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_plugin_tag_clicktoplay.html b/dom/plugins/test/mochitest/test_plugin_tag_clicktoplay.html new file mode 100644 index 000000000..0679e6795 --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_tag_clicktoplay.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> + <head> + <meta><charset="utf-8"/> + <title>Test Modifying Plugin click-to-play Flag</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + </head> + <body> + <script class="testbody" type="application/javascript"> + Components.utils.import("resource://gre/modules/Services.jsm"); + Services.prefs.setBoolPref("plugins.click_to_play", true); + var pluginHost = Components.classes["@mozilla.org/plugin/host;1"] + .getService(Components.interfaces.nsIPluginHost); + + var testPlugin = getTestPlugin(); + var secondTestPlugin = getTestPlugin("Second Test Plug-in"); + ok(testPlugin, "Should have Test Plug-in"); + ok(secondTestPlugin, "Should have Second Test Plug-in"); + + // make sure both start off as click-to-play + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_CLICKTOPLAY); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in"); + + testPlugin.enabledState = Components.interfaces.nsIPluginTag.STATE_ENABLED; + is(pluginHost.getStateForType("application/x-test"), Components.interfaces.nsIPluginTag.STATE_ENABLED, "click-to-play should be off for Test Plug-in now"); + is(pluginHost.getStateForType("application/x-second-test"), Components.interfaces.nsIPluginTag.STATE_CLICKTOPLAY, "click-to-play should still be on for the Second Test Plug-in"); + + testPlugin.enabledState = Components.interfaces.nsIPluginTag.STATE_CLICKTOPLAY; + is(pluginHost.getStateForType("application/x-test"), Components.interfaces.nsIPluginTag.STATE_CLICKTOPLAY, "click-to-play should be on for Test Plug-in now"); + + Services.prefs.clearUserPref("plugins.click_to_play"); + </script> + </body> +</html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_3rdparty.html b/dom/plugins/test/mochitest/test_pluginstream_3rdparty.html new file mode 100644 index 000000000..aa7dbb5a1 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_3rdparty.html @@ -0,0 +1,76 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<html> +<head> + <title>NPAPI NPN_GetURL NPStream Test</title> + <meta charset=UTF-8> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe"></iframe> + + <script> + /** + * Tests that we still properly do or don't send cookies for requests from + * plugins when the user has disabled 3rd-party cookies. See + * pluginstream.js where we verify that we get the same content as for XHR + * requests. + */ + SimpleTest.waitForExplicitFinish(); + function get_embed_elt() { + var e = document.createElement("embed"); + e.setAttribute("streammode", "normal"); + e.setAttribute("streamchunksize", "1024"); + e.setAttribute("frame", "testframe"); + e.setAttribute("id", "embedtest"); + e.setAttribute("style", "width: 400px; height: 100px;"); + e.setAttribute("type", "application/x-test"); + return e; + } + + function* test_runner() { + function create_embed(host) { + var e = get_embed_elt(); + + const url = + `http://${host}/tests/dom/plugins/test/mochitest/file_checkcookie.sjs`; + e.setAttribute('geturl', url); + document.body.appendChild(e); + + return new Promise(resolve => { + $('testframe').addEventListener("load", function loaded() { + $('testframe').removeEventListener("load", loaded); + resolve(); + }); + }); + } + + // Same origin + yield create_embed("mochi.test:8888"); + yield create_embed("example.org"); + } + + document.cookie = "found=a_cookie"; + var example_iframe = document.createElement("iframe"); + example_iframe.src = "http://example.org/tests/dom/plugins/test/mochitest/file_setcookie.html"; + example_iframe.addEventListener("load", () => { + $('testframe').addEventListener("load", () => frameLoaded(false, true)); + SpecialPowers.pushPrefEnv({ set: [[ 'network.cookie.cookieBehavior', 1 ]] }, + () => (spawn_task(test_runner).then(SimpleTest.finish))); + }); + document.body.appendChild(example_iframe); + </script> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_asfile.html b/dom/plugins/test/mochitest/test_pluginstream_asfile.html new file mode 100644 index 000000000..b021a74ce --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_asfile.html @@ -0,0 +1,33 @@ +<html> +<head> + <title>NPAPI NP_ASFILE NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - Similar to the above tests, but NPP_NewStream sets the stream mode + - to NP_ASFILE. In this test, stream data is written in a series of + - NPP_Write calls, as per NP_NORMAL, and also written to a file whose + - path is passed to NPP_StreamAsFile. The plugin compares the file + - and the stream to verify they're indentical, then passes the stream + - back to the browser via NPN_GetURL as above. + --> + <embed src="loremipsum_file.txt" streammode="asfile" + frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_asfileonly.html b/dom/plugins/test/mochitest/test_pluginstream_asfileonly.html new file mode 100644 index 000000000..96c9b85dc --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_asfileonly.html @@ -0,0 +1,30 @@ +<html> +<head> + <title>NPAPI NP_ASFILEONLY NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - Similar to above, but NPP_NewStream sets to the stream mode + - to NP_ASFILEONLY, so the entire stream is written to a file + - and the file is read by the plugin when NPP_StreamAsFile is called. + --> + <embed src="loremipsum_file.txt" streammode="asfileonly" + frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_err.html b/dom/plugins/test/mochitest/test_pluginstream_err.html new file mode 100644 index 000000000..0ac2a5efc --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_err.html @@ -0,0 +1,165 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=517078 + +Tests for plugin stream error conditions. +--> +<head> + <title>NPAPI Stream Error Tests</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runNextTest()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=517078"> + Mozilla Bug 517078</a> - Plugin Stream Error Tests +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<div id="test"> +<script class="testbody" type="text/javascript"> +//// +// These tests verify that nothing "bad" happens when a plugin returns an +// error from one of the NPP_ stream functions. "Bad" is defined here +// as the plugin being terminated, or NPP_ stream functions being +// called inappropriately by the browser after the plugin has returned +// a stream error. +// + +function $(id) { return document.getElementById(id); } + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + +var tests = [ + { + "src": "loremipsum.txt", + "streammode": "normal", + "functiontofail": "npp_newstream", + "failurecode": "1", + "frame": "testframe" + }, + { + "src": "loremipsum.txt", + "streammode": "normal", + "functiontofail": "npp_newstream", + "failurecode": "3", + "frame": "testframe" + }, + { + "src": "loremipsum.txt", + "streammode": "normal", + "functiontofail": "npp_newstream", + "failurecode": "5", + "frame": "testframe" + }, + { + "geturl": "loremipsum.txt", + "streammode": "normal", + "functiontofail": "npp_newstream", + "failurecode": "1", + "frame": "testframe" + }, + { + "src": "loremipsum.txt", + "streammode": "normal", + "functiontofail": "npp_write", + "frame": "testframe" + }, + { + "src": "loremipsum.txt", + "streammode": "asfile", + "functiontofail": "npp_write", + "frame": "testframe" + }, + { + "src": "loremipsum.txt", + "streammode": "normal", + "functiontofail": "npp_destroystream", + "failurecode": "1", + "frame": "testframe" + }, +]; + +function iframeonload(evt) { + var contentLength = evt.target.contentDocument.body.innerHTML.length; + var plugin = gTestWindow.document.getElementById("embedtest"); + var functionToFail = plugin.getAttribute("functiontofail"); + if (contentLength > 0) { + is(evt.target.contentDocument.body.innerHTML, "pass", + "test frame has unexpected content"); + setTimeout(function() { + // This verifies that the plugin hasn't been unloaded, and that + // no calls to NPP_ functions have been made unexpectedly. + is(plugin.getError(), "pass", "plugin reported an error"); + gTestWindow.close(); + setTimeout(runNextTest, 10); + }, functionToFail == "npp_newstream" ? 500 : 10); + } +} + +var index = 0; +var gTestWindow; +function runNextTest() { + if (index == tests.length * 2) { + SimpleTest.finish(); + return; + } + + gTestWindow = window.open("plugin_window.html", + "", + "width=620,height=320"); +} + +function continueTest() { + // We run each test as an embed and an object, as their initial stream + // handling differs. + var tag = index % 2 ? "embed" : "object"; + var test = tests[Math.floor(index / 2)]; + + var p = gTestWindow.document.createElement("p"); + p.innerHTML = "Plugin Stream Test " + index; + gTestWindow.document.getElementById("test").appendChild(p); + + if (test.frame) { + var iframe = gTestWindow.document.createElement("iframe"); + iframe.name = test.frame; + iframe.onload = iframeonload; + gTestWindow.document.getElementById("test").appendChild(iframe); + } + + var plugin = gTestWindow.document.createElement(tag); + plugin.setAttribute("id", "embedtest"); + plugin.setAttribute("style", "width: 400px; height: 100px;"); + plugin.setAttribute("type", "application/x-test"); + for (var name in test) { + if (tag == "embed") { + plugin.setAttribute(name, test[name]); + } else if (name == "src") { + plugin.setAttribute("data", test[name]); + } else { + var param = document.createElement("param"); + param.name = name; + param.value = test[name]; + plugin.appendChild(param); + } + } + gTestWindow.document.getElementById("test").appendChild(plugin); + + gTestWindow.document.getElementById("test") + .appendChild(document.createElement("br")); + + index++; +} + +</script> +</div> +</body> +</html> + diff --git a/dom/plugins/test/mochitest/test_pluginstream_geturl.html b/dom/plugins/test/mochitest/test_pluginstream_geturl.html new file mode 100644 index 000000000..fe69427a4 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_geturl.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPN_GetURL NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - The plugin reports that data can be sent to + - it in 1024-byte chunks, and the stream is initiated by a call to + - NPN_GetURL. + --> + <embed geturl="loremipsum.txt" streammode="normal" + streamchunksize="1024" frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + </body> + </html> +
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html b/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html new file mode 100644 index 000000000..ee4c2b119 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html @@ -0,0 +1,30 @@ +<body> +<head> + <title>NPAPI NPN_GetURLNotify Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - The stream is requested by + - the plugin using NPN_GetURLNotify, and the plugin does not send the + - stream back to the browser until NPP_URLNotify is called. + --> + <embed geturlnotify="loremipsum.txt" streammode="normal" + streamchunksize="1024" frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_newstream.html b/dom/plugins/test/mochitest/test_pluginstream_newstream.html new file mode 100644 index 000000000..3972fd7ed --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_newstream.html @@ -0,0 +1,32 @@ +<html> +<head> + <title>NPAPI NPN_NewStream NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - A stream is sent to the browser via NPP_NewStream, NP_NORMAL. + - When NPP_DestroyStream is called, the plugin sends the stream + - content back to the browser by calling NPN_NewStream and + - NPN_Write. The stream content should be displayed in the specified + - frame. + --> + <embed src="loremipsum.txt" streammode="normal" + newstream="true" frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_post.html b/dom/plugins/test/mochitest/test_pluginstream_post.html new file mode 100644 index 000000000..da12ff3b0 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_post.html @@ -0,0 +1,33 @@ +<html> +<head> + <title>NPAPI NPN_PostURL NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - In this test, a stream is sent to the plugin via NPP_NewStream, + - NP_NORMAL. When the stream is destroyed, the plugin posts the + - stream content to post.sjs via NPN_PostURL, with a frame specified + - to display the post's response. Post.sjs just reflects + - the body of the post back in the HTTP response, so the original + - stream content should end up being displayed in the frame. + --> + <embed src="loremipsum.txt" streammode="normal" + frame="testframe" posturl="post.sjs" postmode="frame" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_poststream.html b/dom/plugins/test/mochitest/test_pluginstream_poststream.html new file mode 100644 index 000000000..ed2e8ce2c --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_poststream.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPN_PostURL NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - Same as test_pluginstream_post.html, except NULL is passed to NPN_PostURL + - as the frame parameter, so the HTTP response to the post is passed to the + - plugin via NPP_NewStream. Once this stream is received, it's displayed + - in a frame in the browser via a call to NPN_GetURL. + --> + <embed src="loremipsum.txt" streammode="normal" + frame="testframe" posturl="post.sjs" postmode="stream" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_referer.html b/dom/plugins/test/mochitest/test_pluginstream_referer.html new file mode 100644 index 000000000..e1b63fb95 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_referer.html @@ -0,0 +1,55 @@ +<head> + <title>Do plugin stream requests send the Referer header correctly?</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var pending = 3; + function testDone() { + --pending; + if (0 == pending) + SimpleTest.finish() + } + + function runTests() { + var p = document.getElementById('plugin1'); + var p2 = document.getElementById('plugin2'); + + ok(p.streamTest('plugin-stream-referer.sjs', false, null, null, + function(r, t) { + is(r, 0, "GET plugin-stream-referer.sjs"); + is(t, "Referer found: " + window.location, + "GET Referer correct"); + testDone(); + }, null, true), "referer GET"); + + ok(p.streamTest('plugin-stream-referer.sjs', true, "Dummy Data", null, + function(r, t) { + is(r, 0, "POST plugin-stream-referer.sjs"); + is(t, "No Referer found", "POST Referer absent"); + testDone(); + }, null, true), "referer POST"); + + ok(p2.streamTest('plugin-stream-referer.sjs', false, null, null, + function(r, t) { + is(r, 0, "GET plugin-stream-referer.sjs (2)"); + var expectedreferer = String(window.location).replace("test_pluginstream_referer.html", "loremipsum.xtest"); + is(t, "Referer found: " + expectedreferer, + "GET Referer correct with plugin src"); + testDone(); + }, null, true), "referer GET (2)"); + + } + </script> + + <p id="display"></p> + + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> + <embed id="plugin2" type="application/x-test" src="loremipsum.xtest" width="200" height="200"></embed> diff --git a/dom/plugins/test/mochitest/test_pluginstream_seek.html b/dom/plugins/test/mochitest/test_pluginstream_seek.html new file mode 100644 index 000000000..6915a766e --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_seek.html @@ -0,0 +1,33 @@ +<body> +<head> + <title>NPAPI Seekable NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - Tests a seekable stream. Calls NPN_RequestRead with the specified + - range, and verifies that an NPP_Write call is made with the correct + - parameters, including the buffer data for the byte range. Once all + - calls to NPP_Write have finished, the plugin calls NPN_DestroyStream + - and then displays the entire stream's content in a browser frame via + - NPN_GetURL. + --> + <embed src="loremipsum.txt" streammode="seek" + frame="testframe" streamchunksize="1024" range="100,100" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> +</body> +</html>
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/test_pluginstream_seek_close.html b/dom/plugins/test/mochitest/test_pluginstream_seek_close.html new file mode 100644 index 000000000..46fd3962a --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_seek_close.html @@ -0,0 +1,45 @@ +<body> +<head> + <title>NPAPI Seekable NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +<script> + SimpleTest.expectAssertions(0, 1); + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function frameLoaded() { + var testframe = document.getElementById('testframe'); + var content = testframe.contentDocument.body.innerHTML; + if (!content.length) + return; + + ok(true, "We didn't crash"); + SimpleTest.finish(); + } +</script> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - Tests a seekable stream. Calls NPN_RequestRead with the specified + - range, and verifies that an NPP_Write call is made with the correct + - parameters, including the buffer data for the byte range. Once all + - calls to NPP_Write have finished, the plugin calls NPN_DestroyStream + - and then displays the entire stream's content in a browser frame via + - NPN_GetURL. + --> + <embed src="neverending.sjs" streammode="seek" closestream + frame="testframe" streamchunksize="1024" range="100,100" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_src.html b/dom/plugins/test/mochitest/test_pluginstream_src.html new file mode 100644 index 000000000..cf934d022 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_src.html @@ -0,0 +1,33 @@ +<html> +<head> + <title>NPAPI src="" NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - A stream is sent to the browser via NPP_NewStream, NP_NORMAL. + - The plugin reports that data can only be sent to it in 100-byte + - chunks. When NPP_DestroyStream is called, the plugin sends the stream + - content back to the browser by passing it as a data: url to + - NPN_GetURL, using a frame, so that the stream content should + - be displayed in the frame in the browser. + --> + <embed src="loremipsum.txt" streammode="normal" + streamchunksize="100" frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> +</body> +</html>
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/test_pluginstream_src_dynamic.html b/dom/plugins/test/mochitest/test_pluginstream_src_dynamic.html new file mode 100644 index 000000000..2a7db9916 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_src_dynamic.html @@ -0,0 +1,43 @@ +<html> +<head> + <title>NPAPI src="" NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - A stream is sent to the browser via NPP_NewStream, NP_NORMAL. + - The plugin reports that data can only be sent to it in 100-byte + - chunks. When NPP_DestroyStream is called, the plugin sends the stream + - content back to the browser by passing it as a data: url to + - NPN_GetURL, using a frame, so that the stream content should + - be displayed in the frame in the browser. + - + - We create the object element dynamically, which in some cases has caused us to deliver the data="" + - stream twice. This verifies that we only deliver the data="" stream once. + --> + + <script type="text/javascript"> + var e = document.createElement('object'); + e.setAttribute('data', 'loremipsum.xtest'); + e.setAttribute('type', 'application/x-test'); + e.setAttribute('streammode', 'normal'); + e.setAttribute('streamchunksize', '100'); + e.setAttribute('frame', 'testframe'); + e.setAttribute('style', 'width: 400px; height: 100px;'); + document.body.appendChild(e); + </script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_src_referer.html b/dom/plugins/test/mochitest/test_pluginstream_src_referer.html new file mode 100644 index 000000000..9e96016ff --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_src_referer.html @@ -0,0 +1,30 @@ +<head> + <title>Do plugin stream src requests send the Referer header correctly?</title> + <script type="application/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> + +<body> + <p id="display"></p> + + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function frameLoaded() { + var testframe = document.getElementById('pluginframe'); + var content = testframe.contentDocument.body.innerHTML; + if (!content.length) + return; + + is(content, "Referer found: " + window.location); + SimpleTest.finish(); + } + </script> + + <iframe name="pluginframe" id="pluginframe" onload="frameLoaded()"></iframe> + + <embed id="plugin" type="application/x-test" src="plugin-stream-referer.sjs" width="200" height="200" frame="pluginframe"></embed> + diff --git a/dom/plugins/test/mochitest/test_positioning.html b/dom/plugins/test/mochitest/test_positioning.html new file mode 100644 index 000000000..4a4fc1d3d --- /dev/null +++ b/dom/plugins/test/mochitest/test_positioning.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test whether windowless plugins receive correct visible/invisible notifications.</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + + <style type="text/css"> + body { + height: 10000px; + } + </style> + +<body onload="startTest()"> + <p id="display"></p> + + <script type="application/javascript;version=1.8"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var p = null; + + function startTest() { + p = document.getElementById('theplugin'); + + // Wait for the plugin to have painted once + var interval = setInterval(function() { + if (!p.getPaintCount()) + return; + + clearInterval(interval); + doScroll(); + }, 100); + } + + const kScrollAmount = 1000; + var startY; + + function doScroll() { + let [x, y, w, h] = p.getWindowPosition(); + startY = y; + + scrollBy(0, kScrollAmount); + setTimeout(checkScroll, 500); + } + + function checkScroll() { + let [x, y, w, h] = p.getWindowPosition(); + + is(y, startY - kScrollAmount, "Window should be informed of its new position."); + SimpleTest.finish(); + } + </script> + + <embed id="theplugin" type="application/x-test" width="200" height="200"></embed> diff --git a/dom/plugins/test/mochitest/test_privatemode_perwindowpb.xul b/dom/plugins/test/mochitest/test_privatemode_perwindowpb.xul new file mode 100644 index 000000000..ae6c9d292 --- /dev/null +++ b/dom/plugins/test/mochitest/test_privatemode_perwindowpb.xul @@ -0,0 +1,120 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="NPAPI Private Mode Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +<embed id="plugin2" type="application/x-test" width="200" height="200"></embed> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +function runTests() { + // Allow all cookies, then run the actual tests + SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, runTestsCallback); +} + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +function whenDelayedStartupFinished(aWindow, aCallback) { + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + SimpleTest.executeSoon(aCallback); + } + }, "browser-delayed-startup-finished", false); +} + +function runTestsCallback() { + var pluginElement1 = document.getElementById("plugin1"); + var pluginElement2 = document.getElementById("plugin2"); + + var state1 = false; + var state2 = false; + var exceptionThrown = false; + + try { + state1 = pluginElement1.queryPrivateModeState(); + state2 = pluginElement2.queryPrivateModeState(); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Exception thrown getting private mode state."); + is(state1, false, "Browser returned incorrect private mode state."); + is(state2, false, "Browser returned incorrect private mode state."); + + pluginElement1.setCookie("foo"); + is(pluginElement1.getCookie(), "foo", "Cookie was set and retrieved correctly in public mode."); + + // open a window with private mode and get the references of the elements. + var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + var contentPage = getRootDirectory(window.location.href) + "privatemode_perwindowpb.xul"; + + function testOnWindow(aIsPrivate, aCallback) { + var win = mainWindow.OpenBrowserWindow({private: aIsPrivate}); + whenDelayedStartupFinished(win, function () { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href == "about:privatebrowsing") { + win.gBrowser.loadURI(contentPage); + return; + } + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + win.gBrowser.selectedBrowser.focus(); + SimpleTest.executeSoon(function() { aCallback(win); }); + }, true); + SimpleTest.executeSoon(function() { win.gBrowser.loadURI(contentPage); }); + }); + } + + testOnWindow(true, function(aWin) { + pluginElement1 = aWin.gBrowser.contentDocument.getElementById("plugin1"); + pluginElement2 = aWin.gBrowser.contentDocument.getElementById("plugin2"); + + var officialState1, officialState2; + try { + officialState1 = pluginElement1.queryPrivateModeState(); + officialState2 = pluginElement2.queryPrivateModeState(); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Exception thrown getting private mode state."); + is(officialState1, true, "Querying private mode reported incorrectly"); + is(officialState2, true, "Querying private mode reported incorrectly"); + + // It would be nice to assert that we don't see the public cookie in private mode, + // but the NPAPI complains when the resulting string is empty. + // is(pluginElement1.getCookie(), "", "Public cookie was not retrieved in private mode."); + pluginElement1.setCookie("bar"); + is(pluginElement1.getCookie(), "bar", "Cookie was set and retrieved correctly in private mode."); + + aWin.close(); + + pluginElement1 = document.getElementById("plugin1"); + is(pluginElement1.getCookie(), "foo", "Private cookie was not retrieved in public mode."); + + SimpleTest.finish(); + }); +} +]]> +</script> +</window> diff --git a/dom/plugins/test/mochitest/test_propertyAndMethod.html b/dom/plugins/test/mochitest/test_propertyAndMethod.html new file mode 100644 index 000000000..6e638e419 --- /dev/null +++ b/dom/plugins/test/mochitest/test_propertyAndMethod.html @@ -0,0 +1,51 @@ +<html> + <head> + <title>NPObject with property and method with the same name</title> + + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + </head> + + <body onload="run()"> + + <script class="testbody" type="application/javascript"> + if (typeof Object.getPrototypeOf !== "function") { + if (typeof "test".__proto__ === "object") { + Object.getPrototypeOf = function(object) { + return object.__proto__; + }; + } else { + Object.getPrototypeOf = function(object) { + // May break if the constructor has been tampered with + return object.constructor.prototype; + }; + } + } + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function run() { + var plugin = document.getElementById("plugin"); + var pluginProto = Object.getPrototypeOf(plugin); + + delete pluginProto.propertyAndMethod; + ok(isNaN(plugin.propertyAndMethod + 0), "Shouldn't be set yet!"); + + plugin.propertyAndMethod = 5; + is(+plugin.propertyAndMethod, 5, "Should be set to 5!"); + + delete pluginProto.propertyAndMethod; + ok(isNaN(plugin.propertyAndMethod + 0), "Shouldn't be set any more!"); + + var res = plugin.propertyAndMethod(); + is(res, 5, "Method invocation should return 5!"); + + SimpleTest.finish(); + } + </script> + + <embed id="plugin" type="application/x-test" wmode="window"></embed> + </body> +</html> diff --git a/dom/plugins/test/mochitest/test_queryCSSZoomFactor.html b/dom/plugins/test/mochitest/test_queryCSSZoomFactor.html new file mode 100644 index 000000000..682d23ae6 --- /dev/null +++ b/dom/plugins/test/mochitest/test_queryCSSZoomFactor.html @@ -0,0 +1,48 @@ +<html> + <head> + <title>NPAPI NPNVcontentsScaleFactor Test</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + </head> + + <body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function checkZoomFactor(zoomFactor, expectedValue) { + // Done as if/ok instead of is() so we don't spam test results + if (isNaN(zoomFactor)) { + ok(false, "Return should be valid when getting CSS zoom factor"); + } + return (expectedValue - zoomFactor) < 0.00001; + } + + function testZoom() { + var pluginElement = document.getElementById("plugin"); + // setTimeout loop on value checks, as zoom value updates can take some + // time and we don't have a good event to listen for. + if (!checkZoomFactor(pluginElement.queryCSSZoomFactorGetValue(), 2.0) || + !checkZoomFactor(pluginElement.queryCSSZoomFactorSetValue(), 2.0)) { + setTimeout(testZoom, 0); + return; + } + ok(true, "Zoom values set to 2.0 as expected"); + // set back to 1 when we're done otherwise later tests can fail + SpecialPowers.setFullZoom(window, 1.0); + SimpleTest.finish(); + } + + function runTests() { + var pluginElement = document.getElementById("plugin"); + // Don't check SetValue yet, needs to happen after zoom has been explicitly set. + ok(checkZoomFactor(pluginElement.queryCSSZoomFactorGetValue(), 1.0), "Zoom values set to 1.0 as expected"); + SpecialPowers.setFullZoom(window, 2.0); + // Check for new zoom value sometime after we've spun event loop to repaint. + setTimeout(testZoom, 0); + } + </script> + + <embed id="plugin" type="application/x-test" width="400" height="400"></embed> + </body> +</html> diff --git a/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html b/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html new file mode 100644 index 000000000..c1f8919ab --- /dev/null +++ b/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPNVcontentsScaleFactor Test</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var pluginElement = document.getElementById("plugin"); + var contentsScaleFactor; + var exceptionThrown = false; + try { + contentsScaleFactor = pluginElement.queryContentsScaleFactor(); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Exception thrown getting contents scale factor."); + is(isNaN(contentsScaleFactor), false, "Invalid return getting contents scale factor"); + ok(true, "Got Scale Factor of " + contentsScaleFactor); + SimpleTest.finish(); + } + </script> + + <embed id="plugin" type="application/x-test" width="400" height="400"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html b/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html new file mode 100644 index 000000000..6f11332cb --- /dev/null +++ b/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPNVcontentsScaleFactor Test</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="utils.js"></script> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var pluginElement = document.getElementById("plugin"); + var contentsScaleFactor; + var exceptionThrown = false; + try { + contentsScaleFactor = pluginElement.queryContentsScaleFactor(); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Exception thrown getting contents scale factor."); + is(isNaN(contentsScaleFactor), false, "Invalid return getting contents scale factor"); + ok(true, "Got Scale Factor of " + contentsScaleFactor); + SimpleTest.finish(); + } + </script> + + <embed id="plugin" type="application/x-test" width="400" height="400" wmode="window"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_redirect_handling.html b/dom/plugins/test/mochitest/test_redirect_handling.html new file mode 100644 index 000000000..7d60ec06e --- /dev/null +++ b/dom/plugins/test/mochitest/test_redirect_handling.html @@ -0,0 +1,67 @@ +<html> +<head> + <title>Basic NPAPI Redirect Handling</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> +</head> +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var p = null; + + var redirectingURL = "307-xo-redirect.sjs"; + var redirectTargetURL = "http://example.org/tests/dom/plugins/test/mochitest/loremipsum.txt"; + + var expectedWriteURL = ""; + var expectedNotifyStatus = -1; + + function redirectCallback(url, httpStatus) { + is(url, redirectTargetURL, "Test for expected redirect notify URL."); + is(httpStatus, 307, "Test for expected http redirect status."); + } + + function writeCallback(url) { + is(url, expectedWriteURL, "Test for expected stream write URL."); + } + + function notifyCallback(status, data) { + is(status, expectedNotifyStatus, "Test for expected stream notification status."); + runNextTest(); + } + + function test1() { + expectedWriteURL = ""; + expectedNotifyStatus = 2; + + p.streamTest(redirectingURL, false, null, writeCallback, notifyCallback, redirectCallback, false); + } + + function test2() { + expectedWriteURL = redirectTargetURL; + expectedNotifyStatus = 0; + + p.streamTest(redirectingURL, false, null, writeCallback, notifyCallback, redirectCallback, true); + } + + var tests = [test1, test2]; + var currentTest = -1; + function runNextTest() { + currentTest++; + if (currentTest < tests.length) { + tests[currentTest](); + } else { + SimpleTest.finish(); + } + } + + function runTests() { + p = document.getElementById("plugin1"); + runNextTest(); + } + </script> + + <embed id="plugin1" type="application/x-test" width="200" height="200"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html b/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html new file mode 100644 index 000000000..e39b3217a --- /dev/null +++ b/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<!-- bug 820708 --> +<html> + <head> + <meta><charset="utf-8"/> + <title>Test Refreshing navigator.plugins (bug 820708)</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + </head> + <body> + <script class="testbody" type="application/javascript"> + "use strict"; + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var pluginHost = Components.classes["@mozilla.org/plugin/host;1"] + .getService(Components.interfaces.nsIPluginHost); + var pluginTags = pluginHost.getPluginTags(); + var nextTest = null; + var obsService = Components.classes["@mozilla.org/observer-service;1"] + .getService(Components.interfaces.nsIObserverService); + var observer = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == "plugin-info-updated") { + SimpleTest.executeSoon(nextTest); + } + } + }; + obsService.addObserver(observer, "plugin-info-updated", false); + + var navTestPlugin1 = navigator.plugins.namedItem("Test Plug-in"); + ok(navTestPlugin1, "navigator.plugins should have Test Plug-in"); + var tagTestPlugin = null; + for (var plugin of pluginTags) { + if (plugin.name == navTestPlugin1.name) { + tagTestPlugin = plugin; + break; + } + } + ok(tagTestPlugin, "plugin tags should have Test Plug-in"); + var mimeType = tagTestPlugin.getMimeTypes()[0]; + ok(mimeType, "should have a MIME type for Test Plug-in"); + ok(navigator.mimeTypes[mimeType], "navigator.mimeTypes should have an entry for '" + mimeType + "'"); + ok(!tagTestPlugin.disabled, "test plugin should not be disabled"); + + nextTest = testPart2; + tagTestPlugin.enabledState = Components.interfaces.nsIPluginTag.STATE_DISABLED; + + function testPart2() { + var navTestPlugin2 = navigator.plugins.namedItem("Test Plug-in"); + ok(!navTestPlugin2, "now navigator.plugins should not have Test Plug-in"); + ok(!navigator.mimeTypes[mimeType], "now navigator.mimeTypes should not have an entry for '" + mimeType + "'"); + + nextTest = testPart3; + tagTestPlugin.enabledState = Components.interfaces.nsIPluginTag.STATE_ENABLED; + } + + function testPart3() { + var navTestPlugin3 = navigator.plugins.namedItem("Test Plug-in"); + ok(navTestPlugin3, "now navigator.plugins should have Test Plug-in again"); + ok(navigator.mimeTypes[mimeType], "now navigator.mimeTypes should have an entry for '" + mimeType + "' again"); + obsService.removeObserver(observer, "plugin-info-updated"); + SimpleTest.finish(); + } + </script> + </body> +</html> diff --git a/dom/plugins/test/mochitest/test_secondPlugin.html b/dom/plugins/test/mochitest/test_secondPlugin.html new file mode 100644 index 000000000..4c1f051ca --- /dev/null +++ b/dom/plugins/test/mochitest/test_secondPlugin.html @@ -0,0 +1,73 @@ +<html> + <head> + <title>Second Test Plug-in Test</title> + + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + </head> + + <body onload="run()"> + <script class="testbody" type="application/javascript"> + "use strict"; + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_DISABLED, "Second Test Plug-in"); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Java Test Plug-in"); + + function findPlugin(pluginName) { + for (var i = 0; i < navigator.plugins.length; i++) { + var plugin = navigator.plugins[i]; + if (plugin.name === pluginName) { + return plugin; + } + } + return null; + } + + function findMimeType(mimeTypeType) { + for (var i = 0; i < navigator.mimeTypes.length; i++) { + var mimeType = navigator.mimeTypes[i]; + if (mimeType.type === mimeTypeType) { + return mimeType; + } + } + return null; + } + + function run() { + var pluginElement = document.getElementById("plugin"); + is(pluginElement.identifierToStringTest("foo"), "foo", "Should be able to call a function provided by the plugin"); + + pluginElement = document.getElementById("disabledPlugin"); + is(typeof pluginElement.identifierToStringTest, "undefined", "Should NOT be able to call a function on a disabled plugin"); + + pluginElement = document.getElementById("clickToPlayPlugin"); + is(typeof pluginElement.identifierToStringTest, "undefined", "Should NOT be able to call a function on a click-to-play plugin"); + + ok(navigator.plugins["Test Plug-in"], "Should have queried a plugin named 'Test Plug-in'"); + ok(!navigator.plugins["Second Test Plug-in"], "Should NOT have queried a disabled plugin named 'Second Test Plug-in'"); + ok(navigator.plugins["Java Test Plug-in"], "Should have queried a click-to-play plugin named 'Java Test Plug-in'"); + + ok(findPlugin("Test Plug-in"), "Should have found a plugin named 'Test Plug-in'"); + ok(!findPlugin("Second Test Plug-in"), "Should NOT found a disabled plugin named 'Second Test Plug-in'"); + ok(findPlugin("Java Test Plug-in"), "Should have found a click-to-play plugin named 'Java Test Plug-in'"); + + ok(navigator.mimeTypes["application/x-test"], "Should have queried a MIME type named 'application/x-test'"); + ok(!navigator.mimeTypes["application/x-second-test"], "Should NOT have queried a disabled type named 'application/x-second-test'"); + ok(navigator.mimeTypes["application/x-java-test"], "Should have queried a click-to-play MIME type named 'application/x-java-test'"); + + ok(findMimeType("application/x-test"), "Should have found a MIME type named 'application/x-test'"); + ok(!findMimeType("application/x-second-test"), "Should NOT have found a disabled MIME type named 'application/x-second-test'"); + ok(findMimeType("application/x-java-test"), "Should have found a click-to-play MIME type named 'application/x-java-test'"); + + SimpleTest.finish(); + } + </script> + + <object id="plugin" type="application/x-test" width=200 height=200></object> + <object id="disabledPlugin" type="application/x-second-test" width=200 height=200></object> + <object id="clickToPlayPlugin" type="application/x-java-test" width=200 height=200></object> + </body> +</html> diff --git a/dom/plugins/test/mochitest/test_src_url_change.html b/dom/plugins/test/mochitest/test_src_url_change.html new file mode 100644 index 000000000..8e8f4d326 --- /dev/null +++ b/dom/plugins/test/mochitest/test_src_url_change.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test changing src attribute</title> + <script type="text/javascript" src="/MochiKit/packed.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTests()"> + <script type="application/javascript;version=1.8"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var destroyed = false; + function onDestroy() { + destroyed = true; + } + + function runTests() { + p = document.getElementById('plugin1'); + + p.startWatchingInstanceCount(); + p.callOnDestroy(onDestroy); + + p.setAttribute("src", "loremipsum.txt"); + + is(destroyed, true, "Instance should have been destroyed."); + is(p.getInstanceCount(), 1, "One new instance should have been created."); + + p.stopWatchingInstanceCount(); + + SimpleTest.finish(); + } + </script> + + <p id="display"></p> + + <embed id="plugin1" src="about:blank" type="application/x-test" width="200" height="200"></embed> +</body> +</html> + diff --git a/dom/plugins/test/mochitest/test_streamNotify.html b/dom/plugins/test/mochitest/test_streamNotify.html new file mode 100644 index 000000000..d1aa8be8d --- /dev/null +++ b/dom/plugins/test/mochitest/test_streamNotify.html @@ -0,0 +1,89 @@ +<head> + <title>NPN_Get/PostURLNotify tests</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var pending = 5; + function testDone() { + dump("testDone: " + pending + "\n") + --pending; + + // Wait for a bit so that any unexpected notifications from the + // malformed data or null-post tests are received. + if (0 == pending) + window.setTimeout(SimpleTest.finish, 2000); + } + + function runTests() { + var p = document.getElementById('plugin1'); + + ok(p.streamTest("loremipsum.txt", false, null, null, + function(r, t) { + is(r, 0, "GET loremipsum.txt"); + is(t.substr(0, 11), "Lorem ipsum", + "GET loremipsum.txt correct"); + testDone(); + }, null, true), "streamTest GET"); + + ok(!p.streamTest("post.sjs", true, null, null, + function(r, t) { + ok(false, "Shouldn't get callback from null post"); + }, null, true), "streamTest POST null postdata"); + + ok(p.streamTest("post.sjs", true, "Something good", null, + function(r, t) { + is(r, 0, "POST something good"); + is(t, "Something good", "POST non-null correct"); + testDone(); + }, null, true), "streamTest POST valid postdata"); + + ok(p.streamTest("http://example.invalid/", false, null, null, + function(r, t) { + is(r, 1, "Shouldn't load example.invalid DNS name"); + testDone(); + }, null, true), "streamTest GET bad DNS"); + + ok(!p.streamTest("http://localhost:-8/", false, null, null, + function(r, t) { + ok(false, "Shouldn't get callback from malformed URI"); + }, null, true), "streamTest GET invalid URL"); + + ok(p.streamTest("javascript:'Hello';", false, null, null, + function(r, t) { + is(r, 0, "GET javascript: URI"); + is(t, "Hello", "GET javascript: URI correct"); + testDone(); + }, null, true), "streamTest GET javascript: URI"); + +/* + * XXX/cjones: disabled for now because it appears to be hard to make + * mochitest ignore the malformed javascript + + ok(!p.streamTest("javascript:syntax##$&*@error-*", false, null, + function(r, t) { + is(r, 1, "Shouldn't load invalid javascript: URI"); + testDone(); + }), "streamTest GET bad javascript: URI"); +*/ + + ok(p.streamTest("data:text/plain,World", false, null, null, + function(r, t) { + is(r, 0, "GET data: URI"); + is(t, "World", "GET data: URI correct"); + testDone(); + }, null, true), "streamTest GET data: URI"); + + ok(!p.streamTest("data:malformed?", false, null, null, + function(r, t) { + todo(false, "Shouldn't get callback for invalid data: URI"); + }, null, true), "streamTest GET bad data: URI"); + } + </script> + + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> diff --git a/dom/plugins/test/mochitest/test_streamatclose.html b/dom/plugins/test/mochitest/test_streamatclose.html new file mode 100644 index 000000000..f308d7d79 --- /dev/null +++ b/dom/plugins/test/mochitest/test_streamatclose.html @@ -0,0 +1,46 @@ +<html> +<head> + <title>Stream open at NPP_Destroy</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css"> + +<body onload="startTest()"> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var urlnotified = false; + + var p = null; + + function startTest() { + p = document.getElementById('embedtest'); + ok(p.streamTest("neverending.sjs", false, null, null, + function(r, t) { + is(r, 2, "Stream should have failed"); + urlnotified = true; + }, null, true), "neverending.sjs started successfully"); + + setTimeout(removePlugin, 500); + } + + function removePlugin() { + document.body.removeChild(p); // Fires NPP_Destroy immediately + SimpleTest.executeSoon(done); + } + + function done() { + ok(urlnotified, "NPP_URLNotify should be called if streams are active at NPP_Destroy"); + SimpleTest.finish(); + } + + </script> + + <p id="display"></p> + + <embed id="embedtest" + style="width: 400px; height: 100px;" type="application/x-test"></embed> diff --git a/dom/plugins/test/mochitest/test_stringHandling.html b/dom/plugins/test/mochitest/test_stringHandling.html new file mode 100644 index 000000000..023fcfcb0 --- /dev/null +++ b/dom/plugins/test/mochitest/test_stringHandling.html @@ -0,0 +1,35 @@ +<html> +<head> + <title>NPAPI string test</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + try { + var plugin = document.getElementById("plugin1"); + var badData = 'foo ' + '\x00'.repeat(260000); + var ret = plugin.echoString(badData); + ok(true, "Did not crash."); + is(ret, badData, "Returned string should equal what we passed in."); + } catch (e) { + ok(false, "Failed to call plugin.echoString() properly."); + } finally { + SimpleTest.finish(); + } + } + </script> + + <p id="display"></p> + + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_twostreams.html b/dom/plugins/test/mochitest/test_twostreams.html new file mode 100644 index 000000000..c2b3672c8 --- /dev/null +++ b/dom/plugins/test/mochitest/test_twostreams.html @@ -0,0 +1,46 @@ +<html> +<head> + <title>Dual NPAPI NP_ASFILEONLY NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <script type="text/javascript"> + SimpleTest.expectAssertions(0, 2); + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var framesToLoad = 2; + function frameLoaded(id) { + var frame = document.getElementById('testframe' + id); + if (!frame.contentDocument.body.innerHTML.length) + return; + + --framesToLoad; + if (0 == framesToLoad) { + is(document.getElementById('testframe1').contentDocument.body.innerHTML, + document.getElementById('testframe2').contentDocument.body.innerHTML, + "Frame contents should match"); + SimpleTest.finish(); + } + } + </script> + + <iframe id="testframe1" name="testframe1" onload="frameLoaded(1)"></iframe> + <iframe id="testframe2" name="testframe2" onload="frameLoaded(2)"></iframe> + + <embed src="loremipsum_nocache.txt" streammode="asfileonly" + frame="testframe1" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + <embed src="loremipsum_nocache.txt" streammode="asfileonly" + frame="testframe2" + id="embedtest2" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + diff --git a/dom/plugins/test/mochitest/test_visibility.html b/dom/plugins/test/mochitest/test_visibility.html new file mode 100644 index 000000000..943fddaf4 --- /dev/null +++ b/dom/plugins/test/mochitest/test_visibility.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test whether windowless plugins receive correct visible/invisible notifications.</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + + <style> + .hidden { visibility: hidden; } + </style> + +<body onload="startTest()"> + <p id="display"></p> + + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var didPaint = function() {}; + + function startTest() { + if (p.getPaintCount() < 1) { + setTimeout(startTest, 0); + return; + } + + didPaint = function() { + ok(false, "Plugin should not paint until it is visible!"); + }; + + ok(!p.isVisible(), "Plugin should not be visible."); + paintCountIs(p, 0, "Plugin should not have painted."); + + didPaint = part2; + + p.style.visibility = 'visible'; + } + + function part2() { + ok(p.isVisible(), "Plugin should now be visible."); + paintCountIs(p, 1, "Plugin should have painted once."); + + didPaint = part3; + + p.setColor('FF0000FF'); // this causes an invalidate/repaint + } + + const kTimeout = 5000; // 5 seconds + var part4GiveUp; + var part4Interval; + + function part3() { + ok(p.isVisible(), "Plugin should still be visible."); + paintCountIs(p, 2, "Plugin should have painted twice."); + + didPaint = function() { + ok(false, "Plugin should not paint when it is invisible."); + }; + + p.style.visibility = 'hidden'; + + part4GiveUp = Date.now() + kTimeout; + part4Interval = setInterval(part4, 100); + } + + function part4() { + if (p.isVisible()) { + if (Date.now() < part4GiveUp) + return; + + ok(false, "Plugin never became invisible in part4."); + SimpleTest.finish(); + return; + } + + clearInterval(part4Interval); + + ok(true, "Plugin became invisible again."); + p.setColor('FF00FF00'); + setTimeout(SimpleTest.finish, 500); + // wait to make sure we don't actually paint + } + + function inPaint() { + // We're actually in the middle of painting the plugin so don't do anything + // complex here, for the sake of cases where async plugin painting isn't + // enabled yet + setTimeout(didPaint, 0); + // Don't run that didPaint callback again + didPaint = function() {}; + } + </script> + + <embed id="theplugin" class="hidden" type="application/x-test" drawmode="solid" color="FFFF0000" paintscript="inPaint()"></embed> + + <script type="application/javascript"> + var p = document.getElementById('theplugin'); + </script> diff --git a/dom/plugins/test/mochitest/test_windowed_invalidate.html b/dom/plugins/test/mochitest/test_windowed_invalidate.html new file mode 100644 index 000000000..a19e4c8c5 --- /dev/null +++ b/dom/plugins/test/mochitest/test_windowed_invalidate.html @@ -0,0 +1,66 @@ +<html> +<head> + <title>Test NPN_Invalidate working for a windowed plugin</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var lastPaintCount; + var p = null; + + function checkPainted() { + if (p.getPaintCount() > lastPaintCount) { + ok(true, "Plugin did repaint"); + SimpleTest.finish(); + } else { + setTimeout(checkPainted, 100); + } + } + + function doTest() { + // Cause the plugin to invalidate itself using NPN_Invalidate, + // and then wait for the plugin's paintCount to increase. This is the + // simplest way to check that a windowed plugin has repainted. + p.setColor("FF00FF00"); + checkPainted(); + } + + function checkPaintCountStabilized() { + // Wait for the paint count to stabilize (i.e. doesn't change for a full + // second), so that all buffered-up painting is hopefully finished, + // before running the test + lastPaintCount = p.getPaintCount(); + setTimeout(function() { + var newCount = p.getPaintCount(); + if (newCount == lastPaintCount) { + doTest(); + } else { + checkPaintCountStabilized(); + } + }, 1000); + } + + function runTests() { + p = document.getElementById("p"); + checkPaintCountStabilized(); + } + </script> + + <p id="display"></p> + + <embed id="p" type="application/x-test" wmode="window" drawmode="solid" + color="FFFF0000"> + </embed> + + <div id="verbose"> + </div> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_windowless_flash.html b/dom/plugins/test/mochitest/test_windowless_flash.html new file mode 100644 index 000000000..8274a78ae --- /dev/null +++ b/dom/plugins/test/mochitest/test_windowless_flash.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTests()"> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var p1 = document.getElementById('plugin1'); + var p2 = document.getElementById('plugin2'); + var p3 = document.getElementById('plugin3'); + is(p1.hasWidget(), false, "Flash is always windowless mode even if wmode=window"); + is(p2.hasWidget(), false, "Flash is always windowless mode even if wmode=anything"); + is(p3.hasWidget(), false, "Flash is always windowless mode even if no wmode"); + SimpleTest.finish(); + } + + </script> + <p id="display"></p> + + <div id="div1"> + <embed id="plugin1" type="application/x-shockwave-flash-test" width="200" height="200" wmode="window"></embed> + <embed id="plugin2" type="application/x-shockwave-flash-test" width="200" height="200" wmode="test"></embed> + <embed id="plugin3" type="application/x-shockwave-flash-test" width="200" height="200"></embed> + </div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_windowless_ime.html b/dom/plugins/test/mochitest/test_windowless_ime.html new file mode 100644 index 000000000..edc994052 --- /dev/null +++ b/dom/plugins/test/mochitest/test_windowless_ime.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=539565 +--> +<head> + <title>Test #1 for Bug 539565</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script class="testbody" type="text/javascript"> +function runTests() { + var plugin = document.getElementById("plugin1"); + + plugin.focus(); + synthesizeComposition({ type: "compositionstart", data: "" }); + let data = "composition"; + synthesizeCompositionChange({ + composition: { + string: data, + clauses: [ + { length: data.length, attr: COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + caret: {start: data.length, length: 0} + }); + is(plugin.getLastCompositionText(), data, "can get composition string"); + synthesizeComposition({ type: "compositioncommit", data: "" }); + + synthesizeComposition({ type: "compositionstart", data: "" }); + is(plugin.getLastCompositionText(), "", "can get empty composition string"); + synthesizeComposition({ type: "compositioncommit", data: "" }); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +</head> + +<body onload="runTests()"> + <embed id="plugin1" type="application/x-test" wmode="transparent" width="200" height="200"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_wmode.xul b/dom/plugins/test/mochitest/test_wmode.xul new file mode 100644 index 000000000..b45038835 --- /dev/null +++ b/dom/plugins/test/mochitest/test_wmode.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="WMode Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + <script type="application/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()"> +<embed id="plugin2" type="application/x-test" width="400" height="400" wmode="window"></embed> +<embed id="plugin1" type="application/x-test" width="400" height="400"></embed> +</body> +<script class="testbody" type="application/javascript"> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function runTests() { + var p1 = document.getElementById("plugin1"); + is(p1.hasWidget(), false, "Plugin should be windowless by default"); + + var p2 = document.getElementById("plugin2"); + if (navigator.platform.indexOf("Mac") >= 0) { + is(p2.hasWidget(), false, "Mac does not support windowed plugins"); + } else if (navigator.platform.indexOf("Win") >= 0) { + is(p2.hasWidget(), true, "Windows supports windowed plugins"); + } else if (navigator.platform.indexOf("Linux") >= 0) { + is(p2.hasWidget(), true, "Linux supports windowed plugins"); + } + + SimpleTest.finish(); +} +]]> +</script> +</window> diff --git a/dom/plugins/test/mochitest/test_x11_error_crash.html b/dom/plugins/test/mochitest/test_x11_error_crash.html new file mode 100644 index 000000000..1b52d1e6f --- /dev/null +++ b/dom/plugins/test/mochitest/test_x11_error_crash.html @@ -0,0 +1,27 @@ +<head> + <title>Plugin terminating on X11 error</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout( + "crashAndGetCrashServiceRecord() polls for async crash recording"); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + window.frameLoaded = function frameLoaded_toCrash() { + SimpleTest.expectChildProcessCrash(); + + crashAndGetCrashServiceRecord("triggerXError", function (cm, crash) { + var isPluginCrash = crash.isOfType(cm.PROCESS_TYPE_PLUGIN, cm.CRASH_TYPE_CRASH); + ok(isPluginCrash, "Record should be a plugin crash"); + if (!isPluginCrash) { + dump("Crash type: " + crash.type + "\n"); + } + SimpleTest.finish(); + }); + + } + </script> + <iframe id="iframe1" src="crashing_subpage.html" width="600" height="600"></iframe> diff --git a/dom/plugins/test/mochitest/test_xulbrowser_plugin_visibility.xul b/dom/plugins/test/mochitest/test_xulbrowser_plugin_visibility.xul new file mode 100644 index 000000000..8cccb8bf7 --- /dev/null +++ b/dom/plugins/test/mochitest/test_xulbrowser_plugin_visibility.xul @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" src="plugin-utils.js"></script> + + <script> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var w = window.open('xulbrowser_plugin_visibility.xul', '_blank', 'chrome,resizable=yes,width=400,height=600'); + + function done() + { + w.close(); + SimpleTest.finish(); + } + </script> + + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" /> +</window> diff --git a/dom/plugins/test/mochitest/test_zero_opacity.html b/dom/plugins/test/mochitest/test_zero_opacity.html new file mode 100644 index 000000000..c768cafd5 --- /dev/null +++ b/dom/plugins/test/mochitest/test_zero_opacity.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test whether windowed plugins with opacity:0 get their window set correctly</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + +<body onload="startTest()"> + <script type="application/javascript;version=1.8"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var p = null; + + function startTest() { + p = document.getElementById('theplugin'); + if (!p.hasWidget()) { + todo(false, "This test is only relevant for windowed plugins"); + SimpleTest.finish(); + return; + } + + // Wait for the plugin to have painted once. + var interval = setInterval(function() { + if (!p.getPaintCount()) + return; + + clearInterval(interval); + doTest(); + SimpleTest.finish(); + }, 100); + } + + function doTest() { + is(p.getClipRegionRectCount(), 1, "getClipRegionRectCount should be a single rect"); + is(p.getClipRegionRectEdge(0,2) - p.getClipRegionRectEdge(0,0), 100, "width of clip region rect"); + is(p.getClipRegionRectEdge(0,3) - p.getClipRegionRectEdge(0,1), 50, "height of clip region rect"); + } + </script> + + <p id="display"></p> + + <embed id="theplugin" type="application/x-test" width="100" height="50" style="opacity:0" wmode="window"></embed>
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/xulbrowser_plugin_visibility.xul b/dom/plugins/test/mochitest/xulbrowser_plugin_visibility.xul new file mode 100644 index 000000000..d08730520 --- /dev/null +++ b/dom/plugins/test/mochitest/xulbrowser_plugin_visibility.xul @@ -0,0 +1,139 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + orient="vertical"> + + <tabbox id="tabbox" flex="1"> + <tabs> + <tab id="tab1" label="Tab 1" /> + <tab id="tab2" label="Tab 2" /> + </tabs> + <tabpanels flex="1"> + <browser id="browser1" type="content-primary" flex="1" src="about:blank"/> + <browser id="browser2" type="content-primary" flex="1" src="about:blank"/> + </tabpanels> + </tabbox> + <script type="application/javascript" src="plugin-utils.js"/> + <script type="application/javascript"><![CDATA[ + const ok = window.opener.wrappedJSObject.ok; + const is = window.opener.wrappedJSObject.is; + const done = window.opener.wrappedJSObject.done; + const SimpleTest = window.opener.wrappedJSObject.SimpleTest; + + const nsIWebProgress = Components.interfaces.nsIWebProgress; + const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; + + const kURI = 'http://mochi.test:8888/chrome/dom/plugins/test/mochitest/plugin_visibility_loader.html'; + + function ProgressListener() { + } + ProgressListener.prototype.onStateChange = + function(progress, req, flags, status) { + if ((flags & nsIWebProgressListener.STATE_IS_WINDOW) && + (flags & nsIWebProgressListener.STATE_STOP)) + browserLoaded(); + }; + ProgressListener.prototype.QueryInterface = function(iid) { + if (iid.equals(nsIWebProgressListener) || + iid.equals(Components.interfaces.nsISupportsWeakReference)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }; + + var loadCount = 0; + function browserLoaded() { + ++loadCount; + if (2 == loadCount) + startTest(); + } + + var tabbox = document.getElementById('tabbox'); + var browser1 = document.getElementById('browser1'); + var browser2 = document.getElementById('browser2'); + + var progressListener1, progressListener2; + + function setup() { + progressListener1 = new ProgressListener(); + browser1.addProgressListener(progressListener1, nsIWebProgress.NOTIFY_STATE_WINDOW); + browser1.loadURI(kURI, null, null); + progressListener2 = new ProgressListener(); + browser2.addProgressListener(progressListener2, nsIWebProgress.NOTIFY_STATE_WINDOW); + browser2.loadURI(kURI, null, null); + } + + window.addEventListener("load", setup, false); + + var plugin1, plugin2; + + const kTimeout = 5000; // 5 seconds + var paintGiveUp; + var paintInterval; + + function startTest() { + plugin1 = browser1.contentDocument.getElementById('p').wrappedJSObject; + plugin2 = browser2.contentDocument.getElementById('p').wrappedJSObject; + + paintGiveUp = Date.now() + kTimeout; + paintInterval = setInterval(waitForPaint, 100); + } + + function waitForPaint() { + if (!plugin1.isVisible()) { + if (Date.now() < paintGiveUp) + return; + + ok(false, "Plugin in tab 1 never became visible."); + done(); + return; + } + + clearInterval(paintInterval); + + ok(true, "Plugin in tab 1 should be visible."); + paintCountIs(plugin1, 1, "Plugin in tab 1 should have painted once."); + + ok(!plugin2.isVisible(), "Plugin in tab 2 should not be visible."); + paintCountIs(plugin2, 0, "Plugin in tab 2 should not have painted."); + + tabbox.selectedIndex = 1; + paintGiveUp = Date.now() + kTimeout; + paintInterval = setInterval(part2, 100); + } + + function part2() { + if (!plugin2.isVisible()) { + if (Date.now() < paintGiveUp) + return; + + ok(false, "Plugin in tab 2 never became visible."); + done(); + return; + } + + clearInterval(paintInterval); + + ok(true, "Plugin in tab 2 became visible."); + paintCountIs(plugin2, 1, "Plugin in tab 2 should have painted once."); + + ok(!plugin1.isVisible(), "Plugin in tab 1 should have become invisible."); + paintCountIs(plugin1, 1, "Plugin in tab 1 should have painted once."); + + // Setcolor invalidates + plugin1.setColor('FF00FF00'); + plugin2.setColor('FF00FF00'); + + setTimeout(part3, 500); + } + + function part3() { + paintCountIs(plugin1, 1, + "Plugin in tab 1 should not have repainted after invalidate."); + paintCountIs(plugin2, 2, + "Plugin in tab 2 should have repainted after invalidate."); + done(); + } + ]]></script> + +</window> diff --git a/dom/plugins/test/moz.build b/dom/plugins/test/moz.build new file mode 100644 index 000000000..c7f7b01ed --- /dev/null +++ b/dom/plugins/test/moz.build @@ -0,0 +1,15 @@ +# -*- 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/. + +DIRS += ['testplugin', 'testaddon'] + +XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'cocoa', 'windows'): + MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini'] + MOCHITEST_CHROME_MANIFESTS += ['mochitest/chrome.ini'] + BROWSER_CHROME_MANIFESTS += ['mochitest/browser.ini'] + diff --git a/dom/plugins/test/reftest/border-padding-1-ref.html b/dom/plugins/test/reftest/border-padding-1-ref.html new file mode 100644 index 000000000..1a33644ac --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-1-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<body style="margin:0"> +<div style="width:184px; height:192px; margin:90px 80px; outline:5px dashed blue; + border:dotted black; border-width:4px 8px 4px 8px; + background:cyan;"> + <div style="margin:3px 1px; height:186px; background:lime;"></div> +</div> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-1.html b/dom/plugins/test/reftest/border-padding-1.html new file mode 100644 index 000000000..6fa2446f4 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-1.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('p1')"> +<object type="application/x-test" drawmode="solid" color="ff00ff00" + style="width:200px; height:200px; display:block; margin:90px 80px; + outline:5px dashed blue; + background:cyan; + border:dotted black; border-width:4px 8px 4px 8px; + padding:3px 1px;" id="p1"> +</object> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-2-ref.html b/dom/plugins/test/reftest/border-padding-2-ref.html new file mode 100644 index 000000000..ae92da403 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-2-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('plugin')"> +<div style="width:184px; height:192px; margin:90px 80px; outline:5px dashed blue; + border:dotted black; border-width:4px 8px 4px 8px; + background:cyan;"> + <object style="margin:3px 1px; height:186px; width:182px; display:block;" + type="application/x-test" + id="plugin"> + </object> +</div> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-2.html b/dom/plugins/test/reftest/border-padding-2.html new file mode 100644 index 000000000..6a39d2d81 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-2.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('plugin')"> +<object type="application/x-test" + style="width:200px; height:200px; display:block; margin:90px 80px; + outline:5px dashed blue; + background:cyan; + border:dotted black; border-width:4px 8px 4px 8px; + padding:3px 1px;" + id="plugin"> +</object> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-3-ref.html b/dom/plugins/test/reftest/border-padding-3-ref.html new file mode 100644 index 000000000..5c7bb7456 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-3-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<body style="margin:0"> +<div style="width:184px; height:192px; margin:90px 80px; outline:5px dashed blue; + border:dotted black; border-width:4px 8px 4px 8px; + background:cyan;"> + <div style="margin:3px 1px; height:186px; width:182px; background:rgb(255,128,255);"></object> +</div> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-3.html b/dom/plugins/test/reftest/border-padding-3.html new file mode 100644 index 000000000..533445601 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-3.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('plugin', true)"> +<object type="application/x-test" id="plugin" + drawmode="solid" color="00000000" + style="width:200px; height:200px; display:block; margin:90px 80px; + outline:5px dashed blue; + background:cyan; + border:dotted black; border-width:4px 8px 4px 8px; + padding:3px 1px;"> +</object> +<script> +var prevPaintCount = 0; +function doTestWait() { + if (document.getElementById("plugin").getPaintCount() != prevPaintCount) { + document.documentElement.removeAttribute('class'); + } else { + setTimeout(doTestWait, 0); + } +} + +function doTest() { + prevPaintCount = document.getElementById("plugin").getPaintCount(); + document.getElementById("plugin").setColor("FFFF80FF"); + setTimeout(doTestWait, 0); + +} +window.addEventListener("MozReftestInvalidate", doTest, false); +</script> +</body> +</html> diff --git a/dom/plugins/test/reftest/div-alpha-opacity.html b/dom/plugins/test/reftest/div-alpha-opacity.html new file mode 100644 index 000000000..fec913b64 --- /dev/null +++ b/dom/plugins/test/reftest/div-alpha-opacity.html @@ -0,0 +1,28 @@ +<!doctype html> +<html> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + width:400px; height:400px; + border:2px solid blue; + background-color: rgb(160,160,160); + opacity:0.8; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +</head> +<body> +<div id="two"></div> +<div id="one"></div> +</body> +</html> + diff --git a/dom/plugins/test/reftest/div-alpha-zindex.html b/dom/plugins/test/reftest/div-alpha-zindex.html new file mode 100644 index 000000000..e4672b913 --- /dev/null +++ b/dom/plugins/test/reftest/div-alpha-zindex.html @@ -0,0 +1,27 @@ +<!doctype html> +<html> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + width:400px; height:400px; + background-color: rgb(0,255,0); + opacity:0.6; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +</head> +<body> +<div id="two"></div> +<div id="one"></div> +</body> +</html> + diff --git a/dom/plugins/test/reftest/div-sanity.html b/dom/plugins/test/reftest/div-sanity.html new file mode 100644 index 000000000..9ffa53919 --- /dev/null +++ b/dom/plugins/test/reftest/div-sanity.html @@ -0,0 +1,17 @@ +<!doctype html> +<html><head> +<title>div boxes</title> +<style> +div { + width: 400px; + height: 400px; + display: inline-block; +} +</style> +</head> +<body> +<div style="background-color: #FF0000;"></div> <!-- red --> +<div style="background-color: #00FF00;"></div> <!-- green --> +<div style="background-color: #0000FF;"></div> <!-- blue --> +<div style="background-color: #999999;"></div> <!-- gray --> +</body></html> diff --git a/dom/plugins/test/reftest/plugin-alpha-opacity.html b/dom/plugins/test/reftest/plugin-alpha-opacity.html new file mode 100644 index 000000000..2db6cc4de --- /dev/null +++ b/dom/plugins/test/reftest/plugin-alpha-opacity.html @@ -0,0 +1,29 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + width:404px; height:404px; + border:2px solid blue; + opacity:.8; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('one')"> +<div id="two"></div> +<embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="FFa0a0a0"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-alpha-zindex.html b/dom/plugins/test/reftest/plugin-alpha-zindex.html new file mode 100644 index 000000000..ead9b6f4c --- /dev/null +++ b/dom/plugins/test/reftest/plugin-alpha-zindex.html @@ -0,0 +1,26 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('one')"> +<div id="two"></div> +<embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="9900FF00" id="p1"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-background-1-step.html b/dom/plugins/test/reftest/plugin-background-1-step.html new file mode 100644 index 000000000..9498633b4 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-1-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 1; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-10-step.html b/dom/plugins/test/reftest/plugin-background-10-step.html new file mode 100644 index 000000000..7a0824a56 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-10-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 10; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-2-step.html b/dom/plugins/test/reftest/plugin-background-2-step.html new file mode 100644 index 000000000..cc186a5f2 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-2-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 2; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-5-step.html b/dom/plugins/test/reftest/plugin-background-5-step.html new file mode 100644 index 000000000..2630719c8 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-5-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 5; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-ref.html b/dom/plugins/test/reftest/plugin-background-ref.html new file mode 100644 index 000000000..651fdecef --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <div id="plugin"></div> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background.css b/dom/plugins/test/reftest/plugin-background.css new file mode 100644 index 000000000..f6b251214 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.css @@ -0,0 +1,61 @@ +div { + position: absolute; +} +#bad { + left:220px; top:0px; + z-index: 0; +} +#good { + left:0px; top:0px; + width:220px; height:220px; + /* Core Animation alpha blending rounding differs + from the Core Graphics blending, adjust with care */ + background-color: rgba(0,255,0, 0.51); + z-index: 0; +} + +#topbar { + left:0px; top:0px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} +#topbar { + left:0px; top:0px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} +#leftbar { + left:0px; top:0px; + width:20px; height:220px; + background-color: rgb(0,0,0); + z-index: 2; +} +#rightbar { + left:200px; top:0px; + width:20px; height:220px; + background-color: rgb(0,0,0); + z-index: 2; +} +#bottombar { + left:0px; top:200px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} + +div#plugin { + position: absolute; + left:1px; top:1px; + width:199px; height:199px; + background-color: rgba(0,0,255, 0.2); + z-index: 1; +} + +embed#plugin { + position: absolute; + left:1px; top:1px; + z-index: 1; +} + diff --git a/dom/plugins/test/reftest/plugin-background.html b/dom/plugins/test/reftest/plugin-background.html new file mode 100644 index 000000000..4cd1e3f53 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> +</head> +<script src="utils.js"> +</script> +<body onLoad="forceLoadPlugin('plugin')"> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background.js b/dom/plugins/test/reftest/plugin-background.js new file mode 100644 index 000000000..5d24562cf --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.js @@ -0,0 +1,75 @@ +// The including script sets this for us +//var NUM_STEPS; + +var plugin; +var left = 1, top = 1, width = 199, height = 199; +function movePluginTo(x, y, w, h) { + left = x; top = y; width = w; height = h; + plugin.width = w; + plugin.height = h; + plugin.style.left = left + "px"; + plugin.style.top = top + "px"; +} +function deltaInBounds(dx,dy, dw,dh) { + var l = dx + left; + var r = l + width + dw; + var t = dy + top; + var b = t + height + dh; + return (0 <= l && l <= 20 && + 0 <= t && t <= 20 && + 200 <= r && r <= 220 && + 200 <= b && b <= 220); +} + +var initialFrame; +function start() { + window.removeEventListener("MozReftestInvalidate", start, false); + + window.addEventListener("MozAfterPaint", step, false); + window.addEventListener("MozPaintWaitFinished", step, false); + + initialFrame = window.mozPaintCount; + plugin = document.getElementById("plugin"); + + movePluginTo(0,0, 200,200); +} + +var steps = 0; +var which = "move"; // or "grow" +var dx = 1, dy = 1, dw = 1, dh = 1; +function step() { + if (++steps >= NUM_STEPS) { + window.removeEventListener("MozAfterPaint", step, false); + window.removeEventListener("MozPaintWaitFinished", step, false); + return finish(); + } + + var didSomething = false; + if (which == "grow") { + if (deltaInBounds(0,0, dw,dh)) { + movePluginTo(left,top, width+dw, height+dh); + didSomething = true; + } else { + dw = -dw; dh = -dh; + } + } else { + // "move" + if (deltaInBounds(dx,dy, 0,0)) { + movePluginTo(left+dx,top+dy, width, height); + didSomething = true; + } else { + dx = -dx; dy = -dy; + } + } + which = (which == "grow") ? "move" : "grow"; + + if (!didSomething) { + step(); + } +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +window.addEventListener("MozReftestInvalidate", start, false); diff --git a/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html b/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html new file mode 100644 index 000000000..e339dd266 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html @@ -0,0 +1,56 @@ +<!doctype html> +<html class="reftest-wait"> +<head> + <style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:4; +} +#two { + position:absolute; + top:100px; left:100px; + background-color:rgb(0,0,0,0); + z-index:3; +} +#three { + position:absolute; + left:100px; top:100px; + width:200px; height:200px; + background-color: rgb(255,0,0); + opacity:0.6; + z-index:2; +} +#four { + position:absolute; + top:100px; left:100px; + z-index:1; +} + </style> + <script src="utils.js" type="text/javascript"> + </script> + <script type="text/javascript"> +function paintCanvas() { + var canvas = document.getElementById("two"); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgba(255,0,0, 0.6)"; + ctx.fillRect(0,0, 200,200); +} + + function doTest() { + paintCanvas(); + forceLoadPlugin(['one', 'four']); + } + </script> +</style> +</head> +<body onload="doTest();"> + <embed id="four" type="application/x-test" width="200" height="200" + drawmode="solid" color="FFFF0000"></embed> + <div id="three"></div> + <canvas id="two" width="200" height="200"></canvas> + <embed id="one" type="application/x-test" width="400" height="400" + drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html b/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html new file mode 100644 index 000000000..517099d1b --- /dev/null +++ b/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html @@ -0,0 +1,41 @@ +<!doctype html> +<html class="reftest-wait"> +<head> + <style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; +// Set these using the canvas API +// width:200px; height:200px; +// background-color: rgb(255,0,0); + z-index:0; +} + </style> + <script src="utils.js" type="text/javascript"> + </script> + <script type="text/javascript"> +function paintCanvas() { + var canvas = document.getElementById("two"); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgb(255,0,0)"; + ctx.fillRect(0,0, 200,200); +} + + function doTest() { + paintCanvas(); + forceLoadPlugin('one'); + } + </script> +</style> +</head> +<body onload="doTest()"> + <canvas width="200" height="200" id="two"></canvas> + <embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-sanity.html b/dom/plugins/test/reftest/plugin-sanity.html new file mode 100644 index 000000000..4f9c30eee --- /dev/null +++ b/dom/plugins/test/reftest/plugin-sanity.html @@ -0,0 +1,13 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<title>Plugin boxes</title> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin(['p1', 'p2', 'p3', 'p4'])"> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FFFF0000" id="p1"></embed> <!-- red --> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FF00FF00" id="p2"></embed> <!-- green --> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FF0000FF" id="p3"></embed> <!-- blue --> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FF999999" id="p4"></embed> <!-- gray --> +</body></html> diff --git a/dom/plugins/test/reftest/plugin-transform-1-ref.html b/dom/plugins/test/reftest/plugin-transform-1-ref.html new file mode 100644 index 000000000..259a78b41 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-1-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('p')">
+<embed type="application/x-test" id="p"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-1.html b/dom/plugins/test/reftest/plugin-transform-1.html new file mode 100644 index 000000000..19f6e8c20 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-1.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('p')">
+<embed type="application/x-test" style="-moz-transform:scale(1)" id="p"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-2-ref.html b/dom/plugins/test/reftest/plugin-transform-2-ref.html new file mode 100644 index 000000000..93a3924d7 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-2-ref.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML>
+<html>
+<body>
+<div style="width:100px; height:100px; -moz-transform-origin:top left;
+ -moz-transform:scale(2); background:rgb(0,255,0)"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-2.html b/dom/plugins/test/reftest/plugin-transform-2.html new file mode 100644 index 000000000..7f48640c1 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-2.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('one')">
+<embed type="application/x-test" drawmode="solid" color="FF00FF00"
+ style="width:100px; height:100px; -moz-transform-origin:top left;
+ -moz-transform:scale(2); display:block"
+ id="one"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html b/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html new file mode 100644 index 000000000..52fda4bcf --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html @@ -0,0 +1,28 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:1; +} +#two { + position:absolute; + top:0px; left:0px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); + -moz-transform-origin: 0 0; + -moz-transform: translate(100px,100px); +} +</style> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('one')"> + <div id="two"></div> + <embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html b/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html new file mode 100644 index 000000000..fafec34f4 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="ltr" style="text-align: left;"> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-1.html b/dom/plugins/test/reftest/pluginproblemui-direction-1.html new file mode 100644 index 000000000..9888850dc --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-1.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="rtl" style="text-align: left;"> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html b/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html new file mode 100644 index 000000000..e807b86b5 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="ltr" style="text-align: left;"> + <!-- a variant of pluginproblemui-direction-1.html that covers up + the spot where we get random variation with d2d, so that we + can still test it with d2d --> + <div style="position: absolute; width: 1px; height: 1px; background: red; z-index: 1; left: 401px; top: 19px;"></div> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-2.html b/dom/plugins/test/reftest/pluginproblemui-direction-2.html new file mode 100644 index 000000000..95b358e37 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-2.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="rtl" style="text-align: left;"> + <!-- a variant of pluginproblemui-direction-1.html that covers up + the spot where we get random variation with d2d, so that we + can still test it with d2d --> + <div style="position: absolute; width: 1px; height: 1px; background: red; z-index: 1; left: 401px; top: 19px;"></div> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/reftest-stylo.list b/dom/plugins/test/reftest/reftest-stylo.list new file mode 100644 index 000000000..0f900b369 --- /dev/null +++ b/dom/plugins/test/reftest/reftest-stylo.list @@ -0,0 +1,33 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +# basic sanity checking +# fails random-if(!haveTestPlugin) == plugin-sanity.html plugin-sanity.html +# fails-if(!haveTestPlugin) == plugin-sanity.html plugin-sanity.html +skip fails-if(!haveTestPlugin) fuzzy-if(skiaContent,1,160000) == plugin-alpha-zindex.html plugin-alpha-zindex.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent,1,164000) == plugin-alpha-opacity.html plugin-alpha-opacity.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == windowless-clipping-1.html windowless-clipping-1.html +# bug 631832 +# fuzzy because of anti-aliasing in dashed border +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == border-padding-1.html border-padding-1.html +# bug 629430 +skip random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == border-padding-2.html border-padding-2.html +# bug 629430 +skip random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) skip-if(Android||B2G) == border-padding-3.html border-padding-3.html +# bug 629430 +# bug 773482 +# The following two "pluginproblemui-direction" tests are unreliable on all platforms. They should be re-written or replaced. +#random-if(cocoaWidget||d2d||/^Windows\x20NT\x205\.1/.test(http.oscpu)) fails-if(!haveTestPlugin&&!Android) == pluginproblemui-direction-1.html pluginproblemui-direction-1.html +# bug 567367 +#random-if(cocoaWidget) fails-if(!haveTestPlugin&&!Android) == pluginproblemui-direction-2.html pluginproblemui-direction-2.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent,1,160000) == plugin-canvas-alpha-zindex.html plugin-canvas-alpha-zindex.html +fails fails-if(!haveTestPlugin) fuzzy-if(skiaContent,1,160000) == plugin-transform-alpha-zindex.html plugin-transform-alpha-zindex.html +skip == plugin-busy-alpha-zindex.html plugin-busy-alpha-zindex.html +skip == plugin-background.html plugin-background.html +skip == plugin-background-1-step.html plugin-background-1-step.html +skip == plugin-background-2-step.html plugin-background-2-step.html +skip == plugin-background-5-step.html plugin-background-5-step.html +skip == plugin-background-10-step.html plugin-background-10-step.html +skip == plugin-transform-1.html plugin-transform-1.html +skip == plugin-transform-2.html plugin-transform-2.html +skip == shrink-1.html shrink-1.html +skip skip-if(!haveTestPlugin) == update-1.html update-1.html +skip skip-if(!haveTestPlugin) == windowless-layers.html windowless-layers.html diff --git a/dom/plugins/test/reftest/reftest.list b/dom/plugins/test/reftest/reftest.list new file mode 100644 index 000000000..e2d819536 --- /dev/null +++ b/dom/plugins/test/reftest/reftest.list @@ -0,0 +1,26 @@ +# basic sanity checking +random-if(!haveTestPlugin) != plugin-sanity.html about:blank +fails-if(!haveTestPlugin) == plugin-sanity.html div-sanity.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,160000) == plugin-alpha-zindex.html div-alpha-zindex.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,164000) == plugin-alpha-opacity.html div-alpha-opacity.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == windowless-clipping-1.html windowless-clipping-1-ref.html # bug 631832 +# fuzzy because of anti-aliasing in dashed border +fuzzy(16,256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == border-padding-1.html border-padding-1-ref.html # bug 629430 +fuzzy(16,256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == border-padding-2.html border-padding-2-ref.html # bug 629430 +fuzzy(16,256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin||Android) == border-padding-3.html border-padding-3-ref.html # bug 629430 # bug 773482 +# The following two "pluginproblemui-direction" tests are unreliable on all platforms. They should be re-written or replaced. +#random-if(cocoaWidget||d2d||/^Windows\x20NT\x205\.1/.test(http.oscpu)) fails-if(!haveTestPlugin&&!Android) == pluginproblemui-direction-1.html pluginproblemui-direction-1-ref.html # bug 567367 +#random-if(cocoaWidget) fails-if(!haveTestPlugin&&!Android) == pluginproblemui-direction-2.html pluginproblemui-direction-2-ref.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,160000) == plugin-canvas-alpha-zindex.html div-alpha-zindex.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,160000) == plugin-transform-alpha-zindex.html div-alpha-zindex.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,160000) == plugin-busy-alpha-zindex.html div-alpha-zindex.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,32400) == plugin-background.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,32400) == plugin-background-1-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,32400) == plugin-background-2-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,32400) == plugin-background-5-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,1,32400) == plugin-background-10-step.html plugin-background-ref.html +random-if(!haveTestPlugin) == plugin-transform-1.html plugin-transform-1-ref.html +fails-if(!haveTestPlugin) == plugin-transform-2.html plugin-transform-2-ref.html +skip-if(!haveTestPlugin) == shrink-1.html shrink-1-ref.html +skip-if(!haveTestPlugin) == update-1.html update-1-ref.html +skip-if(!haveTestPlugin) == windowless-layers.html windowless-layers-ref.html diff --git a/dom/plugins/test/reftest/shrink-1-ref.html b/dom/plugins/test/reftest/shrink-1-ref.html new file mode 100644 index 000000000..0906fe578 --- /dev/null +++ b/dom/plugins/test/reftest/shrink-1-ref.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('plugin')"> + <embed id="plugin" type="application/x-test" + width="50px" height="40px"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/shrink-1.html b/dom/plugins/test/reftest/shrink-1.html new file mode 100644 index 000000000..495705698 --- /dev/null +++ b/dom/plugins/test/reftest/shrink-1.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <script src="utils.js"> + </script> + <script> +function doShrink() +{ + var plugin = document.getElementById("plugin"); + plugin.setSlowPaint(true); + plugin.width = "50"; + plugin.height = "40"; + + document.documentElement.removeAttribute("class"); +} + +document.addEventListener("MozReftestInvalidate", doShrink, false); + </script> +</head> +<body onLoad="forceLoadPlugin('plugin', true)"> + <embed id="plugin" type="application/x-test" + width="300" height="500"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/update-1-ref.html b/dom/plugins/test/reftest/update-1-ref.html new file mode 100644 index 000000000..7303d1984 --- /dev/null +++ b/dom/plugins/test/reftest/update-1-ref.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('plugin')"> + <embed id="plugin" type="application/x-test" + drawmode="solid" color="FFFF0000" width="30" height="50"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/update-1.html b/dom/plugins/test/reftest/update-1.html new file mode 100644 index 000000000..f307b4394 --- /dev/null +++ b/dom/plugins/test/reftest/update-1.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <title>Test for bugs 807728 and 810426</title> + <script src="utils.js"> + </script> + <script> +function start() +{ + document.removeEventListener("MozReftestInvalidate", start, false); + + var plugin = document.getElementById("plugin"); + var color = "FF000000"; + var color_count = 0; + var last_paint_count = 0; + // Enough paints to test reusing a surface after it has been + // moved from front to back buffer. + var final_paint_count = window.mozPaintCount + 10; + var final_color = "FFFF0000"; + + // Using mozPaintCount to track when content has been updated as an + // indication that the browser has received updates, instead of + // plugin.getPaintCount() which tracks when the plugin sends updates or + // MozAfterPaint events which track OS paints. Not using + // MozPaintWaitFinished because invalidation causes no geometry changes. + function wait_for_paints() { + var paint_count = window.mozPaintCount; + if (paint_count >= final_paint_count && color == final_color) { + document.documentElement.removeAttribute("class"); + return; + } + if (paint_count != last_paint_count) { + last_paint_count = paint_count; + if (paint_count + 1 >= final_paint_count) { + color = final_color; + // Wait for the paint with the final color + final_paint_count = paint_count + 1; + } else { + ++color_count; + color = "FF00000" + color_count; + } + plugin.setColor(color); + } + setTimeout(wait_for_paints, 0); + } + + wait_for_paints(); +} + +// MozReftestInvalidate is delivered after initial painting has settled. +document.addEventListener("MozReftestInvalidate", start, false); + </script> +</head> +<body onLoad="forceLoadPlugin('plugin', true)"> + <embed id="plugin" type="application/x-test" + drawmode="solid" color="FF000000" width="30" height="50"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/utils.js b/dom/plugins/test/reftest/utils.js new file mode 100644 index 000000000..f046089af --- /dev/null +++ b/dom/plugins/test/reftest/utils.js @@ -0,0 +1,18 @@ +function forceLoadPluginElement(id) { + var e = document.getElementById(id); + var found = e.pluginFoundElement; +} + +function forceLoadPlugin(ids, skipRemoveAttribute) { + if (Array.isArray(ids)) { + ids.forEach(function(element, index, array) { + forceLoadPluginElement(element); + }); + } else { + forceLoadPluginElement(ids); + } + if (skipRemoveAttribute) { + return; + } + document.documentElement.removeAttribute("class"); +} diff --git a/dom/plugins/test/reftest/windowless-clipping-1-ref.html b/dom/plugins/test/reftest/windowless-clipping-1-ref.html new file mode 100644 index 000000000..e59ecb79b --- /dev/null +++ b/dom/plugins/test/reftest/windowless-clipping-1-ref.html @@ -0,0 +1,14 @@ +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('p1')"> +<div style="width:100px; height:100px; overflow:auto;"> + <div style="width:100px; height:100px; overflow:hidden;"> + <embed type="application/x-test" style="width:200px;" id="p1"></embed> + </div> +</div> +<div style="width:100px; height:100px; background-color:lime;"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/windowless-clipping-1.html b/dom/plugins/test/reftest/windowless-clipping-1.html new file mode 100644 index 000000000..dc1c25ac1 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-clipping-1.html @@ -0,0 +1,15 @@ +<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('p1')">
+<div style="width:100px; height:100px; overflow:hidden;">
+ <embed type="application/x-test" style="width:200px;"></embed>
+</div>
+<div style="width:100px; height:100px; overflow:hidden;">
+ <embed type="application/x-test" style="width:200px;"
+ drawmode="solid" color="ff00ff00" id="p1"></embed>
+</div>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/windowless-layers-ref.html b/dom/plugins/test/reftest/windowless-layers-ref.html new file mode 100644 index 000000000..765527b68 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-layers-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('p1')"> + <embed type="application/x-test" style="width:200px; height:200px;" id="p1"> +</body> +</html> diff --git a/dom/plugins/test/reftest/windowless-layers.html b/dom/plugins/test/reftest/windowless-layers.html new file mode 100644 index 000000000..9e24c13a6 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-layers.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin(['p1', 'p2'])"> + <div style="width:200px; height:200px; overflow:hidden; position:absolute; z-index:1"> + <embed type="application/x-test" style="width:200px; height:200px;" id="p1"> + </div> + <div style="width:200px; height:100px; overflow:hidden; position:absolute; z-index:2"> + <embed type="application/x-test" style="width:200px; height:200px;" id="p2"> + </div> +</body> +</html> diff --git a/dom/plugins/test/testaddon/Makefile.in b/dom/plugins/test/testaddon/Makefile.in new file mode 100644 index 000000000..7b6dd6bc5 --- /dev/null +++ b/dom/plugins/test/testaddon/Makefile.in @@ -0,0 +1,25 @@ +# 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 $(topsrcdir)/config/rules.mk + +ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa) +plugin_file_names = Test.plugin SecondTest.plugin ThirdTest.plugin npswftest.plugin npctrltest.plugin +addon_file_name = testaddon_$(TARGET_XPCOM_ABI).xpi +else +plugin_file_names = $(DLL_PREFIX)nptest$(DLL_SUFFIX) $(DLL_PREFIX)npsecondtest$(DLL_SUFFIX) $(DLL_PREFIX)npthirdtest$(DLL_SUFFIX) $(DLL_PREFIX)npswftest$(DLL_SUFFIX) $(DLL_PREFIX)npctrltest$(DLL_SUFFIX) +addon_file_name = testaddon.xpi +endif + +# This is so hacky. Waiting on bug 988938. +testdir = $(topobjdir)/_tests/xpcshell/dom/plugins/test/unit/ +addonpath = $(testdir)/$(addon_file_name) + +ifdef COMPILE_ENVIRONMENT +libs:: + $(NSINSTALL) -D $(testdir) + rm -f $(addonpath) + cd $(srcdir) && zip -rD $(addonpath) install.rdf + cd $(DIST) && zip -rD $(addonpath) $(foreach name,$(plugin_file_names),plugins/$(name)) +endif diff --git a/dom/plugins/test/testaddon/install.rdf b/dom/plugins/test/testaddon/install.rdf new file mode 100644 index 000000000..017badb00 --- /dev/null +++ b/dom/plugins/test/testaddon/install.rdf @@ -0,0 +1,23 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>test-plugin-from-xpi@tests.mozilla.org</em:id> + <em:version>1</em:version> + <em:name>Test plugin from XPI</em:name> + <em:description>This tests shipping a plugin through an extensions.</em:description> + + <em:targetApplication> + <Description> + <em:id>toolkit@mozilla.org</em:id> + <em:minVersion>0</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + + <em:unpack>true</em:unpack> + + </Description> +</RDF> diff --git a/dom/plugins/test/testaddon/moz.build b/dom/plugins/test/testaddon/moz.build new file mode 100644 index 000000000..568f361a5 --- /dev/null +++ b/dom/plugins/test/testaddon/moz.build @@ -0,0 +1,5 @@ +# -*- 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/. diff --git a/dom/plugins/test/testplugin/Info.plist b/dom/plugins/test/testplugin/Info.plist new file mode 100644 index 000000000..dc6aa5cec --- /dev/null +++ b/dom/plugins/test/testplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnptest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.TestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>TEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Test Plug-in</string> + <key>WebPluginDescription</key> + <string>Plug-in for testing purposes.™ (हिन्दी 中文 العربية)</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>tst</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Test ™ mimetype</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/README b/dom/plugins/test/testplugin/README new file mode 100644 index 000000000..b7ac70363 --- /dev/null +++ b/dom/plugins/test/testplugin/README @@ -0,0 +1,441 @@ += Instructions for using the test plugin = + +== MIME type == + +The test plugin registers itself for the MIME type "application/x-test". + +== Event Model == + +* getEventModel() +Returns the NPAPI event model in use. On platforms without event models, +simply returns 0; + +== Rendering == + +By default, the plugin fills its rectangle with gray, with a black border, and +renders the user-agent string (obtained from NPN_UserAgent) centered in black. +This rendering method is not supported for the async drawing models. + +The test plugin supports the following parameters: + +* drawmode="solid" +The plugin will draw a solid color instead of the default rendering described +above. The default solid color is completely transparent black (i.e., nothing). +This should be specified when using one of the async models. + +* asyncmodel="bitmap" +The plugin will use the NPAPI Async Bitmap drawing model extension. On +unsupported platforms this will fallback to non-async rendering. + +* asyncmodel="dxgi" +The plugin will use the NPAPI Async DXGI drawing model extension. Only +supported on Windows Vista or higher. On unsupported platforms this will +fallback to non-async rendering. + +* color +This specifies the color to use for drawmode="solid". The value should be 8 hex +digits, 2 per channel in AARRGGBB format. + +== Generic API Tests == + +* setUndefinedValueTest +Attempts to set the value of an undefined variable (0x0) via NPN_SetValue, +returns true if it succeeds and false if it doesn't. It should never succeed. + +* .getReflector() +Hands back an object which reflects properties as values, e.g. + .getReflector().foo = 'foo' + .getReflector()['foo'] = 'foo' + .getReflector()[1] = 1 + +* .getNPNVdocumentOrigin() +Returns the origin string retrieved from the browser by a NPNVdocumentOrigin +variable request. Does not cache the value, gets it from the browser every time. + +== NPN_ConvertPoint testing == + +* convertPointX(sourceSpace, sourceX, sourceY, destSpace) +* convertPointY(sourceSpace, sourceX, sourceY, destSpace) +The plugin uses NPN_ConvertPoint to convert sourceX and sourceY from the source +to dest space and returns the X or Y result based on the call. + +== NPCocoaEventWindowFocusChanged == + +* getTopLevelWindowActivationState() +Returns the activation state for the top-level window as set by the last +NPCocoaEventWindowFocusChanged event. Returns true for active, false for +inactive, and throws an exception if the state is unknown (uninitialized). + +* getTopLevelWindowActivationEventCount() +Returns the number of NPCocoaEventWindowFocusChanged events received by +the instance. + +== Focus Tests == + +* getFocusState() +Returns the plugin's focus state. Returns true for focused, false for unfocused, +and throws an exception if the state is unknown (uninitialized). This does not +necessarily correspond to actual input focus - this corresponds to focus as +defined by the NPAPI event model in use. + +* getFocusEventCount() +Returns the number of focus events received by the instance. + +== NPRuntime testing == + +The test plugin object supports the following scriptable methods: + +* identifierToStringTest(ident) +Converts a string, int32 or double parameter 'ident' to an NPIdentifier and +then to a string, which is returned. + +* npnEvaluateTest(script) +Calls NPN_Evaluate on the 'script' argument, which is a string containing +some script to be executed. Returns the result of the evaluation. + +* npnInvokeTest(method, expected, args...) +Causes the plugin to call the specified script method using NPN_Invoke, +passing it 1 or more arguments specified in args. The return value of this +call is compared against 'expected', and if they match, npnInvokeTest will +return true. Otherwise, it will return false. + +* npnInvokeDefaultTest(object, argument) +Causes the plugin to call NPN_InvokeDefault on the specified object, +with the specified argument. Returns the result of the invocation. + +* getError() +If an error has occurred during the last stream or npruntime function, +this will return a string error message, otherwise it returns "pass". + +* throwExceptionNextInvoke() +Sets a flag which causes the next call to a scriptable method to throw +one or more exceptions. If no parameters are passed to the next +scriptable method call, it will cause a generic exception to be thrown. +Otherwise there will be one exception thrown per argument, with the argument +used as the exception message. Example: + + plugin.throwExceptionNextInvoke(); + plugin.npnInvokeTest("first exception message", "second exception message"); + +* () - default method +Returns a string consisting of the plugin name, concatenated with any +arguments passed to the method. + +* .crash() - Crashes the plugin + +* getObjectValue() - Returns a custom plugin-implemented scriptable object. +* checkObjectValue(obj) - Returns true if the object from getObjectValue() is + the same object passed into this function. It should return true when + the object is passed to the same plugin instance, and false when passed + to other plugin instances, see bug 532246 and + test_multipleinstanceobjects.html. + +* callOnDestroy(fn) - Calls `fn` when the plugin instance is being destroyed + +* getAuthInfo(protocol, host, port, scheme, realm) - a wrapper for +NPN_GetAuthenticationInfo(). Returns a string "username|password" for +the specified auth criteria, or throws an exception if no data is +available. + +* timerTest(callback) - initiates tests of NPN_ScheduleTimer & +NPN_UnscheduleTimer. When finished, calls the script callback +with a boolean value, indicating whether the tests were successful. + +* asyncCallbackTest(callback) - initiates tests of +NPN_PluginThreadAsyncCall. When finished, calls the script callback +with a boolean value, indicating whether the tests were successful. + +* paintscript="..." content attribute +If the "paintscript" attribute is set on the plugin element during plugin +initialization, then every time the plugin paints it gets the contents of that +attribute and evaluates it as a script in the context of the plugin's DOM +window. This is useful for testing evil plugin code that might, for example, +modify the DOM during painting. + +== Private browsing == + +The test plugin object supports the following scriptable methods: + +* queryPrivateModeState +Returns the value of NPN_GetValue(NPNVprivateModeBool). + +* lastReportedPrivateModeState +Returns the last value set by NPP_SetValue(NPNVprivateModeBool). + +== Windowed/windowless mode == + +The test plugin is windowless by default. + +The test plugin supports the following parameter: + +* wmode="window" +The plugin will be given a native widget on platforms where we support this +(Windows and X). + +The test plugin object supports the following scriptable method: + +* hasWidget() +Returns true if the plugin has an associated widget. This will return true if +wmode="window" was specified and the platform supports windowed plugins. + +== Plugin invalidation == + +* setColor(colorString) +Sets the color used for drawmode="solid" and invalidates the plugin area. +This calls NPN_Invalidate, even for windowed plugins, since that should work +for windowed plugins too (Silverlight depends on it). + +* getPaintCount() +Returns the number of times this plugin instance has processed a paint request. +This can be used to detect whether painting has happened in a plugin's +window. + +* getWidthAtLastPaint() +Returns the window width that was current when the plugin last painted. + +* setInvalidateDuringPaint(value) +When value is true, every time the plugin paints, it will invalidate +itself *during the paint* using NPN_Invalidate. + +* setSlowPaint(value) +When value is true, the instance will sleep briefly during paint. + +== Plugin geometry == + +The test plugin supports the following scriptable methods: + +* getEdge(edge) +Returns the integer screen pixel coordinate of an edge of the plugin's +area: +-- edge=0: returns left edge coordinate +-- edge=1: returns top edge coordinate +-- edge=2: returns right edge coordinate +-- edge=3: returns bottom edge coordinate +The coordinates are relative to the top-left corner of the top-level window +containing the plugin, including the window decorations. Therefore: +-- On Mac, they're relative to the top-left corner of the toplevel Cocoa +window. +-- On Windows, they're relative to the top-left corner of the toplevel HWND's +non-client area. +-- On GTK2, they're relative to the top-left corner of the toplevel window's +window manager frame. +This means they can be added to Gecko's window.screenX/screenY (if DPI is set +to 96) to get screen coordinates. +On the platforms that support window-mode plugins (Windows/GTK2), this only +works for window-mode plugins. It will throw an error for windowless plugins. + +* getClipRegionRectCount() +Returns the number of rectangles in the plugin's clip region. +For plugins with widgets, the clip region is computed as the intersection of the +clip region for the widget (if the platform does not support clip regions +on native widgets, this would just be the widget's rectangle) with the +clip regions of all ancestor widgets which would clip this widget. +On the platforms that support window-mode plugins (Windows/GTK2), this only +works for window-mode plugins. It will throw an error for windowless plugins. +On Mac, all plugins have a clip region containing just a single clip +rectangle only. So if you request wmode="window" but the plugin reports +!hasWidget, you can assume that complex clip regions are not supported. + +* getClipRegionRectEdge(i, edge) +Returns the integer screen pixel coordinate of an edge of a rectangle from the +plugin's clip region. If i is less than zero or greater than or equal to +getClipRegionRectCount(), this will throw an error. The coordinates are +the same as for getEdge. See getClipRegionRectCount() above for +notes on platform plugin limitations. + +== Keyboard events == + +* getLastKeyText() +Returns the text which was inputted by last keyboard events. This is cleared at +every keydown event. +NOTE: Currently, this is implemented only on Windows. + +== Mouse events == + +The test plugin supports the following scriptable methods: + +* getLastMouseX() +Returns the X coordinate of the last mouse event (move, button up, or +button down), relative to the left edge of the plugin, or -1 if no mouse +event has been received. + +* getLastMouseX() +Returns the Y coordinate of the last mouse event (move, button up, or +button down), relative to the top edge of the plugin, or -1 if no mouse +event has been received. + +== Instance lifecycle == + +The test plugin supports the following scriptable methods: + +* startWatchingInstanceCount() +Marks all currently running instances as "ignored". Throws an exception if +there is already a watch (startWatchingInstanceCount has already been +called on some instance without a corresponding stopWatchingInstanceCount). + +* getInstanceCount() +Returns the count of currently running instances that are not ignored. +Throws an exception if there is no watch. + +* stopWatchingInstanceCount() +Stops watching. Throws an exception if there is no watch. + +== NPAPI Timers == + +* unscheduleAllTimers() +Instructs the plugin instance to cancel all timers created via +NPN_ScheduleTimer. + +== Stream Functionality == + +The test plugin enables a variety of NPAPI streaming tests, which are +initiated by passing a variety of attributes to the <embed> element which +causes the plugin to be initialized. The plugin stream test code is +designed to receive a stream from the browser (by specifying a "src", +"geturl", or "geturlnotify" attribute), and then (if a "frame" attribute +is specified) send the data from that stream back to the browser in another +stream, whereupon it will be displayed in the specified frame. If some +error occurs during stream processing, an error message will appear in the +frame instead of the stream data. If no "frame" attribute is present, a +stream can still be received by the plugin, but the plugin will do nothing +with it. + +The attributes which control stream tests are: + +"streammode": one of "normal", "asfile", "asfileonly", "seek". Sets the + stream mode to the specified mode in any call to NPP_NewStream. + Defaults to "asfileonly". + +"streamchunksize": the number of bytes the plugin reports it can accept + in calls to NPP_WriteReady. Defaults to 1,024. + +"src": a url. If specified, the browser will call NPP_NewStream for + this url as soon as the plugin is initialized. + +"geturl": a url. If specified, the plugin will request this url + from the browser when the plugin is initialized, via a call to + NPN_GetURL. + +"geturlnotify": a url. If specified, the plugin will request this url + from the browser when the plugin is initialized, via a call to + NPN_GetURLNotify. The plugin passes some "notifyData" to + NPN_GetURLNotify, which it verifies is present in the call to + NPP_URLNotify. If the "notifyData" does not match, an error + will be displayed in the test frame (if any), instead of the stream + data. + +"frame": the name of a frame in the same HTML document as the <embed> + element which instantiated the plugin. For any of the preceding three + attributes, a stream is received by the plugin via calls to NPP_NewStream, + NPP_WriteReady, NPP_Write, and NPP_DestroyStream. When NPP_DestroyStream + is called (or NPP_UrlNotify, in the case of "geturlnotify"), and a + "frame" attribute is present, the data from the stream is converted into a + data: url, and sent back to the browser in another stream via a call to + NPN_GetURL, whereupon it should be displayed in the specified frame. + +"range": one or more byte ranges, in the format "offset,length;offset,length". + Only valid when "streammode" = "seek". When "range" is present, the plugin + will request the specified byte ranges from the stream via a call to + NPN_RequestRead, which it makes after the browser makes its final call to + NPP_Write. The plugin verifies that the browser makes additional calls + to NPP_Write according to the requested byte ranges, and that the data + received is correct. Any errors will appear in the test "frame", if + specified. + +"posturl": a url. After the plugin receives a stream, and NPP_DestroyStream + is called, if "posturl" is specified, the plugin will post the contents + of the stream to the specified url via NPN_PostURL. See "postmode" for + additional details. + +"postmode": either "frame" or "stream". If "frame", and a "frame" attribute + is present, the plugin will pass the frame name to calls to NPN_PostURL, + so that the HTTP response from that operation will be displayed in the + specified frame. If "stream", the HTTP response is delivered to the plugin + via calls to NPP_NewStream etc, and if a "frame" attribute is present, the + contents of that stream will be passed back to the browser and displayed + in the specified frame via NPN_GetURL. + +"newstream": if "true", then any stream which is sent to a frame in the browser + is sent via calls to NPN_NewStream and NPN_Write. Doing so will cause + the browser to store the stream data in a file, and set the frame's + location to the corresponding file:// url. + +"functiontofail": one of "npp_newstream", "npp_write", "npp_destroystream". + When specified, the given function will return an error code (-1 for + NPP_Write, or else the value of the "failurecode" attribute) the first time + it is called by the browser. + +"failurecode": one of the NPError constants. Used to specify the error + that will be returned by the "functiontofail". + +If the plugin is instantiated as a full-page plugin, the following defaults +are used: + streammode="seek" frame="testframe" range="100,100" + +* streamTest(url, doPost, postData, writeCallback, notifyCallback, redirectCallback, allowRedirects) +This will test how NPN_GetURLNotify and NPN_PostURLNotify behave when they are +called with arbitrary (malformed) URLs. The function will return `true` if +NPN_[Get/Post]URLNotify succeeds, and `false` if it fails. +@url url to request +@param doPost whether to call NPN_PostURLNotify +@param postData null, or a string to send a postdata +@writeCallback will be called when data is received for the stream +@notifyCallback will be called when the urlnotify is received with the notify result +@redirectCallback will be called from urlredirectnotify if a redirect is attempted +@allowRedirects boolean value indicating whether or not to allow redirects + +* setPluginWantsAllStreams(wantsAllStreams) +Set the value returned by the plugin for NPPVpluginWantsAllNetworkStreams. + +== Internal consistency == + +* doInternalConsistencyCheck() +Does internal consistency checking, returning an empty string if everything is +OK, otherwise returning some kind of error string. On Windows, in windowed +mode, this checks that the position of the plugin's internal child +window has not been disturbed relative to the plugin window. + +== Windows native widget message behaviour == + +* Mouse events are handled (saving the last mouse event coordinate) and not +passed to the overridden windowproc. + +* WM_MOUSEWHEEL events are handled and not passed to the parent window or the +overridden windowproc. + +* WM_MOUSEACTIVATE events are handled by calling SetFocus on the plugin's +widget, if the plugin is windowed. If it's not windowed they're passed to +the overriden windowproc (but hopefully never sent by the browser anyway). + +== Getting and Setting Cookies == + +* setCookie(string) +Sets the given string as the cookie for window's URL. + +* getCookie() +Returns the cookie string for the window's URL, the cookie set by setCookie. + +== FPU Control == + +x86-only on some OSes: + +* The .enableFPExceptions() method will enable floating-point exceptions, + as evil plugins or extensions might do. + +== HiDPI Mode == + +* queryContentsScaleFactor() +Returns the contents scale factor. On platforms without support for this query +always returns 1.0 (a double value). Likewise on hardware without HiDPI mode +support. + +== Plugin audio channel support == + +* startAudioPlayback() +Simulates the plugin starting to play back audio. + +* stopAudioPlayback() +Simulates the plugin stopping to play back audio. + +* audioMuted() +Returns the last value set by NPP_SetValue(NPNVmuteAudioBool). diff --git a/dom/plugins/test/testplugin/flashplugin/Info.plist b/dom/plugins/test/testplugin/flashplugin/Info.plist new file mode 100644 index 000000000..0e6168e68 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnpswftest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.FlashTestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>FLASHTEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Shockwave Flash</string> + <key>WebPluginDescription</key> + <string>Flash plug-in for testing purposes.</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-shockwave-flash-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>swf</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Flash test type</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/flashplugin/moz.build b/dom/plugins/test/testplugin/flashplugin/moz.build new file mode 100644 index 000000000..3df524a2b --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/moz.build @@ -0,0 +1,11 @@ +# -*- 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/. + +SharedLibrary('npswftest') + +relative_path = 'flashplugin' +cocoa_name = 'npswftest' +include('../testplugin.mozbuild') diff --git a/dom/plugins/test/testplugin/flashplugin/nptest.def b/dom/plugins/test/testplugin/flashplugin/nptest.def new file mode 100644 index 000000000..3a62d05d9 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPSWFTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/flashplugin/nptest.rc b/dom/plugins/test/testplugin/flashplugin/nptest.rc new file mode 100644 index 000000000..e970d2609 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Flash plug-in for testing purposes." + VALUE "FileExtents", "swf" + VALUE "FileOpenName", "Flash test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npswftest" + VALUE "MIMEType", "application/x-shockwave-flash-test" + VALUE "OriginalFilename", "npswftest.dll" + VALUE "ProductName", "Shockwave Flash" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp b/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp new file mode 100644 index 000000000..140e0225c --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp @@ -0,0 +1,7 @@ +/* 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/. */ + +const char *sPluginName = "Shockwave Flash"; +const char *sPluginDescription = "Flash plug-in for testing purposes."; +const char *sMimeDescription = "application/x-shockwave-flash-test:swf:Flash test type"; diff --git a/dom/plugins/test/testplugin/javaplugin/Info.plist b/dom/plugins/test/testplugin/javaplugin/Info.plist new file mode 100644 index 000000000..16a45f264 --- /dev/null +++ b/dom/plugins/test/testplugin/javaplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnptestjava.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.JavaTestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>JAVATEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Java Test Plug-in</string> + <key>WebPluginDescription</key> + <string>Dummy Java plug-in for testing purposes.</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-java-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>tstjava</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Dummy java type</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/javaplugin/moz.build b/dom/plugins/test/testplugin/javaplugin/moz.build new file mode 100644 index 000000000..4e2abb3ed --- /dev/null +++ b/dom/plugins/test/testplugin/javaplugin/moz.build @@ -0,0 +1,11 @@ +# -*- 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/. + +SharedLibrary('nptestjava') + +relative_path = 'javaplugin' +cocoa_name = 'JavaTest' +include('../testplugin.mozbuild') diff --git a/dom/plugins/test/testplugin/javaplugin/nptest.def b/dom/plugins/test/testplugin/javaplugin/nptest.def new file mode 100644 index 000000000..da24cc4b6 --- /dev/null +++ b/dom/plugins/test/testplugin/javaplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPJAVATEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/javaplugin/nptest.rc b/dom/plugins/test/testplugin/javaplugin/nptest.rc new file mode 100644 index 000000000..61b18ef6e --- /dev/null +++ b/dom/plugins/test/testplugin/javaplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Dummy Java plug-in for testing purposes." + VALUE "FileExtents", "tstjava" + VALUE "FileOpenName", "Dummy java test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "nptestjava" + VALUE "MIMEType", "application/x-java-test" + VALUE "OriginalFilename", "nptestjava.dll" + VALUE "ProductName", "Java Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/javaplugin/nptest_name.cpp b/dom/plugins/test/testplugin/javaplugin/nptest_name.cpp new file mode 100644 index 000000000..ae3a8d146 --- /dev/null +++ b/dom/plugins/test/testplugin/javaplugin/nptest_name.cpp @@ -0,0 +1,7 @@ +/* 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/. */ + +const char *sPluginName = "Java Test Plug-in"; +const char *sPluginDescription = "Dummy Java plug-in for testing purposes."; +const char *sMimeDescription = "application/x-java-test:tstjava:Dummy java type"; diff --git a/dom/plugins/test/testplugin/moz.build b/dom/plugins/test/testplugin/moz.build new file mode 100644 index 000000000..a79e58c1d --- /dev/null +++ b/dom/plugins/test/testplugin/moz.build @@ -0,0 +1,13 @@ +# -*- 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/. + +DIRS += ['secondplugin', 'javaplugin', 'thirdplugin', 'flashplugin', 'silverlightplugin'] + +SharedLibrary('nptest') + +relative_path = '.' +cocoa_name = 'Test' +include('testplugin.mozbuild') diff --git a/dom/plugins/test/testplugin/nptest.cpp b/dom/plugins/test/testplugin/nptest.cpp new file mode 100644 index 000000000..aa759ac16 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.cpp @@ -0,0 +1,4064 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Dave Townsend <dtownsend@oxymoronical.com> + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest.h" +#include "nptest_utils.h" +#include "nptest_platform.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/IntentionalCrash.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <iostream> +#include <string> +#include <sstream> +#include <list> +#include <ctime> + +#ifdef XP_WIN +#include <process.h> +#include <float.h> +#include <windows.h> +#define getpid _getpid +#define strcasecmp _stricmp +#else +#include <unistd.h> +#include <pthread.h> +#endif + +using namespace std; + +#define PLUGIN_VERSION "1.0.0.0" + +extern const char *sPluginName; +extern const char *sPluginDescription; +static char sPluginVersion[] = PLUGIN_VERSION; + +// +// Intentional crash +// + +int gCrashCount = 0; + +static void Crash() +{ + int *pi = nullptr; + *pi = 55; // Crash dereferencing null pointer + ++gCrashCount; +} + +static void +IntentionalCrash() +{ + mozilla::NoteIntentionalCrash("plugin"); + Crash(); +} + +// +// static data +// + +static NPNetscapeFuncs* sBrowserFuncs = nullptr; +static NPClass sNPClass; + +void +asyncCallback(void* cookie); + +// +// identifiers +// + +typedef bool (* ScriptableFunction) + (NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); + +static bool npnEvaluateTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool npnInvokeTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool npnInvokeDefaultTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool setUndefinedValueTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool identifierToStringTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getClipRegionRectCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getLastMouseX(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getLastMouseY(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getPaintCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool resetPaintCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getWidthAtLastPaint(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool setInvalidateDuringPaint(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool setSlowPaint(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getError(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool doInternalConsistencyCheck(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool setColor(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool throwExceptionNextInvoke(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool convertPointX(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool convertPointY(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool streamTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool setPluginWantsAllStreams(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool crashPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool crashOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getObjectValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getJavaCodebase(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool checkObjectValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool setCookie(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getCookie(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getAuthInfo(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool asyncCallbackTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool checkGCRace(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool stallPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool reinitWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool triggerXError(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool propertyAndMethod(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getTopLevelWindowActivationState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getTopLevelWindowActivationEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getFocusState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getFocusEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getEventModel(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getReflector(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool isVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getWindowPosition(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool constructObject(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool setSitesWithData(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getLastKeyText(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getNPNVdocumentOrigin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getMouseUpEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool queryContentsScaleFactor(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool queryCSSZoomFactorGetValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool queryCSSZoomFactorSetValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool echoString(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool startAudioPlayback(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool stopAudioPlayback(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getAudioMuted(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +static bool getLastCompositionText(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); + +static const NPUTF8* sPluginMethodIdentifierNames[] = { + "npnEvaluateTest", + "npnInvokeTest", + "npnInvokeDefaultTest", + "setUndefinedValueTest", + "identifierToStringTest", + "timerTest", + "queryPrivateModeState", + "lastReportedPrivateModeState", + "hasWidget", + "getEdge", + "getClipRegionRectCount", + "getClipRegionRectEdge", + "startWatchingInstanceCount", + "getInstanceCount", + "stopWatchingInstanceCount", + "getLastMouseX", + "getLastMouseY", + "getPaintCount", + "resetPaintCount", + "getWidthAtLastPaint", + "setInvalidateDuringPaint", + "setSlowPaint", + "getError", + "doInternalConsistencyCheck", + "setColor", + "throwExceptionNextInvoke", + "convertPointX", + "convertPointY", + "streamTest", + "setPluginWantsAllStreams", + "crash", + "crashOnDestroy", + "getObjectValue", + "getJavaCodebase", + "checkObjectValue", + "enableFPExceptions", + "setCookie", + "getCookie", + "getAuthInfo", + "asyncCallbackTest", + "checkGCRace", + "hang", + "stall", + "getClipboardText", + "callOnDestroy", + "reinitWidget", + "crashInNestedLoop", + "triggerXError", + "destroySharedGfxStuff", + "propertyAndMethod", + "getTopLevelWindowActivationState", + "getTopLevelWindowActivationEventCount", + "getFocusState", + "getFocusEventCount", + "getEventModel", + "getReflector", + "isVisible", + "getWindowPosition", + "constructObject", + "setSitesWithData", + "setSitesWithDataCapabilities", + "getLastKeyText", + "getNPNVdocumentOrigin", + "getMouseUpEventCount", + "queryContentsScaleFactor", + "queryCSSZoomFactorSetValue", + "queryCSSZoomFactorGetValue", + "echoString", + "startAudioPlayback", + "stopAudioPlayback", + "audioMuted", + "nativeWidgetIsVisible", + "getLastCompositionText", +}; +static NPIdentifier sPluginMethodIdentifiers[MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames)]; +static const ScriptableFunction sPluginMethodFunctions[] = { + npnEvaluateTest, + npnInvokeTest, + npnInvokeDefaultTest, + setUndefinedValueTest, + identifierToStringTest, + timerTest, + queryPrivateModeState, + lastReportedPrivateModeState, + hasWidget, + getEdge, + getClipRegionRectCount, + getClipRegionRectEdge, + startWatchingInstanceCount, + getInstanceCount, + stopWatchingInstanceCount, + getLastMouseX, + getLastMouseY, + getPaintCount, + resetPaintCount, + getWidthAtLastPaint, + setInvalidateDuringPaint, + setSlowPaint, + getError, + doInternalConsistencyCheck, + setColor, + throwExceptionNextInvoke, + convertPointX, + convertPointY, + streamTest, + setPluginWantsAllStreams, + crashPlugin, + crashOnDestroy, + getObjectValue, + getJavaCodebase, + checkObjectValue, + enableFPExceptions, + setCookie, + getCookie, + getAuthInfo, + asyncCallbackTest, + checkGCRace, + hangPlugin, + stallPlugin, + getClipboardText, + callOnDestroy, + reinitWidget, + crashPluginInNestedLoop, + triggerXError, + destroySharedGfxStuff, + propertyAndMethod, + getTopLevelWindowActivationState, + getTopLevelWindowActivationEventCount, + getFocusState, + getFocusEventCount, + getEventModel, + getReflector, + isVisible, + getWindowPosition, + constructObject, + setSitesWithData, + setSitesWithDataCapabilities, + getLastKeyText, + getNPNVdocumentOrigin, + getMouseUpEventCount, + queryContentsScaleFactor, + queryCSSZoomFactorGetValue, + queryCSSZoomFactorSetValue, + echoString, + startAudioPlayback, + stopAudioPlayback, + getAudioMuted, + nativeWidgetIsVisible, + getLastCompositionText, +}; + +static_assert(MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames) == + MOZ_ARRAY_LENGTH(sPluginMethodFunctions), + "Arrays should have the same size"); + +static const NPUTF8* sPluginPropertyIdentifierNames[] = { + "propertyAndMethod" +}; +static NPIdentifier sPluginPropertyIdentifiers[MOZ_ARRAY_LENGTH(sPluginPropertyIdentifierNames)]; +static NPVariant sPluginPropertyValues[MOZ_ARRAY_LENGTH(sPluginPropertyIdentifierNames)]; + +struct URLNotifyData +{ + const char* cookie; + NPObject* writeCallback; + NPObject* notifyCallback; + NPObject* redirectCallback; + bool allowRedirects; + uint32_t size; + char* data; +}; + +static URLNotifyData kNotifyData = { + "static-cookie", + nullptr, + nullptr, + nullptr, + false, + 0, + nullptr +}; + +static const char* SUCCESS_STRING = "pass"; + +static bool sIdentifiersInitialized = false; + +struct timerEvent { + int32_t timerIdReceive; + int32_t timerIdSchedule; + uint32_t timerInterval; + bool timerRepeat; + int32_t timerIdUnschedule; +}; +static timerEvent timerEvents[] = { + {-1, 0, 200, false, -1}, + {0, 0, 400, false, -1}, + {0, 0, 200, true, -1}, + {0, 1, 400, true, -1}, + {0, -1, 0, false, 0}, + {1, -1, 0, false, -1}, + {1, -1, 0, false, 1}, +}; +static uint32_t currentTimerEventCount = 0; +static uint32_t totalTimerEvents = sizeof(timerEvents) / sizeof(timerEvent); + +/** + * Incremented for every startWatchingInstanceCount. + */ +static int32_t sCurrentInstanceCountWatchGeneration = 0; +/** + * Tracks the number of instances created or destroyed since the last + * startWatchingInstanceCount. + */ +static int32_t sInstanceCount = 0; +/** + * True when we've had a startWatchingInstanceCount with no corresponding + * stopWatchingInstanceCount. + */ +static bool sWatchingInstanceCount = false; + +/** + * A list representing sites for which the plugin has stored data. See + * NPP_ClearSiteData and NPP_GetSitesWithData. + */ +struct siteData { + string site; + uint64_t flags; + uint64_t age; +}; +static list<siteData>* sSitesWithData; +static bool sClearByAgeSupported; + +static void initializeIdentifiers() +{ + if (!sIdentifiersInitialized) { + NPN_GetStringIdentifiers(sPluginMethodIdentifierNames, + MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames), sPluginMethodIdentifiers); + NPN_GetStringIdentifiers(sPluginPropertyIdentifierNames, + MOZ_ARRAY_LENGTH(sPluginPropertyIdentifierNames), sPluginPropertyIdentifiers); + + sIdentifiersInitialized = true; + + // Check whether nullptr is handled in NPN_GetStringIdentifiers + NPIdentifier IDList[2]; + static char const *const kIDNames[2] = { nullptr, "setCookie" }; + NPN_GetStringIdentifiers(const_cast<const NPUTF8**>(kIDNames), 2, IDList); + } +} + +static void clearIdentifiers() +{ + memset(sPluginMethodIdentifiers, 0, + MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers) * sizeof(NPIdentifier)); + memset(sPluginPropertyIdentifiers, 0, + MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers) * sizeof(NPIdentifier)); + + sIdentifiersInitialized = false; +} + +static void addRange(InstanceData* instanceData, const char* range) +{ + /* + increased rangestr size from 16 to 17, the 17byte is only for + null terminated value, maybe for actual capacity it needs 16 bytes + */ + char rangestr[17]; + memset(rangestr, 0, sizeof(rangestr)); + strncpy(rangestr, range, sizeof(rangestr) - sizeof(char)); + const char* str1 = strtok(rangestr, ","); + const char* str2 = str1 ? strtok(nullptr, ",") : nullptr; + if (str1 && str2) { + TestRange* byterange = new TestRange; + byterange->offset = atoi(str1); + byterange->length = atoi(str2); + byterange->waiting = true; + byterange->next = instanceData->testrange; + instanceData->testrange = byterange; + } +} + +static void sendBufferToFrame(NPP instance) +{ + InstanceData* instanceData = (InstanceData*)(instance->pdata); + string outbuf; + if (!instanceData->npnNewStream) outbuf = "data:text/html,"; + const char* buf = reinterpret_cast<char *>(instanceData->streamBuf); + int32_t bufsize = instanceData->streamBufSize; + if (instanceData->streamMode == NP_ASFILE || + instanceData->streamMode == NP_ASFILEONLY) { + buf = reinterpret_cast<char *>(instanceData->fileBuf); + bufsize = instanceData->fileBufSize; + } + if (instanceData->err.str().length() > 0) { + outbuf.append(instanceData->err.str()); + } + else if (bufsize > 0) { + outbuf.append(buf); + } + else { + outbuf.append("Error: no data in buffer"); + } + + if (instanceData->npnNewStream && + instanceData->err.str().length() == 0) { + char typeHTML[] = "text/html"; + NPStream* stream; + printf("calling NPN_NewStream..."); + NPError err = NPN_NewStream(instance, typeHTML, + instanceData->frame.c_str(), &stream); + printf("return value %d\n", err); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_NewStream returned " << err; + return; + } + + int32_t bytesToWrite = outbuf.length(); + int32_t bytesWritten = 0; + while ((bytesToWrite - bytesWritten) > 0) { + int32_t numBytes = (bytesToWrite - bytesWritten) < + instanceData->streamChunkSize ? + bytesToWrite - bytesWritten : instanceData->streamChunkSize; + int32_t written = NPN_Write(instance, stream, + numBytes, (void*)(outbuf.c_str() + bytesWritten)); + if (written <= 0) { + instanceData->err << "NPN_Write returned " << written; + break; + } + bytesWritten += numBytes; + printf("%d bytes written, total %d\n", written, bytesWritten); + } + err = NPN_DestroyStream(instance, stream, NPRES_DONE); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_DestroyStream returned " << err; + } + } + else { + // Convert CRLF to LF, and escape most other non-alphanumeric chars. + for (size_t i = 0; i < outbuf.length(); i++) { + if (outbuf[i] == '\n') { + outbuf.replace(i, 1, "%0a"); + i += 2; + } + else if (outbuf[i] == '\r') { + outbuf.replace(i, 1, ""); + i -= 1; + } + else { + int ascii = outbuf[i]; + if (!((ascii >= ',' && ascii <= ';') || + (ascii >= 'A' && ascii <= 'Z') || + (ascii >= 'a' && ascii <= 'z'))) { + char hex[8]; + sprintf(hex, "%%%x", ascii); + outbuf.replace(i, 1, hex); + i += 2; + } + } + } + + NPError err = NPN_GetURL(instance, outbuf.c_str(), + instanceData->frame.c_str()); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURL returned " << err; + } + } +} + +static void XPSleep(unsigned int seconds) +{ +#ifdef XP_WIN + Sleep(1000 * seconds); +#else + sleep(seconds); +#endif +} + +TestFunction +getFuncFromString(const char* funcname) +{ + FunctionTable funcTable[] = + { + { FUNCTION_NPP_NEWSTREAM, "npp_newstream" }, + { FUNCTION_NPP_WRITEREADY, "npp_writeready" }, + { FUNCTION_NPP_WRITE, "npp_write" }, + { FUNCTION_NPP_DESTROYSTREAM, "npp_destroystream" }, + { FUNCTION_NPP_WRITE_RPC, "npp_write_rpc" }, + { FUNCTION_NONE, nullptr } + }; + int32_t i = 0; + while(funcTable[i].funcName) { + if (!strcmp(funcname, funcTable[i].funcName)) return funcTable[i].funcId; + i++; + } + return FUNCTION_NONE; +} + +static void +DuplicateNPVariant(NPVariant& aDest, const NPVariant& aSrc) +{ + if (NPVARIANT_IS_STRING(aSrc)) { + NPString src = NPVARIANT_TO_STRING(aSrc); + char* buf = new char[src.UTF8Length]; + strncpy(buf, src.UTF8Characters, src.UTF8Length); + STRINGN_TO_NPVARIANT(buf, src.UTF8Length, aDest); + } + else if (NPVARIANT_IS_OBJECT(aSrc)) { + NPObject* obj = + NPN_RetainObject(NPVARIANT_TO_OBJECT(aSrc)); + OBJECT_TO_NPVARIANT(obj, aDest); + } + else { + aDest = aSrc; + } +} + +static bool bug813906(NPP npp, const char* const function, const char* const url, const char* const frame) +{ + NPObject *windowObj = nullptr; + NPError err = NPN_GetValue(npp, NPNVWindowNPObject, &windowObj); + if (err != NPERR_NO_ERROR) { + return false; + } + + NPVariant result; + bool res = NPN_Invoke(npp, windowObj, NPN_GetStringIdentifier(function), nullptr, 0, &result); + NPN_ReleaseObject(windowObj); + if (!res) { + return false; + } + + NPN_ReleaseVariantValue(&result); + + err = NPN_GetURL(npp, url, frame); + if (err != NPERR_NO_ERROR) { + err = NPN_GetURL(npp, "about:blank", frame); + return false; + } + + return true; +} + +void +drawAsyncBitmapColor(InstanceData* instanceData) +{ + NPP npp = instanceData->npp; + + uint32_t *pixelData = (uint32_t*)instanceData->backBuffer->bitmap.data; + + uint32_t rgba = instanceData->scriptableObject->drawColor; + + unsigned char subpixels[4]; + memcpy(subpixels, &rgba, sizeof(subpixels)); + + subpixels[0] = uint8_t(float(subpixels[3] * subpixels[0]) / 0xFF); + subpixels[1] = uint8_t(float(subpixels[3] * subpixels[1]) / 0xFF); + subpixels[2] = uint8_t(float(subpixels[3] * subpixels[2]) / 0xFF); + uint32_t premultiplied; + memcpy(&premultiplied, subpixels, sizeof(premultiplied)); + + for (uint32_t* lastPixel = pixelData + instanceData->backBuffer->size.width * instanceData->backBuffer->size.height; + pixelData < lastPixel; + ++pixelData) + { + *pixelData = premultiplied; + } + + NPN_SetCurrentAsyncSurface(npp, instanceData->backBuffer, NULL); + NPAsyncSurface *oldFront = instanceData->frontBuffer; + instanceData->frontBuffer = instanceData->backBuffer; + instanceData->backBuffer = oldFront; +} + +// +// function signatures +// + +NPObject* scriptableAllocate(NPP npp, NPClass* aClass); +void scriptableDeallocate(NPObject* npobj); +void scriptableInvalidate(NPObject* npobj); +bool scriptableHasMethod(NPObject* npobj, NPIdentifier name); +bool scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, uint32_t argCount, NPVariant* result); +bool scriptableInvokeDefault(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); +bool scriptableHasProperty(NPObject* npobj, NPIdentifier name); +bool scriptableGetProperty(NPObject* npobj, NPIdentifier name, NPVariant* result); +bool scriptableSetProperty(NPObject* npobj, NPIdentifier name, const NPVariant* value); +bool scriptableRemoveProperty(NPObject* npobj, NPIdentifier name); +bool scriptableEnumerate(NPObject* npobj, NPIdentifier** identifier, uint32_t* count); +bool scriptableConstruct(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result); + +// +// npapi plugin functions +// + +#ifdef XP_UNIX +NP_EXPORT(char*) +NP_GetPluginVersion() +{ + return sPluginVersion; +} +#endif + +extern const char *sMimeDescription; + +#if defined(XP_UNIX) +NP_EXPORT(const char*) NP_GetMIMEDescription() +#elif defined(XP_WIN) +const char* NP_GetMIMEDescription() +#endif +{ + return sMimeDescription; +} + +#ifdef XP_UNIX +NP_EXPORT(NPError) +NP_GetValue(void* future, NPPVariable aVariable, void* aValue) { + switch (aVariable) { + case NPPVpluginNameString: + *((const char**)aValue) = sPluginName; + break; + case NPPVpluginDescriptionString: + *((const char**)aValue) = sPluginDescription; + break; + default: + return NPERR_INVALID_PARAM; + } + return NPERR_NO_ERROR; +} +#endif + +static bool fillPluginFunctionTable(NPPluginFuncs* pFuncs) +{ + // Check the size of the provided structure based on the offset of the + // last member we need. + if (pFuncs->size < (offsetof(NPPluginFuncs, getsiteswithdata) + sizeof(void*))) + return false; + + pFuncs->newp = NPP_New; + pFuncs->destroy = NPP_Destroy; + pFuncs->setwindow = NPP_SetWindow; + pFuncs->newstream = NPP_NewStream; + pFuncs->destroystream = NPP_DestroyStream; + pFuncs->asfile = NPP_StreamAsFile; + pFuncs->writeready = NPP_WriteReady; + pFuncs->write = NPP_Write; + pFuncs->print = NPP_Print; + pFuncs->event = NPP_HandleEvent; + pFuncs->urlnotify = NPP_URLNotify; + pFuncs->getvalue = NPP_GetValue; + pFuncs->setvalue = NPP_SetValue; + pFuncs->urlredirectnotify = NPP_URLRedirectNotify; + pFuncs->clearsitedata = NPP_ClearSiteData; + pFuncs->getsiteswithdata = NPP_GetSitesWithData; + + return true; +} + +#if defined(XP_MACOSX) +NP_EXPORT(NPError) NP_Initialize(NPNetscapeFuncs* bFuncs) +#elif defined(XP_WIN) +NPError OSCALL NP_Initialize(NPNetscapeFuncs* bFuncs) +#elif defined(XP_UNIX) +NP_EXPORT(NPError) NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs) +#endif +{ + sBrowserFuncs = bFuncs; + + initializeIdentifiers(); + + for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sPluginPropertyValues); i++) { + VOID_TO_NPVARIANT(sPluginPropertyValues[i]); + } + + memset(&sNPClass, 0, sizeof(NPClass)); + sNPClass.structVersion = NP_CLASS_STRUCT_VERSION; + sNPClass.allocate = (NPAllocateFunctionPtr)scriptableAllocate; + sNPClass.deallocate = (NPDeallocateFunctionPtr)scriptableDeallocate; + sNPClass.invalidate = (NPInvalidateFunctionPtr)scriptableInvalidate; + sNPClass.hasMethod = (NPHasMethodFunctionPtr)scriptableHasMethod; + sNPClass.invoke = (NPInvokeFunctionPtr)scriptableInvoke; + sNPClass.invokeDefault = (NPInvokeDefaultFunctionPtr)scriptableInvokeDefault; + sNPClass.hasProperty = (NPHasPropertyFunctionPtr)scriptableHasProperty; + sNPClass.getProperty = (NPGetPropertyFunctionPtr)scriptableGetProperty; + sNPClass.setProperty = (NPSetPropertyFunctionPtr)scriptableSetProperty; + sNPClass.removeProperty = (NPRemovePropertyFunctionPtr)scriptableRemoveProperty; + sNPClass.enumerate = (NPEnumerationFunctionPtr)scriptableEnumerate; + sNPClass.construct = (NPConstructFunctionPtr)scriptableConstruct; + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + if (!fillPluginFunctionTable(pFuncs)) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } +#endif + + return NPERR_NO_ERROR; +} + +#if defined(XP_MACOSX) +NP_EXPORT(NPError) NP_GetEntryPoints(NPPluginFuncs* pFuncs) +#elif defined(XP_WIN) +NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs) +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +{ + if (!fillPluginFunctionTable(pFuncs)) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } + + return NPERR_NO_ERROR; +} +#endif + +#if defined(XP_UNIX) +NP_EXPORT(NPError) NP_Shutdown() +#elif defined(XP_WIN) +NPError OSCALL NP_Shutdown() +#endif +{ + clearIdentifiers(); + + for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sPluginPropertyValues); i++) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + } + + return NPERR_NO_ERROR; +} + +NPError +NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char* argn[], char* argv[], NPSavedData* saved) +{ + // Make sure our pdata field is nullptr at this point. If it isn't, that + // probably means the browser gave us uninitialized memory. + if (instance->pdata) { + printf("NPP_New called with non-NULL NPP->pdata pointer!\n"); + return NPERR_GENERIC_ERROR; + } + + // Make sure we can render this plugin + NPBool browserSupportsWindowless = false; + NPN_GetValue(instance, NPNVSupportsWindowless, &browserSupportsWindowless); + if (!browserSupportsWindowless && !pluginSupportsWindowMode()) { + printf("Windowless mode not supported by the browser, windowed mode not supported by the plugin!\n"); + return NPERR_GENERIC_ERROR; + } + + // set up our our instance data + InstanceData* instanceData = new InstanceData; + instanceData->npp = instance; + instanceData->streamMode = NP_ASFILEONLY; + instanceData->testFunction = FUNCTION_NONE; + instanceData->functionToFail = FUNCTION_NONE; + instanceData->failureCode = 0; + instanceData->callOnDestroy = nullptr; + instanceData->streamChunkSize = 1024; + instanceData->streamBuf = nullptr; + instanceData->streamBufSize = 0; + instanceData->fileBuf = nullptr; + instanceData->fileBufSize = 0; + instanceData->throwOnNextInvoke = false; + instanceData->runScriptOnPaint = false; + instanceData->dontTouchElement = false; + instanceData->testrange = nullptr; + instanceData->hasWidget = false; + instanceData->npnNewStream = false; + instanceData->invalidateDuringPaint = false; + instanceData->slowPaint = false; + instanceData->playingAudio = false; + instanceData->audioMuted = false; + instanceData->writeCount = 0; + instanceData->writeReadyCount = 0; + memset(&instanceData->window, 0, sizeof(instanceData->window)); + instanceData->crashOnDestroy = false; + instanceData->cleanupWidget = true; // only used by nptest_gtk + instanceData->topLevelWindowActivationState = ACTIVATION_STATE_UNKNOWN; + instanceData->topLevelWindowActivationEventCount = 0; + instanceData->focusState = ACTIVATION_STATE_UNKNOWN; + instanceData->focusEventCount = 0; + instanceData->eventModel = 0; + instanceData->closeStream = false; + instanceData->wantsAllStreams = false; + instanceData->mouseUpEventCount = 0; + instanceData->bugMode = -1; + instanceData->asyncDrawing = AD_NONE; + instanceData->frontBuffer = nullptr; + instanceData->backBuffer = nullptr; + instanceData->placeholderWnd = nullptr; + instanceData->cssZoomFactor = 1.0; + instance->pdata = instanceData; + + TestNPObject* scriptableObject = (TestNPObject*)NPN_CreateObject(instance, &sNPClass); + if (!scriptableObject) { + printf("NPN_CreateObject failed to create an object, can't create a plugin instance\n"); + delete instanceData; + return NPERR_GENERIC_ERROR; + } + scriptableObject->npp = instance; + scriptableObject->drawMode = DM_DEFAULT; + scriptableObject->drawColor = 0; + instanceData->scriptableObject = scriptableObject; + + instanceData->instanceCountWatchGeneration = sCurrentInstanceCountWatchGeneration; + + if (NP_FULL == mode) { + instanceData->streamMode = NP_SEEK; + instanceData->frame = "testframe"; + addRange(instanceData, "100,100"); + } + + AsyncDrawing requestAsyncDrawing = AD_NONE; + + bool requestWindow = false; + bool alreadyHasSalign = false; + // handle extra params + for (int i = 0; i < argc; i++) { + if (strcmp(argn[i], "drawmode") == 0) { + if (strcmp(argv[i], "solid") == 0) + scriptableObject->drawMode = DM_SOLID_COLOR; + } + else if (strcmp(argn[i], "color") == 0) { + scriptableObject->drawColor = parseHexColor(argv[i], strlen(argv[i])); + } + else if (strcmp(argn[i], "wmode") == 0) { + if (strcmp(argv[i], "window") == 0) { + requestWindow = true; + } + } + else if (strcmp(argn[i], "asyncmodel") == 0) { + if (strcmp(argv[i], "bitmap") == 0) { + requestAsyncDrawing = AD_BITMAP; + } else if (strcmp(argv[i], "dxgi") == 0) { + requestAsyncDrawing = AD_DXGI; + } + } + if (strcmp(argn[i], "streammode") == 0) { + if (strcmp(argv[i], "normal") == 0) { + instanceData->streamMode = NP_NORMAL; + } + else if ((strcmp(argv[i], "asfile") == 0) && + strlen(argv[i]) == strlen("asfile")) { + instanceData->streamMode = NP_ASFILE; + } + else if (strcmp(argv[i], "asfileonly") == 0) { + instanceData->streamMode = NP_ASFILEONLY; + } + else if (strcmp(argv[i], "seek") == 0) { + instanceData->streamMode = NP_SEEK; + } + } + if (strcmp(argn[i], "streamchunksize") == 0) { + instanceData->streamChunkSize = atoi(argv[i]); + } + if (strcmp(argn[i], "failurecode") == 0) { + instanceData->failureCode = atoi(argv[i]); + } + if (strcmp(argn[i], "functiontofail") == 0) { + instanceData->functionToFail = getFuncFromString(argv[i]); + } + if (strcmp(argn[i], "geturl") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_GETURL; + } + if (strcmp(argn[i], "posturl") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_POSTURL; + } + if (strcmp(argn[i], "geturlnotify") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_GETURLNOTIFY; + } + if (strcmp(argn[i], "postmode") == 0) { + if (strcmp(argv[i], "frame") == 0) { + instanceData->postMode = POSTMODE_FRAME; + } + else if (strcmp(argv[i], "stream") == 0) { + instanceData->postMode = POSTMODE_STREAM; + } + } + if (strcmp(argn[i], "frame") == 0) { + instanceData->frame = argv[i]; + } + if (strcmp(argn[i], "range") == 0) { + string range = argv[i]; + size_t semicolon = range.find(';'); + while (semicolon != string::npos) { + addRange(instanceData, range.substr(0, semicolon).c_str()); + if (semicolon == range.length()) { + range = ""; + break; + } + range = range.substr(semicolon + 1); + semicolon = range.find(';'); + } + if (range.length()) addRange(instanceData, range.c_str()); + } + if (strcmp(argn[i], "newstream") == 0 && + strcmp(argv[i], "true") == 0) { + instanceData->npnNewStream = true; + } + if (strcmp(argn[i], "newcrash") == 0) { + IntentionalCrash(); + } + if (strcmp(argn[i], "paintscript") == 0) { + instanceData->runScriptOnPaint = true; + } + + if (strcmp(argn[i], "donttouchelement") == 0) { + instanceData->dontTouchElement = true; + } + // "cleanupwidget" is only used with nptest_gtk, defaulting to true. It + // indicates whether the plugin should destroy its window in response to + // NPP_Destroy (or let the platform destroy the widget when the parent + // window gets destroyed). + if (strcmp(argn[i], "cleanupwidget") == 0 && + strcmp(argv[i], "false") == 0) { + instanceData->cleanupWidget = false; + } + if (!strcmp(argn[i], "closestream")) { + instanceData->closeStream = true; + } + if (strcmp(argn[i], "bugmode") == 0) { + instanceData->bugMode = atoi(argv[i]); + } + // Try to emulate java's codebase handling: Use the last seen codebase + // value, regardless of whether it is in attributes or params. + if (strcasecmp(argn[i], "codebase") == 0) { + instanceData->javaCodebase = argv[i]; + } + + // Bug 1307694 - There are two flash parameters that are order dependent for + // scaling/sizing the plugin. If they ever change from what is expected, it + // breaks flash on the web. In a test, if the scale tag ever happens + // with an salign before it, fail the plugin creation. + if (strcmp(argn[i], "scale") == 0) { + if (alreadyHasSalign) { + // If salign came before this parameter, error out now. + return NPERR_GENERIC_ERROR; + } + } + if (strcmp(argn[i], "salign") == 0) { + alreadyHasSalign = true; + } +} + + if (!browserSupportsWindowless || !pluginSupportsWindowlessMode()) { + requestWindow = true; + } else if (!pluginSupportsWindowMode()) { + requestWindow = false; + } + if (requestWindow) { + instanceData->hasWidget = true; + } else { + // NPPVpluginWindowBool should default to true, so we may as well + // test that by not setting it in the window case + NPN_SetValue(instance, NPPVpluginWindowBool, (void*)false); + } + + if (scriptableObject->drawMode == DM_SOLID_COLOR && + (scriptableObject->drawColor & 0xFF000000) != 0xFF000000) { + NPN_SetValue(instance, NPPVpluginTransparentBool, (void*)true); + } + + if (requestAsyncDrawing == AD_BITMAP) { + NPBool supportsAsyncBitmap = false; + if ((NPN_GetValue(instance, NPNVsupportsAsyncBitmapSurfaceBool, &supportsAsyncBitmap) == NPERR_NO_ERROR) && + supportsAsyncBitmap) { + if (NPN_SetValue(instance, NPPVpluginDrawingModel, (void*)NPDrawingModelAsyncBitmapSurface) == NPERR_NO_ERROR) { + instanceData->asyncDrawing = AD_BITMAP; + } + } + } +#ifdef XP_WIN + else if (requestAsyncDrawing == AD_DXGI) { + NPBool supportsAsyncDXGI = false; + if ((NPN_GetValue(instance, NPNVsupportsAsyncWindowsDXGISurfaceBool, &supportsAsyncDXGI) == NPERR_NO_ERROR) && + supportsAsyncDXGI) { + if (NPN_SetValue(instance, NPPVpluginDrawingModel, (void*)NPDrawingModelAsyncWindowsDXGISurface) == NPERR_NO_ERROR) { + instanceData->asyncDrawing = AD_DXGI; + } + } + } +#endif + + // If we can't get the right drawing mode, we fail, otherwise our tests might + // appear to be passing when they shouldn't. Real plugins should not do this. + if (instanceData->asyncDrawing != requestAsyncDrawing) { + return NPERR_GENERIC_ERROR; + } + + instanceData->lastReportedPrivateModeState = false; + instanceData->lastMouseX = instanceData->lastMouseY = -1; + instanceData->widthAtLastPaint = -1; + instanceData->paintCount = 0; + + // do platform-specific initialization + NPError err = pluginInstanceInit(instanceData); + if (err != NPERR_NO_ERROR) { + NPN_ReleaseObject(scriptableObject); + delete instanceData; + return err; + } + + NPVariant variantTrue; + BOOLEAN_TO_NPVARIANT(true, variantTrue); + NPObject* o = nullptr; + + // Set a property on NPNVPluginElementNPObject, unless the consumer explicitly + // opted out of this behavior. + if (!instanceData->dontTouchElement) { + err = NPN_GetValue(instance, NPNVPluginElementNPObject, &o); + if (err == NPERR_NO_ERROR) { + NPN_SetProperty(instance, o, + NPN_GetStringIdentifier("pluginFoundElement"), &variantTrue); + NPN_ReleaseObject(o); + o = nullptr; + } + } + + // Set a property on NPNVWindowNPObject + err = NPN_GetValue(instance, NPNVWindowNPObject, &o); + if (err == NPERR_NO_ERROR) { + NPN_SetProperty(instance, o, + NPN_GetStringIdentifier("pluginFoundWindow"), &variantTrue); + NPN_ReleaseObject(o); + o = nullptr; + } + + ++sInstanceCount; + + if (instanceData->testFunction == FUNCTION_NPP_GETURL) { + NPError err = NPN_GetURL(instance, instanceData->testUrl.c_str(), nullptr); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURL returned " << err; + } + } + else if (instanceData->testFunction == FUNCTION_NPP_GETURLNOTIFY) { + NPError err = NPN_GetURLNotify(instance, instanceData->testUrl.c_str(), + nullptr, static_cast<void*>(&kNotifyData)); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURLNotify returned " << err; + } + } + + if ((instanceData->bugMode == 813906) && instanceData->frame.length()) { + bug813906(instance, "f", "browser.xul", instanceData->frame.c_str()); + } + + return NPERR_NO_ERROR; +} + +NPError +NPP_Destroy(NPP instance, NPSavedData** save) +{ + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->crashOnDestroy) + IntentionalCrash(); + + if (instanceData->callOnDestroy) { + NPVariant result; + NPN_InvokeDefault(instance, instanceData->callOnDestroy, nullptr, 0, &result); + NPN_ReleaseVariantValue(&result); + NPN_ReleaseObject(instanceData->callOnDestroy); + } + + if (instanceData->streamBuf) { + free(instanceData->streamBuf); + } + if (instanceData->fileBuf) { + free(instanceData->fileBuf); + } + + TestRange* currentrange = instanceData->testrange; + TestRange* nextrange; + while (currentrange != nullptr) { + nextrange = reinterpret_cast<TestRange*>(currentrange->next); + delete currentrange; + currentrange = nextrange; + } + + if (instanceData->frontBuffer) { + NPN_SetCurrentAsyncSurface(instance, nullptr, nullptr); + NPN_FinalizeAsyncSurface(instance, instanceData->frontBuffer); + NPN_MemFree(instanceData->frontBuffer); + } + if (instanceData->backBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->backBuffer); + NPN_MemFree(instanceData->backBuffer); + } + + pluginInstanceShutdown(instanceData); + NPN_ReleaseObject(instanceData->scriptableObject); + + if (sCurrentInstanceCountWatchGeneration == instanceData->instanceCountWatchGeneration) { + --sInstanceCount; + } + delete instanceData; + + return NPERR_NO_ERROR; +} + +NPError +NPP_SetWindow(NPP instance, NPWindow* window) +{ + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->scriptableObject->drawMode == DM_DEFAULT && + (instanceData->window.width != window->width || + instanceData->window.height != window->height)) { + NPRect r; + r.left = r.top = 0; + r.right = window->width; + r.bottom = window->height; + NPN_InvalidateRect(instance, &r); + } + + void* oldWindow = instanceData->window.window; + pluginDoSetWindow(instanceData, window); + if (instanceData->hasWidget && oldWindow != instanceData->window.window) { + pluginWidgetInit(instanceData, oldWindow); + } + + + if (instanceData->asyncDrawing != AD_NONE) { + if (instanceData->frontBuffer && + instanceData->frontBuffer->size.width >= 0 && + (uint32_t)instanceData->frontBuffer->size.width == window->width && + instanceData ->frontBuffer->size.height >= 0 && + (uint32_t)instanceData->frontBuffer->size.height == window->height) + { + return NPERR_NO_ERROR; + } + if (instanceData->frontBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->frontBuffer); + NPN_MemFree(instanceData->frontBuffer); + } + if (instanceData->backBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->backBuffer); + NPN_MemFree(instanceData->backBuffer); + } + instanceData->frontBuffer = (NPAsyncSurface*)NPN_MemAlloc(sizeof(NPAsyncSurface)); + instanceData->backBuffer = (NPAsyncSurface*)NPN_MemAlloc(sizeof(NPAsyncSurface)); + + NPSize size; + size.width = window->width; + size.height = window->height; + + memcpy(instanceData->backBuffer, instanceData->frontBuffer, sizeof(NPAsyncSurface)); + + NPN_InitAsyncSurface(instance, &size, NPImageFormatBGRA32, nullptr, instanceData->frontBuffer); + NPN_InitAsyncSurface(instance, &size, NPImageFormatBGRA32, nullptr, instanceData->backBuffer); + +#if defined(XP_WIN) + if (instanceData->asyncDrawing == AD_DXGI) { + if (!setupDxgiSurfaces(instance, instanceData)) { + return NPERR_GENERIC_ERROR; + } + } +#endif + } + + if (instanceData->asyncDrawing == AD_BITMAP) { + drawAsyncBitmapColor(instanceData); + } +#if defined(XP_WIN) + else if (instanceData->asyncDrawing == AD_DXGI) { + drawDxgiBitmapColor(instanceData); + } +#endif + + return NPERR_NO_ERROR; +} + +NPError +NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16_t* stype) +{ + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM && + instanceData->failureCode) { + instanceData->err << SUCCESS_STRING; + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + return instanceData->failureCode; + } + + if (stream->notifyData && + static_cast<URLNotifyData*>(stream->notifyData) != &kNotifyData) { + // stream from streamTest + *stype = NP_NORMAL; + } + else { + *stype = instanceData->streamMode; + + if (instanceData->streamBufSize) { + free(instanceData->streamBuf); + instanceData->streamBufSize = 0; + if (instanceData->testFunction == FUNCTION_NPP_POSTURL && + instanceData->postMode == POSTMODE_STREAM) { + instanceData->testFunction = FUNCTION_NPP_GETURL; + } + else { + // We already got a stream and didn't ask for another one. + instanceData->err << "Received unexpected multiple NPP_NewStream"; + } + } + } + return NPERR_NO_ERROR; +} + +NPError +NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) +{ + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_DestroyStream called"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE) { + if (instanceData->writeCount == 1) + instanceData->err << SUCCESS_STRING; + else + instanceData->err << "NPP_Write called after returning -1"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_DESTROYSTREAM && + instanceData->failureCode) { + instanceData->err << SUCCESS_STRING; + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + return instanceData->failureCode; + } + + URLNotifyData* nd = static_cast<URLNotifyData*>(stream->notifyData); + if (nd && nd != &kNotifyData) { + return NPERR_NO_ERROR; + } + + if (instanceData->streamMode == NP_ASFILE && + instanceData->functionToFail == FUNCTION_NONE) { + if (!instanceData->streamBuf) { + instanceData->err << + "Error: no data written with NPP_Write"; + return NPERR_GENERIC_ERROR; + } + + if (!instanceData->fileBuf) { + instanceData->err << + "Error: no data written with NPP_StreamAsFile"; + return NPERR_GENERIC_ERROR; + } + + if (strcmp(reinterpret_cast<char *>(instanceData->fileBuf), + reinterpret_cast<char *>(instanceData->streamBuf))) { + instanceData->err << + "Error: data passed to NPP_Write and NPP_StreamAsFile differed"; + } + } + if (instanceData->frame.length() > 0 && + instanceData->testFunction != FUNCTION_NPP_GETURLNOTIFY && + instanceData->testFunction != FUNCTION_NPP_POSTURL) { + sendBufferToFrame(instance); + } + if (instanceData->testFunction == FUNCTION_NPP_POSTURL) { + NPError err = NPN_PostURL(instance, instanceData->testUrl.c_str(), + instanceData->postMode == POSTMODE_FRAME ? instanceData->frame.c_str() : nullptr, + instanceData->streamBufSize, + reinterpret_cast<char *>(instanceData->streamBuf), false); + if (err != NPERR_NO_ERROR) + instanceData->err << "Error: NPN_PostURL returned error value " << err; + } + return NPERR_NO_ERROR; +} + +int32_t +NPP_WriteReady(NPP instance, NPStream* stream) +{ + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->writeReadyCount++; + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_WriteReady called"; + } + + // temporarily disabled per bug 519870 + //if (instanceData->writeReadyCount == 1) { + // return 0; + //} + + return instanceData->streamChunkSize; +} + +int32_t +NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, void* buffer) +{ + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->writeCount++; + + // temporarily disabled per bug 519870 + //if (instanceData->writeReadyCount == 1) { + // instanceData->err << "NPP_Write called even though NPP_WriteReady " << + // "returned 0"; + //} + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE_RPC) { + // Make an RPC call and pretend to consume the data + NPObject* windowObject = nullptr; + NPN_GetValue(instance, NPNVWindowNPObject, &windowObject); + if (windowObject) + NPN_ReleaseObject(windowObject); + + return len; + } + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_Write called"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE) { + return -1; + } + + URLNotifyData* nd = static_cast<URLNotifyData*>(stream->notifyData); + + if (nd && nd->writeCallback) { + NPVariant args[1]; + STRINGN_TO_NPVARIANT(stream->url, strlen(stream->url), args[0]); + + NPVariant result; + NPN_InvokeDefault(instance, nd->writeCallback, args, 1, &result); + NPN_ReleaseVariantValue(&result); + } + + if (nd && nd != &kNotifyData) { + uint32_t newsize = nd->size + len; + nd->data = (char*) realloc(nd->data, newsize); + memcpy(nd->data + nd->size, buffer, len); + nd->size = newsize; + return len; + } + + if (instanceData->closeStream) { + instanceData->closeStream = false; + if (instanceData->testrange != nullptr) { + NPN_RequestRead(stream, instanceData->testrange); + } + NPN_DestroyStream(instance, stream, NPRES_USER_BREAK); + } + else if (instanceData->streamMode == NP_SEEK && + stream->end != 0 && + stream->end == ((uint32_t)instanceData->streamBufSize + len)) { + // If the complete stream has been written, and we're doing a seek test, + // then call NPN_RequestRead. + // prevent recursion + instanceData->streamMode = NP_NORMAL; + + if (instanceData->testrange != nullptr) { + NPError err = NPN_RequestRead(stream, instanceData->testrange); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_RequestRead returned error %d" << err; + } + printf("called NPN_RequestRead, return %d\n", err); + } + } + + char* streamBuf = reinterpret_cast<char *>(instanceData->streamBuf); + if (offset + len <= instanceData->streamBufSize) { + if (memcmp(buffer, streamBuf + offset, len)) { + instanceData->err << + "Error: data written from NPN_RequestRead doesn't match"; + } + else { + printf("data matches!\n"); + } + TestRange* range = instanceData->testrange; + bool stillwaiting = false; + while(range != nullptr) { + if (offset == range->offset && + (uint32_t)len == range->length) { + range->waiting = false; + } + if (range->waiting) stillwaiting = true; + range = reinterpret_cast<TestRange*>(range->next); + } + if (!stillwaiting) { + NPError err = NPN_DestroyStream(instance, stream, NPRES_DONE); + if (err != NPERR_NO_ERROR) { + instanceData->err << "Error: NPN_DestroyStream returned " << err; + } + } + } + else { + if (instanceData->streamBufSize == 0) { + instanceData->streamBuf = malloc(len + 1); + streamBuf = reinterpret_cast<char *>(instanceData->streamBuf); + } + else { + instanceData->streamBuf = + realloc(reinterpret_cast<char *>(instanceData->streamBuf), + instanceData->streamBufSize + len + 1); + streamBuf = reinterpret_cast<char *>(instanceData->streamBuf); + } + memcpy(streamBuf + instanceData->streamBufSize, buffer, len); + instanceData->streamBufSize = instanceData->streamBufSize + len; + streamBuf[instanceData->streamBufSize] = '\0'; + } + return len; +} + +void +NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname) +{ + size_t size; + + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM || + instanceData->functionToFail == FUNCTION_NPP_WRITE) { + instanceData->err << "NPP_StreamAsFile called"; + } + + if (!fname) + return; + + FILE *file = fopen(fname, "rb"); + if (file) { + fseek(file, 0, SEEK_END); + size = ftell(file); + instanceData->fileBuf = malloc((int32_t)size + 1); + char* buf = reinterpret_cast<char *>(instanceData->fileBuf); + fseek(file, 0, SEEK_SET); + size_t sizeRead = fread(instanceData->fileBuf, 1, size, file); + if (sizeRead != size) { + printf("Unable to read data from file\n"); + instanceData->err << "Unable to read data from file " << fname; + } + fclose(file); + buf[size] = '\0'; + instanceData->fileBufSize = (int32_t)size; + } + else { + printf("Unable to open file\n"); + instanceData->err << "Unable to open file " << fname; + } +} + +void +NPP_Print(NPP instance, NPPrint* platformPrint) +{ +} + +int16_t +NPP_HandleEvent(NPP instance, void* event) +{ + InstanceData* instanceData = (InstanceData*)(instance->pdata); + return pluginHandleEvent(instanceData, event); +} + +void +NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData) +{ + InstanceData* instanceData = (InstanceData*)(instance->pdata); + URLNotifyData* ndata = static_cast<URLNotifyData*>(notifyData); + + if (&kNotifyData == ndata) { + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + } + else if (!strcmp(ndata->cookie, "dynamic-cookie")) { + if (ndata->notifyCallback) { + NPVariant args[2]; + INT32_TO_NPVARIANT(reason, args[0]); + if (ndata->data) { + STRINGN_TO_NPVARIANT(ndata->data, ndata->size, args[1]); + } + else { + STRINGN_TO_NPVARIANT("", 0, args[1]); + } + + NPVariant result; + NPN_InvokeDefault(instance, ndata->notifyCallback, args, 2, &result); + NPN_ReleaseVariantValue(&result); + } + + // clean up the URLNotifyData + if (ndata->writeCallback) { + NPN_ReleaseObject(ndata->writeCallback); + } + if (ndata->notifyCallback) { + NPN_ReleaseObject(ndata->notifyCallback); + } + if (ndata->redirectCallback) { + NPN_ReleaseObject(ndata->redirectCallback); + } + free(ndata->data); + delete ndata; + } + else { + printf("ERROR! NPP_URLNotify called with wrong cookie\n"); + instanceData->err << "Error: NPP_URLNotify called with wrong cookie"; + } +} + +NPError +NPP_GetValue(NPP instance, NPPVariable variable, void* value) +{ + InstanceData* instanceData = (InstanceData*)instance->pdata; + if (variable == NPPVpluginScriptableNPObject) { + NPObject* object = instanceData->scriptableObject; + NPN_RetainObject(object); + *((NPObject**)value) = object; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginNeedsXEmbed) { + // Only relevant for X plugins + // use 4-byte writes like some plugins may do + *(uint32_t*)value = instanceData->hasWidget; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginWantsAllNetworkStreams) { + // use 4-byte writes like some plugins may do + *(uint32_t*)value = instanceData->wantsAllStreams; + return NPERR_NO_ERROR; + } + + return NPERR_GENERIC_ERROR; +} + +NPError +NPP_SetValue(NPP instance, NPNVariable variable, void* value) +{ + if (variable == NPNVprivateModeBool) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->lastReportedPrivateModeState = bool(*static_cast<NPBool*>(value)); + return NPERR_NO_ERROR; + } + if (variable == NPNVmuteAudioBool) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->audioMuted = bool(*static_cast<NPBool*>(value)); + return NPERR_NO_ERROR; + } + if (variable == NPNVCSSZoomFactor) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->cssZoomFactor = *static_cast<double*>(value); + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; +} + +void +NPP_URLRedirectNotify(NPP instance, const char* url, int32_t status, void* notifyData) +{ + if (notifyData) { + URLNotifyData* nd = static_cast<URLNotifyData*>(notifyData); + if (nd->redirectCallback) { + NPVariant args[2]; + STRINGN_TO_NPVARIANT(url, strlen(url), args[0]); + INT32_TO_NPVARIANT(status, args[1]); + + NPVariant result; + NPN_InvokeDefault(instance, nd->redirectCallback, args, 2, &result); + NPN_ReleaseVariantValue(&result); + } + NPN_URLRedirectResponse(instance, notifyData, nd->allowRedirects); + return; + } + NPN_URLRedirectResponse(instance, notifyData, true); +} + +NPError +NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge) +{ + if (!sSitesWithData) + return NPERR_NO_ERROR; + + // Error condition: no support for clear-by-age + if (!sClearByAgeSupported && maxAge != uint64_t(int64_t(-1))) + return NPERR_TIME_RANGE_NOT_SUPPORTED; + + // Iterate over list and remove matches + list<siteData>::iterator iter = sSitesWithData->begin(); + list<siteData>::iterator end = sSitesWithData->end(); + while (iter != end) { + const siteData& data = *iter; + list<siteData>::iterator next = iter; + ++next; + if ((!site || data.site.compare(site) == 0) && + (flags == NP_CLEAR_ALL || data.flags & flags) && + data.age <= maxAge) { + sSitesWithData->erase(iter); + } + iter = next; + } + + return NPERR_NO_ERROR; +} + +char** +NPP_GetSitesWithData() +{ + int length = 0; + char** result; + + if (sSitesWithData) + length = sSitesWithData->size(); + + // Allocate the maximum possible size the list could be. + result = static_cast<char**>(NPN_MemAlloc((length + 1) * sizeof(char*))); + result[length] = nullptr; + + if (length == 0) { + // Represent the no site data case as an array of length 1 with a nullptr + // entry. + return result; + } + + // Iterate the list of stored data, and build a list of strings. + list<string> sites; + { + list<siteData>::iterator iter = sSitesWithData->begin(); + list<siteData>::iterator end = sSitesWithData->end(); + for (; iter != end; ++iter) { + const siteData& data = *iter; + sites.push_back(data.site); + } + } + + // Remove duplicate strings. + sites.sort(); + sites.unique(); + + // Add strings to the result array, and null terminate. + { + int i = 0; + list<string>::iterator iter = sites.begin(); + list<string>::iterator end = sites.end(); + for (; iter != end; ++iter, ++i) { + const string& site = *iter; + result[i] = static_cast<char*>(NPN_MemAlloc(site.length() + 1)); + memcpy(result[i], site.c_str(), site.length() + 1); + } + } + result[sites.size()] = nullptr; + + return result; +} + +// +// npapi browser functions +// + +bool +NPN_SetProperty(NPP instance, NPObject* obj, NPIdentifier propertyName, const NPVariant* value) +{ + return sBrowserFuncs->setproperty(instance, obj, propertyName, value); +} + +NPIdentifier +NPN_GetIntIdentifier(int32_t intid) +{ + return sBrowserFuncs->getintidentifier(intid); +} + +NPIdentifier +NPN_GetStringIdentifier(const NPUTF8* name) +{ + return sBrowserFuncs->getstringidentifier(name); +} + +void +NPN_GetStringIdentifiers(const NPUTF8 **names, int32_t nameCount, NPIdentifier *identifiers) +{ + return sBrowserFuncs->getstringidentifiers(names, nameCount, identifiers); +} + +bool +NPN_IdentifierIsString(NPIdentifier identifier) +{ + return sBrowserFuncs->identifierisstring(identifier); +} + +NPUTF8* +NPN_UTF8FromIdentifier(NPIdentifier identifier) +{ + return sBrowserFuncs->utf8fromidentifier(identifier); +} + +int32_t +NPN_IntFromIdentifier(NPIdentifier identifier) +{ + return sBrowserFuncs->intfromidentifier(identifier); +} + +NPError +NPN_GetValue(NPP instance, NPNVariable variable, void* value) +{ + return sBrowserFuncs->getvalue(instance, variable, value); +} + +NPError +NPN_SetValue(NPP instance, NPPVariable variable, void* value) +{ + return sBrowserFuncs->setvalue(instance, variable, value); +} + +void +NPN_InvalidateRect(NPP instance, NPRect* rect) +{ + sBrowserFuncs->invalidaterect(instance, rect); +} + +bool +NPN_HasProperty(NPP instance, NPObject* obj, NPIdentifier propertyName) +{ + return sBrowserFuncs->hasproperty(instance, obj, propertyName); +} + +NPObject* +NPN_CreateObject(NPP instance, NPClass* aClass) +{ + return sBrowserFuncs->createobject(instance, aClass); +} + +bool +NPN_Invoke(NPP npp, NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result) +{ + return sBrowserFuncs->invoke(npp, obj, methodName, args, argCount, result); +} + +bool +NPN_InvokeDefault(NPP npp, NPObject* obj, const NPVariant *args, uint32_t argCount, NPVariant *result) +{ + return sBrowserFuncs->invokeDefault(npp, obj, args, argCount, result); +} + +bool +NPN_Construct(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + return sBrowserFuncs->construct(npp, npobj, args, argCount, result); +} + +const char* +NPN_UserAgent(NPP instance) +{ + return sBrowserFuncs->uagent(instance); +} + +NPObject* +NPN_RetainObject(NPObject* obj) +{ + return sBrowserFuncs->retainobject(obj); +} + +void +NPN_ReleaseObject(NPObject* obj) +{ + return sBrowserFuncs->releaseobject(obj); +} + +void* +NPN_MemAlloc(uint32_t size) +{ + return sBrowserFuncs->memalloc(size); +} + +char* +NPN_StrDup(const char* str) +{ + return strcpy((char*)sBrowserFuncs->memalloc(strlen(str) + 1), str); +} + +void +NPN_MemFree(void* ptr) +{ + return sBrowserFuncs->memfree(ptr); +} + +uint32_t +NPN_ScheduleTimer(NPP instance, uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID)) +{ + return sBrowserFuncs->scheduletimer(instance, interval, repeat, timerFunc); +} + +void +NPN_UnscheduleTimer(NPP instance, uint32_t timerID) +{ + return sBrowserFuncs->unscheduletimer(instance, timerID); +} + +void +NPN_ReleaseVariantValue(NPVariant *variant) +{ + return sBrowserFuncs->releasevariantvalue(variant); +} + +NPError +NPN_GetURLNotify(NPP instance, const char* url, const char* target, void* notifyData) +{ + return sBrowserFuncs->geturlnotify(instance, url, target, notifyData); +} + +NPError +NPN_GetURL(NPP instance, const char* url, const char* target) +{ + return sBrowserFuncs->geturl(instance, url, target); +} + +NPError +NPN_RequestRead(NPStream* stream, NPByteRange* rangeList) +{ + return sBrowserFuncs->requestread(stream, rangeList); +} + +NPError +NPN_PostURLNotify(NPP instance, const char* url, + const char* target, uint32_t len, + const char* buf, NPBool file, void* notifyData) +{ + return sBrowserFuncs->posturlnotify(instance, url, target, len, buf, file, notifyData); +} + +NPError +NPN_PostURL(NPP instance, const char *url, + const char *target, uint32_t len, + const char *buf, NPBool file) +{ + return sBrowserFuncs->posturl(instance, url, target, len, buf, file); +} + +NPError +NPN_DestroyStream(NPP instance, NPStream* stream, NPError reason) +{ + return sBrowserFuncs->destroystream(instance, stream, reason); +} + +NPError +NPN_NewStream(NPP instance, + NPMIMEType type, + const char* target, + NPStream** stream) +{ + return sBrowserFuncs->newstream(instance, type, target, stream); +} + +int32_t +NPN_Write(NPP instance, + NPStream* stream, + int32_t len, + void* buf) +{ + return sBrowserFuncs->write(instance, stream, len, buf); +} + +bool +NPN_Enumerate(NPP instance, + NPObject *npobj, + NPIdentifier **identifiers, + uint32_t *identifierCount) +{ + return sBrowserFuncs->enumerate(instance, npobj, identifiers, + identifierCount); +} + +bool +NPN_GetProperty(NPP instance, + NPObject *npobj, + NPIdentifier propertyName, + NPVariant *result) +{ + return sBrowserFuncs->getproperty(instance, npobj, propertyName, result); +} + +bool +NPN_Evaluate(NPP instance, NPObject *npobj, NPString *script, NPVariant *result) +{ + return sBrowserFuncs->evaluate(instance, npobj, script, result); +} + +void +NPN_SetException(NPObject *npobj, const NPUTF8 *message) +{ + return sBrowserFuncs->setexception(npobj, message); +} + +NPBool +NPN_ConvertPoint(NPP instance, double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double *destX, double *destY, NPCoordinateSpace destSpace) +{ + return sBrowserFuncs->convertpoint(instance, sourceX, sourceY, sourceSpace, destX, destY, destSpace); +} + +NPError +NPN_SetValueForURL(NPP instance, NPNURLVariable variable, const char *url, const char *value, uint32_t len) +{ + return sBrowserFuncs->setvalueforurl(instance, variable, url, value, len); +} + +NPError +NPN_GetValueForURL(NPP instance, NPNURLVariable variable, const char *url, char **value, uint32_t *len) +{ + return sBrowserFuncs->getvalueforurl(instance, variable, url, value, 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) +{ + return sBrowserFuncs->getauthenticationinfo(instance, protocol, host, port, scheme, realm, + username, ulen, password, plen); +} + +void +NPN_PluginThreadAsyncCall(NPP plugin, void (*func)(void*), void* userdata) +{ + return sBrowserFuncs->pluginthreadasynccall(plugin, func, userdata); +} + +void +NPN_URLRedirectResponse(NPP instance, void* notifyData, NPBool allow) +{ + return sBrowserFuncs->urlredirectresponse(instance, notifyData, allow); +} + +NPError +NPN_InitAsyncSurface(NPP instance, NPSize *size, NPImageFormat format, + void *initData, NPAsyncSurface *surface) +{ + return sBrowserFuncs->initasyncsurface(instance, size, format, initData, surface); +} + +NPError +NPN_FinalizeAsyncSurface(NPP instance, NPAsyncSurface *surface) +{ + return sBrowserFuncs->finalizeasyncsurface(instance, surface); +} + +void +NPN_SetCurrentAsyncSurface(NPP instance, NPAsyncSurface *surface, NPRect *changed) +{ + sBrowserFuncs->setcurrentasyncsurface(instance, surface, changed); +} + +// +// npruntime object functions +// + +NPObject* +scriptableAllocate(NPP npp, NPClass* aClass) +{ + TestNPObject* object = (TestNPObject*)NPN_MemAlloc(sizeof(TestNPObject)); + if (!object) + return nullptr; + memset(object, 0, sizeof(TestNPObject)); + return object; +} + +void +scriptableDeallocate(NPObject* npobj) +{ + NPN_MemFree(npobj); +} + +void +scriptableInvalidate(NPObject* npobj) +{ +} + +bool +scriptableHasMethod(NPObject* npobj, NPIdentifier name) +{ + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers)); i++) { + if (name == sPluginMethodIdentifiers[i]) + return true; + } + return false; +} + +bool +scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (id->throwOnNextInvoke) { + id->throwOnNextInvoke = false; + if (argCount == 0) { + NPN_SetException(npobj, nullptr); + } + else { + for (uint32_t i = 0; i < argCount; i++) { + const NPString* argstr = &NPVARIANT_TO_STRING(args[i]); + NPN_SetException(npobj, argstr->UTF8Characters); + } + } + return false; + } + + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers)); i++) { + if (name == sPluginMethodIdentifiers[i]) + return sPluginMethodFunctions[i](npobj, args, argCount, result); + } + return false; +} + +bool +scriptableInvokeDefault(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (id->throwOnNextInvoke) { + id->throwOnNextInvoke = false; + if (argCount == 0) { + NPN_SetException(npobj, nullptr); + } + else { + for (uint32_t i = 0; i < argCount; i++) { + const NPString* argstr = &NPVARIANT_TO_STRING(args[i]); + NPN_SetException(npobj, argstr->UTF8Characters); + } + } + return false; + } + + ostringstream value; + value << sPluginName; + for (uint32_t i = 0; i < argCount; i++) { + switch(args[i].type) { + case NPVariantType_Int32: + value << ";" << NPVARIANT_TO_INT32(args[i]); + break; + case NPVariantType_String: { + const NPString* argstr = &NPVARIANT_TO_STRING(args[i]); + value << ";" << argstr->UTF8Characters; + break; + } + case NPVariantType_Void: + value << ";undefined"; + break; + case NPVariantType_Null: + value << ";null"; + break; + default: + value << ";other"; + } + } + + char *outval = NPN_StrDup(value.str().c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +} + +bool +scriptableHasProperty(NPObject* npobj, NPIdentifier name) +{ + if (NPN_IdentifierIsString(name)) { + NPUTF8 *asUTF8 = NPN_UTF8FromIdentifier(name); + if (NPN_GetStringIdentifier(asUTF8) != name) { + Crash(); + } + NPN_MemFree(asUTF8); + } + else { + if (NPN_GetIntIdentifier(NPN_IntFromIdentifier(name)) != name) { + Crash(); + } + } + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + return true; + } + } + return false; +} + +bool +scriptableGetProperty(NPObject* npobj, NPIdentifier name, NPVariant* result) +{ + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + DuplicateNPVariant(*result, sPluginPropertyValues[i]); + return true; + } + } + return false; +} + +bool +scriptableSetProperty(NPObject* npobj, NPIdentifier name, const NPVariant* value) +{ + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + DuplicateNPVariant(sPluginPropertyValues[i], *value); + return true; + } + } + return false; +} + +bool +scriptableRemoveProperty(NPObject* npobj, NPIdentifier name) +{ + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + + // Avoid double frees (see test_propertyAndMethod.html, which deletes a + // property that doesn't exist). + VOID_TO_NPVARIANT(sPluginPropertyValues[i]); + return true; + } + } + return false; +} + +bool +scriptableEnumerate(NPObject* npobj, NPIdentifier** identifier, uint32_t* count) +{ + const int bufsize = sizeof(NPIdentifier) * MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames); + NPIdentifier* ids = (NPIdentifier*) NPN_MemAlloc(bufsize); + if (!ids) + return false; + + memcpy(ids, sPluginMethodIdentifiers, bufsize); + *identifier = ids; + *count = MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames); + return true; +} + +bool +scriptableConstruct(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + return false; +} + +// +// test functions +// + +static bool +compareVariants(NPP instance, const NPVariant* var1, const NPVariant* var2) +{ + bool success = true; + InstanceData* id = static_cast<InstanceData*>(instance->pdata); + if (var1->type != var2->type) { + id->err << "Variant types don't match; got " << var1->type << + " expected " << var2->type; + return false; + } + + // Cast var1->type from NPVariantType to int to avoid compiler warnings about + // not needing a default case when we have cases for every enum value. + switch (static_cast<int>(var1->type)) { + case NPVariantType_Int32: { + int32_t result = NPVARIANT_TO_INT32(*var1); + int32_t expected = NPVARIANT_TO_INT32(*var2); + if (result != expected) { + id->err << "Variant values don't match; got " << result << + " expected " << expected; + success = false; + } + break; + } + case NPVariantType_Double: { + double result = NPVARIANT_TO_DOUBLE(*var1); + double expected = NPVARIANT_TO_DOUBLE(*var2); + if (result != expected) { + id->err << "Variant values don't match (double)"; + success = false; + } + break; + } + case NPVariantType_Void: { + // void values are always equivalent + break; + } + case NPVariantType_Null: { + // null values are always equivalent + break; + } + case NPVariantType_Bool: { + bool result = NPVARIANT_TO_BOOLEAN(*var1); + bool expected = NPVARIANT_TO_BOOLEAN(*var2); + if (result != expected) { + id->err << "Variant values don't match (bool)"; + success = false; + } + break; + } + case NPVariantType_String: { + const NPString* result = &NPVARIANT_TO_STRING(*var1); + const NPString* expected = &NPVARIANT_TO_STRING(*var2); + if (strcmp(result->UTF8Characters, expected->UTF8Characters) || + strlen(result->UTF8Characters) != strlen(expected->UTF8Characters)) { + id->err << "Variant values don't match; got " << + result->UTF8Characters << " expected " << + expected->UTF8Characters; + success = false; + } + break; + } + case NPVariantType_Object: { + uint32_t i, identifierCount = 0; + NPIdentifier* identifiers; + NPObject* result = NPVARIANT_TO_OBJECT(*var1); + NPObject* expected = NPVARIANT_TO_OBJECT(*var2); + bool enumerate_result = NPN_Enumerate(instance, expected, + &identifiers, &identifierCount); + if (!enumerate_result) { + id->err << "NPN_Enumerate failed"; + success = false; + } + for (i = 0; i < identifierCount; i++) { + NPVariant resultVariant, expectedVariant; + if (!NPN_GetProperty(instance, expected, identifiers[i], + &expectedVariant)) { + id->err << "NPN_GetProperty returned false"; + success = false; + } + else { + if (!NPN_HasProperty(instance, result, identifiers[i])) { + id->err << "NPN_HasProperty returned false"; + success = false; + } + else { + if (!NPN_GetProperty(instance, result, identifiers[i], + &resultVariant)) { + id->err << "NPN_GetProperty 2 returned false"; + success = false; + } + else { + success = compareVariants(instance, &resultVariant, + &expectedVariant); + NPN_ReleaseVariantValue(&expectedVariant); + } + } + NPN_ReleaseVariantValue(&resultVariant); + } + } + NPN_MemFree(identifiers); + break; + } + default: + id->err << "Unknown variant type"; + success = false; + MOZ_ASSERT_UNREACHABLE("Unknown variant type?!"); + } + + return success; +} + +static bool +throwExceptionNextInvoke(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->throwOnNextInvoke = true; + BOOLEAN_TO_NPVARIANT(true, *result); + return true; +} + +static bool +npnInvokeDefaultTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + bool success = false; + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) + return false; + + NPIdentifier objectIdentifier = variantToIdentifier(args[0]); + if (!objectIdentifier) + return false; + + NPVariant objectVariant; + if (NPN_GetProperty(npp, windowObject, objectIdentifier, + &objectVariant)) { + if (NPVARIANT_IS_OBJECT(objectVariant)) { + NPObject* selfObject = NPVARIANT_TO_OBJECT(objectVariant); + if (selfObject != nullptr) { + NPVariant resultVariant; + if (NPN_InvokeDefault(npp, selfObject, argCount > 1 ? &args[1] : nullptr, + argCount - 1, &resultVariant)) { + *result = resultVariant; + success = true; + } + } + } + NPN_ReleaseVariantValue(&objectVariant); + } + + NPN_ReleaseObject(windowObject); + return success; +} + +static bool +npnInvokeTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->err.str(""); + if (argCount < 2) + return false; + + NPIdentifier function = variantToIdentifier(args[0]); + if (!function) + return false; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) + return false; + + NPVariant invokeResult; + bool invokeReturn = NPN_Invoke(npp, windowObject, function, + argCount > 2 ? &args[2] : nullptr, argCount - 2, &invokeResult); + + bool compareResult = compareVariants(npp, &invokeResult, &args[1]); + + NPN_ReleaseObject(windowObject); + NPN_ReleaseVariantValue(&invokeResult); + BOOLEAN_TO_NPVARIANT(invokeReturn && compareResult, *result); + return true; +} + +static bool +npnEvaluateTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + bool success = false; + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (argCount != 1) + return false; + + if (!NPVARIANT_IS_STRING(args[0])) + return false; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) + return false; + + success = NPN_Evaluate(npp, windowObject, (NPString*)&NPVARIANT_TO_STRING(args[0]), result); + + NPN_ReleaseObject(windowObject); + return success; +} + +static bool +setUndefinedValueTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + NPError err = NPN_SetValue(npp, (NPPVariable)0x0, 0x0); + BOOLEAN_TO_NPVARIANT((err == NPERR_NO_ERROR), *result); + return true; +} + +static bool +identifierToStringTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 1) + return false; + NPIdentifier identifier = variantToIdentifier(args[0]); + if (!identifier) + return false; + + NPUTF8* utf8String = NPN_UTF8FromIdentifier(identifier); + if (!utf8String) + return false; + STRINGZ_TO_NPVARIANT(utf8String, *result); + return true; +} + +static bool +queryPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPBool pms = false; + NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, NPNVprivateModeBool, &pms); + BOOLEAN_TO_NPVARIANT(pms, *result); + return true; +} + +static bool +lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + InstanceData* id = static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + BOOLEAN_TO_NPVARIANT(id->lastReportedPrivateModeState, *result); + return true; +} + +static bool +hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + InstanceData* id = static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + BOOLEAN_TO_NPVARIANT(id->hasWidget, *result); + return true; +} + +static bool +getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 1) + return false; + if (!NPVARIANT_IS_INT32(args[0])) + return false; + int32_t edge = NPVARIANT_TO_INT32(args[0]); + if (edge < EDGE_LEFT || edge > EDGE_BOTTOM) + return false; + + InstanceData* id = static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + int32_t r = pluginGetEdge(id, RectEdge(edge)); + if (r == NPTEST_INT32_ERROR) + return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool +getClipRegionRectCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + InstanceData* id = static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + int32_t r = pluginGetClipRegionRectCount(id); + if (r == NPTEST_INT32_ERROR) + return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool +getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 2) + return false; + if (!NPVARIANT_IS_INT32(args[0])) + return false; + int32_t rectIndex = NPVARIANT_TO_INT32(args[0]); + if (rectIndex < 0) + return false; + if (!NPVARIANT_IS_INT32(args[1])) + return false; + int32_t edge = NPVARIANT_TO_INT32(args[1]); + if (edge < EDGE_LEFT || edge > EDGE_BOTTOM) + return false; + + InstanceData* id = static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + int32_t r = pluginGetClipRegionRectEdge(id, rectIndex, RectEdge(edge)); + if (r == NPTEST_INT32_ERROR) + return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool +startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + if (sWatchingInstanceCount) + return false; + + sWatchingInstanceCount = true; + sInstanceCount = 0; + ++sCurrentInstanceCountWatchGeneration; + return true; +} + +static bool +getInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + if (!sWatchingInstanceCount) + return false; + + INT32_TO_NPVARIANT(sInstanceCount, *result); + return true; +} + +static bool +stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + if (!sWatchingInstanceCount) + return false; + + sWatchingInstanceCount = false; + return true; +} + +static bool +getLastMouseX(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->lastMouseX, *result); + return true; +} + +static bool +getLastMouseY(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->lastMouseY, *result); + return true; +} + +static bool +getPaintCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->paintCount, *result); + return true; +} + +static bool +resetPaintCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->paintCount = 0; + return true; +} + +static bool +getWidthAtLastPaint(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->widthAtLastPaint, *result); + return true; +} + +static bool +setInvalidateDuringPaint(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 1) + return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) + return false; + bool doInvalidate = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->invalidateDuringPaint = doInvalidate; + return true; +} + +static bool +setSlowPaint(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 1) + return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) + return false; + bool slow = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->slowPaint = slow; + return true; +} + +static bool +getError(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (id->err.str().length() == 0) { + char *outval = NPN_StrDup(SUCCESS_STRING); + STRINGZ_TO_NPVARIANT(outval, *result); + } else { + char *outval = NPN_StrDup(id->err.str().c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + } + return true; +} + +static bool +doInternalConsistencyCheck(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + string error; + pluginDoInternalConsistencyCheck(id, error); + NPUTF8* utf8String = (NPUTF8*)NPN_MemAlloc(error.length() + 1); + if (!utf8String) { + return false; + } + memcpy(utf8String, error.c_str(), error.length() + 1); + STRINGZ_TO_NPVARIANT(utf8String, *result); + return true; +} + +static bool +convertPointX(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 4) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_INT32(args[0])) + return false; + int32_t sourceSpace = NPVARIANT_TO_INT32(args[0]); + + if (!NPVARIANT_IS_INT32(args[1])) + return false; + double sourceX = static_cast<double>(NPVARIANT_TO_INT32(args[1])); + + if (!NPVARIANT_IS_INT32(args[2])) + return false; + double sourceY = static_cast<double>(NPVARIANT_TO_INT32(args[2])); + + if (!NPVARIANT_IS_INT32(args[3])) + return false; + int32_t destSpace = NPVARIANT_TO_INT32(args[3]); + + double resultX, resultY; + NPN_ConvertPoint(npp, sourceX, sourceY, (NPCoordinateSpace)sourceSpace, &resultX, &resultY, (NPCoordinateSpace)destSpace); + + DOUBLE_TO_NPVARIANT(resultX, *result); + return true; +} + +static bool +convertPointY(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 4) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_INT32(args[0])) + return false; + int32_t sourceSpace = NPVARIANT_TO_INT32(args[0]); + + if (!NPVARIANT_IS_INT32(args[1])) + return false; + double sourceX = static_cast<double>(NPVARIANT_TO_INT32(args[1])); + + if (!NPVARIANT_IS_INT32(args[2])) + return false; + double sourceY = static_cast<double>(NPVARIANT_TO_INT32(args[2])); + + if (!NPVARIANT_IS_INT32(args[3])) + return false; + int32_t destSpace = NPVARIANT_TO_INT32(args[3]); + + double resultX, resultY; + NPN_ConvertPoint(npp, sourceX, sourceY, (NPCoordinateSpace)sourceSpace, &resultX, &resultY, (NPCoordinateSpace)destSpace); + + DOUBLE_TO_NPVARIANT(resultY, *result); + return true; +} + +static bool +streamTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + // .streamTest(url, doPost, doNull, writeCallback, notifyCallback, redirectCallback, allowRedirects) + if (7 != argCount) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_STRING(args[0])) + return false; + NPString url = NPVARIANT_TO_STRING(args[0]); + + if (!NPVARIANT_IS_BOOLEAN(args[1])) + return false; + bool doPost = NPVARIANT_TO_BOOLEAN(args[1]); + + NPString postData = { nullptr, 0 }; + if (NPVARIANT_IS_STRING(args[2])) { + postData = NPVARIANT_TO_STRING(args[2]); + } + else { + if (!NPVARIANT_IS_NULL(args[2])) { + return false; + } + } + + NPObject* writeCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[3])) { + writeCallback = NPVARIANT_TO_OBJECT(args[3]); + } + else { + if (!NPVARIANT_IS_NULL(args[3])) { + return false; + } + } + + NPObject* notifyCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[4])) { + notifyCallback = NPVARIANT_TO_OBJECT(args[4]); + } + else { + if (!NPVARIANT_IS_NULL(args[4])) { + return false; + } + } + + NPObject* redirectCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[5])) { + redirectCallback = NPVARIANT_TO_OBJECT(args[5]); + } + else { + if (!NPVARIANT_IS_NULL(args[5])) { + return false; + } + } + + if (!NPVARIANT_IS_BOOLEAN(args[6])) + return false; + bool allowRedirects = NPVARIANT_TO_BOOLEAN(args[6]); + + URLNotifyData* ndata = new URLNotifyData; + ndata->cookie = "dynamic-cookie"; + ndata->writeCallback = writeCallback; + ndata->notifyCallback = notifyCallback; + ndata->redirectCallback = redirectCallback; + ndata->size = 0; + ndata->data = nullptr; + ndata->allowRedirects = allowRedirects; + + /* null-terminate "url" */ + char* urlstr = (char*) malloc(url.UTF8Length + 1); + strncpy(urlstr, url.UTF8Characters, url.UTF8Length); + urlstr[url.UTF8Length] = '\0'; + + NPError err; + if (doPost) { + err = NPN_PostURLNotify(npp, urlstr, nullptr, + postData.UTF8Length, postData.UTF8Characters, + false, ndata); + } + else { + err = NPN_GetURLNotify(npp, urlstr, nullptr, ndata); + } + + free(urlstr); + + if (NPERR_NO_ERROR == err) { + if (ndata->writeCallback) { + NPN_RetainObject(ndata->writeCallback); + } + if (ndata->notifyCallback) { + NPN_RetainObject(ndata->notifyCallback); + } + if (ndata->redirectCallback) { + NPN_RetainObject(ndata->redirectCallback); + } + BOOLEAN_TO_NPVARIANT(true, *result); + } + else { + delete ndata; + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +static bool +setPluginWantsAllStreams(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (1 != argCount) + return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) + return false; + bool wantsAllStreams = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + id->wantsAllStreams = wantsAllStreams; + + return true; +} + +static bool +crashPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + IntentionalCrash(); + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool +crashOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + id->crashOnDestroy = true; + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool +setColor(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 1) + return false; + if (!NPVARIANT_IS_STRING(args[0])) + return false; + const NPString* str = &NPVARIANT_TO_STRING(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + id->scriptableObject->drawColor = + parseHexColor(str->UTF8Characters, str->UTF8Length); + + NPRect r; + r.left = 0; + r.top = 0; + r.right = id->window.width; + r.bottom = id->window.height; + if (id->asyncDrawing == AD_NONE) { + NPN_InvalidateRect(npp, &r); + } else if (id->asyncDrawing == AD_BITMAP) { + drawAsyncBitmapColor(id); + } + + VOID_TO_NPVARIANT(*result); + return true; +} + +void notifyDidPaint(InstanceData* instanceData) +{ + ++instanceData->paintCount; + instanceData->widthAtLastPaint = instanceData->window.width; + + if (instanceData->invalidateDuringPaint) { + NPRect r; + r.left = 0; + r.top = 0; + r.right = instanceData->window.width; + r.bottom = instanceData->window.height; + NPN_InvalidateRect(instanceData->npp, &r); + } + + if (instanceData->slowPaint) { + XPSleep(1); + } + + if (instanceData->runScriptOnPaint) { + NPObject* o = nullptr; + NPN_GetValue(instanceData->npp, NPNVPluginElementNPObject, &o); + if (o) { + NPVariant param; + STRINGZ_TO_NPVARIANT("paintscript", param); + NPVariant result; + NPN_Invoke(instanceData->npp, o, NPN_GetStringIdentifier("getAttribute"), + ¶m, 1, &result); + + if (NPVARIANT_IS_STRING(result)) { + NPObject* windowObject; + NPN_GetValue(instanceData->npp, NPNVWindowNPObject, &windowObject); + if (windowObject) { + NPVariant evalResult; + NPN_Evaluate(instanceData->npp, windowObject, + (NPString*)&NPVARIANT_TO_STRING(result), &evalResult); + NPN_ReleaseVariantValue(&evalResult); + NPN_ReleaseObject(windowObject); + } + } + + NPN_ReleaseVariantValue(&result); + NPN_ReleaseObject(o); + } + } +} + +static const NPClass kTestSharedNPClass = { + NP_CLASS_STRUCT_VERSION, + // Everything else is nullptr +}; + +static bool getJavaCodebase(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + char *outval = NPN_StrDup(id->javaCodebase.c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +} + +static bool getObjectValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* o = NPN_CreateObject(npp, + const_cast<NPClass*>(&kTestSharedNPClass)); + if (!o) + return false; + + OBJECT_TO_NPVARIANT(o, *result); + return true; +} + +static bool checkObjectValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + VOID_TO_NPVARIANT(*result); + + if (1 != argCount) + return false; + + if (!NPVARIANT_IS_OBJECT(args[0])) + return false; + + NPObject* o = NPVARIANT_TO_OBJECT(args[0]); + + BOOLEAN_TO_NPVARIANT(o->_class == &kTestSharedNPClass, *result); + return true; +} + +static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + VOID_TO_NPVARIANT(*result); + +#if defined(XP_WIN) && defined(_M_IX86) + _control87(0, _MCW_EM); + return true; +#else + return false; +#endif +} + +// caller is responsible for freeing return buffer +static char* URLForInstanceWindow(NPP instance) { + char *outString = nullptr; + + NPObject* windowObject = nullptr; + NPError err = NPN_GetValue(instance, NPNVWindowNPObject, &windowObject); + if (err != NPERR_NO_ERROR || !windowObject) + return nullptr; + + NPIdentifier locationIdentifier = NPN_GetStringIdentifier("location"); + NPVariant locationVariant; + if (NPN_GetProperty(instance, windowObject, locationIdentifier, &locationVariant)) { + NPObject *locationObject = locationVariant.value.objectValue; + if (locationObject) { + NPIdentifier hrefIdentifier = NPN_GetStringIdentifier("href"); + NPVariant hrefVariant; + if (NPN_GetProperty(instance, locationObject, hrefIdentifier, &hrefVariant)) { + const NPString* hrefString = &NPVARIANT_TO_STRING(hrefVariant); + if (hrefString) { + outString = (char *)malloc(hrefString->UTF8Length + 1); + if (outString) { + strcpy(outString, hrefString->UTF8Characters); + outString[hrefString->UTF8Length] = '\0'; + } + } + NPN_ReleaseVariantValue(&hrefVariant); + } + } + NPN_ReleaseVariantValue(&locationVariant); + } + + NPN_ReleaseObject(windowObject); + + return outString; +} + +static bool +setCookie(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 1) + return false; + if (!NPVARIANT_IS_STRING(args[0])) + return false; + const NPString* cookie = &NPVARIANT_TO_STRING(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + char* url = URLForInstanceWindow(npp); + if (!url) + return false; + NPError err = NPN_SetValueForURL(npp, NPNURLVCookie, url, cookie->UTF8Characters, cookie->UTF8Length); + free(url); + + return (err == NPERR_NO_ERROR); +} + +static bool +getCookie(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + char* url = URLForInstanceWindow(npp); + if (!url) + return false; + char* cookie = nullptr; + unsigned int length = 0; + NPError err = NPN_GetValueForURL(npp, NPNURLVCookie, url, &cookie, &length); + free(url); + if (err != NPERR_NO_ERROR || !cookie) + return false; + + STRINGZ_TO_NPVARIANT(cookie, *result); + return true; +} + +static bool +getAuthInfo(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 5) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_STRING(args[0]) || !NPVARIANT_IS_STRING(args[1]) || + !NPVARIANT_IS_INT32(args[2]) || !NPVARIANT_IS_STRING(args[3]) || + !NPVARIANT_IS_STRING(args[4])) + return false; + + const NPString* protocol = &NPVARIANT_TO_STRING(args[0]); + const NPString* host = &NPVARIANT_TO_STRING(args[1]); + uint32_t port = NPVARIANT_TO_INT32(args[2]); + const NPString* scheme = &NPVARIANT_TO_STRING(args[3]); + const NPString* realm = &NPVARIANT_TO_STRING(args[4]); + + char* username = nullptr; + char* password = nullptr; + uint32_t ulen = 0, plen = 0; + + NPError err = NPN_GetAuthenticationInfo(npp, + protocol->UTF8Characters, + host->UTF8Characters, + port, + scheme->UTF8Characters, + realm->UTF8Characters, + &username, + &ulen, + &password, + &plen); + + if (err != NPERR_NO_ERROR) { + return false; + } + + char* outstring = (char*)NPN_MemAlloc(ulen + plen + 2); + memset(outstring, 0, ulen + plen + 2); + strncpy(outstring, username, ulen); + strcat(outstring, "|"); + strncat(outstring, password, plen); + + STRINGZ_TO_NPVARIANT(outstring, *result); + + NPN_MemFree(username); + NPN_MemFree(password); + + return true; +} + +static void timerCallback(NPP npp, uint32_t timerID) +{ + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + currentTimerEventCount++; + timerEvent event = timerEvents[currentTimerEventCount]; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) + return; + + NPVariant rval; + if (timerID != id->timerID[event.timerIdReceive]) { + id->timerTestResult = false; + } + + if (currentTimerEventCount == totalTimerEvents - 1) { + NPVariant arg; + BOOLEAN_TO_NPVARIANT(id->timerTestResult, arg); + NPN_Invoke(npp, windowObject, NPN_GetStringIdentifier(id->timerTestScriptCallback.c_str()), &arg, 1, &rval); + NPN_ReleaseVariantValue(&arg); + } + + NPN_ReleaseObject(windowObject); + + if (event.timerIdSchedule > -1) { + id->timerID[event.timerIdSchedule] = NPN_ScheduleTimer(npp, event.timerInterval, event.timerRepeat, timerCallback); + } + if (event.timerIdUnschedule > -1) { + NPN_UnscheduleTimer(npp, id->timerID[event.timerIdUnschedule]); + } +} + +static bool +timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + currentTimerEventCount = 0; + + if (argCount < 1 || !NPVARIANT_IS_STRING(args[0])) + return false; + const NPString* argstr = &NPVARIANT_TO_STRING(args[0]); + id->timerTestScriptCallback = argstr->UTF8Characters; + + id->timerTestResult = true; + timerEvent event = timerEvents[currentTimerEventCount]; + + id->timerID[event.timerIdSchedule] = NPN_ScheduleTimer(npp, event.timerInterval, event.timerRepeat, timerCallback); + + return id->timerID[event.timerIdSchedule] != 0; +} + +#ifdef XP_WIN +void +ThreadProc(void* cookie) +#else +void* +ThreadProc(void* cookie) +#endif +{ + NPObject* npobj = (NPObject*)cookie; + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->asyncTestPhase = 1; + NPN_PluginThreadAsyncCall(npp, asyncCallback, (void*)npobj); +#ifndef XP_WIN + return nullptr; +#endif +} + +void +asyncCallback(void* cookie) +{ + NPObject* npobj = (NPObject*)cookie; + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + switch (id->asyncTestPhase) { + // async callback triggered from same thread + case 0: +#ifdef XP_WIN + if (_beginthread(ThreadProc, 0, (void*)npobj) == -1) + id->asyncCallbackResult = false; +#else + pthread_t tid; + if (pthread_create(&tid, 0, ThreadProc, (void*)npobj)) + id->asyncCallbackResult = false; +#endif + break; + + // async callback triggered from different thread + default: + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) + return; + NPVariant arg, rval; + BOOLEAN_TO_NPVARIANT(id->asyncCallbackResult, arg); + NPN_Invoke(npp, windowObject, NPN_GetStringIdentifier(id->asyncTestScriptCallback.c_str()), &arg, 1, &rval); + NPN_ReleaseVariantValue(&arg); + NPN_ReleaseObject(windowObject); + break; + } +} + +static bool +asyncCallbackTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + if (argCount < 1 || !NPVARIANT_IS_STRING(args[0])) + return false; + const NPString* argstr = &NPVARIANT_TO_STRING(args[0]); + id->asyncTestScriptCallback = argstr->UTF8Characters; + + id->asyncTestPhase = 0; + id->asyncCallbackResult = true; + NPN_PluginThreadAsyncCall(npp, asyncCallback, (void*)npobj); + + return true; +} + +static bool +GCRaceInvoke(NPObject*, NPIdentifier, const NPVariant*, uint32_t, NPVariant*) +{ + return false; +} + +static bool +GCRaceInvokeDefault(NPObject* o, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (1 != argCount || !NPVARIANT_IS_INT32(args[0]) || + 35 != NPVARIANT_TO_INT32(args[0])) + return false; + + return true; +} + +static const NPClass kGCRaceClass = { + NP_CLASS_STRUCT_VERSION, + nullptr, + nullptr, + nullptr, + nullptr, + GCRaceInvoke, + GCRaceInvokeDefault, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr +}; + +struct GCRaceData +{ + GCRaceData(NPP npp, NPObject* callback, NPObject* localFunc) + : npp_(npp) + , callback_(callback) + , localFunc_(localFunc) + { + NPN_RetainObject(callback_); + NPN_RetainObject(localFunc_); + } + + ~GCRaceData() + { + NPN_ReleaseObject(callback_); + NPN_ReleaseObject(localFunc_); + } + + NPP npp_; + NPObject* callback_; + NPObject* localFunc_; +}; + +static void +FinishGCRace(void* closure) +{ + GCRaceData* rd = static_cast<GCRaceData*>(closure); + + XPSleep(5); + + NPVariant arg; + OBJECT_TO_NPVARIANT(rd->localFunc_, arg); + + NPVariant result; + bool ok = NPN_InvokeDefault(rd->npp_, rd->callback_, &arg, 1, &result); + if (!ok) + return; + + NPN_ReleaseVariantValue(&result); + delete rd; +} + +bool +checkGCRace(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (1 != argCount || !NPVARIANT_IS_OBJECT(args[0])) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* localFunc = + NPN_CreateObject(npp, const_cast<NPClass*>(&kGCRaceClass)); + + GCRaceData* rd = + new GCRaceData(npp, NPVARIANT_TO_OBJECT(args[0]), localFunc); + NPN_PluginThreadAsyncCall(npp, FinishGCRace, rd); + + OBJECT_TO_NPVARIANT(localFunc, *result); + return true; +} + +bool +hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + mozilla::NoteIntentionalCrash("plugin"); + + bool busyHang = false; + if ((argCount == 1) && NPVARIANT_IS_BOOLEAN(args[0])) { + busyHang = NPVARIANT_TO_BOOLEAN(args[0]); + } + + if (busyHang) { + const time_t start = std::time(nullptr); + while ((std::time(nullptr) - start) < 100000) { + volatile int dummy = 0; + for (int i=0; i<1000; ++i) { + dummy++; + } + } + } else { +#ifdef XP_WIN + Sleep(100000000); + Sleep(100000000); +#else + pause(); + pause(); +#endif + } + + // NB: returning true here means that we weren't terminated, and + // thus the hang detection/handling didn't work correctly. The + // test harness will succeed in calling this function, and the + // test will fail. + return true; +} + +bool +stallPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + uint32_t stallTimeSeconds = 0; + if ((argCount == 1) && NPVARIANT_IS_INT32(args[0])) { + stallTimeSeconds = (uint32_t) NPVARIANT_TO_INT32(args[0]); + } + +#ifdef XP_WIN + Sleep(stallTimeSeconds * 1000U); +#else + sleep(stallTimeSeconds); +#endif + + return true; +} + +#if defined(MOZ_WIDGET_GTK) +bool +getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + string sel = pluginGetClipboardText(id); + + uint32_t len = sel.size(); + char* selCopy = static_cast<char*>(NPN_MemAlloc(1 + len)); + if (!selCopy) + return false; + + memcpy(selCopy, sel.c_str(), len); + selCopy[len] = '\0'; + + STRINGN_TO_NPVARIANT(selCopy, len, *result); + // *result owns str now + + return true; +} + +bool +crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + return pluginCrashInNestedLoop(id); +} + +bool +triggerXError(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + return pluginTriggerXError(id); +} + +bool +destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + return pluginDestroySharedGfxStuff(id); +} + +#else +bool +getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + // XXX Not implemented! + return false; +} + +bool +crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + // XXX Not implemented! + return false; +} + +bool +triggerXError(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + // XXX Not implemented! + return false; +} + +bool +destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + // XXX Not implemented! + return false; +} +#endif + +#if defined(XP_WIN) +bool +nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + bool visible = pluginNativeWidgetIsVisible(id); + BOOLEAN_TO_NPVARIANT(visible, *result); + return true; +} +#else +bool +nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ + // XXX Not implemented! + return false; +} +#endif + +bool +getLastCompositionText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) +{ +#ifdef XP_WIN + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + char *outval = NPN_StrDup(id->lastComposition.c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +#else + // XXX not implemented + return false; +#endif +} + +bool +callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + if (id->callOnDestroy) + return false; + + if (1 != argCount || !NPVARIANT_IS_OBJECT(args[0])) + return false; + + id->callOnDestroy = NPVARIANT_TO_OBJECT(args[0]); + NPN_RetainObject(id->callOnDestroy); + + return true; +} + +// On Linux at least, a windowed plugin resize causes Flash Player to +// reconnect to the browser window. This method simulates that. +bool +reinitWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + if (!id->hasWidget) + return false; + + pluginWidgetInit(id, id->window.window); + return true; +} + +bool +propertyAndMethod(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + INT32_TO_NPVARIANT(5, *result); + return true; +} + +// Returns top-level window activation state as indicated by Cocoa NPAPI's +// NPCocoaEventWindowFocusChanged events - 'true' if active, 'false' if not. +// Throws an exception if no events have been received and thus this state +// is unknown. +bool +getTopLevelWindowActivationState(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + // Throw an exception for unknown state. + if (id->topLevelWindowActivationState == ACTIVATION_STATE_UNKNOWN) { + return false; + } + + if (id->topLevelWindowActivationState == ACTIVATION_STATE_ACTIVATED) { + BOOLEAN_TO_NPVARIANT(true, *result); + } else if (id->topLevelWindowActivationState == ACTIVATION_STATE_DEACTIVATED) { + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +bool +getTopLevelWindowActivationEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + INT32_TO_NPVARIANT(id->topLevelWindowActivationEventCount, *result); + + return true; +} + +// Returns top-level window activation state as indicated by Cocoa NPAPI's +// NPCocoaEventFocusChanged events - 'true' if active, 'false' if not. +// Throws an exception if no events have been received and thus this state +// is unknown. +bool +getFocusState(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + // Throw an exception for unknown state. + if (id->focusState == ACTIVATION_STATE_UNKNOWN) { + return false; + } + + if (id->focusState == ACTIVATION_STATE_ACTIVATED) { + BOOLEAN_TO_NPVARIANT(true, *result); + } else if (id->focusState == ACTIVATION_STATE_DEACTIVATED) { + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +bool +getFocusEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + INT32_TO_NPVARIANT(id->focusEventCount, *result); + + return true; +} + +bool +getEventModel(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + INT32_TO_NPVARIANT(id->eventModel, *result); + + return true; +} + +static bool +ReflectorHasMethod(NPObject* npobj, NPIdentifier name) +{ + return false; +} + +static bool +ReflectorHasProperty(NPObject* npobj, NPIdentifier name) +{ + return true; +} + +static bool +ReflectorGetProperty(NPObject* npobj, NPIdentifier name, NPVariant* result) +{ + if (NPN_IdentifierIsString(name)) { + char* s = NPN_UTF8FromIdentifier(name); + STRINGZ_TO_NPVARIANT(s, *result); + return true; + } + + INT32_TO_NPVARIANT(NPN_IntFromIdentifier(name), *result); + return true; +} + +static const NPClass kReflectorNPClass = { + NP_CLASS_STRUCT_VERSION, + nullptr, + nullptr, + nullptr, + ReflectorHasMethod, + nullptr, + nullptr, + ReflectorHasProperty, + ReflectorGetProperty, + nullptr, + nullptr, + nullptr, + nullptr +}; + +bool +getReflector(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (0 != argCount) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* reflector = + NPN_CreateObject(npp, + const_cast<NPClass*>(&kReflectorNPClass)); // retains + OBJECT_TO_NPVARIANT(reflector, *result); + return true; +} + +bool isVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + BOOLEAN_TO_NPVARIANT(id->window.clipRect.top != 0 || + id->window.clipRect.left != 0 || + id->window.clipRect.bottom != 0 || + id->window.clipRect.right != 0, *result); + return true; +} + +bool getWindowPosition(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + NPObject* window = nullptr; + NPError err = NPN_GetValue(npp, NPNVWindowNPObject, &window); + if (NPERR_NO_ERROR != err || !window) + return false; + + NPIdentifier arrayID = NPN_GetStringIdentifier("Array"); + NPVariant arrayFunctionV; + bool ok = NPN_GetProperty(npp, window, arrayID, &arrayFunctionV); + + NPN_ReleaseObject(window); + + if (!ok) + return false; + + if (!NPVARIANT_IS_OBJECT(arrayFunctionV)) { + NPN_ReleaseVariantValue(&arrayFunctionV); + return false; + } + NPObject* arrayFunction = NPVARIANT_TO_OBJECT(arrayFunctionV); + + NPVariant elements[4]; + INT32_TO_NPVARIANT(id->window.x, elements[0]); + INT32_TO_NPVARIANT(id->window.y, elements[1]); + INT32_TO_NPVARIANT(id->window.width, elements[2]); + INT32_TO_NPVARIANT(id->window.height, elements[3]); + + ok = NPN_InvokeDefault(npp, arrayFunction, elements, 4, result); + + NPN_ReleaseObject(arrayFunction); + + return ok; +} + +bool constructObject(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount == 0 || !NPVARIANT_IS_OBJECT(args[0])) + return false; + + NPObject* ctor = NPVARIANT_TO_OBJECT(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + return NPN_Construct(npp, ctor, args + 1, argCount - 1, result); +} + +bool setSitesWithData(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 1 || !NPVARIANT_IS_STRING(args[0])) + return false; + + // Clear existing data. + delete sSitesWithData; + + const NPString* str = &NPVARIANT_TO_STRING(args[0]); + if (str->UTF8Length == 0) + return true; + + // Parse the comma-delimited string into a vector. + sSitesWithData = new list<siteData>; + const char* iterator = str->UTF8Characters; + const char* end = iterator + str->UTF8Length; + while (1) { + const char* next = strchr(iterator, ','); + if (!next) + next = end; + + // Parse out the three tokens into a siteData struct. + const char* siteEnd = strchr(iterator, ':'); + *((char*) siteEnd) = '\0'; + const char* flagsEnd = strchr(siteEnd + 1, ':'); + *((char*) flagsEnd) = '\0'; + *((char*) next) = '\0'; + + siteData data; + data.site = string(iterator); + data.flags = atoi(siteEnd + 1); + data.age = atoi(flagsEnd + 1); + + sSitesWithData->push_back(data); + + if (next == end) + break; + + iterator = next + 1; + } + + return true; +} + +bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 1 || !NPVARIANT_IS_BOOLEAN(args[0])) + return false; + + sClearByAgeSupported = NPVARIANT_TO_BOOLEAN(args[0]); + return true; +} + +bool getLastKeyText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + char *outval = NPN_StrDup(id->lastKeyText.c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +} + +bool getNPNVdocumentOrigin(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) +{ + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + char *origin = nullptr; + NPError err = NPN_GetValue(npp, NPNVdocumentOrigin, &origin); + if (err != NPERR_NO_ERROR) { + return false; + } + + STRINGZ_TO_NPVARIANT(origin, *result); + return true; +} + +bool getMouseUpEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->mouseUpEventCount, *result); + return true; +} + +bool queryContentsScaleFactor(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + double scaleFactor = 1.0; +#if defined(XP_MACOSX) || defined(XP_WIN) + NPError err = NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, + NPNVcontentsScaleFactor, &scaleFactor); + if (err != NPERR_NO_ERROR) { + return false; + } +#endif + DOUBLE_TO_NPVARIANT(scaleFactor, *result); + return true; +} + +bool queryCSSZoomFactorSetValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + if (!npp) { + return false; + } + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (!id) { + return false; + } + DOUBLE_TO_NPVARIANT(id->cssZoomFactor, *result); + return true; +} + +bool queryCSSZoomFactorGetValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) + return false; + + double zoomFactor = 1.0; + NPError err = NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, + NPNVCSSZoomFactor, &zoomFactor); + if (err != NPERR_NO_ERROR) { + return false; + } + DOUBLE_TO_NPVARIANT(zoomFactor, *result); + return true; +} + +bool echoString(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 1) { + return false; + } + + if (!NPVARIANT_IS_STRING(args[0])) { + return false; + } + + const NPString& arg = NPVARIANT_TO_STRING(args[0]); + NPUTF8* buffer = static_cast<NPUTF8*>(NPN_MemAlloc(sizeof(NPUTF8) * arg.UTF8Length)); + if (!buffer) { + return false; + } + + std::copy(arg.UTF8Characters, arg.UTF8Characters + arg.UTF8Length, buffer); + STRINGN_TO_NPVARIANT(buffer, arg.UTF8Length, *result); + + return true; +} + +static bool +toggleAudioPlayback(NPObject* npobj, uint32_t argCount, bool playingAudio, NPVariant* result) +{ + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->playingAudio = playingAudio; + + NPN_SetValue(npp, NPPVpluginIsPlayingAudio, (void*)playingAudio); + + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool +startAudioPlayback(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + return toggleAudioPlayback(npobj, argCount, true, result); +} + +static bool +stopAudioPlayback(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + return toggleAudioPlayback(npobj, argCount, false, result); +} + +static bool +getAudioMuted(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result) +{ + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + BOOLEAN_TO_NPVARIANT(id->audioMuted, *result); + return true; +} diff --git a/dom/plugins/test/testplugin/nptest.def b/dom/plugins/test/testplugin/nptest.def new file mode 100644 index 000000000..4c543d5b9 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/nptest.h b/dom/plugins/test/testplugin/nptest.h new file mode 100644 index 000000000..e77d636a4 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.h @@ -0,0 +1,171 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_h_ +#define nptest_h_ + +#include "mozilla-config.h" + +#include "npapi.h" +#include "npfunctions.h" +#include "npruntime.h" +#include <stdint.h> +#include <string> +#include <sstream> + +typedef enum { + DM_DEFAULT, + DM_SOLID_COLOR +} DrawMode; + +typedef enum { + FUNCTION_NONE, + FUNCTION_NPP_GETURL, + FUNCTION_NPP_GETURLNOTIFY, + FUNCTION_NPP_POSTURL, + FUNCTION_NPP_POSTURLNOTIFY, + FUNCTION_NPP_NEWSTREAM, + FUNCTION_NPP_WRITEREADY, + FUNCTION_NPP_WRITE, + FUNCTION_NPP_DESTROYSTREAM, + FUNCTION_NPP_WRITE_RPC +} TestFunction; + +typedef enum { + AD_NONE, + AD_BITMAP, + AD_DXGI +} AsyncDrawing; + +typedef enum { + ACTIVATION_STATE_UNKNOWN, + ACTIVATION_STATE_ACTIVATED, + ACTIVATION_STATE_DEACTIVATED +} ActivationState; + +typedef struct FunctionTable { + TestFunction funcId; + const char* funcName; +} FunctionTable; + +typedef enum { + POSTMODE_FRAME, + POSTMODE_STREAM +} PostMode; + +typedef struct TestNPObject : NPObject { + NPP npp; + DrawMode drawMode; + uint32_t drawColor; // 0xAARRGGBB +} TestNPObject; + +typedef struct _PlatformData PlatformData; + +typedef struct TestRange : NPByteRange { + bool waiting; +} TestRange; + +typedef struct InstanceData { + NPP npp; + NPWindow window; + TestNPObject* scriptableObject; + PlatformData* platformData; + int32_t instanceCountWatchGeneration; + bool lastReportedPrivateModeState; + bool hasWidget; + bool npnNewStream; + bool throwOnNextInvoke; + bool runScriptOnPaint; + bool dontTouchElement; + uint32_t timerID[2]; + bool timerTestResult; + bool asyncCallbackResult; + bool invalidateDuringPaint; + bool slowPaint; + bool playingAudio; + bool audioMuted; + int32_t winX; + int32_t winY; + int32_t lastMouseX; + int32_t lastMouseY; + int32_t widthAtLastPaint; + int32_t paintCount; + int32_t writeCount; + int32_t writeReadyCount; + int32_t asyncTestPhase; + TestFunction testFunction; + TestFunction functionToFail; + NPError failureCode; + NPObject* callOnDestroy; + PostMode postMode; + std::string testUrl; + std::string frame; + std::string timerTestScriptCallback; + std::string asyncTestScriptCallback; + std::ostringstream err; + uint16_t streamMode; + int32_t streamChunkSize; + int32_t streamBufSize; + int32_t fileBufSize; + TestRange* testrange; + void* streamBuf; + void* fileBuf; + bool crashOnDestroy; + bool cleanupWidget; + ActivationState topLevelWindowActivationState; + int32_t topLevelWindowActivationEventCount; + ActivationState focusState; + int32_t focusEventCount; + int32_t eventModel; + bool closeStream; + std::string lastKeyText; + bool wantsAllStreams; + int32_t mouseUpEventCount; + int32_t bugMode; + std::string javaCodebase; + AsyncDrawing asyncDrawing; + NPAsyncSurface *frontBuffer; + NPAsyncSurface *backBuffer; + std::string lastComposition; + void* placeholderWnd; + double cssZoomFactor; +} InstanceData; + +void notifyDidPaint(InstanceData* instanceData); + +#if defined(XP_WIN) +bool setupDxgiSurfaces(NPP npp, InstanceData* instanceData); +void drawDxgiBitmapColor(InstanceData* instanceData); +#endif + +#endif // nptest_h_ diff --git a/dom/plugins/test/testplugin/nptest.rc b/dom/plugins/test/testplugin/nptest.rc new file mode 100644 index 000000000..948fb846e --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Plug-in for testing purposes.\x2122 (\x0939\x093f\x0928\x094d\x0926\x0940 \x4e2d\x6587 \x0627\x0644\x0639\x0631\x0628\x064a\x0629)" + VALUE "FileExtents", "tst" + VALUE "FileOpenName", L"Test \x2122 mimetype" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "nptest" + VALUE "MIMEType", "application/x-test" + VALUE "OriginalFilename", "nptest.dll" + VALUE "ProductName", "Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/nptest_droid.cpp b/dom/plugins/test/testplugin/nptest_droid.cpp new file mode 100644 index 000000000..9b062e0c2 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_droid.cpp @@ -0,0 +1,105 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2010, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Brad Lassey <blassey@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ +#include "nptest_platform.h" +#include "npapi.h" + +struct _PlatformData { +}; + using namespace std; + +bool +pluginSupportsWindowMode() +{ + return false; +} + +bool +pluginSupportsWindowlessMode() +{ + return true; +} + +NPError +pluginInstanceInit(InstanceData* instanceData) +{ + printf("NPERR_INCOMPATIBLE_VERSION_ERROR\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; +} + +void +pluginInstanceShutdown(InstanceData* instanceData) +{ + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; +} + +void +pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) +{ + instanceData->window = *newWindow; +} + +void +pluginWidgetInit(InstanceData* instanceData, void* oldWindow) +{ + // XXX nothing here yet since we don't support windowed plugins +} + +int16_t +pluginHandleEvent(InstanceData* instanceData, void* event) +{ + return 0; +} + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) +{ + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) +{ + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) +{ + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, string& error) +{ +} diff --git a/dom/plugins/test/testplugin/nptest_gtk2.cpp b/dom/plugins/test/testplugin/nptest_gtk2.cpp new file mode 100644 index 000000000..500db35d2 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_gtk2.cpp @@ -0,0 +1,774 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * Michael Ventnor <mventnor@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" +#include "npapi.h" +#include <pthread.h> +#include <gdk/gdk.h> +#ifdef MOZ_X11 +#include <gdk/gdkx.h> +#include <X11/extensions/shape.h> +#endif +#include <glib.h> +#include <gtk/gtk.h> +#include <unistd.h> + +#include "mozilla/IntentionalCrash.h" + + using namespace std; + +struct _PlatformData { +#ifdef MOZ_X11 + Display* display; + Visual* visual; + Colormap colormap; +#endif + GtkWidget* plug; +}; + +bool +pluginSupportsWindowMode() +{ + return true; +} + +bool +pluginSupportsWindowlessMode() +{ + return true; +} + +NPError +pluginInstanceInit(InstanceData* instanceData) +{ +#ifdef MOZ_X11 + instanceData->platformData = static_cast<PlatformData*> + (NPN_MemAlloc(sizeof(PlatformData))); + if (!instanceData->platformData) + return NPERR_OUT_OF_MEMORY_ERROR; + + instanceData->platformData->display = nullptr; + instanceData->platformData->visual = nullptr; + instanceData->platformData->colormap = X11None; + instanceData->platformData->plug = nullptr; + + return NPERR_NO_ERROR; +#else + // we only support X11 here, since thats what the plugin system uses + return NPERR_INCOMPATIBLE_VERSION_ERROR; +#endif +} + +void +pluginInstanceShutdown(InstanceData* instanceData) +{ + if (instanceData->hasWidget) { + Window window = reinterpret_cast<XID>(instanceData->window.window); + + if (window != X11None) { + // This window XID should still be valid. + // See bug 429604 and bug 454756. + XWindowAttributes attributes; + if (!XGetWindowAttributes(instanceData->platformData->display, window, + &attributes)) + g_error("XGetWindowAttributes failed at plugin instance shutdown"); + } + } + + GtkWidget* plug = instanceData->platformData->plug; + if (plug) { + instanceData->platformData->plug = 0; + if (instanceData->cleanupWidget) { + // Default/tidy behavior + gtk_widget_destroy(plug); + } else { + // Flash Player style: let the GtkPlug destroy itself on disconnect. + g_signal_handlers_disconnect_matched(plug, G_SIGNAL_MATCH_DATA, 0, 0, + nullptr, nullptr, instanceData); + } + } + + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; +} + +static void +SetCairoRGBA(cairo_t* cairoWindow, uint32_t rgba) +{ + float b = (rgba & 0xFF) / 255.0; + float g = ((rgba & 0xFF00) >> 8) / 255.0; + float r = ((rgba & 0xFF0000) >> 16) / 255.0; + float a = ((rgba & 0xFF000000) >> 24) / 255.0; + + cairo_set_source_rgba(cairoWindow, r, g, b, a); +} + +static void +pluginDrawSolid(InstanceData* instanceData, GdkDrawable* gdkWindow, + int x, int y, int width, int height) +{ + cairo_t* cairoWindow = gdk_cairo_create(gdkWindow); + + if (!instanceData->hasWidget) { + NPRect* clip = &instanceData->window.clipRect; + cairo_rectangle(cairoWindow, clip->left, clip->top, + clip->right - clip->left, clip->bottom - clip->top); + cairo_clip(cairoWindow); + } + + GdkRectangle windowRect = { x, y, width, height }; + gdk_cairo_rectangle(cairoWindow, &windowRect); + SetCairoRGBA(cairoWindow, instanceData->scriptableObject->drawColor); + + cairo_fill(cairoWindow); + cairo_destroy(cairoWindow); +} + +static void +pluginDrawWindow(InstanceData* instanceData, GdkDrawable* gdkWindow, + const GdkRectangle& invalidRect) +{ + NPWindow& window = instanceData->window; + // When we have a widget, window.x/y are meaningless since our + // widget is always positioned correctly and we just draw into it at 0,0 + int x = instanceData->hasWidget ? 0 : window.x; + int y = instanceData->hasWidget ? 0 : window.y; + int width = window.width; + int height = window.height; + + notifyDidPaint(instanceData); + + if (instanceData->scriptableObject->drawMode == DM_SOLID_COLOR) { + // drawing a solid color for reftests + pluginDrawSolid(instanceData, gdkWindow, + invalidRect.x, invalidRect.y, + invalidRect.width, invalidRect.height); + return; + } + + NPP npp = instanceData->npp; + if (!npp) + return; + + const char* uaString = NPN_UserAgent(npp); + if (!uaString) + return; + + GdkGC* gdkContext = gdk_gc_new(gdkWindow); + if (!gdkContext) + return; + + if (!instanceData->hasWidget) { + NPRect* clip = &window.clipRect; + GdkRectangle gdkClip = { clip->left, clip->top, clip->right - clip->left, + clip->bottom - clip->top }; + gdk_gc_set_clip_rectangle(gdkContext, &gdkClip); + } + + // draw a grey background for the plugin frame + GdkColor grey; + grey.red = grey.blue = grey.green = 32767; + gdk_gc_set_rgb_fg_color(gdkContext, &grey); + gdk_draw_rectangle(gdkWindow, gdkContext, TRUE, x, y, width, height); + + // draw a 3-pixel-thick black frame around the plugin + GdkColor black; + black.red = black.green = black.blue = 0; + gdk_gc_set_rgb_fg_color(gdkContext, &black); + gdk_gc_set_line_attributes(gdkContext, 3, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, GDK_JOIN_MITER); + gdk_draw_rectangle(gdkWindow, gdkContext, FALSE, x + 1, y + 1, + width - 3, height - 3); + + // paint the UA string + PangoContext* pangoContext = gdk_pango_context_get(); + PangoLayout* pangoTextLayout = pango_layout_new(pangoContext); + pango_layout_set_width(pangoTextLayout, (width - 10) * PANGO_SCALE); + pango_layout_set_text(pangoTextLayout, uaString, -1); + gdk_draw_layout(gdkWindow, gdkContext, x + 5, y + 5, pangoTextLayout); + g_object_unref(pangoTextLayout); + + g_object_unref(gdkContext); +} + +static gboolean +ExposeWidget(GtkWidget* widget, GdkEventExpose* event, + gpointer user_data) +{ + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + pluginDrawWindow(instanceData, event->window, event->area); + return TRUE; +} + +static gboolean +MotionEvent(GtkWidget* widget, GdkEventMotion* event, + gpointer user_data) +{ + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + instanceData->lastMouseX = event->x; + instanceData->lastMouseY = event->y; + return TRUE; +} + +static gboolean +ButtonEvent(GtkWidget* widget, GdkEventButton* event, + gpointer user_data) +{ + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + instanceData->lastMouseX = event->x; + instanceData->lastMouseY = event->y; + if (event->type == GDK_BUTTON_RELEASE) { + instanceData->mouseUpEventCount++; + } + return TRUE; +} + +static gboolean +DeleteWidget(GtkWidget* widget, GdkEvent* event, gpointer user_data) +{ + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + // Some plugins do not expect the plug to be removed from the socket before + // the plugin instance is destroyed. e.g. bug 485125 + if (instanceData->platformData->plug) + g_error("plug removed"); // this aborts + + return FALSE; +} + +void +pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) +{ + instanceData->window = *newWindow; + +#ifdef MOZ_X11 + NPSetWindowCallbackStruct *ws_info = + static_cast<NPSetWindowCallbackStruct*>(newWindow->ws_info); + instanceData->platformData->display = ws_info->display; + instanceData->platformData->visual = ws_info->visual; + instanceData->platformData->colormap = ws_info->colormap; +#endif +} + +void +pluginWidgetInit(InstanceData* instanceData, void* oldWindow) +{ +#ifdef MOZ_X11 + GtkWidget* oldPlug = instanceData->platformData->plug; + if (oldPlug) { + instanceData->platformData->plug = 0; + gtk_widget_destroy(oldPlug); + } + + GdkNativeWindow nativeWinId = + reinterpret_cast<XID>(instanceData->window.window); + + /* create a GtkPlug container */ + GtkWidget* plug = gtk_plug_new(nativeWinId); + + // Test for bugs 539138 and 561308 + if (!plug->window) + g_error("Plug has no window"); // aborts + + /* make sure the widget is capable of receiving focus */ + GTK_WIDGET_SET_FLAGS (GTK_WIDGET(plug), GTK_CAN_FOCUS); + + /* all the events that our widget wants to receive */ + gtk_widget_add_events(plug, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + g_signal_connect(plug, "expose-event", G_CALLBACK(ExposeWidget), + instanceData); + g_signal_connect(plug, "motion_notify_event", G_CALLBACK(MotionEvent), + instanceData); + g_signal_connect(plug, "button_press_event", G_CALLBACK(ButtonEvent), + instanceData); + g_signal_connect(plug, "button_release_event", G_CALLBACK(ButtonEvent), + instanceData); + g_signal_connect(plug, "delete-event", G_CALLBACK(DeleteWidget), + instanceData); + gtk_widget_show(plug); + + instanceData->platformData->plug = plug; +#endif +} + +int16_t +pluginHandleEvent(InstanceData* instanceData, void* event) +{ +#ifdef MOZ_X11 + XEvent* nsEvent = (XEvent*)event; + + switch (nsEvent->type) { + case GraphicsExpose: { + const XGraphicsExposeEvent& expose = nsEvent->xgraphicsexpose; + NPWindow& window = instanceData->window; + window.window = (void*)(expose.drawable); + + GdkNativeWindow nativeWinId = reinterpret_cast<XID>(window.window); + + GdkDisplay* gdkDisplay = gdk_x11_lookup_xdisplay(expose.display); + if (!gdkDisplay) { + g_warning("Display not opened by GDK"); + return 0; + } + // gdk_pixmap_foreign_new() doesn't check whether a GdkPixmap already + // exists, so check first. + // https://bugzilla.gnome.org/show_bug.cgi?id=590690 + GdkPixmap* gdkDrawable = + GDK_DRAWABLE(gdk_pixmap_lookup_for_display(gdkDisplay, nativeWinId)); + // If there is no existing GdkPixmap or it doesn't have a colormap then + // create our own. + if (gdkDrawable) { + GdkColormap* gdkColormap = gdk_drawable_get_colormap(gdkDrawable); + if (!gdkColormap) { + g_warning("No GdkColormap on GdkPixmap"); + return 0; + } + if (gdk_x11_colormap_get_xcolormap(gdkColormap) + != instanceData->platformData->colormap) { + g_warning("wrong Colormap"); + return 0; + } + if (gdk_x11_visual_get_xvisual(gdk_colormap_get_visual(gdkColormap)) + != instanceData->platformData->visual) { + g_warning("wrong Visual"); + return 0; + } + g_object_ref(gdkDrawable); + } else { + gdkDrawable = + GDK_DRAWABLE(gdk_pixmap_foreign_new_for_display(gdkDisplay, + nativeWinId)); + VisualID visualID = instanceData->platformData->visual->visualid; + GdkVisual* gdkVisual = + gdk_x11_screen_lookup_visual(gdk_drawable_get_screen(gdkDrawable), + visualID); + GdkColormap* gdkColormap = + gdk_x11_colormap_foreign_new(gdkVisual, + instanceData->platformData->colormap); + gdk_drawable_set_colormap(gdkDrawable, gdkColormap); + g_object_unref(gdkColormap); + } + + const NPRect& clip = window.clipRect; + if (expose.x < clip.left || expose.y < clip.top || + expose.x + expose.width > clip.right || + expose.y + expose.height > clip.bottom) { + g_warning("expose rectangle (x=%d,y=%d,w=%d,h=%d) not in clip rectangle (l=%d,t=%d,r=%d,b=%d)", + expose.x, expose.y, expose.width, expose.height, + clip.left, clip.top, clip.right, clip.bottom); + return 0; + } + if (expose.x < window.x || expose.y < window.y || + expose.x + expose.width > window.x + int32_t(window.width) || + expose.y + expose.height > window.y + int32_t(window.height)) { + g_warning("expose rectangle (x=%d,y=%d,w=%d,h=%d) not in plugin rectangle (x=%d,y=%d,w=%d,h=%d)", + expose.x, expose.y, expose.width, expose.height, + window.x, window.y, window.width, window.height); + return 0; + } + + GdkRectangle invalidRect = + { expose.x, expose.y, expose.width, expose.height }; + pluginDrawWindow(instanceData, gdkDrawable, invalidRect); + g_object_unref(gdkDrawable); + break; + } + case MotionNotify: { + XMotionEvent* motion = &nsEvent->xmotion; + instanceData->lastMouseX = motion->x; + instanceData->lastMouseY = motion->y; + break; + } + case ButtonPress: + case ButtonRelease: { + XButtonEvent* button = &nsEvent->xbutton; + instanceData->lastMouseX = button->x; + instanceData->lastMouseY = button->y; + if (nsEvent->type == ButtonRelease) { + instanceData->mouseUpEventCount++; + } + break; + } + default: + break; + } +#endif + + return 0; +} + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) +{ + if (!instanceData->hasWidget) + return NPTEST_INT32_ERROR; + GtkWidget* plug = instanceData->platformData->plug; + if (!plug) + return NPTEST_INT32_ERROR; + GdkWindow* plugWnd = plug->window; + if (!plugWnd) + return NPTEST_INT32_ERROR; + + GdkWindow* toplevelGdk = 0; +#ifdef MOZ_X11 + Window toplevel = 0; + NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel); + if (!toplevel) + return NPTEST_INT32_ERROR; + toplevelGdk = gdk_window_foreign_new(toplevel); +#endif + if (!toplevelGdk) + return NPTEST_INT32_ERROR; + + GdkRectangle toplevelFrameExtents; + gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents); + g_object_unref(toplevelGdk); + + gint pluginWidth, pluginHeight; + gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &pluginWidth, &pluginHeight); + gint pluginOriginX, pluginOriginY; + gdk_window_get_origin(plugWnd, &pluginOriginX, &pluginOriginY); + gint pluginX = pluginOriginX - toplevelFrameExtents.x; + gint pluginY = pluginOriginY - toplevelFrameExtents.y; + + switch (edge) { + case EDGE_LEFT: + return pluginX; + case EDGE_TOP: + return pluginY; + case EDGE_RIGHT: + return pluginX + pluginWidth; + case EDGE_BOTTOM: + return pluginY + pluginHeight; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +#ifdef MOZ_X11 +static void intersectWithShapeRects(Display* display, Window window, + int kind, GdkRegion* region) +{ + int count = -1, order; + XRectangle* shapeRects = + XShapeGetRectangles(display, window, kind, &count, &order); + // The documentation says that shapeRects will be nullptr when the + // extension is not supported. Unfortunately XShapeGetRectangles + // also returns nullptr when the region is empty, so we can't treat + // nullptr as failure. I hope this way is OK. + if (count < 0) + return; + + GdkRegion* shapeRegion = gdk_region_new(); + if (!shapeRegion) { + XFree(shapeRects); + return; + } + + for (int i = 0; i < count; ++i) { + XRectangle* r = &shapeRects[i]; + GdkRectangle rect = { r->x, r->y, r->width, r->height }; + gdk_region_union_with_rect(shapeRegion, &rect); + } + XFree(shapeRects); + + gdk_region_intersect(region, shapeRegion); + gdk_region_destroy(shapeRegion); +} +#endif + +static GdkRegion* computeClipRegion(InstanceData* instanceData) +{ + if (!instanceData->hasWidget) + return 0; + + GtkWidget* plug = instanceData->platformData->plug; + if (!plug) + return 0; + GdkWindow* plugWnd = plug->window; + if (!plugWnd) + return 0; + + gint plugWidth, plugHeight; + gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &plugWidth, &plugHeight); + GdkRectangle pluginRect = { 0, 0, plugWidth, plugHeight }; + GdkRegion* region = gdk_region_rectangle(&pluginRect); + if (!region) + return 0; + + int pluginX = 0, pluginY = 0; + +#ifdef MOZ_X11 + Display* display = GDK_WINDOW_XDISPLAY(plugWnd); + Window window = GDK_WINDOW_XWINDOW(plugWnd); + + Window toplevel = 0; + NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel); + if (!toplevel) + return 0; + + for (;;) { + Window root; + int x, y; + unsigned int width, height, border_width, depth; + if (!XGetGeometry(display, window, &root, &x, &y, &width, &height, + &border_width, &depth)) { + gdk_region_destroy(region); + return 0; + } + + GdkRectangle windowRect = { 0, 0, static_cast<gint>(width), + static_cast<gint>(height) }; + GdkRegion* windowRgn = gdk_region_rectangle(&windowRect); + if (!windowRgn) { + gdk_region_destroy(region); + return 0; + } + intersectWithShapeRects(display, window, ShapeBounding, windowRgn); + intersectWithShapeRects(display, window, ShapeClip, windowRgn); + gdk_region_offset(windowRgn, -pluginX, -pluginY); + gdk_region_intersect(region, windowRgn); + gdk_region_destroy(windowRgn); + + // Stop now if we've reached the toplevel. Stopping here means + // clipping performed by the toplevel window is taken into account. + if (window == toplevel) + break; + + Window parent; + Window* children; + unsigned int nchildren; + if (!XQueryTree(display, window, &root, &parent, &children, &nchildren)) { + gdk_region_destroy(region); + return 0; + } + XFree(children); + + pluginX += x; + pluginY += y; + + window = parent; + } +#endif + // pluginX and pluginY are now relative to the toplevel. Make them + // relative to the window frame top-left. + GdkWindow* toplevelGdk = gdk_window_foreign_new(window); + if (!toplevelGdk) + return 0; + GdkRectangle toplevelFrameExtents; + gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents); + gint toplevelOriginX, toplevelOriginY; + gdk_window_get_origin(toplevelGdk, &toplevelOriginX, &toplevelOriginY); + g_object_unref(toplevelGdk); + + pluginX += toplevelOriginX - toplevelFrameExtents.x; + pluginY += toplevelOriginY - toplevelFrameExtents.y; + + gdk_region_offset(region, pluginX, pluginY); + return region; +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) +{ + GdkRegion* region = computeClipRegion(instanceData); + if (!region) + return NPTEST_INT32_ERROR; + + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(region, &rects, &nrects); + gdk_region_destroy(region); + g_free(rects); + return nrects; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) +{ + GdkRegion* region = computeClipRegion(instanceData); + if (!region) + return NPTEST_INT32_ERROR; + + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(region, &rects, &nrects); + gdk_region_destroy(region); + if (rectIndex >= nrects) { + g_free(rects); + return NPTEST_INT32_ERROR; + } + + GdkRectangle rect = rects[rectIndex]; + g_free(rects); + + switch (edge) { + case EDGE_LEFT: + return rect.x; + case EDGE_TOP: + return rect.y; + case EDGE_RIGHT: + return rect.x + rect.width; + case EDGE_BOTTOM: + return rect.y + rect.height; + } + return NPTEST_INT32_ERROR; +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, string& error) +{ +} + +string +pluginGetClipboardText(InstanceData* instanceData) +{ + GtkClipboard* cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + // XXX this is a BAD WAY to interact with GtkClipboard. We use this + // deprecated interface only to test nested event loop handling. + gchar* text = gtk_clipboard_wait_for_text(cb); + string retText = text ? text : ""; + + g_free(text); + + return retText; +} + +//----------------------------------------------------------------------------- +// NB: this test is quite gross in that it's not only +// nondeterministic, but dependent on the guts of the nested glib +// event loop handling code in PluginModule. We first sleep long +// enough to make sure that the "detection timer" will be pending when +// we enter the nested glib loop, then similarly for the "process browser +// events" timer. Then we "schedule" the crasher thread to run at about the +// same time we expect that the PluginModule "process browser events" task +// will run. If all goes well, the plugin process will crash and generate the +// XPCOM "plugin crashed" task, and the browser will run that task while still +// in the "process some events" loop. + +static void* +CrasherThread(void* data) +{ + // Give the parent thread a chance to send the message. + usleep(200); + + // Exit (without running atexit hooks) rather than crashing with a signal + // so as to make timing more reliable. The process terminates immediately + // rather than waiting for a thread in the parent process to attach and + // generate a minidump. + _exit(1); + + // not reached + return(nullptr); +} + +bool +pluginCrashInNestedLoop(InstanceData* instanceData) +{ + // wait at least long enough for nested loop detector task to be pending ... + sleep(1); + + // Run the nested loop detector by processing all events that are waiting. + bool found_event = false; + while (g_main_context_iteration(nullptr, FALSE)) { + found_event = true; + } + if (!found_event) { + g_warning("DetectNestedEventLoop did not fire"); + return true; // trigger a test failure + } + + // wait at least long enough for the "process browser events" task to be + // pending ... + sleep(1); + + // we'll be crashing soon, note that fact now to avoid messing with + // timing too much + mozilla::NoteIntentionalCrash("plugin"); + + // schedule the crasher thread ... + pthread_t crasherThread; + if (0 != pthread_create(&crasherThread, nullptr, CrasherThread, nullptr)) { + g_warning("Failed to create thread"); + return true; // trigger a test failure + } + + // .. and hope it crashes at about the same time as the "process browser + // events" task (that should run in this loop) is being processed in the + // parent. + found_event = false; + while (g_main_context_iteration(nullptr, FALSE)) { + found_event = true; + } + if (found_event) { + g_warning("Should have crashed in ProcessBrowserEvents"); + } else { + g_warning("ProcessBrowserEvents did not fire"); + } + + // if we get here without crashing, then we'll trigger a test failure + return true; +} + +bool +pluginTriggerXError(InstanceData* instanceData) +{ + mozilla::NoteIntentionalCrash("plugin"); + int num_prop_return; + // Window parameter is None to generate a fatal error, and this function + // should not return. + XListProperties(GDK_DISPLAY(), X11None, &num_prop_return); + + // if we get here without crashing, then we'll trigger a test failure + return true; +} + +static int +SleepThenDie(Display* display) +{ + mozilla::NoteIntentionalCrash("plugin"); + fprintf(stderr, "[testplugin:%d] SleepThenDie: sleeping\n", getpid()); + sleep(1); + + fprintf(stderr, "[testplugin:%d] SleepThenDie: dying\n", getpid()); + _exit(1); +} + +bool +pluginDestroySharedGfxStuff(InstanceData* instanceData) +{ + // Closing the X socket results in the gdk error handler being + // invoked, which exit()s us. We want to give the parent process a + // little while to do whatever it wanted to do, so steal the IO + // handler from gdk and set up our own that delays seppuku. + XSetIOErrorHandler(SleepThenDie); + close(ConnectionNumber(GDK_DISPLAY())); + return true; +} diff --git a/dom/plugins/test/testplugin/nptest_macosx.mm b/dom/plugins/test/testplugin/nptest_macosx.mm new file mode 100644 index 000000000..c38da31dc --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_macosx.mm @@ -0,0 +1,312 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" +#include "nsAlgorithm.h" +#include <CoreServices/CoreServices.h> +#include <algorithm> + +using namespace std; + +bool +pluginSupportsWindowMode() +{ + return false; +} + +bool +pluginSupportsWindowlessMode() +{ + return true; +} + +NPError +pluginInstanceInit(InstanceData* instanceData) +{ + NPP npp = instanceData->npp; + + NPBool supportsCoreGraphics = false; + if ((NPN_GetValue(npp, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) == NPERR_NO_ERROR) && + supportsCoreGraphics) { + NPN_SetValue(npp, NPPVpluginDrawingModel, (void*)NPDrawingModelCoreGraphics); + } else { + printf("CoreGraphics drawing model not supported, can't create a plugin instance.\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + NPBool supportsCocoaEvents = false; + if ((NPN_GetValue(npp, NPNVsupportsCocoaBool, &supportsCocoaEvents) == NPERR_NO_ERROR) && + supportsCocoaEvents) { + NPN_SetValue(npp, NPPVpluginEventModel, (void*)NPEventModelCocoa); + instanceData->eventModel = NPEventModelCocoa; + } else { + printf("Cocoa event model not supported, can't create a plugin instance.\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + return NPERR_NO_ERROR; +} + +void +pluginInstanceShutdown(InstanceData* instanceData) +{ +} + +static bool +RectEquals(const NPRect& r1, const NPRect& r2) +{ + return r1.left == r2.left && r1.top == r2.top && + r1.right == r2.right && r1.bottom == r2.bottom; +} + +void +pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) +{ + // Ugh. Due to a terrible Gecko bug, we have to ignore position changes + // when the clip rect doesn't change; the position can be wrong + // when set by a path other than nsPluginFrame::FixUpPluginWindow. + int32_t oldX = instanceData->window.x; + int32_t oldY = instanceData->window.y; + bool clipChanged = + !RectEquals(instanceData->window.clipRect, newWindow->clipRect); + instanceData->window = *newWindow; + if (!clipChanged) { + instanceData->window.x = oldX; + instanceData->window.y = oldY; + } +} + +void +pluginWidgetInit(InstanceData* instanceData, void* oldWindow) +{ + // Should never be called since we don't support window mode +} + +static void +GetColorsFromRGBA(uint32_t rgba, float* r, float* g, float* b, float* a) +{ + *b = (rgba & 0xFF) / 255.0; + *g = ((rgba & 0xFF00) >> 8) / 255.0; + *r = ((rgba & 0xFF0000) >> 16) / 255.0; + *a = ((rgba & 0xFF000000) >> 24) / 255.0; +} + +static void +pluginDraw(InstanceData* instanceData, NPCocoaEvent* event) +{ + if (!instanceData) + return; + + notifyDidPaint(instanceData); + + NPP npp = instanceData->npp; + if (!npp) + return; + + const char* uaString = NPN_UserAgent(npp); + if (!uaString) + return; + + NPWindow window = instanceData->window; + + CGContextRef cgContext = event->data.draw.context; + + float windowWidth = window.width; + float windowHeight = window.height; + + switch(instanceData->scriptableObject->drawMode) { + case DM_DEFAULT: { + CFStringRef uaCFString = CFStringCreateWithCString(kCFAllocatorDefault, uaString, kCFStringEncodingASCII); + // save the cgcontext gstate + CGContextSaveGState(cgContext); + + // we get a flipped context + CGContextTranslateCTM(cgContext, 0.0, windowHeight); + CGContextScaleCTM(cgContext, 1.0, -1.0); + + // draw a gray background for the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + CGContextSetGrayFillColor(cgContext, 0.5, 1.0); + CGContextDrawPath(cgContext, kCGPathFill); + + // draw a black frame around the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + CGContextSetGrayStrokeColor(cgContext, 0.0, 1.0); + CGContextSetLineWidth(cgContext, 6.0); + CGContextStrokePath(cgContext); + + // draw the UA string using Core Text + CGContextSetTextMatrix(cgContext, CGAffineTransformIdentity); + + // Initialize a rectangular path. + CGMutablePathRef path = CGPathCreateMutable(); + CGRect bounds = CGRectMake(10.0, 10.0, std::max(0.0, windowWidth - 20.0), + std::max(0.0, windowHeight - 20.0)); + CGPathAddRect(path, NULL, bounds); + + // Initialize an attributed string. + CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), uaCFString); + + // Create a color and add it as an attribute to the string. + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + CGFloat components[] = { 0.0, 0.0, 0.0, 1.0 }; + CGColorRef red = CGColorCreate(rgbColorSpace, components); + CGColorSpaceRelease(rgbColorSpace); + CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 50), kCTForegroundColorAttributeName, red); + + // Create the framesetter with the attributed string. + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString); + CFRelease(attrString); + + // Create the frame and draw it into the graphics context + CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); + CFRelease(framesetter); + if (frame) { + CTFrameDraw(frame, cgContext); + CFRelease(frame); + } + + // restore the cgcontext gstate + CGContextRestoreGState(cgContext); + break; + } + case DM_SOLID_COLOR: { + // save the cgcontext gstate + CGContextSaveGState(cgContext); + + // we get a flipped context + CGContextTranslateCTM(cgContext, 0.0, windowHeight); + CGContextScaleCTM(cgContext, 1.0, -1.0); + + // draw a solid background for the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + + float r,g,b,a; + GetColorsFromRGBA(instanceData->scriptableObject->drawColor, &r, &g, &b, &a); + CGContextSetRGBFillColor(cgContext, r, g, b, a); + CGContextDrawPath(cgContext, kCGPathFill); + + // restore the cgcontext gstate + CGContextRestoreGState(cgContext); + break; + } + } +} + +int16_t +pluginHandleEvent(InstanceData* instanceData, void* event) +{ + NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event; + if (!cocoaEvent) + return kNPEventNotHandled; + + switch (cocoaEvent->type) { + case NPCocoaEventDrawRect: + pluginDraw(instanceData, cocoaEvent); + break; + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseDragged: + instanceData->lastMouseX = (int32_t)cocoaEvent->data.mouse.pluginX; + instanceData->lastMouseY = (int32_t)cocoaEvent->data.mouse.pluginY; + if (cocoaEvent->type == NPCocoaEventMouseUp) { + instanceData->mouseUpEventCount++; + } + break; + case NPCocoaEventWindowFocusChanged: + instanceData->topLevelWindowActivationState = cocoaEvent->data.focus.hasFocus ? + ACTIVATION_STATE_ACTIVATED : ACTIVATION_STATE_DEACTIVATED; + instanceData->topLevelWindowActivationEventCount = instanceData->topLevelWindowActivationEventCount + 1; + break; + case NPCocoaEventFocusChanged: + instanceData->focusState = cocoaEvent->data.focus.hasFocus ? + ACTIVATION_STATE_ACTIVATED : ACTIVATION_STATE_DEACTIVATED; + instanceData->focusEventCount = instanceData->focusEventCount + 1; + break; + default: + return kNPEventNotHandled; + } + + return kNPEventHandled; +} + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) +{ + NPWindow* w = &instanceData->window; + switch (edge) { + case EDGE_LEFT: + return w->x; + case EDGE_TOP: + return w->y; + case EDGE_RIGHT: + return w->x + w->width; + case EDGE_BOTTOM: + return w->y + w->height; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) +{ + return 1; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) +{ + if (rectIndex != 0) + return NPTEST_INT32_ERROR; + + // We have to add the Cocoa titlebar height here since the clip rect + // is being returned relative to that + static const int COCOA_TITLEBAR_HEIGHT = 22; + + NPWindow* w = &instanceData->window; + switch (edge) { + case EDGE_LEFT: + return w->clipRect.left; + case EDGE_TOP: + return w->clipRect.top + COCOA_TITLEBAR_HEIGHT; + case EDGE_RIGHT: + return w->clipRect.right; + case EDGE_BOTTOM: + return w->clipRect.bottom + COCOA_TITLEBAR_HEIGHT; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, string& error) +{ +} diff --git a/dom/plugins/test/testplugin/nptest_name.cpp b/dom/plugins/test/testplugin/nptest_name.cpp new file mode 100644 index 000000000..f9b57dfa8 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_name.cpp @@ -0,0 +1,6 @@ +const char *sPluginName = "Test Plug-in"; +const char *sPluginDescription = "Plug-in for testing purposes.\xE2\x84\xA2 " \ + "(\xe0\xa4\xb9\xe0\xa4\xbf\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xa6\xe0\xa5\x80 " \ + "\xe4\xb8\xad\xe6\x96\x87 " \ + "\xd8\xa7\xd9\x84\xd8\xb9\xd8\xb1\xd8\xa8\xd9\x8a\xd8\xa9)"; +const char *sMimeDescription = "application/x-test:tst:Test \xE2\x84\xA2 mimetype"; diff --git a/dom/plugins/test/testplugin/nptest_platform.h b/dom/plugins/test/testplugin/nptest_platform.h new file mode 100644 index 000000000..c9542c78b --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_platform.h @@ -0,0 +1,160 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_platform_h_ +#define nptest_platform_h_ + +#include "nptest.h" + +/** + * Returns true if the plugin supports windowed mode + */ +bool pluginSupportsWindowMode(); + +/** + * Returns true if the plugin supports windowless mode. At least one of + * "pluginSupportsWindowMode" and "pluginSupportsWindowlessMode" must + * return true. + */ +bool pluginSupportsWindowlessMode(); + +/** + * Initialize the plugin instance. Returning an error here will cause the + * plugin instantiation to fail. + */ +NPError pluginInstanceInit(InstanceData* instanceData); + +/** + * Shutdown the plugin instance. + */ +void pluginInstanceShutdown(InstanceData* instanceData); + +/** + * Set the instanceData's window to newWindow. + */ +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow); + +/** + * Initialize the window for a windowed plugin. oldWindow is the old + * native window value. This will never be called for windowless plugins. + */ +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow); + +/** + * Handle an event for a windowless plugin. (Windowed plugins are + * responsible for listening for their own events.) + */ +int16_t pluginHandleEvent(InstanceData* instanceData, void* event); + +enum RectEdge { + EDGE_LEFT = 0, + EDGE_TOP = 1, + EDGE_RIGHT = 2, + EDGE_BOTTOM = 3 +}; + +enum { + NPTEST_INT32_ERROR = 0x7FFFFFFF +}; + +/** + * Return the coordinate of the given edge of the plugin's area, relative + * to the top-left corner of the toplevel window containing the plugin, + * including window decorations. Only works for window-mode plugins + * and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge); + +/** + * Return the number of rectangles in the plugin's clip region. Only + * works for window-mode plugins and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData); + +/** + * Return the coordinate of the given edge of a rectangle in the plugin's + * clip region, relative to the top-left corner of the toplevel window + * containing the plugin, including window decorations. Only works for + * window-mode plugins and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge); + +/** + * Check that the platform-specific plugin state is internally consistent. + * Just return if everything is OK, otherwise append error messages + * to 'error' separated by \n. + */ +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, std::string& error); + +/** + * Get the current clipboard item as text. If the clipboard item + * isn't text, the returned value is undefined. + */ +std::string pluginGetClipboardText(InstanceData* instanceData); + +/** + * Crash while in a nested event loop. The goal is to catch the + * browser processing the XPCOM event generated from the plugin's + * crash while other plugin code is still on the stack. + * See https://bugzilla.mozilla.org/show_bug.cgi?id=550026. + */ +bool pluginCrashInNestedLoop(InstanceData* instanceData); + +/** + * Generate an X11 protocol error to terminate the plugin process. + */ +bool pluginTriggerXError(InstanceData* instanceData); + +/** + * Destroy gfx things that might be shared with the parent process + * when we're run out-of-process. It's not expected that this + * function will be called when the test plugin is loaded in-process, + * and bad things will happen if it is called. + * + * This call leaves the plugin subprocess in an undefined state. It + * must not be used after this call or weird things will happen. + */ +bool pluginDestroySharedGfxStuff(InstanceData* instanceData); + +/** + * Checks to see if the native widget is marked as visible. Works + * in e10s and non-e10s. Useful in testing e10s related compositor + * plugin window functionality. Supported on Windows. + */ +bool pluginNativeWidgetIsVisible(InstanceData* instanceData); + +#endif // nptest_platform_h_ diff --git a/dom/plugins/test/testplugin/nptest_utils.cpp b/dom/plugins/test/testplugin/nptest_utils.cpp new file mode 100644 index 000000000..d33603143 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_utils.cpp @@ -0,0 +1,113 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * 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. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_utils.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +NPUTF8* +createCStringFromNPVariant(const NPVariant* variant) +{ + size_t length = NPVARIANT_TO_STRING(*variant).UTF8Length; + NPUTF8* result = (NPUTF8*)malloc(length + 1); + memcpy(result, NPVARIANT_TO_STRING(*variant).UTF8Characters, length); + result[length] = '\0'; + return result; +} + +NPIdentifier +variantToIdentifier(NPVariant variant) +{ + if (NPVARIANT_IS_STRING(variant)) + return stringVariantToIdentifier(variant); + else if (NPVARIANT_IS_INT32(variant)) + return int32VariantToIdentifier(variant); + else if (NPVARIANT_IS_DOUBLE(variant)) + return doubleVariantToIdentifier(variant); + return 0; +} + +NPIdentifier +stringVariantToIdentifier(NPVariant variant) +{ + assert(NPVARIANT_IS_STRING(variant)); + NPUTF8* utf8String = createCStringFromNPVariant(&variant); + NPIdentifier identifier = NPN_GetStringIdentifier(utf8String); + free(utf8String); + return identifier; +} + +NPIdentifier +int32VariantToIdentifier(NPVariant variant) +{ + assert(NPVARIANT_IS_INT32(variant)); + int32_t integer = NPVARIANT_TO_INT32(variant); + return NPN_GetIntIdentifier(integer); +} + +NPIdentifier +doubleVariantToIdentifier(NPVariant variant) +{ + assert(NPVARIANT_IS_DOUBLE(variant)); + double value = NPVARIANT_TO_DOUBLE(variant); + // sadly there is no "getdoubleidentifier" + int32_t integer = static_cast<int32_t>(value); + return NPN_GetIntIdentifier(integer); +} + +/* + * Parse a color in hex format, #AARRGGBB or AARRGGBB. + */ +uint32_t +parseHexColor(const char* color, int len) +{ + uint8_t bgra[4] = { 0, 0, 0, 0xFF }; + int i = 0; + + // Ignore unsupported formats. + if (len != 9 && len != 8) + return 0; + + // start from the right and work to the left + while (len >= 2) { // we have at least #AA or AA left. + char byte[3]; + // parse two hex digits + byte[0] = color[len - 2]; + byte[1] = color[len - 1]; + byte[2] = '\0'; + + bgra[i] = (uint8_t)(strtoul(byte, nullptr, 16) & 0xFF); + i++; + len -= 2; + } + return (bgra[3] << 24) | (bgra[2] << 16) | (bgra[1] << 8) | bgra[0]; +} diff --git a/dom/plugins/test/testplugin/nptest_utils.h b/dom/plugins/test/testplugin/nptest_utils.h new file mode 100644 index 000000000..78228841e --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_utils.h @@ -0,0 +1,45 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * 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. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_utils_h_ +#define nptest_utils_h_ + +#include "nptest.h" + +NPUTF8* createCStringFromNPVariant(const NPVariant* variant); + +NPIdentifier variantToIdentifier(NPVariant variant); +NPIdentifier stringVariantToIdentifier(NPVariant variant); +NPIdentifier int32VariantToIdentifier(NPVariant variant); +NPIdentifier doubleVariantToIdentifier(NPVariant variant); + +uint32_t parseHexColor(const char* color, int len); + +#endif // nptest_utils_h_ diff --git a/dom/plugins/test/testplugin/nptest_windows.cpp b/dom/plugins/test/testplugin/nptest_windows.cpp new file mode 100644 index 000000000..8b02872e9 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_windows.cpp @@ -0,0 +1,878 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * Jim Mathies <jmathies@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" + +#include <windows.h> +#include <windowsx.h> +#include <stdio.h> + +#include <d3d10_1.h> +#include <d2d1.h> + +using namespace std; + +void SetSubclass(HWND hWnd, InstanceData* instanceData); +void ClearSubclass(HWND hWnd); +LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +struct _PlatformData { + HWND childWindow; + IDXGIAdapter1 *adapter; + ID3D10Device1 *device; + ID3D10Texture2D *frontBuffer; + ID3D10Texture2D *backBuffer; + ID2D1Factory *d2d1Factory; +}; + +bool +pluginSupportsWindowMode() +{ + return true; +} + +bool +pluginSupportsWindowlessMode() +{ + return true; +} + +NPError +pluginInstanceInit(InstanceData* instanceData) +{ + NPP npp = instanceData->npp; + + instanceData->platformData = static_cast<PlatformData*> + (NPN_MemAlloc(sizeof(PlatformData))); + if (!instanceData->platformData) + return NPERR_OUT_OF_MEMORY_ERROR; + + instanceData->platformData->childWindow = nullptr; + instanceData->platformData->device = nullptr; + instanceData->platformData->frontBuffer = nullptr; + instanceData->platformData->backBuffer = nullptr; + instanceData->platformData->adapter = nullptr; + instanceData->platformData->d2d1Factory = nullptr; + return NPERR_NO_ERROR; +} + +static inline bool +openSharedTex2D(ID3D10Device* device, HANDLE handle, ID3D10Texture2D** out) +{ + HRESULT hr = device->OpenSharedResource(handle, __uuidof(ID3D10Texture2D), (void**)out); + if (FAILED(hr) || !*out) { + return false; + } + return true; +} + +// This is overloaded in d2d1.h so we can't use decltype(). +typedef HRESULT (WINAPI*D2D1CreateFactoryFunc)( + D2D1_FACTORY_TYPE factoryType, + REFIID iid, + CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, + void **factory +); + +static IDXGIAdapter1* +FindDXGIAdapter(NPP npp, IDXGIFactory1* factory) +{ + DXGI_ADAPTER_DESC preferred; + if (NPN_GetValue(npp, NPNVpreferredDXGIAdapter, &preferred) != NPERR_NO_ERROR) { + return nullptr; + } + + UINT index = 0; + for (;;) { + IDXGIAdapter1* adapter = nullptr; + if (FAILED(factory->EnumAdapters1(index, &adapter)) || !adapter) { + return nullptr; + } + + DXGI_ADAPTER_DESC desc; + if (SUCCEEDED(adapter->GetDesc(&desc)) && + desc.AdapterLuid.LowPart == preferred.AdapterLuid.LowPart && + desc.AdapterLuid.HighPart == preferred.AdapterLuid.HighPart && + desc.VendorId == preferred.VendorId && + desc.DeviceId == preferred.DeviceId) + { + return adapter; + } + + adapter->Release(); + index++; + } +} + +// Note: we leak modules since we need them anyway. +bool +setupDxgiSurfaces(NPP npp, InstanceData* instanceData) +{ + HMODULE dxgi = LoadLibraryA("dxgi.dll"); + if (!dxgi) { + return false; + } + decltype(CreateDXGIFactory1)* createDXGIFactory1 = + (decltype(CreateDXGIFactory1)*)GetProcAddress(dxgi, "CreateDXGIFactory1"); + if (!createDXGIFactory1) { + return false; + } + + IDXGIFactory1* factory1 = nullptr; + HRESULT hr = createDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory1); + if (FAILED(hr) || !factory1) { + return false; + } + + instanceData->platformData->adapter = FindDXGIAdapter(npp, factory1); + if (!instanceData->platformData->adapter) { + return false; + } + + HMODULE d3d10 = LoadLibraryA("d3d10_1.dll"); + if (!d3d10) { + return false; + } + + decltype(D3D10CreateDevice1)* createDevice = + (decltype(D3D10CreateDevice1)*)GetProcAddress(d3d10, "D3D10CreateDevice1"); + if (!createDevice) { + return false; + } + + + hr = createDevice( + instanceData->platformData->adapter, + D3D10_DRIVER_TYPE_HARDWARE, nullptr, + D3D10_CREATE_DEVICE_BGRA_SUPPORT | + D3D10_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS, + D3D10_FEATURE_LEVEL_10_1, + D3D10_1_SDK_VERSION, &instanceData->platformData->device); + if (FAILED(hr) || !instanceData->platformData->device) { + return false; + } + + if (!openSharedTex2D(instanceData->platformData->device, + instanceData->frontBuffer->sharedHandle, + &instanceData->platformData->frontBuffer)) + { + return false; + } + if (!openSharedTex2D(instanceData->platformData->device, + instanceData->backBuffer->sharedHandle, + &instanceData->platformData->backBuffer)) + { + return false; + } + + HMODULE d2d1 = LoadLibraryA("D2d1.dll"); + if (!d2d1) { + return false; + } + auto d2d1CreateFactory = (D2D1CreateFactoryFunc)GetProcAddress(d2d1, "D2D1CreateFactory"); + if (!d2d1CreateFactory) { + return false; + } + + D2D1_FACTORY_OPTIONS options; + options.debugLevel = D2D1_DEBUG_LEVEL_NONE; + + hr = d2d1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, + __uuidof(ID2D1Factory), + &options, + (void**)&instanceData->platformData->d2d1Factory); + if (FAILED(hr) || !instanceData->platformData->d2d1Factory) { + return false; + } + + return true; +} + +void +drawDxgiBitmapColor(InstanceData* instanceData) +{ + NPP npp = instanceData->npp; + + HRESULT hr; + + IDXGISurface* surface = nullptr; + hr = instanceData->platformData->backBuffer->QueryInterface( + __uuidof(IDXGISurface), (void **)&surface); + if (FAILED(hr) || !surface) { + return; + } + + D2D1_RENDER_TARGET_PROPERTIES props = + D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED)); + + ID2D1RenderTarget* target = nullptr; + hr = instanceData->platformData->d2d1Factory->CreateDxgiSurfaceRenderTarget( + surface, + &props, + &target); + if (FAILED(hr) || !target) { + surface->Release(); + return; + } + + IDXGIKeyedMutex* mutex = nullptr; + hr = instanceData->platformData->backBuffer->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&mutex); + if (mutex) { + mutex->AcquireSync(0, 0); + } + + target->BeginDraw(); + + unsigned char subpixels[4]; + memcpy(subpixels, + &instanceData->scriptableObject->drawColor, + sizeof(subpixels)); + + auto rect = D2D1::RectF( + 0, 0, + instanceData->backBuffer->size.width, + instanceData->backBuffer->size.height); + auto color = D2D1::ColorF( + float(subpixels[3] * subpixels[2]) / 0xFF, + float(subpixels[3] * subpixels[1]) / 0xFF, + float(subpixels[3] * subpixels[0]) / 0xFF, + float(subpixels[3]) / 0xff); + + ID2D1SolidColorBrush* brush = nullptr; + hr = target->CreateSolidColorBrush(color, &brush); + if (SUCCEEDED(hr) && brush) { + target->FillRectangle(rect, brush); + brush->Release(); + brush = nullptr; + } + hr = target->EndDraw(); + + if (mutex) { + mutex->ReleaseSync(0); + mutex->Release(); + mutex = nullptr; + } + + target->Release(); + surface->Release(); + target = nullptr; + surface = nullptr; + + NPN_SetCurrentAsyncSurface(npp, instanceData->backBuffer, NULL); + std::swap(instanceData->backBuffer, instanceData->frontBuffer); + std::swap(instanceData->platformData->backBuffer, + instanceData->platformData->frontBuffer); +} + +void +pluginInstanceShutdown(InstanceData* instanceData) +{ + PlatformData *pd = instanceData->platformData; + if (pd->frontBuffer) { + pd->frontBuffer->Release(); + } + if (pd->backBuffer) { + pd->backBuffer->Release(); + } + if (pd->d2d1Factory) { + pd->d2d1Factory->Release(); + } + if (pd->device) { + pd->device->Release(); + } + if (pd->adapter) { + pd->adapter->Release(); + } + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; +} + +void +pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) +{ + instanceData->window = *newWindow; +} + +#define CHILD_WIDGET_SIZE 10 + +void +pluginWidgetInit(InstanceData* instanceData, void* oldWindow) +{ + HWND hWnd = (HWND)instanceData->window.window; + if (oldWindow) { + // chrashtests/539897-1.html excercises this code + HWND hWndOld = (HWND)oldWindow; + ClearSubclass(hWndOld); + if (instanceData->platformData->childWindow) { + ::DestroyWindow(instanceData->platformData->childWindow); + } + } + + SetSubclass(hWnd, instanceData); + + instanceData->platformData->childWindow = + ::CreateWindowW(L"SCROLLBAR", L"Dummy child window", + WS_CHILD, 0, 0, CHILD_WIDGET_SIZE, CHILD_WIDGET_SIZE, hWnd, nullptr, + nullptr, nullptr); +} + +static void +drawToDC(InstanceData* instanceData, HDC dc, + int x, int y, int width, int height) +{ + switch (instanceData->scriptableObject->drawMode) { + case DM_DEFAULT: + { + const RECT fill = { x, y, x + width, y + height }; + + int oldBkMode = ::SetBkMode(dc, TRANSPARENT); + HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); + if (brush) { + ::FillRect(dc, &fill, brush); + ::DeleteObject(brush); + } + if (width > 6 && height > 6) { + brush = ::CreateSolidBrush(RGB(192, 192, 192)); + if (brush) { + RECT inset = { x + 3, y + 3, x + width - 3, y + height - 3 }; + ::FillRect(dc, &inset, brush); + ::DeleteObject(brush); + } + } + + const char* uaString = NPN_UserAgent(instanceData->npp); + if (uaString && width > 10 && height > 10) { + HFONT font = + ::CreateFontA(20, 0, 0, 0, 400, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, 5, // CLEARTYPE_QUALITY + DEFAULT_PITCH, "Arial"); + if (font) { + HFONT oldFont = (HFONT)::SelectObject(dc, font); + RECT inset = { x + 5, y + 5, x + width - 5, y + height - 5 }; + ::DrawTextA(dc, uaString, -1, &inset, + DT_LEFT | DT_TOP | DT_NOPREFIX | DT_WORDBREAK); + ::SelectObject(dc, oldFont); + ::DeleteObject(font); + } + } + ::SetBkMode(dc, oldBkMode); + } + break; + + case DM_SOLID_COLOR: + { + HDC offscreenDC = ::CreateCompatibleDC(dc); + if (!offscreenDC) + return; + + const BITMAPV4HEADER bitmapheader = { + sizeof(BITMAPV4HEADER), + width, + height, + 1, // planes + 32, // bits + BI_BITFIELDS, + 0, // unused size + 0, 0, // unused metrics + 0, 0, // unused colors used/important + 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, // ARGB masks + }; + uint32_t *pixelData; + HBITMAP offscreenBitmap = + ::CreateDIBSection(dc, reinterpret_cast<const BITMAPINFO*>(&bitmapheader), + 0, reinterpret_cast<void**>(&pixelData), 0, 0); + if (!offscreenBitmap) + return; + + uint32_t rgba = instanceData->scriptableObject->drawColor; + unsigned int alpha = ((rgba & 0xFF000000) >> 24); + BYTE r = ((rgba & 0xFF0000) >> 16); + BYTE g = ((rgba & 0xFF00) >> 8); + BYTE b = (rgba & 0xFF); + + // Windows expects premultiplied + r = BYTE(float(alpha * r) / 0xFF); + g = BYTE(float(alpha * g) / 0xFF); + b = BYTE(float(alpha * b) / 0xFF); + uint32_t premultiplied = + (alpha << 24) + (r << 16) + (g << 8) + b; + + for (uint32_t* lastPixel = pixelData + width * height; + pixelData < lastPixel; + ++pixelData) + *pixelData = premultiplied; + + ::SelectObject(offscreenDC, offscreenBitmap); + BLENDFUNCTION blendFunc; + blendFunc.BlendOp = AC_SRC_OVER; + blendFunc.BlendFlags = 0; + blendFunc.SourceConstantAlpha = 255; + blendFunc.AlphaFormat = AC_SRC_ALPHA; + ::AlphaBlend(dc, x, y, width, height, offscreenDC, 0, 0, width, height, + blendFunc); + + ::DeleteObject(offscreenDC); + ::DeleteObject(offscreenBitmap); + } + break; + } +} + +void +pluginDraw(InstanceData* instanceData) +{ + NPP npp = instanceData->npp; + if (!npp) + return; + + HDC hdc = nullptr; + PAINTSTRUCT ps; + + notifyDidPaint(instanceData); + + if (instanceData->hasWidget) + hdc = ::BeginPaint((HWND)instanceData->window.window, &ps); + else + hdc = (HDC)instanceData->window.window; + + if (hdc == nullptr) + return; + + // Push the browser's hdc on the resource stack. If this test plugin is windowless, + // we share the drawing surface with the rest of the browser. + int savedDCID = SaveDC(hdc); + + // When we have a widget, window.x/y are meaningless since our widget + // is always positioned correctly and we just draw into it at 0,0. + int x = instanceData->hasWidget ? 0 : instanceData->window.x; + int y = instanceData->hasWidget ? 0 : instanceData->window.y; + int width = instanceData->window.width; + int height = instanceData->window.height; + drawToDC(instanceData, hdc, x, y, width, height); + + // Pop our hdc changes off the resource stack + RestoreDC(hdc, savedDCID); + + if (instanceData->hasWidget) + ::EndPaint((HWND)instanceData->window.window, &ps); +} + +/* script interface */ + +int32_t +pluginGetEdge(InstanceData* instanceData, RectEdge edge) +{ + if (!instanceData || !instanceData->hasWidget) + return NPTEST_INT32_ERROR; + + // Get the plugin client rect in screen coordinates + RECT rect = {0}; + if (!::GetClientRect((HWND)instanceData->window.window, &rect)) + return NPTEST_INT32_ERROR; + ::MapWindowPoints((HWND)instanceData->window.window, nullptr, + (LPPOINT)&rect, 2); + + // Get the toplevel window frame rect in screen coordinates + HWND rootWnd = ::GetAncestor((HWND)instanceData->window.window, GA_ROOT); + if (!rootWnd) + return NPTEST_INT32_ERROR; + RECT rootRect; + if (!::GetWindowRect(rootWnd, &rootRect)) + return NPTEST_INT32_ERROR; + + switch (edge) { + case EDGE_LEFT: + return rect.left - rootRect.left; + case EDGE_TOP: + return rect.top - rootRect.top; + case EDGE_RIGHT: + return rect.right - rootRect.left; + case EDGE_BOTTOM: + return rect.bottom - rootRect.top; + } + + return NPTEST_INT32_ERROR; +} + +static BOOL +getWindowRegion(HWND wnd, HRGN rgn) +{ + if (::GetWindowRgn(wnd, rgn) != ERROR) + return TRUE; + + RECT clientRect; + if (!::GetClientRect(wnd, &clientRect)) + return FALSE; + return ::SetRectRgn(rgn, 0, 0, clientRect.right, clientRect.bottom); +} + +static RGNDATA* +computeClipRegion(InstanceData* instanceData) +{ + HWND wnd = (HWND)instanceData->window.window; + HRGN rgn = ::CreateRectRgn(0, 0, 0, 0); + if (!rgn) + return nullptr; + HRGN ancestorRgn = ::CreateRectRgn(0, 0, 0, 0); + if (!ancestorRgn) { + ::DeleteObject(rgn); + return nullptr; + } + if (!getWindowRegion(wnd, rgn)) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return nullptr; + } + + HWND ancestor = wnd; + for (;;) { + ancestor = ::GetAncestor(ancestor, GA_PARENT); + if (!ancestor || ancestor == ::GetDesktopWindow()) { + ::DeleteObject(ancestorRgn); + + DWORD size = ::GetRegionData(rgn, 0, nullptr); + if (!size) { + ::DeleteObject(rgn); + return nullptr; + } + + HANDLE heap = ::GetProcessHeap(); + RGNDATA* data = static_cast<RGNDATA*>(::HeapAlloc(heap, 0, size)); + if (!data) { + ::DeleteObject(rgn); + return nullptr; + } + DWORD result = ::GetRegionData(rgn, size, data); + ::DeleteObject(rgn); + if (!result) { + ::HeapFree(heap, 0, data); + return nullptr; + } + + return data; + } + + if (!getWindowRegion(ancestor, ancestorRgn)) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return 0; + } + + POINT pt = { 0, 0 }; + ::MapWindowPoints(ancestor, wnd, &pt, 1); + if (::OffsetRgn(ancestorRgn, pt.x, pt.y) == ERROR || + ::CombineRgn(rgn, rgn, ancestorRgn, RGN_AND) == ERROR) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return 0; + } + } +} + +int32_t +pluginGetClipRegionRectCount(InstanceData* instanceData) +{ + RGNDATA* data = computeClipRegion(instanceData); + if (!data) + return NPTEST_INT32_ERROR; + + int32_t result = data->rdh.nCount; + ::HeapFree(::GetProcessHeap(), 0, data); + return result; +} + +static int32_t +addOffset(LONG coord, int32_t offset) +{ + if (offset == NPTEST_INT32_ERROR) + return NPTEST_INT32_ERROR; + return coord + offset; +} + +int32_t +pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) +{ + RGNDATA* data = computeClipRegion(instanceData); + if (!data) + return NPTEST_INT32_ERROR; + + HANDLE heap = ::GetProcessHeap(); + if (rectIndex >= int32_t(data->rdh.nCount)) { + ::HeapFree(heap, 0, data); + return NPTEST_INT32_ERROR; + } + + RECT rect = reinterpret_cast<RECT*>(data->Buffer)[rectIndex]; + ::HeapFree(heap, 0, data); + + switch (edge) { + case EDGE_LEFT: + return addOffset(rect.left, pluginGetEdge(instanceData, EDGE_LEFT)); + case EDGE_TOP: + return addOffset(rect.top, pluginGetEdge(instanceData, EDGE_TOP)); + case EDGE_RIGHT: + return addOffset(rect.right, pluginGetEdge(instanceData, EDGE_LEFT)); + case EDGE_BOTTOM: + return addOffset(rect.bottom, pluginGetEdge(instanceData, EDGE_TOP)); + } + + return NPTEST_INT32_ERROR; +} + +static +void +createDummyWindowForIME(InstanceData* instanceData) +{ + WNDCLASSW wndClass; + wndClass.style = 0; + wndClass.lpfnWndProc = DefWindowProcW; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hInstance = GetModuleHandleW(NULL); + wndClass.hIcon = nullptr; + wndClass.hCursor = nullptr; + wndClass.hbrBackground = (HBRUSH)COLOR_WINDOW; + wndClass.lpszMenuName = NULL; + wndClass.lpszClassName = L"SWFlash_PlaceholderX"; + RegisterClassW(&wndClass); + + instanceData->placeholderWnd = + static_cast<void*>(CreateWindowW(L"SWFlash_PlaceholderX", L"", WS_CHILD, 0, + 0, 0, 0, HWND_MESSAGE, NULL, + GetModuleHandleW(NULL), NULL)); +} + +/* windowless plugin events */ + +static bool +handleEventInternal(InstanceData* instanceData, NPEvent* pe, LRESULT* result) +{ + switch ((UINT)pe->event) { + case WM_PAINT: + pluginDraw(instanceData); + return true; + + case WM_MOUSEACTIVATE: + if (instanceData->hasWidget) { + ::SetFocus((HWND)instanceData->window.window); + *result = MA_ACTIVATEANDEAT; + return true; + } + return false; + + case WM_MOUSEWHEEL: + return true; + + case WM_WINDOWPOSCHANGED: { + WINDOWPOS* pPos = (WINDOWPOS*)pe->lParam; + instanceData->winX = instanceData->winY = 0; + if (pPos) { + instanceData->winX = pPos->x; + instanceData->winY = pPos->y; + return true; + } + return false; + } + + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: { + int x = instanceData->hasWidget ? 0 : instanceData->winX; + int y = instanceData->hasWidget ? 0 : instanceData->winY; + instanceData->lastMouseX = GET_X_LPARAM(pe->lParam) - x; + instanceData->lastMouseY = GET_Y_LPARAM(pe->lParam) - y; + if ((UINT)pe->event == WM_LBUTTONUP) { + instanceData->mouseUpEventCount++; + } + return true; + } + + case WM_KEYDOWN: + instanceData->lastKeyText.erase(); + *result = 0; + return true; + + case WM_CHAR: { + *result = 0; + wchar_t uniChar = static_cast<wchar_t>(pe->wParam); + if (!uniChar) { + return true; + } + char utf8Char[6]; + int len = + ::WideCharToMultiByte(CP_UTF8, 0, &uniChar, 1, utf8Char, 6, + nullptr, nullptr); + if (len == 0 || len > 6) { + return true; + } + instanceData->lastKeyText.append(utf8Char, len); + return true; + } + + case WM_IME_STARTCOMPOSITION: + instanceData->lastComposition.erase(); + if (!instanceData->placeholderWnd) { + createDummyWindowForIME(instanceData); + } + return true; + + case WM_IME_ENDCOMPOSITION: + instanceData->lastComposition.erase(); + return true; + + case WM_IME_COMPOSITION: { + if (pe->lParam & GCS_COMPSTR) { + HIMC hIMC = ImmGetContext((HWND)instanceData->placeholderWnd); + if (!hIMC) { + return false; + } + WCHAR compStr[256]; + LONG len = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, compStr, + 256 * sizeof(WCHAR)); + CHAR buffer[256]; + len = ::WideCharToMultiByte(CP_UTF8, 0, compStr, len / sizeof(WCHAR), + buffer, 256, nullptr, nullptr); + instanceData->lastComposition.append(buffer, len); + ::ImmReleaseContext((HWND)instanceData->placeholderWnd, hIMC); + } + return true; + } + + default: + return false; + } +} + +int16_t +pluginHandleEvent(InstanceData* instanceData, void* event) +{ + NPEvent* pe = (NPEvent*)event; + + if (pe == nullptr || instanceData == nullptr || + instanceData->window.type != NPWindowTypeDrawable) + return 0; + + LRESULT result = 0; + return handleEventInternal(instanceData, pe, &result); +} + +/* windowed plugin events */ + +LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + WNDPROC wndProc = (WNDPROC)GetProp(hWnd, "MozillaWndProc"); + if (!wndProc) + return 0; + InstanceData* pInstance = (InstanceData*)GetProp(hWnd, "InstanceData"); + if (!pInstance) + return 0; + + NPEvent event = { static_cast<uint16_t>(uMsg), wParam, lParam }; + + LRESULT result = 0; + if (handleEventInternal(pInstance, &event, &result)) + return result; + + if (uMsg == WM_CLOSE) { + ClearSubclass((HWND)pInstance->window.window); + } + + return CallWindowProc(wndProc, hWnd, uMsg, wParam, lParam); +} + +void +ClearSubclass(HWND hWnd) +{ + if (GetProp(hWnd, "MozillaWndProc")) { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)GetProp(hWnd, "MozillaWndProc")); + RemoveProp(hWnd, "MozillaWndProc"); + RemoveProp(hWnd, "InstanceData"); + } +} + +void +SetSubclass(HWND hWnd, InstanceData* instanceData) +{ + // Subclass the plugin window so we can handle our own windows events. + SetProp(hWnd, "InstanceData", (HANDLE)instanceData); + WNDPROC origProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PluginWndProc); + SetProp(hWnd, "MozillaWndProc", (HANDLE)origProc); +} + +static void checkEquals(int a, int b, const char* msg, string& error) +{ + if (a == b) { + return; + } + + error.append(msg); + char buf[100]; + sprintf(buf, " (got %d, expected %d)\n", a, b); + error.append(buf); +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, string& error) +{ + if (instanceData->platformData->childWindow) { + RECT childRect; + ::GetWindowRect(instanceData->platformData->childWindow, &childRect); + RECT ourRect; + HWND hWnd = (HWND)instanceData->window.window; + ::GetWindowRect(hWnd, &ourRect); + checkEquals(childRect.left, ourRect.left, "Child widget left", error); + checkEquals(childRect.top, ourRect.top, "Child widget top", error); + checkEquals(childRect.right, childRect.left + CHILD_WIDGET_SIZE, "Child widget width", error); + checkEquals(childRect.bottom, childRect.top + CHILD_WIDGET_SIZE, "Child widget height", error); + } +} + +bool pluginNativeWidgetIsVisible(InstanceData* instanceData) +{ + HWND hWnd = (HWND)instanceData->window.window; + wchar_t className[60]; + if (::GetClassNameW(hWnd, className, sizeof(className) / sizeof(char16_t)) && + !wcsicmp(className, L"GeckoPluginWindow")) { + return ::IsWindowVisible(hWnd); + } + // something isn't right, fail the check + return false; +} diff --git a/dom/plugins/test/testplugin/secondplugin/Info.plist b/dom/plugins/test/testplugin/secondplugin/Info.plist new file mode 100644 index 000000000..afa83a63c --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnpsecondtest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.SecondTestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>SECONDTEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Second Test Plug-in</string> + <key>WebPluginDescription</key> + <string>Second plug-in for testing purposes.</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-Second-Test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>ts2</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Second test type</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/secondplugin/moz.build b/dom/plugins/test/testplugin/secondplugin/moz.build new file mode 100644 index 000000000..f95ed4190 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/moz.build @@ -0,0 +1,11 @@ +# -*- 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/. + +SharedLibrary('npsecondtest') + +relative_path = 'secondplugin' +cocoa_name = 'SecondTest' +include('../testplugin.mozbuild') diff --git a/dom/plugins/test/testplugin/secondplugin/nptest.def b/dom/plugins/test/testplugin/secondplugin/nptest.def new file mode 100644 index 000000000..c6584387d --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPSECONDTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/secondplugin/nptest.rc b/dom/plugins/test/testplugin/secondplugin/nptest.rc new file mode 100644 index 000000000..835906d0c --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Second plug-in for testing purposes." + VALUE "FileExtents", "ts2" + VALUE "FileOpenName", "Second test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npsecondtest" + VALUE "MIMEType", "application/x-Second-Test" + VALUE "OriginalFilename", "npsecondtest.dll" + VALUE "ProductName", "Second Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp b/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp new file mode 100644 index 000000000..12cc68b69 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp @@ -0,0 +1,7 @@ +/* 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/. */ + +const char *sPluginName = "Second Test Plug-in"; +const char *sPluginDescription = "Second plug-in for testing purposes."; +const char *sMimeDescription = "application/x-Second-Test:ts2:Second test type"; diff --git a/dom/plugins/test/testplugin/silverlightplugin/Info.plist b/dom/plugins/test/testplugin/silverlightplugin/Info.plist new file mode 100644 index 000000000..7a8094b83 --- /dev/null +++ b/dom/plugins/test/testplugin/silverlightplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnpctrltest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.SilverlightTestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>SILVERLIGHTTEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Silverlight Test Plug-in</string> + <key>WebPluginDescription</key> + <string>Silverlight plug-in for testing purposes.</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-silverlight-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>xaml</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Silverlight test type</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/silverlightplugin/moz.build b/dom/plugins/test/testplugin/silverlightplugin/moz.build new file mode 100644 index 000000000..6050e0473 --- /dev/null +++ b/dom/plugins/test/testplugin/silverlightplugin/moz.build @@ -0,0 +1,11 @@ +# -*- 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/. + +SharedLibrary('npctrltest') + +relative_path = 'silverlightplugin' +cocoa_name = 'npctrltest' +include('../testplugin.mozbuild') diff --git a/dom/plugins/test/testplugin/silverlightplugin/nptest.def b/dom/plugins/test/testplugin/silverlightplugin/nptest.def new file mode 100644 index 000000000..b25c6c8c5 --- /dev/null +++ b/dom/plugins/test/testplugin/silverlightplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPCTRLTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/silverlightplugin/nptest.rc b/dom/plugins/test/testplugin/silverlightplugin/nptest.rc new file mode 100644 index 000000000..a48654bc5 --- /dev/null +++ b/dom/plugins/test/testplugin/silverlightplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Silverlight plug-in for testing purposes." + VALUE "FileExtents", "xaml" + VALUE "FileOpenName", "Silverlight test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npctrltest" + VALUE "MIMEType", "application/x-silverlight-test" + VALUE "OriginalFilename", "npctrltest.dll" + VALUE "ProductName", "Silverlight Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/silverlightplugin/nptest_name.cpp b/dom/plugins/test/testplugin/silverlightplugin/nptest_name.cpp new file mode 100644 index 000000000..2cdfaa5f1 --- /dev/null +++ b/dom/plugins/test/testplugin/silverlightplugin/nptest_name.cpp @@ -0,0 +1,7 @@ +/* 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/. */ + +const char *sPluginName = "Silverlight Test Plug-in"; +const char *sPluginDescription = "Silverlight plug-in for testing purposes."; +const char *sMimeDescription = "application/x-silverlight-test:xaml:Silverlight test type"; diff --git a/dom/plugins/test/testplugin/testplugin.mozbuild b/dom/plugins/test/testplugin/testplugin.mozbuild new file mode 100644 index 000000000..9ed4f8966 --- /dev/null +++ b/dom/plugins/test/testplugin/testplugin.mozbuild @@ -0,0 +1,72 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + 'nptest.cpp', + 'nptest_utils.cpp', +] + +UNIFIED_SOURCES += [ + '%s/nptest_name.cpp' % relative_path, +] + +toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] +if toolkit == 'cocoa': + UNIFIED_SOURCES += [ + 'nptest_macosx.mm' + ] +elif toolkit in ('gtk2', 'gtk3'): + UNIFIED_SOURCES += [ + 'nptest_gtk2.cpp', + ] +elif toolkit == 'android': + UNIFIED_SOURCES += [ + 'nptest_droid.cpp', + ] +elif toolkit == 'windows': + UNIFIED_SOURCES += [ + 'nptest_windows.cpp', + ] + OS_LIBS += [ + 'msimg32', + 'imm32' + ] + +# must link statically with the CRT; nptest isn't Gecko code +USE_STATIC_LIBS = True + +# Don't use STL wrappers; nptest isn't Gecko code +DISABLE_STL_WRAPPING = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + RCFILE = 'nptest.rc' + RESFILE = 'nptest.res' + DEFFILE = SRCDIR + '/nptest.def' + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' and '64' in CONFIG['OS_TEST']: + OS_LIBS += ['-framework Carbon'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'): + CXXFLAGS += CONFIG['MOZ_GTK2_CFLAGS'] + CFLAGS += CONFIG['MOZ_GTK2_CFLAGS'] + OS_LIBS += CONFIG['MOZ_GTK2_LIBS'] + OS_LIBS += CONFIG['XLDFLAGS'] + OS_LIBS += CONFIG['XLIBS'] + OS_LIBS += CONFIG['XEXT_LIBS'] + +if CONFIG['_MSC_VER']: + # This is intended as a temporary hack to support building with VS2015. + # conversion from 'X' to 'Y' requires a narrowing conversion + CXXFLAGS += ['-wd4838'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + FINAL_TARGET = 'dist/plugins/%s.plugin/Contents/MacOS' % cocoa_name + OBJDIR_FILES.dist.plugins['%s.plugin' % cocoa_name].Contents += ['%s/Info.plist' % relative_path] +else: + FINAL_TARGET = 'dist/plugins' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/plugins/test/testplugin/thirdplugin/Info.plist b/dom/plugins/test/testplugin/thirdplugin/Info.plist new file mode 100644 index 000000000..96e18ba75 --- /dev/null +++ b/dom/plugins/test/testplugin/thirdplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnpthirdtest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.ThirdTestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>THIRDTEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Third Test Plug-in</string> + <key>WebPluginDescription</key> + <string>Third plug-in for testing purposes.</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-Third-Test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>ts2</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Third test type</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/thirdplugin/moz.build b/dom/plugins/test/testplugin/thirdplugin/moz.build new file mode 100644 index 000000000..f0d7b1a5b --- /dev/null +++ b/dom/plugins/test/testplugin/thirdplugin/moz.build @@ -0,0 +1,11 @@ +# -*- 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/. + +SharedLibrary('npthirdtest') + +relative_path = 'thirdplugin' +cocoa_name = 'ThirdTest' +include('../testplugin.mozbuild') diff --git a/dom/plugins/test/testplugin/thirdplugin/nptest.def b/dom/plugins/test/testplugin/thirdplugin/nptest.def new file mode 100644 index 000000000..8ea68ba41 --- /dev/null +++ b/dom/plugins/test/testplugin/thirdplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPTHIRDTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/thirdplugin/nptest.rc b/dom/plugins/test/testplugin/thirdplugin/nptest.rc new file mode 100644 index 000000000..de1576920 --- /dev/null +++ b/dom/plugins/test/testplugin/thirdplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Third plug-in for testing purposes." + VALUE "FileExtents", "ts2" + VALUE "FileOpenName", "Third test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npthirdtest" + VALUE "MIMEType", "application/x-Third-Test" + VALUE "OriginalFilename", "npthirdtest.dll" + VALUE "ProductName", "Third Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/thirdplugin/nptest_name.cpp b/dom/plugins/test/testplugin/thirdplugin/nptest_name.cpp new file mode 100644 index 000000000..34eb5973d --- /dev/null +++ b/dom/plugins/test/testplugin/thirdplugin/nptest_name.cpp @@ -0,0 +1,7 @@ +/* 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/. */ + +const char *sPluginName = "Third Test Plug-in"; +const char *sPluginDescription = "Third plug-in for testing purposes."; +const char *sMimeDescription = "application/x-Third-Test:ts3:Third test type"; diff --git a/dom/plugins/test/unit/head_plugins.js b/dom/plugins/test/unit/head_plugins.js new file mode 100644 index 000000000..4d32a52bf --- /dev/null +++ b/dom/plugins/test/unit/head_plugins.js @@ -0,0 +1,195 @@ +/* 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/. + */ + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +const gIsWindows = mozinfo.os == "win"; +const gIsOSX = mozinfo.os == "mac"; +const gIsLinux = mozinfo.os == "linux"; +const gDirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + +function allow_all_plugins() { + Services.prefs.setBoolPref("plugin.load_flash_only", false); +} + +// Finds the test plugin library +function get_test_plugin(secondplugin=false) { + var pluginEnum = gDirSvc.get("APluginsDL", Ci.nsISimpleEnumerator); + while (pluginEnum.hasMoreElements()) { + let dir = pluginEnum.getNext().QueryInterface(Ci.nsILocalFile); + let name = get_platform_specific_plugin_name(secondplugin); + let plugin = dir.clone(); + plugin.append(name); + if (plugin.exists()) { + plugin.normalize(); + return plugin; + } + } + return null; +} + +// Finds the test nsIPluginTag +function get_test_plugintag(aName="Test Plug-in") { + const Cc = Components.classes; + const Ci = Components.interfaces; + + var name = aName || "Test Plug-in"; + var host = Cc["@mozilla.org/plugin/host;1"]. + getService(Ci.nsIPluginHost); + var tags = host.getPluginTags(); + + for (var i = 0; i < tags.length; i++) { + if (tags[i].name == name) + return tags[i]; + } + return null; +} + +// Creates a fake ProfDS directory key, copied from do_get_profile +function do_get_profile_startup() { + let env = Components.classes["@mozilla.org/process/environment;1"] + .getService(Components.interfaces.nsIEnvironment); + // the python harness sets this in the environment for us + let profd = env.get("XPCSHELL_TEST_PROFILE_DIR"); + let file = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + file.initWithPath(profd); + + let dirSvc = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties); + let provider = { + getFile: function(prop, persistent) { + persistent.value = true; + if (prop == "ProfDS") { + return file.clone(); + } + throw Components.results.NS_ERROR_FAILURE; + }, + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIDirectoryServiceProvider) || + iid.equals(Components.interfaces.nsISupports)) { + return this; + } + throw Components.results.NS_ERROR_NO_INTERFACE; + } + }; + dirSvc.QueryInterface(Components.interfaces.nsIDirectoryService) + .registerProvider(provider); + return file.clone(); +} + +function get_platform_specific_plugin_name(secondplugin=false) { + if (secondplugin) { + if (gIsWindows) return "npsecondtest.dll"; + if (gIsOSX) return "SecondTest.plugin"; + if (gIsLinux) return "libnpsecondtest.so"; + } else { + if (gIsWindows) return "nptest.dll"; + if (gIsOSX) return "Test.plugin"; + if (gIsLinux) return "libnptest.so"; + } + return null; +} + +function get_platform_specific_plugin_suffix() { + if (gIsWindows) return ".dll"; + else if (gIsOSX) return ".plugin"; + else if (gIsLinux) return ".so"; + else return null; +} + +function get_test_plugin_no_symlink() { + let dirSvc = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties); + let pluginEnum = dirSvc.get("APluginsDL", Ci.nsISimpleEnumerator); + while (pluginEnum.hasMoreElements()) { + let dir = pluginEnum.getNext().QueryInterface(Ci.nsILocalFile); + let plugin = dir.clone(); + plugin.append(get_platform_specific_plugin_name()); + if (plugin.exists()) { + return plugin; + } + } + return null; +} + +var gGlobalScope = this; +function loadAddonManager() { + let ns = {}; + Cu.import("resource://gre/modules/Services.jsm", ns); + let head = "../../../../toolkit/mozapps/extensions/test/xpcshell/head_addons.js"; + let file = do_get_file(head); + let uri = ns.Services.io.newFileURI(file); + ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope); + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + startupManager(); +} + +// Install addon and return a Promise<boolean> that is +// resolve with true on success, false otherwise. +function installAddon(relativePath) { + let deferred = Promise.defer(); + let success = () => deferred.resolve(true); + let fail = () => deferred.resolve(false); + let listener = { + onDownloadCancelled: fail, + onDownloadFailed: fail, + onInstallCancelled: fail, + onInstallFailed: fail, + onInstallEnded: success, + }; + + let installCallback = install => { + install.addListener(listener); + install.install(); + }; + + let file = do_get_file(relativePath, false); + AddonManager.getInstallForFile(file, installCallback, + "application/x-xpinstall"); + + return deferred.promise; +} + +// Uninstall addon and return a Promise<boolean> that is +// resolve with true on success, false otherwise. +function uninstallAddon(id) { + let deferred = Promise.defer(); + + AddonManager.getAddonByID(id, addon => { + if (!addon) { + deferred.resolve(false); + } + + let listener = {}; + let handler = addon => { + if (addon.id !== id) { + return; + } + + AddonManager.removeAddonListener(listener); + deferred.resolve(true); + }; + + listener.onUninstalled = handler; + listener.onDisabled = handler; + + AddonManager.addAddonListener(listener); + addon.uninstall(); + }); + + return deferred.promise; +} + +// Returns a Promise<Addon> that is resolved with +// the corresponding addon or rejected. +function getAddonByID(id) { + let deferred = Promise.defer(); + AddonManager.getAddonByID(id, addon => deferred.resolve(addon)); + return deferred.promise; +} diff --git a/dom/plugins/test/unit/test_allowed_types.js b/dom/plugins/test/unit/test_allowed_types.js new file mode 100644 index 000000000..09b30b31a --- /dev/null +++ b/dom/plugins/test/unit/test_allowed_types.js @@ -0,0 +1,142 @@ +/* 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/. + */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +function run_test() { + const pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + const pluginDefaultState = Services.prefs.getIntPref("plugin.default.state"); + + Services.prefs.setBoolPref("plugin.load_flash_only", false); + + function reload_plugins_with_allowed_types(allowed_types) { + if (typeof allowed_types === "undefined") { + // If we didn't get an allowed_types string, then unset the pref, + // so the caller can test the behavior when the pref isn't set. + Services.prefs.clearUserPref("plugin.allowed_types"); + } else { + Services.prefs.setCharPref("plugin.allowed_types", allowed_types); + } + pluginHost.reloadPlugins(); + } + + function get_status_for_type(type) { + try { + return pluginHost.getStateForType(type); + } catch(ex) { + // If the type is not allowed, then nsIPluginHost.getStateForType throws + // NS_ERROR_NOT_AVAILABLE, for which we return undefined to make it easier + // to write assertions about the API. + if (ex.result === Cr.NS_ERROR_NOT_AVAILABLE) { + return undefined; + } + throw ex; + } + } + + // If allowed_types isn't set, then all plugin types are enabled. + reload_plugins_with_allowed_types(); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + // If allowed_types is set to the empty string, then all plugin types are enabled. + reload_plugins_with_allowed_types(""); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + // If allowed_types is set to anything other than the empty string, + // then only types that exactly match its comma-separated entries are enabled, + // so a single space disables all types. + reload_plugins_with_allowed_types(" "); + do_check_eq(get_status_for_type("application/x-test"), undefined); + do_check_eq(get_status_for_type("application/x-Second-Test"), undefined); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + // The rest of the assertions test various values of allowed_types to ensure + // that the correct types are enabled. + + reload_plugins_with_allowed_types("application/x-test"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), undefined); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-Second-Test"); + do_check_eq(get_status_for_type("application/x-test"), undefined); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-nonexistent"); + do_check_eq(get_status_for_type("application/x-test"), undefined); + do_check_eq(get_status_for_type("application/x-Second-Test"), undefined); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-test,application/x-Second-Test"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-Second-Test,application/x-test"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-test,application/x-nonexistent"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), undefined); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-nonexistent,application/x-test"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), undefined); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-test,application/x-Second-Test,application/x-nonexistent"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-test,application/x-nonexistent,application/x-Second-Test"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-Second-Test,application/x-test,application/x-nonexistent"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-Second-Test,application/x-nonexistent,application/x-test"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-nonexistent,application/x-test,application/x-Second-Test"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + reload_plugins_with_allowed_types("application/x-nonexistent,application/x-Second-Test,application/x-test"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-Second-Test"), pluginDefaultState); + do_check_eq(get_status_for_type("application/x-nonexistent"), undefined); + + // Plugin types are case-insensitive, so matching should be too. + reload_plugins_with_allowed_types("APPLICATION/X-TEST"); + do_check_eq(get_status_for_type("application/x-test"), pluginDefaultState); + reload_plugins_with_allowed_types("application/x-test"); + do_check_eq(get_status_for_type("APPLICATION/X-TEST"), pluginDefaultState); + + // Types must match exactly, so supersets should not enable subset types. + reload_plugins_with_allowed_types("application/x-test-superset"); + do_check_eq(get_status_for_type("application/x-test"), undefined); + reload_plugins_with_allowed_types("superset-application/x-test"); + do_check_eq(get_status_for_type("application/x-test"), undefined); + + // Clean up. + Services.prefs.clearUserPref("plugin.allowed_types"); + Services.prefs.clearUserPref("plugin.importedState"); +} diff --git a/dom/plugins/test/unit/test_bug471245.js b/dom/plugins/test/unit/test_bug471245.js new file mode 100644 index 000000000..8e38afcdc --- /dev/null +++ b/dom/plugins/test/unit/test_bug471245.js @@ -0,0 +1,23 @@ +/* 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/. + */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +function run_test() { + allow_all_plugins(); + do_get_profile_startup(); + + var plugin = get_test_plugintag(); + do_check_true(plugin == null); + + // Initialises a profile folder + do_get_profile(); + + var plugin = get_test_plugintag(); + do_check_false(plugin == null); + + // Clean up + Services.prefs.clearUserPref("plugin.importedState"); +} diff --git a/dom/plugins/test/unit/test_bug813245.js b/dom/plugins/test/unit/test_bug813245.js new file mode 100644 index 000000000..069e4a014 --- /dev/null +++ b/dom/plugins/test/unit/test_bug813245.js @@ -0,0 +1,87 @@ +/* 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/. + */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +// Plugin registry uses different field delimeters on different platforms +var DELIM = mozinfo.os == "win" ? "|" : ":"; + +var gProfD = do_get_profile_startup(); + +// Writes out some plugin registry to the profile +function write_registry(version, info) { + let runtime = Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime); + + var header = "Generated File. Do not edit.\n\n"; + header += "[HEADER]\n"; + header += "Version" + DELIM + version + DELIM + "$\n"; + header += "Arch" + DELIM + runtime.XPCOMABI + DELIM + "$\n"; + header += "\n"; + header += "[PLUGINS]\n"; + + var registry = gProfD.clone(); + registry.append("pluginreg.dat"); + var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"] + .createInstance(Components.interfaces.nsIFileOutputStream); + // write, create, truncate + foStream.init(registry, 0x02 | 0x08 | 0x20, 0o666, 0); + + var charset = "UTF-8"; // Can be any character encoding name that Mozilla supports + var os = Cc["@mozilla.org/intl/converter-output-stream;1"]. + createInstance(Ci.nsIConverterOutputStream); + os.init(foStream, charset, 0, 0x0000); + + os.writeString(header); + os.writeString(info); + os.close(); +} + +function run_test() { + allow_all_plugins(); + var plugin = get_test_plugintag(); + do_check_true(plugin == null); + + var file = get_test_plugin(); + if (!file) { + do_throw("Plugin library not found"); + } + + // write plugin registry data + let registry = ""; + + if (gIsOSX) { + registry += file.leafName + DELIM + "$\n"; + registry += file.path + DELIM + "$\n"; + } else { + registry += file.path + DELIM + "$\n"; + registry += DELIM + "$\n"; + } + registry += "0.0.0.0" + DELIM + "$\n"; + registry += "16725225600" + DELIM + "0" + DELIM + "5" + DELIM + "$\n"; + registry += "Plug-in for testing purposes." + DELIM + "$\n"; + registry += "Test Plug-in" + DELIM + "$\n"; + registry += "999999999999999999999999999999999999999999999999|0|5|$\n"; + registry += "0" + DELIM + "application/x-test" + DELIM + "Test mimetype" + + DELIM + "tst" + DELIM + "$\n"; + + registry += "\n"; + registry += "[INVALID]\n"; + registry += "\n"; + write_registry("0.15", registry); + + // Initialise profile folder + do_get_profile(); + + var plugin = get_test_plugintag(); + if (!plugin) + do_throw("Plugin tag not found"); + + // The plugin registry should have been rejected. + // If not, the test plugin version would be 0.0.0.0 + do_check_eq(plugin.version, "1.0.0.0"); + + // Clean up + Services.prefs.clearUserPref("plugin.importedState"); +} diff --git a/dom/plugins/test/unit/test_bug854467.js b/dom/plugins/test/unit/test_bug854467.js new file mode 100644 index 000000000..0487dffc6 --- /dev/null +++ b/dom/plugins/test/unit/test_bug854467.js @@ -0,0 +1,40 @@ +/* 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/. + */ + +function check_state(aTag, aExpectedClicktoplay, aExpectedDisabled) { + do_check_eq(aTag.clicktoplay, aExpectedClicktoplay); + do_check_eq(aTag.disabled, aExpectedDisabled); +} + +function run_test() { + allow_all_plugins(); + let tag = get_test_plugintag(); + tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED; + check_state(tag, false, false); + + /* test going to click-to-play from always enabled and back */ + tag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY; + check_state(tag, true, false); + tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED; + check_state(tag, false, false); + + /* test going to disabled from always enabled and back */ + tag.enabledState = Ci.nsIPluginTag.STATE_DISABLED; + check_state(tag, false, true); + tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED; + check_state(tag, false, false); + + /* test going to click-to-play from disabled and back */ + tag.enabledState = Ci.nsIPluginTag.STATE_DISABLED; + check_state(tag, false, true); + tag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY; + check_state(tag, true, false); + tag.enabledState = Ci.nsIPluginTag.STATE_DISABLED; + check_state(tag, false, true); + + /* put everything back to normal and check that that succeeded */ + tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED; + check_state(tag, false, false); +} diff --git a/dom/plugins/test/unit/test_nice_plugin_name.js b/dom/plugins/test/unit/test_nice_plugin_name.js new file mode 100644 index 000000000..32d2870af --- /dev/null +++ b/dom/plugins/test/unit/test_nice_plugin_name.js @@ -0,0 +1,80 @@ +/* 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/. + */ + +const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1"; +const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}"); + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +var gAppInfo = null; + +function createAppInfo(ID, name, version, platformVersion="1.0") { + let tmp = {}; + Cu.import("resource://testing-common/AppInfo.jsm", tmp); + tmp.updateAppInfo({ + ID, name, version, platformVersion, + crashReporter: true + }); + gAppInfo = tmp.getAppInfo(); +} + +var gPluginHost = null; + +function test_expected_permission_string(aPermString) { + gPluginHost.reloadPlugins(false); + let plugin = get_test_plugintag(); + do_check_false(plugin == null); + do_check_eq(gPluginHost.getPermissionStringForType("application/x-test"), + aPermString); +} + +function run_test() { + allow_all_plugins(); + do_check_true(gIsWindows || gIsOSX || gIsLinux); + do_check_true(!(gIsWindows && gIsOSX) && !(gIsWindows && gIsLinux) && + !(gIsOSX && gIsLinux)); + + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9"); + gPluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + + let expectedDefaultPermissionString = null; + if (gIsWindows) expectedDefaultPermissionString = "plugin:nptest"; + if (gIsOSX) expectedDefaultPermissionString = "plugin:test"; + if (gIsLinux) expectedDefaultPermissionString = "plugin:libnptest"; + test_expected_permission_string(expectedDefaultPermissionString); + + let suffix = get_platform_specific_plugin_suffix(); + let pluginFile = get_test_plugin_no_symlink(); + let pluginDir = pluginFile.parent; + pluginFile.copyTo(null, "npblah235" + suffix); + let pluginCopy = pluginDir.clone(); + pluginCopy.append("npblah235" + suffix); + let tempDir = do_get_tempdir(); + pluginFile.moveTo(tempDir, null); + test_expected_permission_string("plugin:npblah"); + + pluginCopy.moveTo(null, "npasdf-3.2.2" + suffix); + test_expected_permission_string("plugin:npasdf"); + + pluginCopy.moveTo(null, "npasdf_##29387!{}{[][" + suffix); + test_expected_permission_string("plugin:npasdf"); + + pluginCopy.moveTo(null, "npqtplugin7" + suffix); + test_expected_permission_string("plugin:npqtplugin"); + + pluginCopy.moveTo(null, "npFoo3d" + suffix); + test_expected_permission_string("plugin:npfoo3d"); + + pluginCopy.moveTo(null, "NPSWF32_11_5_502_146" + suffix); + test_expected_permission_string("plugin:npswf"); + + pluginCopy.remove(true); + pluginFile.moveTo(pluginDir, null); + test_expected_permission_string(expectedDefaultPermissionString); + + // Clean up + Services.prefs.clearUserPref("plugin.importedState"); +} diff --git a/dom/plugins/test/unit/test_persist_in_prefs.js b/dom/plugins/test/unit/test_persist_in_prefs.js new file mode 100644 index 000000000..ede0074d3 --- /dev/null +++ b/dom/plugins/test/unit/test_persist_in_prefs.js @@ -0,0 +1,76 @@ +/* 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/. + */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +// Plugin registry uses different field delimeters on different platforms +var DELIM = mozinfo.os == "win" ? "|" : ":"; + +var gProfD = do_get_profile_startup(); + +function run_test() { + allow_all_plugins(); + + do_check_true(gIsWindows || gIsOSX || gIsLinux); + + let file = get_test_plugin_no_symlink(); + if (!file) + do_throw("Plugin library not found"); + + const pluginDir = file.parent; + const tempDir = do_get_tempdir(); + const suffix = get_platform_specific_plugin_suffix(); + const pluginName = file.leafName.substring(0, file.leafName.length - suffix.length).toLowerCase(); + const pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + const statePref = "plugin.state." + pluginName; + + // Initialise profile folder + do_get_profile(); + + let plugin = get_test_plugintag(); + if (!plugin) + do_throw("Plugin tag not found"); + + plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED; + + // prepare a copy of the plugin and backup the original + file.copyTo(null, "nptestcopy" + suffix); + let copy = pluginDir.clone(); + copy.append("nptestcopy" + suffix); + file.moveTo(tempDir, null); + + // test that the settings persist through a few variations of test-plugin names + let testNames = [ + pluginName + "2", + pluginName.toUpperCase() + "_11_5_42_2323", + pluginName + "-5.2.7" + ]; + testNames.forEach(function(leafName) { + dump("Checking " + leafName + ".\n"); + copy.moveTo(null, leafName + suffix); + pluginHost.reloadPlugins(false); + plugin = get_test_plugintag(); + do_check_false(plugin == null); + do_check_true(plugin.disabled); + do_check_false(plugin.clicktoplay); + }); + + // check that the state persists even if the plugin is not always present + copy.moveTo(tempDir, null); + pluginHost.reloadPlugins(false); + copy.moveTo(pluginDir, null); + pluginHost.reloadPlugins(false); + + plugin = get_test_plugintag(); + do_check_false(plugin == null); + do_check_true(plugin.disabled); + do_check_false(plugin.clicktoplay); + + // clean up + Services.prefs.clearUserPref(statePref); + Services.prefs.clearUserPref("plugin.importedState"); + copy.remove(true); + file.moveTo(pluginDir, null); +} diff --git a/dom/plugins/test/unit/test_plugin_default_state.js b/dom/plugins/test/unit/test_plugin_default_state.js new file mode 100644 index 000000000..cb68c61e7 --- /dev/null +++ b/dom/plugins/test/unit/test_plugin_default_state.js @@ -0,0 +1,31 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +function run_test() { + allow_all_plugins(); + let pluginDefaultState = Services.prefs.getIntPref("plugin.default.state"); + // if this fails, we just have to switch around the values we're testing + do_check_neq(pluginDefaultState, Ci.nsIPluginTag.STATE_DISABLED); + let nonDefaultState = (pluginDefaultState != Ci.nsIPluginTag.STATE_ENABLED ? + Ci.nsIPluginTag.STATE_ENABLED : + Ci.nsIPluginTag.STATE_CLICKTOPLAY); + let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let testPlugin = get_test_plugintag(); + // the test plugin should have the default enabledState + do_check_eq(testPlugin.enabledState, pluginDefaultState); + + let secondTestPlugin = get_test_plugintag("Second Test Plug-in"); + // set an enabledState for the second test plugin + secondTestPlugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED; + // change what the default enabledState is + Services.prefs.setIntPref("plugin.default.state", nonDefaultState); + // the test plugin should follow the default (it has no individual pref yet) + do_check_eq(testPlugin.enabledState, nonDefaultState); + // the second test plugin should retain its preferred state + do_check_eq(secondTestPlugin.enabledState, Ci.nsIPluginTag.STATE_DISABLED); + + // clean up + testPlugin.enabledState = pluginDefaultState; + secondTestPlugin.enabledState = pluginDefaultState; + Services.prefs.clearUserPref("plugin.default.state"); + Services.prefs.clearUserPref("plugin.importedState"); +} diff --git a/dom/plugins/test/unit/test_plugin_default_state_xpi.js b/dom/plugins/test/unit/test_plugin_default_state_xpi.js new file mode 100644 index 000000000..f1aeb3ac9 --- /dev/null +++ b/dom/plugins/test/unit/test_plugin_default_state_xpi.js @@ -0,0 +1,118 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Promise.jsm"); + +const ADDON_ID = "test-plugin-from-xpi@tests.mozilla.org"; +const XRE_EXTENSIONS_DIR_LIST = "XREExtDL"; +const NS_APP_PLUGINS_DIR_LIST = "APluginsDL"; + +const gPluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); +const gXPCOMABI = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).XPCOMABI; +var gProfileDir = null; + +function getAddonRoot(profileDir, id) { + let dir = profileDir.clone(); + dir.append("extensions"); + Assert.ok(dir.exists(), "Extensions dir should exist: " + dir.path); + dir.append(id); + return dir; +} + +function getTestaddonFilename() { + let abiPart = ""; + if (gIsOSX) { + abiPart = "_" + gXPCOMABI; + } + return "testaddon" + abiPart + ".xpi"; +} + +function run_test() { + allow_all_plugins(); + loadAddonManager(); + gProfileDir = do_get_profile(); + do_register_cleanup(() => shutdownManager()); + run_next_test(); +} + +add_task(function* test_state() { + // Remove test so we will have only one "Test Plug-in" registered. + // xpcshell tests have plugins in per-test profiles, so that's fine. + let file = get_test_plugin(); + file.remove(true); + file = get_test_plugin(true); + file.remove(true); + + Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_CLICKTOPLAY); + Services.prefs.setIntPref("plugin.defaultXpi.state", Ci.nsIPluginTag.STATE_ENABLED); + + let success = yield installAddon(getTestaddonFilename()); + Assert.ok(success, "Should have installed addon."); + let addonDir = getAddonRoot(gProfileDir, ADDON_ID); + + let provider = { + classID: Components.ID("{0af6b2d7-a06c-49b7-babc-636d292b0dbb}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider, + Ci.nsIDirectoryServiceProvider2]), + + getFile: function (prop, persistant) { + throw Cr.NS_ERROR_FAILURE; + }, + + getFiles: function (prop) { + let result = []; + + switch (prop) { + case XRE_EXTENSIONS_DIR_LIST: + result.push(addonDir); + break; + case NS_APP_PLUGINS_DIR_LIST: + let pluginDir = addonDir.clone(); + pluginDir.append("plugins"); + result.push(pluginDir); + break; + default: + throw Cr.NS_ERROR_FAILURE; + } + + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]), + hasMoreElements: () => result.length > 0, + getNext: () => result.shift(), + }; + }, + }; + + let dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider); + + // We installed a non-restartless addon, need to restart the manager. + restartManager(); + gPluginHost.reloadPlugins(); + + Assert.ok(addonDir.exists(), "Addon path should exist: " + addonDir.path); + Assert.ok(addonDir.isDirectory(), "Addon path should be a directory: " + addonDir.path); + let pluginDir = addonDir.clone(); + pluginDir.append("plugins"); + Assert.ok(pluginDir.exists(), "Addon plugins path should exist: " + pluginDir.path); + Assert.ok(pluginDir.isDirectory(), "Addon plugins path should be a directory: " + pluginDir.path); + + let addon = yield getAddonByID(ADDON_ID); + Assert.ok(!addon.appDisabled, "Addon should not be appDisabled"); + Assert.ok(addon.isActive, "Addon should be active"); + Assert.ok(addon.isCompatible, "Addon should be compatible"); + Assert.ok(!addon.userDisabled, "Addon should not be user disabled"); + + let testPlugin = get_test_plugintag(); + Assert.notEqual(testPlugin, null, "Test plugin should have been found"); + Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Test plugin from addon should have state enabled"); + + pluginDir.append(testPlugin.filename); + Assert.ok(pluginDir.exists(), "Plugin file should exist in addon directory: " + pluginDir.path); + + testPlugin = get_test_plugintag("Second Test Plug-in"); + Assert.notEqual(testPlugin, null, "Second test plugin should have been found"); + Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Second test plugin from addon should have state enabled"); +}); diff --git a/dom/plugins/test/unit/xpcshell.ini b/dom/plugins/test/unit/xpcshell.ini new file mode 100644 index 000000000..8dae66b20 --- /dev/null +++ b/dom/plugins/test/unit/xpcshell.ini @@ -0,0 +1,29 @@ +[DEFAULT] +skip-if = toolkit == 'android' +head = head_plugins.js +tail = +tags = addons +firefox-appdir = browser +support-files = + !/toolkit/mozapps/extensions/test/xpcshell/head_addons.js + +[test_allowed_types.js] +skip-if = appname == "thunderbird" +reason = plugins are disabled by default in Thunderbird +[test_bug471245.js] +# Bug 676953: test fails consistently on Android +fail-if = os == "android" +[test_bug813245.js] +# Bug 676953: test fails consistently on Android +fail-if = os == "android" +[test_nice_plugin_name.js] +# Bug 676953: test fails consistently on Android +fail-if = os == "android" +[test_persist_in_prefs.js] +skip-if = appname == "thunderbird" +reason = plugins are disabled by default in Thunderbird +[test_bug854467.js] +[test_plugin_default_state.js] +skip-if = appname == "thunderbird" +reason = plugins are disabled by default in Thunderbird +[test_plugin_default_state_xpi.js] |