From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/plugins/ipc/AStream.h | 27 + dom/plugins/ipc/BrowserStreamChild.cpp | 304 ++ dom/plugins/ipc/BrowserStreamChild.h | 173 + dom/plugins/ipc/BrowserStreamParent.cpp | 222 + dom/plugins/ipc/BrowserStreamParent.h | 78 + dom/plugins/ipc/ChildAsyncCall.cpp | 52 + dom/plugins/ipc/ChildAsyncCall.h | 41 + dom/plugins/ipc/ChildTimer.cpp | 40 + dom/plugins/ipc/ChildTimer.h | 58 + dom/plugins/ipc/D3D11SurfaceHolder.cpp | 87 + dom/plugins/ipc/D3D11SurfaceHolder.h | 50 + dom/plugins/ipc/MiniShmParent.cpp | 218 + dom/plugins/ipc/MiniShmParent.h | 96 + dom/plugins/ipc/NPEventAndroid.h | 55 + dom/plugins/ipc/NPEventOSX.h | 194 + dom/plugins/ipc/NPEventUnix.h | 96 + dom/plugins/ipc/NPEventWindows.h | 160 + dom/plugins/ipc/PBrowserStream.ipdl | 69 + dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl | 37 + dom/plugins/ipc/PPluginInstance.ipdl | 329 ++ dom/plugins/ipc/PPluginModule.ipdl | 171 + dom/plugins/ipc/PPluginScriptableObject.ipdl | 102 + dom/plugins/ipc/PPluginStream.ipdl | 37 + dom/plugins/ipc/PPluginSurface.ipdl | 18 + dom/plugins/ipc/PStreamNotify.ipdl | 39 + dom/plugins/ipc/PluginAsyncSurrogate.cpp | 998 +++++ dom/plugins/ipc/PluginAsyncSurrogate.h | 188 + dom/plugins/ipc/PluginBackgroundDestroyer.cpp | 40 + dom/plugins/ipc/PluginBackgroundDestroyer.h | 56 + dom/plugins/ipc/PluginBridge.h | 44 + dom/plugins/ipc/PluginDataResolver.h | 26 + dom/plugins/ipc/PluginHangUIParent.cpp | 445 ++ dom/plugins/ipc/PluginHangUIParent.h | 159 + dom/plugins/ipc/PluginInstanceChild.cpp | 4684 ++++++++++++++++++++ dom/plugins/ipc/PluginInstanceChild.h | 713 +++ dom/plugins/ipc/PluginInstanceParent.cpp | 2509 +++++++++++ dom/plugins/ipc/PluginInstanceParent.h | 475 ++ dom/plugins/ipc/PluginInterposeOSX.h | 137 + dom/plugins/ipc/PluginInterposeOSX.mm | 1158 +++++ dom/plugins/ipc/PluginLibrary.h | 115 + dom/plugins/ipc/PluginMessageUtils.cpp | 155 + dom/plugins/ipc/PluginMessageUtils.h | 750 ++++ dom/plugins/ipc/PluginModuleChild.cpp | 2681 +++++++++++ dom/plugins/ipc/PluginModuleChild.h | 387 ++ dom/plugins/ipc/PluginModuleParent.cpp | 3384 ++++++++++++++ dom/plugins/ipc/PluginModuleParent.h | 686 +++ dom/plugins/ipc/PluginProcessChild.cpp | 149 + dom/plugins/ipc/PluginProcessChild.h | 55 + dom/plugins/ipc/PluginProcessParent.cpp | 257 ++ dom/plugins/ipc/PluginProcessParent.h | 94 + dom/plugins/ipc/PluginQuirks.cpp | 78 + dom/plugins/ipc/PluginQuirks.h | 68 + dom/plugins/ipc/PluginScriptableObjectChild.cpp | 1298 ++++++ dom/plugins/ipc/PluginScriptableObjectChild.h | 342 ++ dom/plugins/ipc/PluginScriptableObjectParent.cpp | 1393 ++++++ dom/plugins/ipc/PluginScriptableObjectParent.h | 233 + dom/plugins/ipc/PluginScriptableObjectUtils-inl.h | 166 + dom/plugins/ipc/PluginScriptableObjectUtils.h | 306 ++ dom/plugins/ipc/PluginStreamChild.cpp | 64 + dom/plugins/ipc/PluginStreamChild.h | 55 + dom/plugins/ipc/PluginStreamParent.cpp | 72 + dom/plugins/ipc/PluginStreamParent.h | 46 + dom/plugins/ipc/PluginSurfaceParent.cpp | 34 + dom/plugins/ipc/PluginSurfaceParent.h | 40 + dom/plugins/ipc/PluginTypes.ipdlh | 35 + dom/plugins/ipc/PluginUtilsOSX.h | 92 + dom/plugins/ipc/PluginUtilsOSX.mm | 462 ++ dom/plugins/ipc/PluginUtilsWin.cpp | 237 + dom/plugins/ipc/PluginUtilsWin.h | 23 + dom/plugins/ipc/PluginWidgetChild.cpp | 71 + dom/plugins/ipc/PluginWidgetChild.h | 39 + dom/plugins/ipc/PluginWidgetParent.cpp | 237 + dom/plugins/ipc/PluginWidgetParent.h | 65 + dom/plugins/ipc/StreamNotifyChild.h | 64 + dom/plugins/ipc/StreamNotifyParent.h | 48 + dom/plugins/ipc/hangui/HangUIDlg.h | 18 + dom/plugins/ipc/hangui/HangUIDlg.rc | 26 + dom/plugins/ipc/hangui/MiniShmBase.h | 334 ++ dom/plugins/ipc/hangui/MiniShmChild.cpp | 173 + dom/plugins/ipc/hangui/MiniShmChild.h | 68 + dom/plugins/ipc/hangui/PluginHangUI.h | 54 + dom/plugins/ipc/hangui/PluginHangUIChild.cpp | 425 ++ dom/plugins/ipc/hangui/PluginHangUIChild.h | 113 + dom/plugins/ipc/hangui/module.ver | 6 + dom/plugins/ipc/hangui/moz.build | 27 + dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest | 38 + dom/plugins/ipc/interpose/moz.build | 13 + .../ipc/interpose/plugin_child_interpose.mm | 134 + dom/plugins/ipc/moz.build | 153 + 89 files changed, 30248 insertions(+) create mode 100644 dom/plugins/ipc/AStream.h create mode 100644 dom/plugins/ipc/BrowserStreamChild.cpp create mode 100644 dom/plugins/ipc/BrowserStreamChild.h create mode 100644 dom/plugins/ipc/BrowserStreamParent.cpp create mode 100644 dom/plugins/ipc/BrowserStreamParent.h create mode 100644 dom/plugins/ipc/ChildAsyncCall.cpp create mode 100644 dom/plugins/ipc/ChildAsyncCall.h create mode 100644 dom/plugins/ipc/ChildTimer.cpp create mode 100644 dom/plugins/ipc/ChildTimer.h create mode 100644 dom/plugins/ipc/D3D11SurfaceHolder.cpp create mode 100644 dom/plugins/ipc/D3D11SurfaceHolder.h create mode 100644 dom/plugins/ipc/MiniShmParent.cpp create mode 100644 dom/plugins/ipc/MiniShmParent.h create mode 100644 dom/plugins/ipc/NPEventAndroid.h create mode 100644 dom/plugins/ipc/NPEventOSX.h create mode 100644 dom/plugins/ipc/NPEventUnix.h create mode 100644 dom/plugins/ipc/NPEventWindows.h create mode 100644 dom/plugins/ipc/PBrowserStream.ipdl create mode 100644 dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl create mode 100644 dom/plugins/ipc/PPluginInstance.ipdl create mode 100644 dom/plugins/ipc/PPluginModule.ipdl create mode 100644 dom/plugins/ipc/PPluginScriptableObject.ipdl create mode 100644 dom/plugins/ipc/PPluginStream.ipdl create mode 100644 dom/plugins/ipc/PPluginSurface.ipdl create mode 100644 dom/plugins/ipc/PStreamNotify.ipdl create mode 100644 dom/plugins/ipc/PluginAsyncSurrogate.cpp create mode 100644 dom/plugins/ipc/PluginAsyncSurrogate.h create mode 100644 dom/plugins/ipc/PluginBackgroundDestroyer.cpp create mode 100644 dom/plugins/ipc/PluginBackgroundDestroyer.h create mode 100644 dom/plugins/ipc/PluginBridge.h create mode 100644 dom/plugins/ipc/PluginDataResolver.h create mode 100644 dom/plugins/ipc/PluginHangUIParent.cpp create mode 100644 dom/plugins/ipc/PluginHangUIParent.h create mode 100644 dom/plugins/ipc/PluginInstanceChild.cpp create mode 100644 dom/plugins/ipc/PluginInstanceChild.h create mode 100644 dom/plugins/ipc/PluginInstanceParent.cpp create mode 100644 dom/plugins/ipc/PluginInstanceParent.h create mode 100644 dom/plugins/ipc/PluginInterposeOSX.h create mode 100644 dom/plugins/ipc/PluginInterposeOSX.mm create mode 100644 dom/plugins/ipc/PluginLibrary.h create mode 100644 dom/plugins/ipc/PluginMessageUtils.cpp create mode 100644 dom/plugins/ipc/PluginMessageUtils.h create mode 100644 dom/plugins/ipc/PluginModuleChild.cpp create mode 100644 dom/plugins/ipc/PluginModuleChild.h create mode 100755 dom/plugins/ipc/PluginModuleParent.cpp create mode 100644 dom/plugins/ipc/PluginModuleParent.h create mode 100644 dom/plugins/ipc/PluginProcessChild.cpp create mode 100644 dom/plugins/ipc/PluginProcessChild.h create mode 100644 dom/plugins/ipc/PluginProcessParent.cpp create mode 100644 dom/plugins/ipc/PluginProcessParent.h create mode 100644 dom/plugins/ipc/PluginQuirks.cpp create mode 100644 dom/plugins/ipc/PluginQuirks.h create mode 100644 dom/plugins/ipc/PluginScriptableObjectChild.cpp create mode 100644 dom/plugins/ipc/PluginScriptableObjectChild.h create mode 100644 dom/plugins/ipc/PluginScriptableObjectParent.cpp create mode 100644 dom/plugins/ipc/PluginScriptableObjectParent.h create mode 100644 dom/plugins/ipc/PluginScriptableObjectUtils-inl.h create mode 100644 dom/plugins/ipc/PluginScriptableObjectUtils.h create mode 100644 dom/plugins/ipc/PluginStreamChild.cpp create mode 100644 dom/plugins/ipc/PluginStreamChild.h create mode 100644 dom/plugins/ipc/PluginStreamParent.cpp create mode 100644 dom/plugins/ipc/PluginStreamParent.h create mode 100644 dom/plugins/ipc/PluginSurfaceParent.cpp create mode 100644 dom/plugins/ipc/PluginSurfaceParent.h create mode 100644 dom/plugins/ipc/PluginTypes.ipdlh create mode 100644 dom/plugins/ipc/PluginUtilsOSX.h create mode 100644 dom/plugins/ipc/PluginUtilsOSX.mm create mode 100644 dom/plugins/ipc/PluginUtilsWin.cpp create mode 100644 dom/plugins/ipc/PluginUtilsWin.h create mode 100644 dom/plugins/ipc/PluginWidgetChild.cpp create mode 100644 dom/plugins/ipc/PluginWidgetChild.h create mode 100644 dom/plugins/ipc/PluginWidgetParent.cpp create mode 100644 dom/plugins/ipc/PluginWidgetParent.h create mode 100644 dom/plugins/ipc/StreamNotifyChild.h create mode 100644 dom/plugins/ipc/StreamNotifyParent.h create mode 100644 dom/plugins/ipc/hangui/HangUIDlg.h create mode 100644 dom/plugins/ipc/hangui/HangUIDlg.rc create mode 100644 dom/plugins/ipc/hangui/MiniShmBase.h create mode 100644 dom/plugins/ipc/hangui/MiniShmChild.cpp create mode 100644 dom/plugins/ipc/hangui/MiniShmChild.h create mode 100644 dom/plugins/ipc/hangui/PluginHangUI.h create mode 100644 dom/plugins/ipc/hangui/PluginHangUIChild.cpp create mode 100644 dom/plugins/ipc/hangui/PluginHangUIChild.h create mode 100644 dom/plugins/ipc/hangui/module.ver create mode 100644 dom/plugins/ipc/hangui/moz.build create mode 100644 dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest create mode 100644 dom/plugins/ipc/interpose/moz.build create mode 100644 dom/plugins/ipc/interpose/plugin_child_interpose.mm create mode 100644 dom/plugins/ipc/moz.build (limited to 'dom/plugins/ipc') 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(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(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(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(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 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 mDeliveryTracker; + base::RepeatingTimer 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(this); + nsNPAPIStreamWrapper* wrapper = + reinterpret_cast(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 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(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 mStreamPeer; + RefPtr 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 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 + +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 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 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 mDevice11; + RefPtr 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 + +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 + +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 +{ + 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 +{ + 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(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 // 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 +{ + 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(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(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(&aResult->lParamData.rect); + } else if (aResult->event.event == WM_WINDOWPOSCHANGED) { + // restore the lParam to point at the WINDOWPOS + aResult->event.lParam = reinterpret_cast(&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(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(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 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(rawSurrogate); + return true; +} + +/* static */ PluginAsyncSurrogate* +PluginAsyncSurrogate::Cast(NPP aInstance) +{ + MOZ_ASSERT(aInstance); + PluginDataResolver* resolver = + reinterpret_cast(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 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(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 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(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(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 + timer(mParent->GetHistogramKey()); + bool result = false; + MOZ_ASSERT(mParent); + if (mParent->IsChrome()) { + PluginProcessParent* process = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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 mInstance; + uint16_t mMode; + InfallibleTArray mNames; + InfallibleTArray 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 mPendingNewStreamCalls; + UniquePtr 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 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(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 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* 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(aHangUITimeoutPref) * 1000U), + mIPCTimeoutMs(static_cast(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(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 + directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + if (!directoryService) { + return false; + } + nsCOMPtr 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 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(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(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 workItem = new nsPluginHangUITelemetry(responseCode, + dontAskCode, + LastShowDurationMs(), + mTimeoutPrefMs); + NS_DispatchToMainThread(workItem); + return true; +} + +nsresult +PluginHangUIParent::GetHangUIOwnerWindowHandle(NativeWindowHandle& windowHandle) +{ + windowHandle = nullptr; + + nsresult rv; + nsCOMPtr winMediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 widget = WidgetUtils::DOMWindowToWidget(win); + if (!widget) { + return NS_ERROR_FAILURE; + } + + windowHandle = reinterpret_cast(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 +#include +#include +#include "gtk2xtbin.h" + +#elif defined(OS_WIN) + +#include +#include + +#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 +#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 +CreateDrawTargetForSurface(gfxASurface *aSurface) +{ + SurfaceFormat format = aSurface->GetSurfaceFormat(); + RefPtr 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& aNames, + const InfallibleTArray& 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 argn(new char*[1 + argc]); + UniquePtr argv(new char*[1 + argc]); + argn[argc] = 0; + argv[argc] = 0; + + for (int i = 0; i < argc; ++i) { + argn[i] = const_cast(NullableStringGet(mNames[i])); + argv[i] = const_cast(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(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(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(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(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(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(aValue) = mCachedWinlessPluginHWND; + return NPERR_NO_ERROR; + } + NPError result; + if (!CallNPN_GetValue_NPNVnetscapeWindow(&mCachedWinlessPluginHWND, &result)) { + return NPERR_GENERIC_ERROR; + } + *static_cast(aValue) = mCachedWinlessPluginHWND; + return result; + } + else { + *static_cast(aValue) = mPluginWindowHWND; + return NPERR_NO_ERROR; + } +#elif defined(MOZ_X11) + NPError result; + CallNPN_GetValue_NPNVnetscapeWindow(static_cast(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(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(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(aValue) = mContentsScaleFactor; + return NPERR_NO_ERROR; + } +#endif /* defined(XP_MACOSX) || defined(XP_WIN) */ + + case NPNVCSSZoomFactor: { + *static_cast(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(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(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 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(&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(); + 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(&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 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=)", + FULLFUNCTION, + aWindow.window, + aWindow.x, aWindow.y, + aWindow.width, aWindow.height)); + +#ifdef MOZ_WIDGET_GTK + if (mXEmbed) { + mWindow.window = reinterpret_cast(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(aWindow.window); +#endif + + return true; +} + +// Destroy window +void PluginInstanceChild::DeleteWindow() +{ + PLUGIN_LOG_DEBUG(("%s (aWindow=)", + 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=)", + 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(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=, clip=", + 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(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( + GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC)); + if (wndProc != PluginWindowProc) { + mPluginWndProc = reinterpret_cast( + SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast(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(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(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(DefWindowProcA)); + + return true; +} + +void +PluginInstanceChild::DestroyPluginWindow() +{ + if (mPluginWindowHWND) { + // Unsubclass the window. + WNDPROC wndProc = reinterpret_cast( + 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(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( + 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(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(PluginWindowProc) || + newLong == reinterpret_cast(NeuteredWindowProc) || + newLong == reinterpret_cast(DefWindowProcA) || + newLong == reinterpret_cast(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( + GetProp(hWnd, kPluginInstanceChildProperty)); + + // Hook our subclass back up, just like we do on setwindow. + WNDPROC currentProc = + reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (currentProc != PluginWindowProc) { + self->mPluginWndProc = + reinterpret_cast(sUser32SetWindowLongWHookStub(hWnd, nIndex, + reinterpret_cast(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( + GetProp(hWnd, kPluginInstanceChildProperty)); + + // Hook our subclass back up, just like we do on setwindow. + WNDPROC currentProc = + reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (currentProc != PluginWindowProc) { + self->mPluginWndProc = + reinterpret_cast(sUser32SetWindowLongWHookStub(hWnd, nIndex, + reinterpret_cast(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(SetWindowLongPtrAHook), + (void**) &sUser32SetWindowLongAHookStub); + if (!sUser32SetWindowLongWHookStub) + sUser32Intercept.AddHook("SetWindowLongPtrW", reinterpret_cast(SetWindowLongPtrWHook), + (void**) &sUser32SetWindowLongWHookStub); +#else + if (!sUser32SetWindowLongAHookStub) + sUser32Intercept.AddHook("SetWindowLongA", reinterpret_cast(SetWindowLongAHook), + (void**) &sUser32SetWindowLongAHookStub); + if (!sUser32SetWindowLongWHookStub) + sUser32Intercept.AddHook("SetWindowLongW", reinterpret_cast(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 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(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(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(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(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 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(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(ImmGetContextProc), + (void**)&sImm32ImmGetContextStub); + sImm32Intercept.AddHook( + "ImmReleaseContext", + reinterpret_cast(ImmReleaseContextProc), + (void**)&sImm32ImmReleaseContextStub); + sImm32Intercept.AddHook( + "ImmGetCompositionStringW", + reinterpret_cast(ImmGetCompositionStringProc), + (void**)&sImm32ImmGetCompositionStringStub); + sImm32Intercept.AddHook( + "ImmSetCandidateWindow", + reinterpret_cast(ImmSetCandidateWindowProc), + (void**)&sImm32ImmSetCandidateWindowStub); + sImm32Intercept.AddHook( + "ImmNotifyIME", + reinterpret_cast(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 pluginInstance(sCurrentPluginInstance); + if (event.event == WM_IME_STARTCOMPOSITION || + event.event == WM_IME_COMPOSITION || + event.event == WM_KILLFOCUS) { + sCurrentPluginInstance = this; + } + + MessageLoop* loop = MessageLoop::current(); + AutoRestore modalLoop(loop->os_modal_loop()); + + handled = mPluginIface->event(&mData, reinterpret_cast(&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(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( + 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(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(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(SetWindowLongPtr(hWnd, GWLP_WNDPROC, + reinterpret_cast(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(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 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(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(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 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(actor); + RefPtr 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(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(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(Manager()); + + if (mClosure) + instance->mPluginIface->urlnotify(instance->GetNPP(), mURL.get(), + reason, mClosure); +} + +bool +PluginInstanceChild::DeallocPStreamNotifyChild(PStreamNotifyChild* notifyData) +{ + AssertPluginThread(); + + if (!static_cast(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(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 notifyStreams; + ManagedPStreamNotifyChild(notifyStreams); + uint32_t notifyStreamCount = notifyStreams.Length(); + for (uint32_t i = 0; i < notifyStreamCount; i++) { + StreamNotifyChild* sn = static_cast(notifyStreams[i]); + if (sn->mClosure == notifyData) { + sn->SendRedirectNotifyResponse(static_cast(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 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 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() == nbytes.value()); + + surface->version = 0; + surface->size = *size; + surface->format = format; + surface->bitmap.data = shmem.get(); + 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); + + 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 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 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 + (this, &PluginInstanceChild::DoAsyncSetWindow, aSurfaceType, aWindow, true); + RefPtr 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 ", + 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(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(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(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 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(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(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=, clip=", + 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(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(&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(&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 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 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 dt; + if (aSurface == mCurrentSurface && + aSurface->GetType() == gfxSurfaceType::Image && + aSurface->GetSurfaceFormat() == SurfaceFormat::B8G8R8X8) { + gfxImageSurface* imageSurface = static_cast(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 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(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 whiteImage; + RefPtr 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 dt = CreateDrawTargetForSurface(whiteImage); + RefPtr 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(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(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 dt = CreateDrawTargetForSurface(aSurface); + RefPtr 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 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 on surface ", + 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 surface = + mHelperSurface ? mHelperSurface : mCurrentSurface; + RefPtr dt = CreateDrawTargetForSurface(surface); + RefPtr 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 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=, clip=", + 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(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(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(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 ", + this, mSurfaceDifferenceRect.x, mSurfaceDifferenceRect.y, + mSurfaceDifferenceRect.width, mSurfaceDifferenceRect.height)); + + // Read back previous content + RefPtr dt = CreateDrawTargetForSurface(mCurrentSurface); + RefPtr 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 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 task = new ChildAsyncCall(this, aFunc, aUserData); + PostChildAsyncCall(task.forget()); +} + +void +PluginInstanceChild::PostChildAsyncCall(already_AddRefed aTask) +{ + RefPtr task = aTask; + + { + MutexAutoLock lock(mAsyncCallMutex); + mPendingAsyncCalls.AppendElement(task); + } + ProcessChild::message_loop()->PostTask(task.forget()); +} + +void +PluginInstanceChild::SwapSurfaces() +{ + RefPtr 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(mCurrentSurface.get())->GetShmem()); + if (gfxSharedImageSurface::IsSharedImage(mBackSurface)) + DeallocShmem(static_cast(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& 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& 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 streams; + ManagedPBrowserStreamChild(streams); + + // First make sure none of these streams become deleted + for (uint32_t i = 0; i < streams.Length(); ) { + if (static_cast(streams[i])->InstanceDying()) + ++i; + else + streams.RemoveElementAt(i); + } + for (uint32_t i = 0; i < streams.Length(); ++i) + static_cast(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(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; + 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 + +#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& aNames, + const InfallibleTArray& 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 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 mNames; + InfallibleTArray 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, DirectBitmap> mDirectBitmaps; + +#if defined(XP_WIN) + nsDataHashtable, 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 mPendingAsyncCalls; + nsTArray > 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 > mDeletingHash; + +#if defined(MOZ_WIDGET_COCOA) +private: +#if defined(__i386__) + NPEventModel mEventModel; +#endif + CGColorSpaceRef mShColorSpace; + CGContextRef mShContext; + RefPtr 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 mCurrentSurface; + + // Back surface, just keeping reference to + // surface which is on ParentProcess side + RefPtr 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 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 mCurrentInvalidateTask; + + // Keep AsyncSetWindow task pointer to be able to Cancel it on Destroy + RefPtr 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 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 // 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 +#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 +#elif defined(XP_MACOSX) +#include +#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(Manager()); + instance->mNPNIface->urlredirectresponse(instance->mNPP, this, static_cast(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* 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(); + } +#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 owner = GetOwner(); + if (!owner) { + return false; + } + nsCOMPtr 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 + 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(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 device = DeviceManagerDx::Get()->GetContentDevice(); + if (!device) { + return false; + } + + RefPtr dxgi; + if (FAILED(device->QueryInterface(__uuidof(IDXGIDevice), getter_AddRefs(dxgi))) || !dxgi) { + return false; + } + RefPtr 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(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(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(&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 forwarder = ImageBridgeChild::GetSingleton(); + if (!forwarder) { + return true; + } + + RefPtr d3d11 = DeviceManagerDx::Get()->GetContentDevice(); + if (!d3d11) { + return true; + } + + // Create the texture we'll give to the plugin process. + HANDLE sharedHandle = 0; + RefPtr 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 resource; + if (FAILED(back->QueryInterface(IID_IDXGIResource, getter_AddRefs(resource))) || !resource) { + return true; + } + if (FAILED(resource->GetSharedHandle(&sharedHandle) || !sharedHandle)) { + return true; + } + } + + RefPtr holder = new D3D11SurfaceHolder(back, format, size); + mD3D11Surfaces.Put(reinterpret_cast(sharedHandle), holder); + + *outHandle = reinterpret_cast(sharedHandle); + *outError = NPERR_NO_ERROR; +#endif + return true; +} + +bool +PluginInstanceParent::RecvFinalizeDXGISurface(const WindowsHandle& handle) +{ +#if defined(XP_WIN) + mD3D11Surfaces.Remove(reinterpret_cast(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 nbytes = CheckedInt(uint32_t(size.height)) * stride; + if (!nbytes.isValid() || nbytes.value() != buffer.Size()) { + MOZ_ASSERT_UNREACHABLE("bad shmem size"); + return false; + } + + ImageContainer* container = GetImageContainer(); + if (!container) { + return false; + } + + RefPtr source = + gfx::Factory::CreateWrappingDataSourceSurface(buffer.get(), stride, size, format); + if (!source) { + return false; + } + + // Allocate a texture for the compositor. + RefPtr allocator = mParent->EnsureTextureAllocatorForDirectBitmap(); + RefPtr 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 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(), 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 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 surface; + if (!mD3D11Surfaces.Get(reinterpret_cast(handle), getter_AddRefs(surface))) { + return false; + } + if (!surface->IsValid()) { + return false; + } + + ImageContainer* container = GetImageContainer(); + if (!container) { + return false; + } + + RefPtr allocator = mParent->EnsureTextureAllocatorForDXGISurface(); + RefPtr 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 image = new TextureWrapperImage(texture, pictureRect); + SetCurrentImage(image); + + PLUGIN_LOG_DEBUG((" (RecvShowDirect3D10Surface received handle=%p rect=%s)", + reinterpret_cast(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 ", + this, updatedRect.left, updatedRect.top, + updatedRect.right - updatedRect.left, + updatedRect.bottom - updatedRect.top)); + + MOZ_ASSERT(!IsUsingDirectDrawing()); + + // XXXjwatt rewrite to use Moz2D + RefPtr 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 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(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(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 sourceSurface = + gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface, isPlugin); + RefPtr image = new SourceSurfaceImage(surface->GetSize(), sourceSurface); + + AutoTArray 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(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 = 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 ", + 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 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 ", + 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 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(mBackground.get()); + return SurfaceDescriptorX11(xsurf); +#endif + +#ifdef XP_WIN + MOZ_ASSERT(gfxSharedImageSurface::IsSharedImage(mBackground), + "Expected shared image surface"); + gfxSharedImageSurface* shmem = + static_cast(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(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(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(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(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(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(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + case NPNVmuteAudioBool: + if (!CallNPP_SetValue_NPNVmuteAudioBool(*static_cast(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + case NPNVCSSZoomFactor: + if (!CallNPP_SetValue_NPNVCSSZoomFactor(*static_cast(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(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(event); +#else + NPEvent* npevent = reinterpret_cast(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(); + + 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(stream->notifyData), + NullableString(stream->headers))) { + return NPERR_GENERIC_ERROR; + } + + Telemetry::AutoTimer + 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(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(s); + if (sp->mNPP != this) + NS_RUNTIMEABORT("Mismatched plugin data"); + + sp->NPP_DestroyStream(reason); + return NPERR_NO_ERROR; + } + else { + PluginStreamParent* sp = + static_cast(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(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(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(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(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(mNPP->ndata); + if (!inst) { + return false; + } + + inst->RedrawPlugin(); + return true; +} + +bool +PluginInstanceParent::RecvNegotiatedCarbon() +{ + nsNPAPIPluginInstance *inst = static_cast(mNPP->ndata); + if (!inst) { + return false; + } + inst->CarbonNPAPIFailure(); + return true; +} + +nsPluginInstanceOwner* +PluginInstanceParent::GetOwner() +{ + nsNPAPIPluginInstance* inst = static_cast(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( + ::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(PluginWindowHookProc)); + DebugOnly 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 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(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 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(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* 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 +#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* 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 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, 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, 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 mIOSurface; + RefPtr mFrontIOSurface; +#endif // definied(MOZ_WIDGET_COCOA) + + // ObjectFrame layer wrapper + RefPtr mFrontSurface; + // For windowless+transparent instances, this surface contains a + // "pretty recent" copy of the pixels under its 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 mBackground; + + RefPtr 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 +#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 +#import +#import +#import + +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 plugin_visible_windows_set_; +// Tracks full screen windows currently visible. +std::set plugin_fullscreen_windows_set_; +// Tracks modal windows currently visible. +std::set 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& 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 callback) = 0; + virtual nsresult NPP_GetSitesWithData(nsCOMPtr 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 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 +{ + explicit DeletingObjectEntry(const NPObject* key) + : nsPtrHashKey(key) + , mDeleted(false) + { } + + bool mDeleted; +}; + +} /* namespace plugins */ + +} /* namespace mozilla */ + +namespace IPC { + +template <> +struct ParamTraits +{ + 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 +{ + 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 +{ + 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 +{ + 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(sizeof(UniChar))) { + return false; + } + + auto chars = mozilla::MakeUnique(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 +{ + 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(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 +{ + 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 +{ + 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 +{ + 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 +{ + 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 +{ + 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 +{ + 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 +#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 +#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 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(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(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 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> task = + new DeleteTask(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(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(static_cast(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(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(static_cast(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->ndata); + if (s->IsBrowserStream()) { + BrowserStreamChild* bs = static_cast(s); + bs->EnsureCorrectInstance(p); + bs->NPN_DestroyStream(aReason); + } + else { + PluginStreamChild* ps = static_cast(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(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(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(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(CreateFileWHookFn), + (void**) &sCreateFileWStub); + sKernel32Intercept.AddHook("CreateFileA", + reinterpret_cast(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 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& aNames, + const InfallibleTArray& 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(PMCGetWindowInfoHook), + (void**) &sGetWindowInfoPtrStub); + } + + if ((mQuirks & QUIRK_FLASH_HOOK_GETKEYSTATE) && + !sGetKeyStatePtrStub) { + sUser32Intercept.AddHook("GetKeyState", reinterpret_cast(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&& aNames, + InfallibleTArray&& 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(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 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(aChildInstance); + NPError rv = childInstance->DoNPP_New(); + RefPtr task = + new AsyncNewResultSender(childInstance, rv); + childInstance->PostChildAsyncCall(task.forget()); +} + +bool +PluginModuleChild::RecvAsyncNPP_New(PPluginInstanceChild* aActor) +{ + PLUGIN_LOG_DEBUG_METHOD; + PluginInstanceChild* childInstance = + reinterpret_cast(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(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(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 featureArray; + for (size_t i = 0; i < params.features().Length(); ++i) { + featureArray.AppendElement(params.features()[i].get()); + } + + nsTArray 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 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 +#include + +#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& aNames, + const InfallibleTArray& aValues) + override; + + virtual bool + DeallocPPluginInstanceChild(PPluginInstanceChild* aActor) override; + + virtual bool + RecvPPluginInstanceConstructor(PPluginInstanceChild* aActor, + const nsCString& aMimeType, + const uint16_t& aMode, + InfallibleTArray&& aNames, + InfallibleTArray&& 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> 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 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 +#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 host = nsPluginHost::GetInst(); + RefPtr plugin; + *rv = host->GetPluginForContentProcess(aPluginId, getter_AddRefs(plugin)); + if (NS_FAILED(*rv)) { + return true; + } + PluginModuleChromeParent* chromeParent = static_cast(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 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(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + mapping->AssociateWithProcessId(aProcessId); + return mapping; + } + mapping = static_cast(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(PR_LIST_TAIL(&sModuleListHead)); + MOZ_ASSERT(mapping); + return mapping; + } + + mapping = + static_cast(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mProcessIdValid && mapping->mProcessId == aProcessId) { + return mapping; + } + mapping = static_cast(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static PluginModuleMapping* + FindModuleByPluginId(uint32_t aPluginId) + { + PluginModuleMapping* mapping = + static_cast(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + return mapping; + } + mapping = static_cast(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 host = nsPluginHost::GetInst(); + nsPluginTag* pluginTag = host->PluginWithId(aPluginId); + if (!pluginTag || !pluginTag->mPlugin) { + return nullptr; + } + RefPtr plugin = pluginTag->mPlugin; + + return static_cast(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 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 mapping = + PluginModuleMapping::AssociateWithProcessId(aPluginId, aOtherPid); + MOZ_ASSERT(mapping); +} + +/* static */ PluginModuleContentParent* +PluginModuleContentParent::Initialize(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherPid) +{ + nsAutoPtr moduleMapping( + PluginModuleMapping::Resolve(aOtherPid)); + MOZ_ASSERT(moduleMapping); + PluginModuleContentParent* parent = moduleMapping->GetModule(); + MOZ_ASSERT(parent); + + DebugOnly 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 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 parent( + new PluginModuleChromeParent(aFilePath, aPluginId, + aPluginTag->mSandboxLevel, + aPluginTag->mSupportsAsyncInit)); + UniquePtr 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 profiler(do_GetService("@mozilla.org/tools/profiler;1")); + bool profilerActive = false; + DebugOnly rv = profiler->IsActive(&profilerActive); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (profilerActive) { + nsCOMPtr currentProfilerParams; + rv = profiler->GetStartParams(getter_AddRefs(currentProfilerParams)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + nsCOMPtr gatherer; + rv = profiler->GetProfileGatherer(getter_AddRefs(gatherer)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + mGatherer = static_cast(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 0) ? (1000 * aChildTimeout) : + MessageChannel::kNoTimeout; + SetReplyTimeoutMs(timeoutMs); +} + +void +PluginModuleParent::TimeoutChanged(const char* aPref, void* aModule) +{ + PluginModuleParent* module = static_cast(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(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(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& processHandles, InfallibleTArray& cpuUsage) +{ + InfallibleTArray 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(aProtocol); + case PPluginBackgroundDestroyerMsgStart: { + PPluginBackgroundDestroyerParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PPluginScriptableObjectMsgStart: { + PPluginScriptableObjectParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PBrowserStreamMsgStart: { + PBrowserStreamParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PPluginStreamMsgStart: { + PPluginStreamParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PStreamNotifyMsgStart: { + PStreamNotifyParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } +#ifdef XP_WIN + case PPluginSurfaceMsgStart: { + PPluginSurfaceParent* actor = + static_cast(aProtocol); + return static_cast(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 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 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 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 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 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(LoneManagedOrNullAsserts(ManagedPCrashReporterParent())); +} + +#ifdef MOZ_CRASHREPORTER_INJECTOR +static void +RemoveMinidump(nsIFile* minidump) +{ + if (!minidump) + return; + + minidump->Remove(false); + nsCOMPtr 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 dumpFile; + nsAutoCString flashProcessType; + TakeMinidump(getter_AddRefs(dumpFile), &sequence); + +#ifdef MOZ_CRASHREPORTER_INJECTOR + nsCOMPtr 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 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& aNames, + const InfallibleTArray& 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(static_cast(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 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 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(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& 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 surrogate = + PluginAsyncSurrogate::Cast(instance); + mSurrogateInstances.AppendElement(surrogate); + *error = NPERR_NO_ERROR; + return NS_PLUGIN_INIT_PENDING; + } + } + + // create the instance on the other side + InfallibleTArray names; + InfallibleTArray 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 + 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(mTimeBlocked.ToMilliseconds())); + mTimeBlocked = TimeDuration(); +} + +nsresult +PluginModuleParent::NPP_NewInternal(NPMIMEType pluginType, NPP instance, + uint16_t mode, + InfallibleTArray& names, + InfallibleTArray& 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 surrogate( + dont_AddRef(PluginAsyncSurrogate::Cast(instance))); + // Now replace it with the instance + instance->pdata = static_cast(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 + 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 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 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(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 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&& 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(); + 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(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 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 pse = do_QueryInterface(aSubject); + mPmp->GatheredAsyncProfile(pse); + } + return NS_OK; +} + +void +PluginModuleChromeParent::InitPluginProfiling() +{ + nsCOMPtr 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 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 profiler(do_GetService("@mozilla.org/tools/profiler;1")); + if (NS_WARN_IF(!profiler)) { + return; + } + nsCOMPtr gatherer; + profiler->GetProfileGatherer(getter_AddRefs(gatherer)); + mGatherer = static_cast(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& aNames, + const InfallibleTArray& aValues) + override; + + virtual bool + DeallocPPluginInstanceParent(PPluginInstanceParent* aActor) override; + +public: + explicit PluginModuleParent(bool aIsChrome, bool aAllowAsyncInit); + virtual ~PluginModuleParent(); + + bool RemovePendingSurrogate(const RefPtr& 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&& aSites, + const uint64_t& aCallbackId) override; + + void SetPluginFuncs(NPPluginFuncs* aFuncs); + + nsresult NPP_NewInternal(NPMIMEType pluginType, NPP instance, uint16_t mode, + InfallibleTArray& names, + InfallibleTArray& 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 callback) override; + virtual nsresult NPP_GetSitesWithData(nsCOMPtr callback) override; + +private: + std::map> mClearSiteDataCallbacks; + std::map> 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 mTaskFactory; + nsString mPluginDumpID; + nsString mBrowserDumpID; + nsString mHangID; + RefPtr 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> mSurrogateInstances; + nsresult mAsyncNewRv; + uint32_t mRunID; + + RefPtr mTextureAllocatorForDirectBitmap; + RefPtr 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 mChromeTaskFactory; + + enum HangAnnotationFlags + { + kInPluginCall = (1u << 0), + kHangUIShown = (1u << 1), + kHangUIContinued = (1u << 2), + kHangUIDontShow = (1u << 3) + }; + Atomic mHangAnnotationFlags; +#ifdef XP_WIN + InfallibleTArray 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 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 mPluginOfflineObserver; +#ifdef MOZ_ENABLE_PROFILER_SPS + RefPtr 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 +#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 values = CommandLine::ForCurrentProcess()->argv(); + MOZ_ASSERT(values.size() >= 2, "not enough args"); + + pluginFilename = UnmungePluginDsoPath(values[1]); + +#elif defined(OS_WIN) + std::vector 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(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& aAllowedFiles, nsIProperties* aDirSvc, + const char* aDir, const nsAString& aSuffix = EmptyString()) +{ + nsCOMPtr 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& aAllowedFilesRead, + vector& aAllowedFilesReadWrite, + vector& aAllowedDirectories) +{ + if (aSandboxLevel < 2) { + return; + } + + nsresult rv; + nsCOMPtr 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 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 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 aLaunchCompleteTask = UniquePtr(), + 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 mTaskFactory; + UniquePtr 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(aIdentifier); + if (bits & 1) { + int32_t num = int32_t(bits >> 1); + mIdentifier = PluginIdentifier(num); + } else { + mStored = static_cast(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(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((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(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(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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + AutoTArray identifiers; + bool success; + actor->CallEnumerate(&identifiers, &success); + + if (!success) { + return false; + } + + *aCount = identifiers.Length(); + if (!*aCount) { + *aIdentifiers = nullptr; + return true; + } + + *aIdentifiers = reinterpret_cast( + 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(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(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(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(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(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(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&& 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 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&& 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 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* 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&& 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 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::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(); + } + + 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&& aArgs, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + AnswerInvokeDefault(InfallibleTArray&& 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* aProperties, + bool* aSuccess) override; + + virtual bool + AnswerConstruct(InfallibleTArray&& 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 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> IdentifierTable; + static IdentifierTable sIdentifiers; + + struct NPObjectData : public nsPtrHashKey + { + explicit NPObjectData(const NPObject* key) + : nsPtrHashKey(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* 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(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(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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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 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(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor 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(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(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(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(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(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(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&& 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 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&& 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 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* 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&& 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 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(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&& aArgs, + Variant* aResult, + bool* aSuccess) override; + + virtual bool + AnswerInvokeDefault(InfallibleTArray&& 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* aProperties, + bool* aSuccess) override; + + virtual bool + AnswerConstruct(InfallibleTArray&& 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 VariantTraits; + +template<> +class VariantTraits +{ +public: + typedef mozilla::plugins::PluginScriptableObjectParent ScriptableObjectType; +}; + +template<> +class VariantTraits +{ +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(::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 +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::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(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( + reinterpret_cast( + aRemoteVariant.get_PPluginScriptableObjectParent())); + return actor->GetObject(true); + } + + case Variant::TPPluginScriptableObjectChild: { + PluginScriptableObjectChild* actor = + const_cast( + reinterpret_cast( + 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( + reinterpret_cast( + 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( + reinterpret_cast( + 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 +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& vars = EnsureAndGetShadowArray(); + uint32_t count = vars.Length(); + for (uint32_t index = 0; index < count; index++) { + ReleaseRemoteVariant(vars[index]); + } + } + + operator const InfallibleTArray&() + { + return EnsureAndGetShadowArray(); + } + + bool IsOk() + { + return mOk; + } + +private: + InfallibleTArray& + 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 mArray; + InfallibleTArray mShadowArray; + bool mOk; + bool mUsingShadowArray; +}; + +template +struct ProtectedActorTraits +{ + static bool Nullable(); +}; + +template > +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 +{ + static bool Nullable() { return true; } +}; + +template<> +struct ProtectedActorTraits +{ + 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(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(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(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(mimeType.get()), + NullableStringGet(target), + &mStream); + if (*result == NPERR_NO_ERROR) + mStream->pdata = static_cast(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(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 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 mCARenderer; + RefPtr mFrontSurface; + RefPtr 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 +#import +#import +#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(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 + (sApplicationASN); + + if (!sApplicationInfoItem) { + sApplicationInfoItem = ::CFBundleGetFunctionPointerForName(launchServices, + CFSTR("_LSSetApplicationInformationItem")); + } + + LSSetInformationItemType setInformationItemFunc + = reinterpret_cast + (sApplicationInfoItem); + + void * displayNameKeyAddr = ::CFBundleGetDataPointerForName(launchServices, + CFSTR("_kLSDisplayNameKey")); + + CFStringRef displayNameKey = nil; + if (displayNameKeyAddr) { + displayNameKey = reinterpret_cast(*(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 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 +#include "PluginUtilsWin.h" +#include "PluginModuleParent.h" +#include "mozilla/StaticMutex.h" + +namespace mozilla { +namespace plugins { +namespace PluginUtilsWin { + +typedef nsTHashtable> 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 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(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(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 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 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 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(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 mWidget; +#if defined(MOZ_WIDGET_GTK) + nsAutoPtr 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 + +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 + +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 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(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 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(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(aView); + mWriteHeader = reinterpret_cast(static_cast(aView) + + aSize / 2U); + } else { + mWriteHeader = static_cast(aView); + mReadHeader = reinterpret_cast(static_cast(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 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(mWriteHeader + 1); + return NS_OK; + } + + static VOID CALLBACK + SOnEvent(PVOID aContext, BOOLEAN aIsTimer) + { + MiniShmBase* object = static_cast(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 +#include + +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::max()) { + mappingSize = static_cast(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 + +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 +#include +#include +#include +#include +#include + +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 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(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(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(aObject); + object->RecvCancel(); +} + +bool +PluginHangUIChild::RecvShow() +{ + return (QueueUserAPC(&ShowAPC, + mMainThread, + reinterpret_cast(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(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 + +#include + +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 @@ + + + +Firefox Plugin Hang User Interface + + + + + + + + + + + + + + + + + + + + + + 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 +#import + +// 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(MacPluginChild##function), \ + reinterpret_cast(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'] -- cgit v1.2.3