diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/plugins/ipc | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
92 files changed, 29412 insertions, 0 deletions
diff --git a/dom/plugins/ipc/AStream.h b/dom/plugins/ipc/AStream.h new file mode 100644 index 0000000000..06ac822320 --- /dev/null +++ b/dom/plugins/ipc/AStream.h @@ -0,0 +1,26 @@ +/* -*- 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 0000000000..21e6706ba3 --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamChild.cpp @@ -0,0 +1,217 @@ +/* -*- 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::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), + mInstanceDying(false), + mState(CONSTRUCTING), + mURL(url), + mHeaders(headers), + mStreamNotify(notifyData), + mDeliveryTracker(this) { + PLUGIN_LOG_DEBUG(("%s (%s, %i, %i, %p, %s)", FULLFUNCTION, url.get(), length, + lastmodified, (void*)notifyData, headers.get())); + + AssertPluginThread(); + + memset(&mStream, 0, sizeof(mStream)); + mStream.ndata = static_cast<AStream*>(this); + mStream.url = NullableStringGet(mURL); + mStream.end = length; + mStream.lastmodified = lastmodified; + mStream.headers = NullableStringGet(mHeaders); + if (notifyData) { + mStream.notifyData = notifyData->mClosure; + notifyData->SetAssociatedStream(this); + } +} + +NPError BrowserStreamChild::StreamConstructed(const nsCString& mimeType, + const bool& seekable, + uint16_t* stype) { + NPError rv = NPERR_NO_ERROR; + + *stype = NP_NORMAL; + rv = mInstance->mPluginIface->newstream( + &mInstance->mData, const_cast<char*>(NullableStringGet(mimeType)), + &mStream, seekable, stype); + + // NP_NORMAL is the only permissible stream type + if (*stype != NP_NORMAL) { + rv = NPERR_INVALID_PARAM; + // The plugin thinks the stream is alive, so we kill it explicitly + (void)mInstance->mPluginIface->destroystream(&mInstance->mData, &mStream, + NPRES_NETWORK_ERR); + } + + 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!"); +} + +mozilla::ipc::IPCResult BrowserStreamChild::RecvWrite(const int32_t& offset, + const uint32_t& newlength, + const Buffer& data) { + PLUGIN_LOG_DEBUG_FUNCTION; + + AssertPluginThread(); + + if (ALIVE != mState) + MOZ_CRASH("Unexpected state: received data after NPP_DestroyStream?"); + + if (kStreamOpen != mStreamStatus) return IPC_OK(); + + 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 IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserStreamChild::RecvNPP_DestroyStream( + const NPReason& reason) { + PLUGIN_LOG_DEBUG_METHOD; + + if (ALIVE != mState) + MOZ_CRASH("Unexpected state: recevied NPP_DestroyStream twice?"); + + mState = DYING; + mDestroyPending = DESTROY_PENDING; + if (NPRES_DONE != reason) mStreamStatus = reason; + + EnsureDeliveryPending(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserStreamChild::Recv__delete__() { + AssertPluginThread(); + + if (DELETING != mState) MOZ_CRASH("Bad state, not DELETING"); + + return IPC_OK(); +} + +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(); + + if (DESTROY_PENDING == mDestroyPending) { + mDestroyPending = DESTROYED; + if (mState != DYING) MOZ_CRASH("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) MOZ_CRASH("Unexpected state"); + + NS_ASSERTION(mPendingData.Length(), "Called from Deliver with empty pending"); + + while (mPendingData[0].curpos < + static_cast<int32_t>(mPendingData[0].data.Length())) { + int32_t r = + mInstance->mPluginIface->writeready(&mInstance->mData, &mStream); + if (kStreamOpen != mStreamStatus) return false; + if (0 == r) // plugin wants to suspend delivery + return true; + + r = mInstance->mPluginIface->write( + &mInstance->mData, &mStream, + mPendingData[0].offset + mPendingData[0].curpos, // offset + mPendingData[0].data.Length() - mPendingData[0].curpos, // length + const_cast<char*>(mPendingData[0].data.BeginReading() + + mPendingData[0].curpos)); + if (kStreamOpen != mStreamStatus) return false; + if (0 == r) return true; + if (r < 0) { // error condition + mStreamStatus = NPRES_NETWORK_ERR; + + // Set up stream destruction + EnsureDeliveryPending(); + 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 mozilla::plugins diff --git a/dom/plugins/ipc/BrowserStreamChild.h b/dom/plugins/ipc/BrowserStreamChild.h new file mode 100644 index 0000000000..5df88d9aab --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamChild.h @@ -0,0 +1,150 @@ +/* -*- 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); + + mozilla::ipc::IPCResult RecvWrite(const int32_t& offset, + const uint32_t& newsize, + const Buffer& data); + mozilla::ipc::IPCResult RecvNPP_DestroyStream(const NPReason& reason); + virtual mozilla::ipc::IPCResult Recv__delete__() override; + + void EnsureCorrectInstance(PluginInstanceChild* i) { + if (i != mInstance) MOZ_CRASH("Incorrect stream instance"); + } + void EnsureCorrectStream(NPStream* s) { + if (s != &mStream) MOZ_CRASH("Incorrect stream data"); + } + + 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; + + /** + * 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 stream destruction. 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; + + // When NPP_Destroy is called for our instance (manager), this flag is set + // cancels the stream and avoids sending StreamDestroyed. + bool mInstanceDying; + + enum { CONSTRUCTING, ALIVE, DYING, DELETING } mState; + nsCString mURL; + nsCString mHeaders; + StreamNotifyChild* mStreamNotify; + + struct PendingData { + int32_t offset; + Buffer data; + int32_t curpos; + }; + nsTArray<PendingData> mPendingData; + + /** + * Asynchronous RecvWrite messages are never delivered to the plugin + * immediately, because that may be in the midst of an unexpected RPC + * stack frame. It instead posts a runnable using this tracker to cancel + * in case we are destroyed. + */ + ScopedRunnableMethodFactory<BrowserStreamChild> mDeliveryTracker; + base::RepeatingTimer<BrowserStreamChild> mSuspendedTimer; +}; + +} // namespace plugins +} // namespace mozilla + +#endif /* mozilla_plugins_BrowserStreamChild_h */ diff --git a/dom/plugins/ipc/BrowserStreamParent.cpp b/dom/plugins/ipc/BrowserStreamParent.cpp new file mode 100644 index 0000000000..f1a5a0e2dc --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamParent.cpp @@ -0,0 +1,85 @@ +/* -*- 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 "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::plugins { + +BrowserStreamParent::BrowserStreamParent(PluginInstanceParent* npp, + NPStream* stream) + : mNPP(npp), mStream(stream), mState(INITIALIZING) { + mStream->pdata = static_cast<AStream*>(this); + nsNPAPIStreamWrapper* wrapper = + reinterpret_cast<nsNPAPIStreamWrapper*>(mStream->ndata); + if (wrapper) { + mStreamListener = wrapper->GetStreamListener(); + } +} + +BrowserStreamParent::~BrowserStreamParent() { mStream->pdata = nullptr; } + +void BrowserStreamParent::ActorDestroy(ActorDestroyReason aWhy) { + // Implement me! Bug 1005159 +} + +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; + } else { + mState = DYING; + Unused << SendNPP_DestroyStream(reason); + } +} + +mozilla::ipc::IPCResult BrowserStreamParent::RecvStreamDestroyed() { + if (DYING != mState) { + NS_ERROR("Unexpected state"); + return IPC_FAIL_NO_REASON(this); + } + + mStreamPeer = nullptr; + + mState = DELETING; + IProtocol* mgr = Manager(); + if (!Send__delete__(this)) { + return IPC_FAIL_NO_REASON(mgr); + } + return IPC_OK(); +} + +int32_t BrowserStreamParent::WriteReady() { + if (mState == INITIALIZING) { + return 0; + } + return kSendDataChunk; +} + +int32_t BrowserStreamParent::Write(int32_t offset, int32_t len, void* buffer) { + PLUGIN_LOG_DEBUG_FUNCTION; + + NS_ASSERTION(ALIVE == mState, "Sending data after NPP_DestroyStream?"); + NS_ASSERTION(len > 0, "Non-positive length to NPP_Write"); + + if (len > kSendDataChunk) len = kSendDataChunk; + + return SendWrite(offset, mStream->end, + nsCString(static_cast<char*>(buffer), len)) + ? len + : -1; +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/BrowserStreamParent.h b/dom/plugins/ipc/BrowserStreamParent.h new file mode 100644 index 0000000000..492288eb3e --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamParent.h @@ -0,0 +1,58 @@ +/* -*- 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 mozilla::ipc::IPCResult RecvStreamDestroyed() override; + + int32_t WriteReady(); + int32_t Write(int32_t offset, int32_t len, void* buffer); + + void NPP_DestroyStream(NPReason reason); + + void SetAlive() { + if (mState == INITIALIZING) { + mState = ALIVE; + } + } + + private: + using PBrowserStreamParent::SendNPP_DestroyStream; + + PluginInstanceParent* mNPP; + NPStream* mStream; + nsCOMPtr<nsISupports> mStreamPeer; + RefPtr<nsNPAPIPluginStreamListener> mStreamListener; + + enum { INITIALIZING, DEFERRING_DESTROY, ALIVE, DYING, DELETING } mState; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/ChildTimer.cpp b/dom/plugins/ipc/ChildTimer.cpp new file mode 100644 index 0000000000..e9a096c62c --- /dev/null +++ b/dom/plugins/ipc/ChildTimer.cpp @@ -0,0 +1,31 @@ +/* -*- 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::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 mozilla::plugins diff --git a/dom/plugins/ipc/ChildTimer.h b/dom/plugins/ipc/ChildTimer.h new file mode 100644 index 0000000000..8b9ff72ded --- /dev/null +++ b/dom/plugins/ipc/ChildTimer.h @@ -0,0 +1,55 @@ +/* -*- 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" +#include "mozilla/UniquePtr.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() = default; + + uint32_t ID() const { return mID; } + + class IDComparator { + public: + bool Equals(const UniquePtr<ChildTimer>& t, uint32_t id) const { + return t->ID() == id; + } + }; + + private: + PluginInstanceChild* mInstance; + TimerFunc mFunc; + bool mRepeating; + uint32_t mID; + base::RepeatingTimer<ChildTimer> mTimer; + + void Run(); + + static uint32_t gNextTimerID; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_ChildTimer_h diff --git a/dom/plugins/ipc/D3D11SurfaceHolder.cpp b/dom/plugins/ipc/D3D11SurfaceHolder.cpp new file mode 100644 index 0000000000..429f3be673 --- /dev/null +++ b/dom/plugins/ipc/D3D11SurfaceHolder.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsDebug.h" +#include "D3D11SurfaceHolder.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/layers/TextureD3D11.h" +#include <d3d11.h> + +namespace mozilla { +namespace plugins { + +using namespace mozilla::gfx; +using namespace mozilla::layers; + +D3D11SurfaceHolder::D3D11SurfaceHolder(ID3D11Texture2D* back, + SurfaceFormat format, + const IntSize& size) + : mDevice11(DeviceManagerDx::Get()->GetContentDevice()), + mBack(back), + mFormat(format), + mSize(size) {} + +D3D11SurfaceHolder::~D3D11SurfaceHolder() {} + +bool D3D11SurfaceHolder::IsValid() { + // If a TDR occurred, platform devices will be recreated. + if (DeviceManagerDx::Get()->GetContentDevice() != mDevice11) { + return false; + } + return true; +} + +bool D3D11SurfaceHolder::CopyToTextureClient(TextureClient* aClient) { + MOZ_ASSERT(NS_IsMainThread()); + + D3D11TextureData* data = aClient->GetInternalData()->AsD3D11TextureData(); + if (!data) { + // We don't support this yet. We expect to have a D3D11 compositor, and + // therefore D3D11 surfaces. + NS_WARNING("Plugin DXGI surface has unsupported TextureClient"); + return false; + } + + RefPtr<ID3D11DeviceContext> context; + mDevice11->GetImmediateContext(getter_AddRefs(context)); + if (!context) { + NS_WARNING("Could not get an immediate D3D11 context"); + return false; + } + + TextureClientAutoLock autoLock(aClient, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return false; + } + + RefPtr<IDXGIKeyedMutex> mutex; + HRESULT hr = mBack->QueryInterface(__uuidof(IDXGIKeyedMutex), + (void**)getter_AddRefs(mutex)); + if (FAILED(hr) || !mutex) { + NS_WARNING("Could not acquire an IDXGIKeyedMutex"); + return false; + } + + { + AutoTextureLock lock(mutex, hr); + if (hr == WAIT_ABANDONED || hr == WAIT_TIMEOUT || FAILED(hr)) { + NS_WARNING( + "Could not acquire DXGI surface lock - plugin forgot to release?"); + return false; + } + + context->CopyResource(data->GetD3D11Texture(), mBack); + } + return true; +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/D3D11SurfaceHolder.h b/dom/plugins/ipc/D3D11SurfaceHolder.h new file mode 100644 index 0000000000..4031091f7c --- /dev/null +++ b/dom/plugins/ipc/D3D11SurfaceHolder.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 _include_dom_plugins_ipc_D3D11SurfaceHolder_h__ +#define _include_dom_plugins_ipc_D3D11SurfaceHolder_h__ + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace layers { +class D3D11ShareHandleImage; +class TextureClient; +} // namespace layers + +namespace plugins { + +class D3D11SurfaceHolder { + public: + D3D11SurfaceHolder(ID3D11Texture2D* back, gfx::SurfaceFormat format, + const gfx::IntSize& size); + + NS_INLINE_DECL_REFCOUNTING(D3D11SurfaceHolder); + + bool IsValid(); + bool CopyToTextureClient(layers::TextureClient* aClient); + + gfx::SurfaceFormat GetFormat() const { return mFormat; } + const gfx::IntSize& GetSize() const { return mSize; } + + private: + ~D3D11SurfaceHolder(); + + private: + RefPtr<ID3D11Device> mDevice11; + RefPtr<ID3D11Texture2D> mBack; + gfx::SurfaceFormat mFormat; + gfx::IntSize mSize; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // _include_dom_plugins_ipc_D3D11nSurfaceHolder_h__ diff --git a/dom/plugins/ipc/FunctionBroker.cpp b/dom/plugins/ipc/FunctionBroker.cpp new file mode 100644 index 0000000000..9068cf322d --- /dev/null +++ b/dom/plugins/ipc/FunctionBroker.cpp @@ -0,0 +1,1429 @@ +/* -*- 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 "FunctionBroker.h" +#include "FunctionBrokerParent.h" +#include "PluginQuirks.h" + +#if defined(XP_WIN) +# include <commdlg.h> +# include <schannel.h> +# include <sddl.h> +#endif // defined(XP_WIN) + +using namespace mozilla; +using namespace mozilla::ipc; +using namespace mozilla::plugins; + +namespace mozilla::plugins { + +template <int QuirkFlag> +static bool CheckQuirks(int aQuirks) { + return static_cast<bool>(aQuirks & QuirkFlag); +} + +void FreeDestructor(void* aObj) { free(aObj); } + +#if defined(XP_WIN) + +// Specialization of EndpointHandlers for Flash file dialog brokering. +struct FileDlgEHContainer { + template <Endpoint e> + struct EndpointHandler; +}; + +template <> +struct FileDlgEHContainer::EndpointHandler<CLIENT> + : public BaseEndpointHandler<CLIENT, + FileDlgEHContainer::EndpointHandler<CLIENT>> { + using BaseEndpointHandler<CLIENT, EndpointHandler<CLIENT>>::Copy; + + inline static void Copy(OpenFileNameIPC& aDest, const LPOPENFILENAMEW& aSrc) { + aDest.CopyFromOfn(aSrc); + } + inline static void Copy(LPOPENFILENAMEW& aDest, + const OpenFileNameRetIPC& aSrc) { + aSrc.AddToOfn(aDest); + } +}; + +template <> +struct FileDlgEHContainer::EndpointHandler<SERVER> + : public BaseEndpointHandler<SERVER, + FileDlgEHContainer::EndpointHandler<SERVER>> { + using BaseEndpointHandler<SERVER, EndpointHandler<SERVER>>::Copy; + + inline static void Copy(OpenFileNameRetIPC& aDest, + const LPOPENFILENAMEW& aSrc) { + aDest.CopyFromOfn(aSrc); + } + inline static void Copy(ServerCallData* aScd, LPOPENFILENAMEW& aDest, + const OpenFileNameIPC& aSrc) { + MOZ_ASSERT(!aDest); + ServerCallData::DestructorType* destructor = [](void* aObj) { + OpenFileNameIPC::FreeOfnStrings(static_cast<LPOPENFILENAMEW>(aObj)); + DeleteDestructor<OPENFILENAMEW>(aObj); + }; + aDest = aScd->Allocate<OPENFILENAMEW>(destructor); + aSrc.AllocateOfnStrings(aDest); + aSrc.AddToOfn(aDest); + } +}; + +// FunctionBroker type that uses FileDlgEHContainer +template <FunctionHookId functionId, typename FunctionType> +using FileDlgFunctionBroker = + FunctionBroker<functionId, FunctionType, FileDlgEHContainer>; + +// Specialization of EndpointHandlers for Flash SSL brokering. +struct SslEHContainer { + template <Endpoint e> + struct EndpointHandler; +}; + +template <> +struct SslEHContainer::EndpointHandler<CLIENT> + : public BaseEndpointHandler<CLIENT, + SslEHContainer::EndpointHandler<CLIENT>> { + using BaseEndpointHandler<CLIENT, EndpointHandler<CLIENT>>::Copy; + + inline static void Copy(uint64_t& aDest, const PSecHandle& aSrc) { + MOZ_ASSERT((aSrc->dwLower == aSrc->dwUpper) && IsOdd(aSrc->dwLower)); + aDest = static_cast<uint64_t>(aSrc->dwLower); + } + inline static void Copy(PSecHandle& aDest, const uint64_t& aSrc) { + MOZ_ASSERT(IsOdd(aSrc)); + aDest->dwLower = static_cast<ULONG_PTR>(aSrc); + aDest->dwUpper = static_cast<ULONG_PTR>(aSrc); + } + inline static void Copy(IPCSchannelCred& aDest, const PSCHANNEL_CRED& aSrc) { + if (aSrc) { + aDest.CopyFrom(aSrc); + } + } + inline static void Copy(IPCInternetBuffers& aDest, + const LPINTERNET_BUFFERSA& aSrc) { + aDest.CopyFrom(aSrc); + } +}; + +template <> +struct SslEHContainer::EndpointHandler<SERVER> + : public BaseEndpointHandler<SERVER, + SslEHContainer::EndpointHandler<SERVER>> { + using BaseEndpointHandler<SERVER, EndpointHandler<SERVER>>::Copy; + + // PSecHandle is the same thing as PCtxtHandle and PCredHandle. + inline static void Copy(uint64_t& aDest, const PSecHandle& aSrc) { + // If the SecHandle was an error then don't store it. + if (!aSrc) { + aDest = 0; + return; + } + + static uint64_t sNextVal = 1; + UlongPair key(aSrc->dwLower, aSrc->dwUpper); + // Fetch val by reference to update the value in the map + uint64_t& val = sPairToIdMap[key]; + if (val == 0) { + MOZ_ASSERT(IsOdd(sNextVal)); + val = sNextVal; + sIdToPairMap[val] = key; + sNextVal += 2; + } + aDest = val; + } + + // HANDLEs and HINTERNETs marshal with obfuscation (for return values) + inline static void Copy(uint64_t& aDest, void* const& aSrc) { + // If the HANDLE/HINTERNET was an error then don't store it. + if (!aSrc) { + aDest = 0; + return; + } + + static uint64_t sNextVal = 1; + // Fetch val by reference to update the value in the map + uint64_t& val = sPtrToIdMap[aSrc]; + if (val == 0) { + MOZ_ASSERT(IsOdd(sNextVal)); + val = sNextVal; + sIdToPtrMap[val] = aSrc; + sNextVal += 2; + } + aDest = val; + } + + // HANDLEs and HINTERNETs unmarshal with obfuscation + inline static void Copy(void*& aDest, const uint64_t& aSrc) { + aDest = nullptr; + MOZ_RELEASE_ASSERT(IsOdd(aSrc)); + + // If the src is not found in the map then we get aDest == 0 + void* ptr = sIdToPtrMap[aSrc]; + aDest = reinterpret_cast<void*>(ptr); + MOZ_RELEASE_ASSERT(aDest); + } + + inline static void Copy(PSCHANNEL_CRED& aDest, const IPCSchannelCred& aSrc) { + if (aDest) { + aSrc.CopyTo(aDest); + } + } + + inline static void Copy(ServerCallData* aScd, PSecHandle& aDest, + const uint64_t& aSrc) { + MOZ_ASSERT(!aDest); + MOZ_RELEASE_ASSERT(IsOdd(aSrc)); + + // If the src is not found in the map then we get the pair { 0, 0 } + aDest = aScd->Allocate<SecHandle>(); + const UlongPair& pair = sIdToPairMap[aSrc]; + MOZ_RELEASE_ASSERT(pair.first || pair.second); + aDest->dwLower = pair.first; + aDest->dwUpper = pair.second; + } + + inline static void Copy(ServerCallData* aScd, PSCHANNEL_CRED& aDest, + const IPCSchannelCred& aSrc) { + MOZ_ASSERT(!aDest); + aDest = aScd->Allocate<SCHANNEL_CRED>(); + Copy(aDest, aSrc); + } + + inline static void Copy(ServerCallData* aScd, LPINTERNET_BUFFERSA& aDest, + const IPCInternetBuffers& aSrc) { + MOZ_ASSERT(!aDest); + aSrc.CopyTo(aDest); + ServerCallData::DestructorType* destructor = [](void* aObj) { + LPINTERNET_BUFFERSA inetBuf = static_cast<LPINTERNET_BUFFERSA>(aObj); + IPCInternetBuffers::FreeBuffers(inetBuf); + FreeDestructor(inetBuf); + }; + aScd->PostDestructor(aDest, destructor); + } +}; + +// FunctionBroker type that uses SslEHContainer +template <FunctionHookId functionId, typename FunctionType> +using SslFunctionBroker = + FunctionBroker<functionId, FunctionType, SslEHContainer>; + +/* GetKeyState */ + +typedef FunctionBroker<ID_GetKeyState, decltype(GetKeyState)> GetKeyStateFB; + +template <> +ShouldHookFunc* const GetKeyStateFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_GETKEYSTATE>; + +/* SetCursorPos */ + +typedef FunctionBroker<ID_SetCursorPos, decltype(SetCursorPos)> SetCursorPosFB; + +/* GetSaveFileNameW */ + +typedef FileDlgFunctionBroker<ID_GetSaveFileNameW, decltype(GetSaveFileNameW)> + GetSaveFileNameWFB; + +// Remember files granted access in the chrome process +static void GrantFileAccess(base::ProcessId aClientId, LPOPENFILENAME& aLpofn, + bool isSave) { +# if defined(MOZ_SANDBOX) + if (aLpofn->Flags & OFN_ALLOWMULTISELECT) { + // We only support multiselect with the OFN_EXPLORER flag. + // This guarantees that ofn.lpstrFile follows the pattern below. + MOZ_ASSERT(aLpofn->Flags & OFN_EXPLORER); + + // lpstrFile is one of two things: + // 1. A null terminated full path to a file, or + // 2. A path to a folder, followed by a NULL, followed by a + // list of file names, each NULL terminated, followed by an + // additional NULL (so it is also double-NULL terminated). + std::wstring path = std::wstring(aLpofn->lpstrFile); + MOZ_ASSERT(aLpofn->nFileOffset > 0); + // For condition #1, nFileOffset points to the file name in the path. + // It will be preceeded by a non-NULL character from the path. + if (aLpofn->lpstrFile[aLpofn->nFileOffset - 1] != L'\0') { + FunctionBrokerParent::GetSandboxPermissions()->GrantFileAccess( + aClientId, path.c_str(), isSave); + } else { + // This is condition #2 + wchar_t* nextFile = aLpofn->lpstrFile + path.size() + 1; + while (*nextFile != L'\0') { + std::wstring nextFileStr(nextFile); + std::wstring fullPath = path + std::wstring(L"\\") + nextFileStr; + FunctionBrokerParent::GetSandboxPermissions()->GrantFileAccess( + aClientId, fullPath.c_str(), isSave); + nextFile += nextFileStr.size() + 1; + } + } + } else { + FunctionBrokerParent::GetSandboxPermissions()->GrantFileAccess( + aClientId, aLpofn->lpstrFile, isSave); + } +# else + MOZ_ASSERT_UNREACHABLE( + "GetFileName IPC message is only available on " + "Windows builds with sandbox."); +# endif +} + +template <> +template <> +BROKER_DISABLE_CFGUARD BOOL GetSaveFileNameWFB::RunFunction( + GetSaveFileNameWFB::FunctionType* aOrigFunction, base::ProcessId aClientId, + LPOPENFILENAMEW& aLpofn) const { + BOOL result = aOrigFunction(aLpofn); + if (result) { + // Record any file access permission that was just granted. + GrantFileAccess(aClientId, aLpofn, true); + } + return result; +} + +template <> +template <> +struct GetSaveFileNameWFB::Response::Info::ShouldMarshal<0> { + static const bool value = true; +}; + +/* GetOpenFileNameW */ + +typedef FileDlgFunctionBroker<ID_GetOpenFileNameW, decltype(GetOpenFileNameW)> + GetOpenFileNameWFB; + +template <> +template <> +BROKER_DISABLE_CFGUARD BOOL GetOpenFileNameWFB::RunFunction( + GetOpenFileNameWFB::FunctionType* aOrigFunction, base::ProcessId aClientId, + LPOPENFILENAMEW& aLpofn) const { + BOOL result = aOrigFunction(aLpofn); + if (result) { + // Record any file access permission that was just granted. + GrantFileAccess(aClientId, aLpofn, false); + } + return result; +} + +template <> +template <> +struct GetOpenFileNameWFB::Response::Info::ShouldMarshal<0> { + static const bool value = true; +}; + +/* InternetOpenA */ + +typedef SslFunctionBroker<ID_InternetOpenA, decltype(InternetOpenA)> + InternetOpenAFB; + +template <> +ShouldHookFunc* const InternetOpenAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +/* InternetConnectA */ + +typedef SslFunctionBroker<ID_InternetConnectA, decltype(InternetConnectA)> + InternetConnectAFB; + +template <> +ShouldHookFunc* const InternetConnectAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef InternetConnectAFB::Request ICAReqHandler; + +template <> +bool ICAReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPCSTR& srv, const INTERNET_PORT& port, + const LPCSTR& user, const LPCSTR& pass, + const DWORD& svc, const DWORD& flags, + const DWORD_PTR& cxt) { + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h)); +} + +/* InternetCloseHandle */ + +typedef SslFunctionBroker<ID_InternetCloseHandle, decltype(InternetCloseHandle)> + InternetCloseHandleFB; + +template <> +ShouldHookFunc* const InternetCloseHandleFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef InternetCloseHandleFB::Request ICHReqHandler; + +template <> +bool ICHReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> HINTERNET" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h)); +} + +/* InternetQueryDataAvailable */ + +typedef SslFunctionBroker<ID_InternetQueryDataAvailable, + decltype(InternetQueryDataAvailable)> + InternetQueryDataAvailableFB; + +template <> +ShouldHookFunc* const InternetQueryDataAvailableFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef InternetQueryDataAvailableFB::Request IQDAReq; +typedef InternetQueryDataAvailableFB::RequestDelegate<BOOL HOOK_CALL(HINTERNET)> + IQDADelegateReq; + +template <> +void IQDAReq::Marshal(IpdlTuple& aTuple, const HINTERNET& file, + const LPDWORD& nBytes, const DWORD& flags, + const DWORD_PTR& cxt) { + IQDADelegateReq::Marshal(aTuple, file); +} + +template <> +bool IQDAReq::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& file, LPDWORD& nBytes, DWORD& flags, + DWORD_PTR& cxt) { + bool success = IQDADelegateReq::Unmarshal(aScd, aTuple, file); + if (!success) { + return false; + } + flags = 0; + cxt = 0; + nBytes = aScd.Allocate<DWORD>(); + return true; +} + +template <> +bool IQDAReq::ShouldBroker(Endpoint endpoint, const HINTERNET& file, + const LPDWORD& nBytes, const DWORD& flags, + const DWORD_PTR& cxt) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> HINTERNET" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || ((flags == 0) && (cxt == 0) && + IsOdd(reinterpret_cast<uint64_t>(file))); +} + +template <> +template <> +struct InternetQueryDataAvailableFB::Response::Info::ShouldMarshal<1> { + static const bool value = true; +}; + +/* InternetReadFile */ + +typedef SslFunctionBroker<ID_InternetReadFile, decltype(InternetReadFile)> + InternetReadFileFB; + +template <> +ShouldHookFunc* const InternetReadFileFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef InternetReadFileFB::Request IRFRequestHandler; +typedef InternetReadFileFB::RequestDelegate<BOOL HOOK_CALL(HINTERNET, DWORD)> + IRFDelegateReq; + +template <> +void IRFRequestHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const LPVOID& buf, const DWORD& nBytesToRead, + const LPDWORD& nBytesRead) { + IRFDelegateReq::Marshal(aTuple, h, nBytesToRead); +} + +template <> +bool IRFRequestHandler::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& h, LPVOID& buf, + DWORD& nBytesToRead, LPDWORD& nBytesRead) { + bool ret = IRFDelegateReq::Unmarshal(aScd, aTuple, h, nBytesToRead); + if (!ret) { + return false; + } + + nBytesRead = aScd.Allocate<DWORD>(); + MOZ_ASSERT(nBytesToRead > 0); + aScd.AllocateMemory(nBytesToRead, buf); + return true; +} + +template <> +bool IRFRequestHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPVOID& buf, + const DWORD& nBytesToRead, + const LPDWORD& nBytesRead) { + // For server-side validation, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h)); +} + +typedef InternetReadFileFB::Response IRFResponseHandler; +typedef InternetReadFileFB::ResponseDelegate<BOOL HOOK_CALL( + nsDependentCSubstring)> + IRFDelegateResponseHandler; + +// Marshal the output parameter that we sent to the response delegate. +template <> +template <> +struct IRFResponseHandler::Info::ShouldMarshal<0> { + static const bool value = true; +}; + +template <> +void IRFResponseHandler::Marshal(IpdlTuple& aTuple, const BOOL& ret, + const HINTERNET& h, const LPVOID& buf, + const DWORD& nBytesToRead, + const LPDWORD& nBytesRead) { + nsDependentCSubstring str; + if (*nBytesRead) { + str.Assign(static_cast<const char*>(buf), *nBytesRead); + } + IRFDelegateResponseHandler::Marshal(aTuple, ret, str); +} + +template <> +bool IRFResponseHandler::Unmarshal(const IpdlTuple& aTuple, BOOL& ret, + HINTERNET& h, LPVOID& buf, + DWORD& nBytesToRead, LPDWORD& nBytesRead) { + nsDependentCSubstring str; + bool success = IRFDelegateResponseHandler::Unmarshal(aTuple, ret, str); + if (!success) { + return false; + } + + if (str.Length()) { + memcpy(buf, str.Data(), str.Length()); + *nBytesRead = str.Length(); + } + return true; +} + +/* InternetWriteFile */ + +typedef SslFunctionBroker<ID_InternetWriteFile, decltype(InternetWriteFile)> + InternetWriteFileFB; + +template <> +ShouldHookFunc* const InternetWriteFileFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef InternetWriteFileFB::Request IWFReqHandler; +typedef InternetWriteFileFB::RequestDelegate<int HOOK_CALL( + HINTERNET, nsDependentCSubstring)> + IWFDelegateReqHandler; + +template <> +void IWFReqHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& file, + const LPCVOID& buf, const DWORD& nToWrite, + const LPDWORD& nWritten) { + MOZ_ASSERT(nWritten); + IWFDelegateReqHandler::Marshal( + aTuple, file, + nsDependentCSubstring(static_cast<const char*>(buf), nToWrite)); +} + +template <> +bool IWFReqHandler::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& file, LPCVOID& buf, DWORD& nToWrite, + LPDWORD& nWritten) { + nsDependentCSubstring str; + if (!IWFDelegateReqHandler::Unmarshal(aScd, aTuple, file, str)) { + return false; + } + + aScd.AllocateString(str, buf, false); + nToWrite = str.Length(); + nWritten = aScd.Allocate<DWORD>(); + return true; +} + +template <> +bool IWFReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& file, + const LPCVOID& buf, const DWORD& nToWrite, + const LPDWORD& nWritten) { + // For server-side validation, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(file)); +} + +template <> +template <> +struct InternetWriteFileFB::Response::Info::ShouldMarshal<3> { + static const bool value = true; +}; + +/* InternetSetOptionA */ + +typedef SslFunctionBroker<ID_InternetSetOptionA, decltype(InternetSetOptionA)> + InternetSetOptionAFB; + +template <> +ShouldHookFunc* const InternetSetOptionAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef InternetSetOptionAFB::Request ISOAReqHandler; +typedef InternetSetOptionAFB::RequestDelegate<BOOL HOOK_CALL( + HINTERNET, DWORD, nsDependentCSubstring)> + ISOADelegateReqHandler; + +template <> +void ISOAReqHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const DWORD& opt, const LPVOID& buf, + const DWORD& bufLen) { + ISOADelegateReqHandler::Marshal( + aTuple, h, opt, + nsDependentCSubstring(static_cast<const char*>(buf), bufLen)); +} + +template <> +bool ISOAReqHandler::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& h, DWORD& opt, LPVOID& buf, + DWORD& bufLen) { + nsDependentCSubstring str; + if (!ISOADelegateReqHandler::Unmarshal(aScd, aTuple, h, opt, str)) { + return false; + } + + aScd.AllocateString(str, buf, false); + bufLen = str.Length(); + return true; +} + +template <> +bool ISOAReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const DWORD& opt, const LPVOID& buf, + const DWORD& bufLen) { + // For server-side validation, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h)); +} + +/* HttpAddRequestHeadersA */ + +typedef SslFunctionBroker<ID_HttpAddRequestHeadersA, + decltype(HttpAddRequestHeadersA)> + HttpAddRequestHeadersAFB; + +template <> +ShouldHookFunc* const HttpAddRequestHeadersAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef HttpAddRequestHeadersAFB::Request HARHAReqHandler; + +template <> +bool HARHAReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPCSTR& head, const DWORD& headLen, + const DWORD& mods) { + // For server-side validation, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h)); +} + +/* HttpOpenRequestA */ + +typedef SslFunctionBroker<ID_HttpOpenRequestA, decltype(HttpOpenRequestA)> + HttpOpenRequestAFB; + +template <> +ShouldHookFunc* const HttpOpenRequestAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef HttpOpenRequestAFB::Request HORAReqHandler; +typedef HttpOpenRequestAFB::RequestDelegate<HINTERNET HOOK_CALL( + HINTERNET, LPCSTR, LPCSTR, LPCSTR, LPCSTR, CopyableTArray<nsCString>, DWORD, + DWORD_PTR)> + HORADelegateReqHandler; + +template <> +void HORAReqHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const LPCSTR& verb, const LPCSTR& obj, + const LPCSTR& ver, const LPCSTR& ref, + LPCSTR* const& acceptTypes, const DWORD& flags, + const DWORD_PTR& cxt) { + CopyableTArray<nsCString> arrayAcceptTypes; + LPCSTR* curAcceptType = acceptTypes; + if (curAcceptType) { + while (*curAcceptType) { + arrayAcceptTypes.AppendElement(nsCString(*curAcceptType)); + ++curAcceptType; + } + } + // XXX Could we move arrayAcceptTypes here? + HORADelegateReqHandler::Marshal(aTuple, h, verb, obj, ver, ref, + arrayAcceptTypes, flags, cxt); +} + +template <> +bool HORAReqHandler::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& h, LPCSTR& verb, LPCSTR& obj, + LPCSTR& ver, LPCSTR& ref, LPCSTR*& acceptTypes, + DWORD& flags, DWORD_PTR& cxt) { + CopyableTArray<nsCString> arrayAcceptTypes; + if (!HORADelegateReqHandler::Unmarshal(aScd, aTuple, h, verb, obj, ver, ref, + arrayAcceptTypes, flags, cxt)) { + return false; + } + if (arrayAcceptTypes.Length() == 0) { + acceptTypes = nullptr; + } else { + aScd.AllocateMemory((arrayAcceptTypes.Length() + 1) * sizeof(LPCSTR), + acceptTypes); + for (size_t i = 0; i < arrayAcceptTypes.Length(); ++i) { + aScd.AllocateString(arrayAcceptTypes[i], acceptTypes[i]); + } + acceptTypes[arrayAcceptTypes.Length()] = nullptr; + } + return true; +} + +template <> +bool HORAReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPCSTR& verb, const LPCSTR& obj, + const LPCSTR& ver, const LPCSTR& ref, + LPCSTR* const& acceptTypes, + const DWORD& flags, const DWORD_PTR& cxt) { + // For the server-side test, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h)); +} + +/* HttpQueryInfoA */ + +typedef SslFunctionBroker<ID_HttpQueryInfoA, decltype(HttpQueryInfoA)> + HttpQueryInfoAFB; + +template <> +ShouldHookFunc* const HttpQueryInfoAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef HttpQueryInfoAFB::Request HQIARequestHandler; +typedef HttpQueryInfoAFB::RequestDelegate<BOOL HOOK_CALL(HINTERNET, DWORD, BOOL, + DWORD, BOOL, DWORD)> + HQIADelegateRequestHandler; + +template <> +void HQIARequestHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const DWORD& lvl, const LPVOID& buf, + const LPDWORD& bufLen, const LPDWORD& idx) { + HQIADelegateRequestHandler::Marshal(aTuple, h, lvl, bufLen != nullptr, + bufLen ? *bufLen : 0, idx != nullptr, + idx ? *idx : 0); +} + +template <> +bool HQIARequestHandler::Unmarshal(ServerCallData& aScd, + const IpdlTuple& aTuple, HINTERNET& h, + DWORD& lvl, LPVOID& buf, LPDWORD& bufLen, + LPDWORD& idx) { + BOOL hasBufLen, hasIdx; + DWORD tempBufLen, tempIdx; + bool success = HQIADelegateRequestHandler::Unmarshal( + aScd, aTuple, h, lvl, hasBufLen, tempBufLen, hasIdx, tempIdx); + if (!success) { + return false; + } + + bufLen = nullptr; + if (hasBufLen) { + aScd.AllocateMemory(tempBufLen, buf, bufLen); + } + + idx = nullptr; + if (hasIdx) { + idx = aScd.Allocate<DWORD>(tempIdx); + } + + return true; +} + +template <> +bool HQIARequestHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const DWORD& lvl, const LPVOID& buf, + const LPDWORD& bufLen, + const LPDWORD& idx) { + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h)); +} + +// Marshal all of the output parameters that we sent to the response delegate. +template <> +template <> +struct HttpQueryInfoAFB::Response::Info::ShouldMarshal<0> { + static const bool value = true; +}; +template <> +template <> +struct HttpQueryInfoAFB::Response::Info::ShouldMarshal<1> { + static const bool value = true; +}; +template <> +template <> +struct HttpQueryInfoAFB::Response::Info::ShouldMarshal<2> { + static const bool value = true; +}; + +typedef HttpQueryInfoAFB::Response HQIAResponseHandler; +typedef HttpQueryInfoAFB::ResponseDelegate<BOOL HOOK_CALL(nsDependentCSubstring, + DWORD, DWORD)> + HQIADelegateResponseHandler; + +template <> +void HQIAResponseHandler::Marshal(IpdlTuple& aTuple, const BOOL& ret, + const HINTERNET& h, const DWORD& lvl, + const LPVOID& buf, const LPDWORD& bufLen, + const LPDWORD& idx) { + nsDependentCSubstring str; + if (buf && ret) { + MOZ_ASSERT(bufLen); + str.Assign(static_cast<const char*>(buf), *bufLen); + } + // Note that we send the bufLen separately to handle the case where buf wasn't + // allocated or large enough to hold the entire return value. bufLen is then + // the required buffer size. + HQIADelegateResponseHandler::Marshal(aTuple, ret, str, bufLen ? *bufLen : 0, + idx ? *idx : 0); +} + +template <> +bool HQIAResponseHandler::Unmarshal(const IpdlTuple& aTuple, BOOL& ret, + HINTERNET& h, DWORD& lvl, LPVOID& buf, + LPDWORD& bufLen, LPDWORD& idx) { + DWORD totalBufLen = *bufLen; + nsDependentCSubstring str; + DWORD tempBufLen, tempIdx; + bool success = HQIADelegateResponseHandler::Unmarshal(aTuple, ret, str, + tempBufLen, tempIdx); + if (!success) { + return false; + } + + if (bufLen) { + *bufLen = tempBufLen; + } + if (idx) { + *idx = tempIdx; + } + + if (buf && ret) { + // When HttpQueryInfo returns strings, the buffer length will not include + // the null terminator. Rather than (brittle-y) trying to determine if the + // return buffer is a string, we always tack on a null terminator if the + // buffer has room for it. + MOZ_ASSERT(str.Length() == *bufLen); + memcpy(buf, str.Data(), str.Length()); + if (str.Length() < totalBufLen) { + char* cbuf = static_cast<char*>(buf); + cbuf[str.Length()] = '\0'; + } + } + return true; +} + +/* HttpSendRequestA */ + +typedef SslFunctionBroker<ID_HttpSendRequestA, decltype(HttpSendRequestA)> + HttpSendRequestAFB; + +template <> +ShouldHookFunc* const HttpSendRequestAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef HttpSendRequestAFB::Request HSRARequestHandler; +typedef HttpSendRequestAFB::RequestDelegate<BOOL HOOK_CALL( + HINTERNET, nsDependentCSubstring, nsDependentCSubstring)> + HSRADelegateRequestHandler; + +template <> +void HSRARequestHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const LPCSTR& head, const DWORD& headLen, + const LPVOID& opt, const DWORD& optLen) { + nsDependentCSubstring headStr; + headStr.SetIsVoid(head == nullptr); + if (head) { + // HttpSendRequest allows headLen == -1L for length of a null terminated + // string. + DWORD ncHeadLen = headLen; + if (ncHeadLen == -1L) { + ncHeadLen = strlen(head); + } + headStr.Rebind(head, ncHeadLen); + } + nsDependentCSubstring optStr; + optStr.SetIsVoid(opt == nullptr); + if (opt) { + optStr.Rebind(static_cast<const char*>(opt), optLen); + } + HSRADelegateRequestHandler::Marshal(aTuple, h, headStr, optStr); +} + +template <> +bool HSRARequestHandler::Unmarshal(ServerCallData& aScd, + const IpdlTuple& aTuple, HINTERNET& h, + LPCSTR& head, DWORD& headLen, LPVOID& opt, + DWORD& optLen) { + nsDependentCSubstring headStr; + nsDependentCSubstring optStr; + bool success = + HSRADelegateRequestHandler::Unmarshal(aScd, aTuple, h, headStr, optStr); + if (!success) { + return false; + } + + if (headStr.IsVoid()) { + head = nullptr; + MOZ_ASSERT(headLen == 0); + } else { + aScd.AllocateString(headStr, head, false); + headLen = headStr.Length(); + } + + if (optStr.IsVoid()) { + opt = nullptr; + MOZ_ASSERT(optLen == 0); + } else { + aScd.AllocateString(optStr, opt, false); + optLen = optStr.Length(); + } + return true; +} + +template <> +bool HSRARequestHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPCSTR& head, const DWORD& headLen, + const LPVOID& opt, const DWORD& optLen) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> HINTERNET" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h)); +} + +/* HttpSendRequestExA */ + +typedef SslFunctionBroker<ID_HttpSendRequestExA, decltype(HttpSendRequestExA)> + HttpSendRequestExAFB; + +template <> +ShouldHookFunc* const HttpSendRequestExAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef RequestInfo<ID_HttpSendRequestExA> HSRExAReqInfo; + +template <> +template <> +struct HSRExAReqInfo::FixedValue<2> { + static const LPINTERNET_BUFFERSA value; +}; +const LPINTERNET_BUFFERSA HSRExAReqInfo::FixedValue<2>::value = nullptr; + +// Docs for HttpSendRequestExA say this parameter 'must' be zero but Flash +// passes other values. +// template<> template<> +// struct HSRExAReqInfo::FixedValue<3> { static const DWORD value = 0; }; + +template <> +template <> +struct HSRExAReqInfo::FixedValue<4> { + static const DWORD_PTR value; +}; +const DWORD_PTR HSRExAReqInfo::FixedValue<4>::value = 0; + +/* HttpEndRequestA */ + +typedef SslFunctionBroker<ID_HttpEndRequestA, decltype(HttpEndRequestA)> + HttpEndRequestAFB; + +template <> +ShouldHookFunc* const HttpEndRequestAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef RequestInfo<ID_HttpEndRequestA> HERAReqInfo; + +template <> +template <> +struct HERAReqInfo::FixedValue<1> { + static const LPINTERNET_BUFFERSA value; +}; +const LPINTERNET_BUFFERSA HERAReqInfo::FixedValue<1>::value = nullptr; + +template <> +template <> +struct HERAReqInfo::FixedValue<2> { + static const DWORD value; +}; +const DWORD HERAReqInfo::FixedValue<2>::value = 0; + +template <> +template <> +struct HERAReqInfo::FixedValue<3> { + static const DWORD_PTR value; +}; +const DWORD_PTR HERAReqInfo::FixedValue<3>::value = 0; + +/* InternetQueryOptionA */ + +typedef SslFunctionBroker<ID_InternetQueryOptionA, + decltype(InternetQueryOptionA)> + InternetQueryOptionAFB; + +template <> +ShouldHookFunc* const InternetQueryOptionAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef InternetQueryOptionAFB::Request IQOARequestHandler; +typedef InternetQueryOptionAFB::RequestDelegate<BOOL HOOK_CALL(HINTERNET, DWORD, + DWORD)> + IQOADelegateRequestHandler; + +template <> +void IQOARequestHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const DWORD& opt, const LPVOID& buf, + const LPDWORD& bufLen) { + MOZ_ASSERT(bufLen); + IQOADelegateRequestHandler::Marshal(aTuple, h, opt, buf ? *bufLen : 0); +} + +template <> +bool IQOARequestHandler::Unmarshal(ServerCallData& aScd, + const IpdlTuple& aTuple, HINTERNET& h, + DWORD& opt, LPVOID& buf, LPDWORD& bufLen) { + DWORD tempBufLen; + bool success = + IQOADelegateRequestHandler::Unmarshal(aScd, aTuple, h, opt, tempBufLen); + if (!success) { + return false; + } + + aScd.AllocateMemory(tempBufLen, buf, bufLen); + return true; +} + +template <> +bool IQOARequestHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const DWORD& opt, const LPVOID& buf, + const LPDWORD& bufLen) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> HINTERNET" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h)); +} + +// Marshal all of the output parameters that we sent to the response delegate. +template <> +template <> +struct InternetQueryOptionAFB::Response::Info::ShouldMarshal<0> { + static const bool value = true; +}; +template <> +template <> +struct InternetQueryOptionAFB::Response::Info::ShouldMarshal<1> { + static const bool value = true; +}; + +typedef InternetQueryOptionAFB::Response IQOAResponseHandler; +typedef InternetQueryOptionAFB::ResponseDelegate<BOOL HOOK_CALL( + nsDependentCSubstring, DWORD)> + IQOADelegateResponseHandler; + +template <> +void IQOAResponseHandler::Marshal(IpdlTuple& aTuple, const BOOL& ret, + const HINTERNET& h, const DWORD& opt, + const LPVOID& buf, const LPDWORD& bufLen) { + nsDependentCSubstring str; + if (buf && ret) { + MOZ_ASSERT(*bufLen); + str.Assign(static_cast<const char*>(buf), *bufLen); + } + IQOADelegateResponseHandler::Marshal(aTuple, ret, str, *bufLen); +} + +template <> +bool IQOAResponseHandler::Unmarshal(const IpdlTuple& aTuple, BOOL& ret, + HINTERNET& h, DWORD& opt, LPVOID& buf, + LPDWORD& bufLen) { + nsDependentCSubstring str; + bool success = + IQOADelegateResponseHandler::Unmarshal(aTuple, ret, str, *bufLen); + if (!success) { + return false; + } + + if (buf && ret) { + MOZ_ASSERT(str.Length() == *bufLen); + memcpy(buf, str.Data(), str.Length()); + } + return true; +} + +/* InternetErrorDlg */ + +typedef SslFunctionBroker<ID_InternetErrorDlg, decltype(InternetErrorDlg)> + InternetErrorDlgFB; + +template <> +ShouldHookFunc* const InternetErrorDlgFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef RequestInfo<ID_InternetErrorDlg> IEDReqInfo; + +template <> +template <> +struct IEDReqInfo::FixedValue<4> { + static LPVOID* const value; +}; +LPVOID* const IEDReqInfo::FixedValue<4>::value = nullptr; + +typedef InternetErrorDlgFB::Request IEDReqHandler; + +template <> +bool IEDReqHandler::ShouldBroker(Endpoint endpoint, const HWND& hwnd, + const HINTERNET& h, const DWORD& err, + const DWORD& flags, LPVOID* const& data) { + const DWORD SUPPORTED_FLAGS = + FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | + FLAGS_ERROR_UI_FLAGS_GENERATE_DATA | FLAGS_ERROR_UI_FLAGS_NO_UI; + + // We broker if (1) the handle h is brokered (odd in client), + // (2) we support the requested action flags and (3) there is no user + // data, which wouldn't make sense for our supported flags anyway. + return ((endpoint == SERVER) || IsOdd(reinterpret_cast<uint64_t>(h))) && + (!(flags & ~SUPPORTED_FLAGS)) && (data == nullptr); +} + +/* AcquireCredentialsHandleA */ + +typedef SslFunctionBroker<ID_AcquireCredentialsHandleA, + decltype(AcquireCredentialsHandleA)> + AcquireCredentialsHandleAFB; + +template <> +ShouldHookFunc* const AcquireCredentialsHandleAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef RequestInfo<ID_AcquireCredentialsHandleA> ACHAReqInfo; + +template <> +template <> +struct ACHAReqInfo::FixedValue<0> { + static const LPSTR value; +}; +const LPSTR ACHAReqInfo::FixedValue<0>::value = nullptr; + +template <> +template <> +struct ACHAReqInfo::FixedValue<1> { + static const LPSTR value; +}; +const LPSTR ACHAReqInfo::FixedValue<1>::value = + const_cast<char*>(UNISP_NAME_A); // -Wwritable-strings + +template <> +template <> +struct ACHAReqInfo::FixedValue<2> { + static const unsigned long value; +}; +const unsigned long ACHAReqInfo::FixedValue<2>::value = SECPKG_CRED_OUTBOUND; + +template <> +template <> +struct ACHAReqInfo::FixedValue<3> { + static void* const value; +}; +void* const ACHAReqInfo::FixedValue<3>::value = nullptr; + +template <> +template <> +struct ACHAReqInfo::FixedValue<5> { + static const SEC_GET_KEY_FN value; +}; +const SEC_GET_KEY_FN ACHAReqInfo::FixedValue<5>::value = nullptr; + +template <> +template <> +struct ACHAReqInfo::FixedValue<6> { + static void* const value; +}; +void* const ACHAReqInfo::FixedValue<6>::value = nullptr; + +typedef AcquireCredentialsHandleAFB::Request ACHARequestHandler; +typedef AcquireCredentialsHandleAFB::RequestDelegate<SECURITY_STATUS HOOK_CALL( + LPSTR, LPSTR, unsigned long, void*, PSCHANNEL_CRED, SEC_GET_KEY_FN, void*)> + ACHADelegateRequestHandler; + +template <> +void ACHARequestHandler::Marshal(IpdlTuple& aTuple, const LPSTR& principal, + const LPSTR& pkg, const unsigned long& credUse, + const PVOID& logonId, const PVOID& auth, + const SEC_GET_KEY_FN& getKeyFn, + const PVOID& getKeyArg, + const PCredHandle& cred, + const PTimeStamp& expiry) { + const PSCHANNEL_CRED& scCred = reinterpret_cast<const PSCHANNEL_CRED&>(auth); + ACHADelegateRequestHandler::Marshal(aTuple, principal, pkg, credUse, logonId, + scCred, getKeyFn, getKeyArg); +} + +template <> +bool ACHARequestHandler::Unmarshal(ServerCallData& aScd, + const IpdlTuple& aTuple, LPSTR& principal, + LPSTR& pkg, unsigned long& credUse, + PVOID& logonId, PVOID& auth, + SEC_GET_KEY_FN& getKeyFn, PVOID& getKeyArg, + PCredHandle& cred, PTimeStamp& expiry) { + PSCHANNEL_CRED& scCred = reinterpret_cast<PSCHANNEL_CRED&>(auth); + if (!ACHADelegateRequestHandler::Unmarshal(aScd, aTuple, principal, pkg, + credUse, logonId, scCred, getKeyFn, + getKeyArg)) { + return false; + } + + cred = aScd.Allocate<CredHandle>(); + expiry = aScd.Allocate<::TimeStamp>(); + return true; +} + +typedef ResponseInfo<ID_AcquireCredentialsHandleA> ACHARspInfo; + +// Response phase must send output parameters +template <> +template <> +struct ACHARspInfo::ShouldMarshal<7> { + static const bool value = true; +}; +template <> +template <> +struct ACHARspInfo::ShouldMarshal<8> { + static const bool value = true; +}; + +/* QueryCredentialsAttributesA */ + +typedef SslFunctionBroker<ID_QueryCredentialsAttributesA, + decltype(QueryCredentialsAttributesA)> + QueryCredentialsAttributesAFB; + +template <> +ShouldHookFunc* const QueryCredentialsAttributesAFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +/* FreeCredentialsHandle */ + +typedef SslFunctionBroker<ID_FreeCredentialsHandle, + decltype(FreeCredentialsHandle)> + FreeCredentialsHandleFB; + +template <> +ShouldHookFunc* const FreeCredentialsHandleFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_SSL>; + +typedef FreeCredentialsHandleFB::Request FCHReq; + +template <> +bool FCHReq::ShouldBroker(Endpoint endpoint, const PCredHandle& h) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> CredHandle" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || ((h->dwLower == h->dwUpper) && + IsOdd(static_cast<uint64_t>(h->dwLower))); +} + +/* CreateMutexW */ + +// Get the user's SID as a string. Returns an empty string on failure. +static std::wstring GetUserSid() { + std::wstring ret; + // Get user SID from process token information + HANDLE token; + BOOL success = ::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token); + if (!success) { + return ret; + } + DWORD bufLen; + success = ::GetTokenInformation(token, TokenUser, nullptr, 0, &bufLen); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + return ret; + } + void* buf = malloc(bufLen); + success = ::GetTokenInformation(token, TokenUser, buf, bufLen, &bufLen); + MOZ_ASSERT(success); + if (success) { + TOKEN_USER* tokenUser = static_cast<TOKEN_USER*>(buf); + PSID sid = tokenUser->User.Sid; + LPWSTR sidStr; + success = ::ConvertSidToStringSid(sid, &sidStr); + if (success) { + ret = sidStr; + ::LocalFree(sidStr); + } + } + free(buf); + ::CloseHandle(token); + return ret; +} + +// Get the name Windows uses for the camera mutex. Returns an empty string +// on failure. +// The camera mutex is identified in Windows code using a hard-coded GUID +// string, "eed3bd3a-a1ad-4e99-987b-d7cb3fcfa7f0", and the user's SID. The GUID +// value was determined by investigating Windows code. It is referenced in +// CCreateSwEnum::CCreateSwEnum(void) in devenum.dll. +static std::wstring GetCameraMutexName() { + std::wstring userSid = GetUserSid(); + if (userSid.empty()) { + return userSid; + } + return std::wstring(L"eed3bd3a-a1ad-4e99-987b-d7cb3fcfa7f0 - ") + userSid; +} + +typedef FunctionBroker<ID_CreateMutexW, decltype(CreateMutexW)> CreateMutexWFB; + +template <> +ShouldHookFunc* const CreateMutexWFB::BaseType::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_CREATEMUTEXW>; + +typedef CreateMutexWFB::Request CMWReqHandler; +typedef CMWReqHandler::Info CMWReqInfo; +typedef CreateMutexWFB::Response CMWRspHandler; + +template <> +bool CMWReqHandler::ShouldBroker(Endpoint endpoint, + const LPSECURITY_ATTRIBUTES& aAttribs, + const BOOL& aOwner, const LPCWSTR& aName) { + // Statically hold the camera mutex name so that we dont recompute it for + // every CreateMutexW call in the client process. + static std::wstring camMutexName = GetCameraMutexName(); + + // Only broker if we are requesting the camera mutex. Note that we only + // need to check that the client is actually requesting the camera. The + // command is always valid on the server as long as we can construct the + // mutex name. + if (endpoint == SERVER) { + return !camMutexName.empty(); + } + + return (!aOwner) && aName && (!camMutexName.empty()) && + (camMutexName == aName); +} + +// We dont need to marshal any parameters. We construct all of them +// server-side. +template <> +template <> +struct CMWReqInfo::ShouldMarshal<0> { + static const bool value = false; +}; +template <> +template <> +struct CMWReqInfo::ShouldMarshal<1> { + static const bool value = false; +}; +template <> +template <> +struct CMWReqInfo::ShouldMarshal<2> { + static const bool value = false; +}; + +template <> +template <> +BROKER_DISABLE_CFGUARD HANDLE CreateMutexWFB::RunFunction( + CreateMutexWFB::FunctionType* aOrigFunction, base::ProcessId aClientId, + LPSECURITY_ATTRIBUTES& aAttribs, BOOL& aOwner, LPCWSTR& aName) const { + // Use CreateMutexW to get the camera mutex and DuplicateHandle to open it + // for use in the child process. + // Recall that aAttribs, aOwner and aName are all unmarshaled so they are + // unassigned garbage. + SECURITY_ATTRIBUTES mutexAttrib = {sizeof(SECURITY_ATTRIBUTES), + nullptr /* ignored */, TRUE}; + std::wstring camMutexName = GetCameraMutexName(); + if (camMutexName.empty()) { + return 0; + } + HANDLE serverMutex = + ::CreateMutexW(&mutexAttrib, FALSE, camMutexName.c_str()); + if (serverMutex == 0) { + return 0; + } + ScopedProcessHandle clientProcHandle; + if (!base::OpenProcessHandle(aClientId, &clientProcHandle.rwget())) { + return 0; + } + HANDLE ret; + if (!::DuplicateHandle(::GetCurrentProcess(), serverMutex, clientProcHandle, + &ret, SYNCHRONIZE, FALSE, DUPLICATE_CLOSE_SOURCE)) { + return 0; + } + return ret; +} + +#endif // defined(XP_WIN) + +/*****************************************************************************/ + +#define FUN_HOOK(x) static_cast<FunctionHook*>(x) +void AddBrokeredFunctionHooks(FunctionHookArray& aHooks) { + // We transfer ownership of the FunctionHook objects to the array. +#if defined(XP_WIN) + aHooks[ID_GetKeyState] = + FUN_HOOK(new GetKeyStateFB("user32.dll", "GetKeyState", &GetKeyState)); + aHooks[ID_SetCursorPos] = + FUN_HOOK(new SetCursorPosFB("user32.dll", "SetCursorPos", &SetCursorPos)); + aHooks[ID_GetSaveFileNameW] = FUN_HOOK(new GetSaveFileNameWFB( + "comdlg32.dll", "GetSaveFileNameW", &GetSaveFileNameW)); + aHooks[ID_GetOpenFileNameW] = FUN_HOOK(new GetOpenFileNameWFB( + "comdlg32.dll", "GetOpenFileNameW", &GetOpenFileNameW)); + aHooks[ID_InternetOpenA] = FUN_HOOK( + new InternetOpenAFB("wininet.dll", "InternetOpenA", &InternetOpenA)); + aHooks[ID_InternetConnectA] = FUN_HOOK(new InternetConnectAFB( + "wininet.dll", "InternetConnectA", &InternetConnectA)); + aHooks[ID_InternetCloseHandle] = FUN_HOOK(new InternetCloseHandleFB( + "wininet.dll", "InternetCloseHandle", &InternetCloseHandle)); + aHooks[ID_InternetQueryDataAvailable] = + FUN_HOOK(new InternetQueryDataAvailableFB("wininet.dll", + "InternetQueryDataAvailable", + &InternetQueryDataAvailable)); + aHooks[ID_InternetReadFile] = FUN_HOOK(new InternetReadFileFB( + "wininet.dll", "InternetReadFile", &InternetReadFile)); + aHooks[ID_InternetWriteFile] = FUN_HOOK(new InternetWriteFileFB( + "wininet.dll", "InternetWriteFile", &InternetWriteFile)); + aHooks[ID_InternetSetOptionA] = FUN_HOOK(new InternetSetOptionAFB( + "wininet.dll", "InternetSetOptionA", &InternetSetOptionA)); + aHooks[ID_HttpAddRequestHeadersA] = FUN_HOOK(new HttpAddRequestHeadersAFB( + "wininet.dll", "HttpAddRequestHeadersA", &HttpAddRequestHeadersA)); + aHooks[ID_HttpOpenRequestA] = FUN_HOOK(new HttpOpenRequestAFB( + "wininet.dll", "HttpOpenRequestA", &HttpOpenRequestA)); + aHooks[ID_HttpQueryInfoA] = FUN_HOOK( + new HttpQueryInfoAFB("wininet.dll", "HttpQueryInfoA", &HttpQueryInfoA)); + aHooks[ID_HttpSendRequestA] = FUN_HOOK(new HttpSendRequestAFB( + "wininet.dll", "HttpSendRequestA", &HttpSendRequestA)); + aHooks[ID_HttpSendRequestExA] = FUN_HOOK(new HttpSendRequestExAFB( + "wininet.dll", "HttpSendRequestExA", &HttpSendRequestExA)); + aHooks[ID_HttpEndRequestA] = FUN_HOOK(new HttpEndRequestAFB( + "wininet.dll", "HttpEndRequestA", &HttpEndRequestA)); + aHooks[ID_InternetQueryOptionA] = FUN_HOOK(new InternetQueryOptionAFB( + "wininet.dll", "InternetQueryOptionA", &InternetQueryOptionA)); + aHooks[ID_InternetErrorDlg] = FUN_HOOK(new InternetErrorDlgFB( + "wininet.dll", "InternetErrorDlg", InternetErrorDlg)); + aHooks[ID_AcquireCredentialsHandleA] = + FUN_HOOK(new AcquireCredentialsHandleAFB("sspicli.dll", + "AcquireCredentialsHandleA", + &AcquireCredentialsHandleA)); + aHooks[ID_QueryCredentialsAttributesA] = + FUN_HOOK(new QueryCredentialsAttributesAFB("sspicli.dll", + "QueryCredentialsAttributesA", + &QueryCredentialsAttributesA)); + aHooks[ID_FreeCredentialsHandle] = FUN_HOOK(new FreeCredentialsHandleFB( + "sspicli.dll", "FreeCredentialsHandle", &FreeCredentialsHandle)); + aHooks[ID_CreateMutexW] = FUN_HOOK( + new CreateMutexWFB("kernel32.dll", "CreateMutexW", &CreateMutexW)); +#endif // defined(XP_WIN) +} + +#undef FUN_HOOK + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/FunctionBroker.h b/dom/plugins/ipc/FunctionBroker.h new file mode 100644 index 0000000000..ddbde631e3 --- /dev/null +++ b/dom/plugins/ipc/FunctionBroker.h @@ -0,0 +1,1452 @@ +/* -*- 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_PluginHooksWin_h +#define dom_plugins_ipc_PluginHooksWin_h 1 + +#include <map> +#include <algorithm> +#include <utility> +#include "base/task.h" +#include "mozilla/ipc/ProcessChild.h" +#include "FunctionBrokerChild.h" +#include "transport/runnable_utils.h" +#include "PluginMessageUtils.h" +#include "mozilla/Logging.h" +#include "FunctionHook.h" +#include "FunctionBrokerIPCUtils.h" + +#if defined(XP_WIN) +# define SECURITY_WIN32 +# include <security.h> +# include <wininet.h> +# include <schnlsp.h> +# if defined(MOZ_SANDBOX) +# include "sandboxPermissions.h" +# endif +#endif // defined(XP_WIN) + +/** + * This functionality supports automatic method hooking (FunctionHook) and + * brokering (FunctionBroker), which are used to intercept system calls + * (using the nsDllInterceptor) and replace them with new functionality (hook) + * or proxy them on another process (broker). + * There isn't much of a public interface to this (see FunctionHook + * for initialization functionality) since the majority of the behavior + * comes from intercepting calls to DLL methods (making those DLL methods the + * public interface). Generic RPC can be achieved without DLLs or function + * interception by directly calling the FunctionBroker::InterceptorStub. + * + * The system supports the most common logic surrounding brokering by allowing + * the client to supply strategies for them. Some examples of common tasks that + * are supported by automatic brokering: + * + * * Intercepting a new Win32 method: + * + * Step 1: Add a typedef or subclass of either FunctionHook (non-brokering) or + * FunctionBroker (automatic brokering) to FunctionBroker.cpp, using a new + * FunctionHookID (added to that enum). + * For example: + * typedef FunctionBroker<ID_GetKeyState, decltype(GetKeyState)> GetKeyStateFB + * Use a subclass instead of a typedef if you need to maintain data or state. + * + * Step 2: Add an instance of that object to the FunctionHookList in + * AddFunctionHook(FunctionHookList&) or + * AddBrokeredFunctionHook(FunctionHookList&). + * This typically just means calling the constructor with the correct info. + * At a minimum, this means supplying the names of the DLL and method to + * broker, and a pointer to the original version of the method. + * For example: + * aHooks[ID_GetKeyState] = + * new GetKeyStateFB("user32.dll", "GetKeyState", &GetKeyState); + * + * Step 3: If brokering, make sure the system can (un)marshal the parameters, + * either by the means below or by adding the type to IpdlTuple, which we use + * for type-safely (un)marshaling the parameter list. + * + * * Only brokering _some_ calls to the method: + * + * FunctionBroker's constructor allows the user to supply a ShouldBroker + * function, which takes the parameters of the method call and returns false + * if we should use the original method instead of brokering. + * + * * Only passing _some_ parameters to the brokering process / returning + * parameters to client: + * + * If a system call changes a parameter call-by-reference style then the + * parameter's value needs to be returned to the client. The FunctionBroker + * has "phase" (request/response) objects that it uses to determine which + * parameters are sent/returned. This example tells InternetWriteFileFB to + * return its third parameter: + * template<> template<> + * struct InternetWriteFileFB::Response::Info::ShouldMarshal<3> { + * static const bool value = true; + * }; + * By default, all parameters have ShouldMarshal set in the request phase + * and only the return value (parameter -1) has it set in the response phase. + * + * * Marshalling special parameter/return types: + * + * The IPCTypeMap in FunctionBroker maps a parameter or return type + * to a type that IpdlTuple knows how to marshal. By default, the map is + * the identity but some types need special handling. + * The map is endpoint-specific (it is a member of the EndpointHandler), + * so a different type can be used + * for client -> server and for server -> client. Note that the + * types must be able to Copy() from one another -- the default Copy() + * implementation uses the type's assignment operator. + * The EndpointHandler itself is a template parameter of the FunctionBroker. + * The default EndpointHandler recognizes basic types. + * See e.g. FileDlgEndpointHandler<CLIENT>::IPCTypeMap<LPOPENFILENAMEW> + * for an example of specialization. + * + * * Anything more complex involving parameter transmission: + * + * Sometimes marshaling parameters can require something more complex. In + * those cases, you will need to specialize the Marshal and Unmarshal + * methods of the request or response handler and perform your complex logic + * there. A wise approach is to map your complex parameters into a simpler + * parameter list and delegate the Marshal/Unmarshal calls to them. For + * example, an API might take a void* and an int as a buffer and length. + * Obviously a void* cannot generally be marshaled. However, we can delegate + * this call to a parameter list that takes a string in place of the buffer and + * length. Something like: + * + * typedef RequestHandler<ID_HookedFunc, + * int HOOK_CALL (nsDependentCSubstring)> + * HookedFuncDelegateReq; + * + * template<> + * void HookedFuncFB::Request::Marshal(IpdlTuple& aTuple, const void*& aBuf, + * const int& aBufLen) + * { + * MOZ_ASSERT(nWritten); + * HookedFuncDelegateReq::Marshal(aTuple, + * nsDependentCSubstring(aBuf, aBufLen)); + * } + * + * template<> + * bool HookedFuncFB::Request::Unmarshal(ServerCallData& aScd, const IpdlTuple& + * aTuple, void*& aBuf, int& aBufLen) + * { + * nsDependentCSubstring str; + * if (!HookedFuncDelegateReq::Unmarshal(aScd, aTuple, str)) { + * return false; + * } + * + * // Request phase unmarshal uses ServerCallData for dynamically-allocating + * // memory. + * aScd.AllocateString(str, aBuf, false); + * aBufLen = str.Length(); + * return true; + * } + * + * See e.g. InternetWriteFileFB for a complete example of delegation. + * + * * Brokering but need the server to do more than just run the function: + * + * Specialize the FunctionBroker's RunFunction. By default, it just runs + * the function. See GetSaveFileNameWFB for an example that does more. + * + */ + +#if defined(XP_WIN) && defined(__clang__) +# if __has_declspec_attribute(guard) +// Workaround for https://bugs.llvm.org/show_bug.cgi?id=47617 +// Some of the brokered function thunks don't get properly marked as call +// targets, so we have to disable CFG when returning to the original function. +# define BROKER_DISABLE_CFGUARD __declspec(guard(nocf)) +# else +# define BROKER_DISABLE_CFGUARD /* nothing */ +# endif +#else +# define BROKER_DISABLE_CFGUARD /* nothing */ +#endif + +namespace mozilla { +namespace plugins { + +#if defined(XP_WIN) + +// Currently, all methods we hook use the WINAPI calling convention. +# define HOOK_CALL WINAPI + +typedef std::pair<ULONG_PTR, ULONG_PTR> UlongPair; +typedef std::map<UlongPair, uint64_t> UlongPairToIdMap; +extern UlongPairToIdMap sPairToIdMap; +typedef std::map<uint64_t, UlongPair> IdToUlongPairMap; +extern IdToUlongPairMap sIdToPairMap; +typedef std::map<void*, uint64_t> PtrToIdMap; +extern PtrToIdMap sPtrToIdMap; +typedef std::map<uint64_t, void*> IdToPtrMap; +extern IdToPtrMap sIdToPtrMap; + +#else // defined(XP_WIN) + +// Any methods we hook use the default calling convention. +# define HOOK_CALL + +#endif // defined(XP_WIN) + +inline bool IsOdd(uint64_t aVal) { return aVal & 1; } + +// This enum is used to track if this process is currently running the client +// or server side of brokering. +enum Endpoint { SERVER, CLIENT }; +inline const char* EndpointMsg(Endpoint aVal) { + return aVal == SERVER ? "SERVER" : "CLIENT"; +} + +template <typename ParamType> +inline void LogParameterValue(int aIndex, const ParamType& aParam) { + // To avoid overhead, don't do this in release. +#ifdef DEBUG + if (!MOZ_LOG_TEST(sPluginHooksLog, LogLevel::Verbose)) { + return; + } + std::wstring paramString; + IPC::LogParam(aParam, ¶mString); + HOOK_LOG(LogLevel::Verbose, + ("Parameter %d: %S", aIndex, paramString.c_str())); +#endif +} + +// This specialization is needed to log the common pattern where null is used +// as a fixed value for a pointer-type that is unknown to IPC. +template <typename ParamType> +inline void LogParameterValue(int aIndex, ParamType* const& aParam) { +#ifdef DEBUG + HOOK_LOG(LogLevel::Verbose, + ("Parameter %d: pointer value - %p", aIndex, aParam)); +#endif +} + +template <> +inline void LogParameterValue(int aIndex, const nsDependentCSubstring& aParam) { +#ifdef DEBUG + HOOK_LOG(LogLevel::Verbose, + ("Parameter %d : %s", aIndex, FormatBlob(aParam).Data())); +#endif +} + +template <> +inline void LogParameterValue(int aIndex, char* const& aParam) { +#ifdef DEBUG + // A char* can be a block of raw memory. + nsDependentCSubstring str; + if (aParam) { + str.Rebind(const_cast<char*>(aParam), + strnlen(aParam, MAX_BLOB_CHARS_TO_LOG)); + } else { + str.SetIsVoid(true); + } + LogParameterValue(aIndex, str); +#endif +} + +template <> +inline void LogParameterValue(int aIndex, const char* const& aParam) { +#ifdef DEBUG + LogParameterValue(aIndex, const_cast<char* const&>(aParam)); +#endif +} + +#if defined(XP_WIN) +template <> +inline void LogParameterValue(int aIndex, const SEC_GET_KEY_FN& aParam) { +# ifdef DEBUG + MOZ_ASSERT(aParam == nullptr); + HOOK_LOG(LogLevel::Verbose, ("Parameter %d: null function.", aIndex)); +# endif +} + +template <> +inline void LogParameterValue(int aIndex, LPVOID* const& aParam) { +# ifdef DEBUG + MOZ_ASSERT(aParam == nullptr); + HOOK_LOG(LogLevel::Verbose, ("Parameter %d: null void pointer.", aIndex)); +# endif +} +#endif // defined(XP_WIN) + +// Used to check if a fixed parameter value is equal to the parameter given +// in the original function call. +template <typename ParamType> +inline bool ParameterEquality(const ParamType& aParam1, + const ParamType& aParam2) { + return aParam1 == aParam2; +} + +// Specialization: char* equality is string equality +template <> +inline bool ParameterEquality(char* const& aParam1, char* const& aParam2) { + return ((!aParam1 && !aParam2) || + (aParam1 && aParam2 && !strcmp(aParam1, aParam2))); +} + +// Specialization: const char* const equality is string equality +template <> +inline bool ParameterEquality(const char* const& aParam1, + const char* const& aParam2) { + return ParameterEquality(const_cast<char* const&>(aParam1), + const_cast<char* const&>(aParam2)); +} + +/** + * A type map _from_ the type of a parameter in the original function + * we are brokering _to_ a type that we can marshal. We must be able + * to Copy() the marshaled type using the parameter type. + * The default maps from type T back to type T. + */ +template <typename OrigType> +struct IPCTypeMap { + typedef OrigType ipc_type; +}; +template <> +struct IPCTypeMap<char*> { + typedef nsDependentCSubstring ipc_type; +}; +template <> +struct IPCTypeMap<const char*> { + typedef nsDependentCSubstring ipc_type; +}; +template <> +struct IPCTypeMap<wchar_t*> { + typedef nsString ipc_type; +}; +template <> +struct IPCTypeMap<const wchar_t*> { + typedef nsString ipc_type; +}; +template <> +struct IPCTypeMap<long> { + typedef int32_t ipc_type; +}; +template <> +struct IPCTypeMap<unsigned long> { + typedef uint32_t ipc_type; +}; + +#if defined(XP_WIN) +template <> +struct IPCTypeMap<PSecHandle> { + typedef uint64_t ipc_type; +}; +template <> +struct IPCTypeMap<PTimeStamp> { + typedef uint64_t ipc_type; +}; +template <> +struct IPCTypeMap<void*> { + typedef uint64_t ipc_type; +}; // HANDLEs +template <> +struct IPCTypeMap<HWND> { + typedef NativeWindowHandle ipc_type; +}; +template <> +struct IPCTypeMap<PSCHANNEL_CRED> { + typedef IPCSchannelCred ipc_type; +}; +template <> +struct IPCTypeMap<LPINTERNET_BUFFERSA> { + typedef IPCInternetBuffers ipc_type; +}; +template <> +struct IPCTypeMap<LPDWORD> { + typedef uint32_t ipc_type; +}; +#endif + +template <typename AllocType> +static void DeleteDestructor(void* aObj) { + delete static_cast<AllocType*>(aObj); +} + +extern void FreeDestructor(void* aObj); + +// The ServerCallData is a list of ServerCallItems that should be freed when +// the server has completed a function call and marshaled a response. +class ServerCallData { + public: + typedef void(DestructorType)(void*); + + // Allocate a certain type. + template <typename AllocType> + AllocType* Allocate( + DestructorType* aDestructor = &DeleteDestructor<AllocType>) { + AllocType* ret = new AllocType(); + mList.AppendElement(FreeItem(ret, aDestructor)); + return ret; + } + + template <typename AllocType> + AllocType* Allocate( + const AllocType& aValueToCopy, + DestructorType* aDestructor = &DeleteDestructor<AllocType>) { + AllocType* ret = Allocate<AllocType>(aDestructor); + *ret = aValueToCopy; + return ret; + } + + // Allocate memory, storing the pointer in buf. + template <typename PtrType> + void AllocateMemory(unsigned long aBufLen, PtrType& aBuf) { + if (aBufLen) { + aBuf = static_cast<PtrType>(malloc(aBufLen)); + mList.AppendElement(FreeItem(aBuf, FreeDestructor)); + } else { + aBuf = nullptr; + } + } + + template <typename PtrType> + void AllocateString(const nsACString& aStr, PtrType& aBuf, + bool aCopyNullTerminator = true) { + uint32_t nullByte = aCopyNullTerminator ? 1 : 0; + char* tempBuf = static_cast<char*>(malloc(aStr.Length() + nullByte)); + memcpy(tempBuf, aStr.Data(), aStr.Length() + nullByte); + mList.AppendElement(FreeItem(tempBuf, FreeDestructor)); + aBuf = tempBuf; + } + + // Run the given destructor on the given memory, for special cases where + // memory is allocated elsewhere but must still be freed. + void PostDestructor(void* aMem, DestructorType* aDestructor) { + mList.AppendElement(FreeItem(aMem, aDestructor)); + } + +#if defined(XP_WIN) + // Allocate memory and a DWORD block-length, storing them in the + // corresponding parameters. + template <typename PtrType> + void AllocateMemory(DWORD aBufLen, PtrType& aBuf, LPDWORD& aBufLenCopy) { + aBufLenCopy = static_cast<LPDWORD>(malloc(sizeof(DWORD))); + *aBufLenCopy = aBufLen; + mList.AppendElement(FreeItem(aBufLenCopy, FreeDestructor)); + AllocateMemory(aBufLen, aBuf); + } +#endif // defined(XP_WIN) + + private: + // FreeItems are used to free objects that were temporarily needed for + // dispatch, such as buffers that are given as a parameter. + class FreeItem { + void* mPtr; + DestructorType* mDestructor; + FreeItem(FreeItem& aOther); // revoked + public: + explicit FreeItem(void* aPtr, DestructorType* aDestructor) + : mPtr(aPtr), mDestructor(aDestructor) { + MOZ_ASSERT(mDestructor || !aPtr); + } + + FreeItem(FreeItem&& aOther) + : mPtr(aOther.mPtr), mDestructor(aOther.mDestructor) { + aOther.mPtr = nullptr; + aOther.mDestructor = nullptr; + } + + ~FreeItem() { + if (mDestructor) { + mDestructor(mPtr); + } + } + }; + + typedef nsTArray<FreeItem> FreeItemList; + FreeItemList mList; +}; + +// Holds an IpdlTuple and a ServerCallData. This is used by the phase handlers +// (RequestHandler and ResponseHandler) in the Unmarshaling phase. +// Server-side unmarshaling (during the request phase) uses a ServerCallData +// to keep track of allocated memory. In the client, ServerCallDatas are +// not used and that value will always be null. +class IpdlTupleContext { + public: + explicit IpdlTupleContext(const IpdlTuple* aTuple, + ServerCallData* aScd = nullptr) + : mTuple(aTuple), mScd(aScd) { + MOZ_ASSERT(aTuple); + } + + ServerCallData* GetServerCallData() { return mScd; } + const IpdlTuple* GetIpdlTuple() { return mTuple; } + + private: + const IpdlTuple* mTuple; + ServerCallData* mScd; +}; + +template <typename DestType, typename SrcType> +inline void Copy(DestType& aDest, const SrcType& aSrc) { + aDest = (DestType)aSrc; +} + +template <> +inline void Copy(nsDependentCSubstring& aDest, + const nsDependentCSubstring& aSrc) { + if (aSrc.IsVoid()) { + aDest.SetIsVoid(true); + } else { + aDest.Rebind(aSrc.Data(), aSrc.Length()); + } +} + +#if defined(XP_WIN) + +template <> +inline void Copy(uint64_t& aDest, const PTimeStamp& aSrc) { + aDest = static_cast<uint64_t>(aSrc->QuadPart); +} + +template <> +inline void Copy(PTimeStamp& aDest, const uint64_t& aSrc) { + aDest->QuadPart = static_cast<LONGLONG>(aSrc); +} + +#endif // defined(XP_WIN) + +template <Endpoint e, typename SelfType> +struct BaseEndpointHandler; +template <typename SelfType> +struct BaseEndpointHandler<CLIENT, SelfType> { + static const Endpoint OtherSide = SERVER; + + template <typename DestType, typename SrcType> + inline static void Copy(ServerCallData* aScd, DestType& aDest, + const SrcType& aSrc) { + MOZ_ASSERT(!aScd); // never used in the CLIENT + SelfType::Copy(aDest, aSrc); + } + + template <typename DestType, typename SrcType> + inline static void Copy(DestType& aDest, const SrcType& aSrc) { + mozilla::plugins::Copy(aDest, aSrc); + } + + // const char* should be null terminated but this is not always the case. + // In those cases, we must override this default behavior. + inline static void Copy(nsDependentCSubstring& aDest, + const char* const& aSrc) { + // In the client, we just bind to the caller's string + if (aSrc) { + aDest.Rebind(aSrc, strlen(aSrc)); + } else { + aDest.SetIsVoid(true); + } + } + + inline static void Copy(const char*& aDest, + const nsDependentCSubstring& aSrc) { + MOZ_ASSERT_UNREACHABLE("Cannot return const parameters."); + } + + inline static void Copy(nsDependentCSubstring& aDest, char* const& aSrc) { + // In the client, we just bind to the caller's string + if (aSrc) { + aDest.Rebind(aSrc, strlen(aSrc)); + } else { + aDest.SetIsVoid(true); + } + } + + inline static void Copy(nsString& aDest, wchar_t* const& aSrc) { + if (aSrc) { + // We are using nsString as a "raw" container for a wchar_t string. We + // just use its data as a wchar_t* later (so the reinterpret_cast is + // safe). + aDest.Rebind(reinterpret_cast<char16_t*>(aSrc), wcslen(aSrc)); + } else { + aDest.SetIsVoid(true); + } + } + + inline static void Copy(char*& aDest, const nsDependentCSubstring& aSrc) { + MOZ_ASSERT_UNREACHABLE("Returning char* parameters is not yet suported."); + } + +#if defined(XP_WIN) + inline static void Copy(uint32_t& aDest, const LPDWORD& aSrc) { + aDest = *aSrc; + } + + inline static void Copy(LPDWORD& aDest, const uint32_t& aSrc) { + *aDest = aSrc; + } +#endif // #if defined(XP_WIN) +}; + +template <typename SelfType> +struct BaseEndpointHandler<SERVER, SelfType> { + static const Endpoint OtherSide = CLIENT; + + // Specializations of this method may allocate memory for types that need it + // during Unmarshaling. They record the allocation in the ServerCallData. + // When copying values in the SERVER, we should be sure to carefully validate + // the information that came from the client as the client may be compromised + // by malicious code. + template <typename DestType, typename SrcType> + inline static void Copy(ServerCallData* aScd, DestType& aDest, + const SrcType& aSrc) { + SelfType::Copy(aDest, aSrc); + } + + template <typename DestType, typename SrcType> + inline static void Copy(DestType& aDest, const SrcType& aSrc) { + mozilla::plugins::Copy(aDest, aSrc); + } + + inline static void Copy(nsDependentCSubstring& aDest, + const nsDependentCSubstring& aSrc) { + aDest.Rebind(aSrc.Data(), aSrc.Length()); + aDest.SetIsVoid(aSrc.IsVoid()); + } + + // const char* should be null terminated but this is not always the case. + // In those cases, we override this default behavior. + inline static void Copy(nsDependentCSubstring& aDest, + const char* const& aSrc) { + MOZ_ASSERT_UNREACHABLE( + "Const parameter cannot be returned by brokering process."); + } + + inline static void Copy(nsDependentCSubstring& aDest, char* const& aSrc) { + MOZ_ASSERT_UNREACHABLE("Returning char* parameters is not yet suported."); + } + + inline static void Copy(ServerCallData* aScd, char*& aDest, + const nsDependentCSubstring& aSrc) { + // In the parent, we must allocate the string. + MOZ_ASSERT(aScd); + if (aSrc.IsVoid()) { + aDest = nullptr; + return; + } + aScd->AllocateMemory(aSrc.Length() + 1, aDest); + memcpy(aDest, aSrc.Data(), aSrc.Length()); + aDest[aSrc.Length()] = '\0'; + } + + inline static void Copy(ServerCallData* aScd, const char*& aDest, + const nsDependentCSubstring& aSrc) { + char* nonConstDest; + Copy(aScd, nonConstDest, aSrc); + aDest = nonConstDest; + } + + inline static void Copy(ServerCallData* aScd, wchar_t*& aDest, + const nsString& aSrc) { + // Allocating the string with aScd means it will last during the server call + // and be freed when the call is complete. + MOZ_ASSERT(aScd); + if (aSrc.IsVoid()) { + aDest = nullptr; + return; + } + aScd->AllocateMemory((aSrc.Length() + 1) * sizeof(wchar_t), aDest); + memcpy(aDest, aSrc.Data(), aSrc.Length() * sizeof(wchar_t)); + aDest[aSrc.Length()] = L'\0'; + } + + inline static void Copy(ServerCallData* aScd, const wchar_t*& aDest, + const nsString& aSrc) { + wchar_t* nonConstDest; + Copy(aScd, nonConstDest, aSrc); + aDest = nonConstDest; + } + +#if defined(XP_WIN) + inline static void Copy(uint32_t& aDest, const LPDWORD& aSrc) { + aDest = *aSrc; + } + + inline static void Copy(LPDWORD& aDest, const uint32_t& aSrc) { + MOZ_RELEASE_ASSERT(aDest); + *aDest = aSrc; + } + + inline static void Copy(ServerCallData* aScd, PTimeStamp& aDest, + const uint64_t& aSrc) { + MOZ_ASSERT(!aDest); + aDest = aScd->Allocate<::TimeStamp>(); + Copy(aDest, aSrc); + } +#endif // defined(XP_WIN) +}; + +// PhaseHandler is a RequestHandler or a ResponseHandler. +template <Endpoint endpoint, typename PhaseHandler> +struct Marshaler { + // Driver + template <int firstIndex = 0, typename... VarParams> + static void Marshal(IpdlTuple& aMarshaledTuple, const VarParams&... aParams) { + MarshalParameters<firstIndex>(aMarshaledTuple, aParams...); + } + + // Driver + template <int firstIndex = 0, typename... VarParams> + static bool Unmarshal(IpdlTupleContext& aUnmarshaledTuple, + VarParams&... aParams) { + return UnmarshalParameters<firstIndex>(aUnmarshaledTuple, 0, aParams...); + } + + template <int paramIndex, typename OrigType, + bool shouldMarshal = + PhaseHandler::Info::template ShouldMarshal<paramIndex>::value> + struct MaybeMarshalParameter {}; + + /** + * shouldMarshal = true case + */ + template <int paramIndex, typename OrigType> + struct MaybeMarshalParameter<paramIndex, OrigType, true> { + template <typename IPCType = typename PhaseHandler::template IPCTypeMap< + OrigType>::ipc_type> + static void MarshalParameter(IpdlTuple& aMarshaledTuple, + const OrigType& aParam) { + HOOK_LOG(LogLevel::Verbose, ("%s marshaling parameter %d.", + EndpointMsg(endpoint), paramIndex)); + IPCType ipcObject; + // EndpointHandler must be able to Copy() from OrigType to IPCType + PhaseHandler::EHContainer::template EndpointHandler<endpoint>::Copy( + ipcObject, aParam); + LogParameterValue(paramIndex, ipcObject); + aMarshaledTuple.AddElement(ipcObject); + } + }; + + /** + * shouldMarshal = false case + */ + template <int paramIndex, typename OrigType> + struct MaybeMarshalParameter<paramIndex, OrigType, false> { + static void MarshalParameter(IpdlTuple& aMarshaledTuple, + const OrigType& aParam) { + HOOK_LOG(LogLevel::Verbose, ("%s not marshaling parameter %d.", + EndpointMsg(endpoint), paramIndex)); + } + }; + + /** + * Recursive case: marshals aFirstParam to aMarshaledTuple (if desired), + * then marshals the aRemainingParams. + */ + template <int paramIndex, typename VarParam, typename... VarParams> + static void MarshalParameters(IpdlTuple& aMarshaledTuple, + const VarParam& aFirstParam, + const VarParams&... aRemainingParams) { + MaybeMarshalParameter<paramIndex, VarParam>::MarshalParameter( + aMarshaledTuple, aFirstParam); + MarshalParameters<paramIndex + 1, VarParams...>(aMarshaledTuple, + aRemainingParams...); + } + + /** + * Base case: empty parameter list -- nothing to marshal. + */ + template <int paramIndex> + static void MarshalParameters(IpdlTuple& aMarshaledTuple) {} + + template <int tupleIndex, typename OrigType, + bool shouldMarshal = + PhaseHandler::Info::template ShouldMarshal<tupleIndex>::value, + bool hasFixedValue = + PhaseHandler::Info::template HasFixedValue<tupleIndex>::value> + struct MaybeUnmarshalParameter {}; + + /** + * ShouldMarshal = true case. HasFixedValue must be false in that case. + */ + template <int tupleIndex, typename VarParam> + struct MaybeUnmarshalParameter<tupleIndex, VarParam, true, false> { + template <typename IPCType = typename PhaseHandler::template IPCTypeMap< + VarParam>::ipc_type> + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + VarParam& aParam) { + const IPCType* ipcObject = + aUnmarshaledTuple.GetIpdlTuple()->Element<IPCType>(aNextTupleIdx); + if (!ipcObject) { + HOOK_LOG(LogLevel::Error, ("%s failed to unmarshal parameter %d.", + EndpointMsg(endpoint), tupleIndex)); + return false; + } + HOOK_LOG(LogLevel::Verbose, ("%s unmarshaled parameter %d.", + EndpointMsg(endpoint), tupleIndex)); + LogParameterValue(tupleIndex, *ipcObject); + PhaseHandler::EHContainer::template EndpointHandler<endpoint>::Copy( + aUnmarshaledTuple.GetServerCallData(), aParam, *ipcObject); + ++aNextTupleIdx; + return true; + } + }; + + /** + * ShouldMarshal = true : nsDependentCSubstring specialization + */ + template <int tupleIndex> + struct MaybeUnmarshalParameter<tupleIndex, nsDependentCSubstring, true, + false> { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + nsDependentCSubstring& aParam) { + // Deserialize as an nsCString and then copy the info into the + // nsDependentCSubstring + const nsCString* ipcObject = + aUnmarshaledTuple.GetIpdlTuple()->Element<nsCString>(aNextTupleIdx); + if (!ipcObject) { + HOOK_LOG(LogLevel::Error, ("%s failed to unmarshal parameter %d.", + EndpointMsg(endpoint), tupleIndex)); + return false; + } + HOOK_LOG(LogLevel::Verbose, ("%s unmarshaled parameter %d.", + EndpointMsg(endpoint), tupleIndex)); + + aParam.Rebind(ipcObject->Data(), ipcObject->Length()); + aParam.SetIsVoid(ipcObject->IsVoid()); + LogParameterValue(tupleIndex, aParam); + ++aNextTupleIdx; + return true; + } + }; + + /** + * ShouldMarshal = true : char* specialization + */ + template <int tupleIndex> + struct MaybeUnmarshalParameter<tupleIndex, char*, true, false> { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, char*& aParam) { + nsDependentCSubstring tempStr; + bool ret = + MaybeUnmarshalParameter<tupleIndex, nsDependentCSubstring, true, + false>::UnmarshalParameter(aUnmarshaledTuple, + aNextTupleIdx, + tempStr); + PhaseHandler::EHContainer::template EndpointHandler<endpoint>::Copy( + aUnmarshaledTuple.GetServerCallData(), aParam, tempStr); + return ret; + } + }; + + /** + * ShouldMarshal = true : const char* specialization + */ + template <int tupleIndex> + struct MaybeUnmarshalParameter<tupleIndex, const char*, true, false> { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + const char*& aParam) { + char* tempStr; + bool ret = + MaybeUnmarshalParameter<tupleIndex, char*, true, + false>::UnmarshalParameter(aUnmarshaledTuple, + aNextTupleIdx, + tempStr); + aParam = tempStr; + return ret; + } + }; + + /** + * ShouldMarshal = false, fixed parameter case + */ + template <int tupleIndex, typename VarParam> + struct MaybeUnmarshalParameter<tupleIndex, VarParam, false, true> { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + VarParam& aParam) { + // Copy default value if this is client->server communication (and if it + // exists) + PhaseHandler::template CopyFixedParam<tupleIndex, VarParam>(aParam); + HOOK_LOG(LogLevel::Verbose, + ("%s parameter %d not unmarshaling -- using fixed value.", + EndpointMsg(endpoint), tupleIndex)); + LogParameterValue(tupleIndex, aParam); + return true; + } + }; + + /** + * ShouldMarshal = false, unfixed parameter case. Assume user has done + * special handling. + */ + template <int tupleIndex, typename VarParam> + struct MaybeUnmarshalParameter<tupleIndex, VarParam, false, false> { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + VarParam& aParam) { + HOOK_LOG(LogLevel::Verbose, + ("%s parameter %d not automatically unmarshaling.", + EndpointMsg(endpoint), tupleIndex)); + // DLP: TODO: specializations fail LogParameterValue(tupleIndex, aParam); + return true; + } + }; + + /** + * Recursive case: unmarshals aFirstParam to aUnmarshaledTuple (if desired), + * then unmarshals the aRemainingParams. + * The endpoint specifies the side this process is on: client or server. + */ + template <int tupleIndex, typename VarParam, typename... VarParams> + static bool UnmarshalParameters(IpdlTupleContext& aUnmarshaledTuple, + int aNextTupleIdx, VarParam& aFirstParam, + VarParams&... aRemainingParams) { + // TODO: DLP: I currently increment aNextTupleIdx in the method (its a + // reference). This is awful. + if (!MaybeUnmarshalParameter<tupleIndex, VarParam>::UnmarshalParameter( + aUnmarshaledTuple, aNextTupleIdx, aFirstParam)) { + return false; + } + return UnmarshalParameters<tupleIndex + 1, VarParams...>( + aUnmarshaledTuple, aNextTupleIdx, aRemainingParams...); + } + + /** + * Base case: empty parameter list -- nothing to unmarshal. + */ + template <int> + static bool UnmarshalParameters(IpdlTupleContext& aUnmarshaledTuple, + int aNextTupleIdx) { + return true; + } +}; + +// The default marshals all parameters. +template <FunctionHookId functionId> +struct RequestInfo { + template <int paramIndex> + struct FixedValue; + + template <int paramIndex, typename = int> + struct HasFixedValue { + static const bool value = false; + }; + template <int paramIndex> + struct HasFixedValue<paramIndex, decltype(FixedValue<paramIndex>::value, 0)> { + static const bool value = true; + }; + + // By default we the request should marshal any non-fixed parameters. + template <int paramIndex> + struct ShouldMarshal { + static const bool value = !HasFixedValue<paramIndex>::value; + }; +}; + +/** + * This base stores the RequestHandler's IPCTypeMap. It really only + * exists to circumvent the arbitrary C++ rule (enforced by mingw) forbidding + * full class specialization of a class (IPCTypeMap<T>) inside of an + * unspecialized template class (RequestHandler<T>). + */ +struct RequestHandlerBase { + // Default to the namespace-level IPCTypeMap + template <typename OrigType> + struct IPCTypeMap { + typedef typename mozilla::plugins::IPCTypeMap<OrigType>::ipc_type ipc_type; + }; +}; + +#if defined(XP_WIN) + +// Request phase uses OpenFileNameIPC for an LPOPENFILENAMEW parameter. +template <> +struct RequestHandlerBase::IPCTypeMap<LPOPENFILENAMEW> { + typedef OpenFileNameIPC ipc_type; +}; + +#endif // defined(XP_WIN) + +struct BaseEHContainer { + template <Endpoint e> + struct EndpointHandler : public BaseEndpointHandler<e, EndpointHandler<e>> {}; +}; + +template <FunctionHookId functionId, typename FunctionType, + typename EHContainer> +struct RequestHandler; + +template <FunctionHookId functionId, typename EHContainerType, + typename ResultType, typename... ParamTypes> +struct RequestHandler<functionId, ResultType HOOK_CALL(ParamTypes...), + EHContainerType> : public RequestHandlerBase { + typedef ResultType(HOOK_CALL FunctionType)(ParamTypes...); + typedef RequestHandler<functionId, FunctionType, EHContainerType> SelfType; + typedef RequestInfo<functionId> Info; + typedef EHContainerType EHContainer; + + static void Marshal(IpdlTuple& aTuple, const ParamTypes&... aParams) { + ReqMarshaler::Marshal(aTuple, aParams...); + } + + static bool Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + ParamTypes&... aParams) { + IpdlTupleContext cxt(&aTuple, &aScd); + return ReqUnmarshaler::Unmarshal(cxt, aParams...); + } + + typedef Marshaler<CLIENT, SelfType> ReqMarshaler; + typedef Marshaler<SERVER, SelfType> ReqUnmarshaler; + + /** + * Returns true if a call made with the given parameters should be + * brokered (vs. passed-through to the original function). + */ + static bool ShouldBroker(Endpoint aEndpoint, const ParamTypes&... aParams) { + // True if all filtered parameters match their filter value. + return CheckFixedParams(aParams...); + } + + template <int paramIndex, typename VarParam> + static void CopyFixedParam(VarParam& aParam) { + aParam = Info::template FixedValue<paramIndex>::value; + } + + protected: + // Returns true if filtered parameters match their filter value. + static bool CheckFixedParams(const ParamTypes&... aParams) { + return CheckFixedParamsHelper<0>(aParams...); + } + + // If no FixedValue<paramIndex> is defined and equal to FixedType then always + // pass. + template <int paramIndex, typename = int> + struct CheckFixedParam { + template <typename ParamType> + static inline bool Check(const ParamType& aParam) { + return true; + } + }; + + // If FixedValue<paramIndex> is defined then check equality. + template <int paramIndex> + struct CheckFixedParam< + paramIndex, decltype(Info::template FixedValue<paramIndex>::value, 0)> { + template <typename ParamType> + static inline bool Check(ParamType& aParam) { + return ParameterEquality(aParam, + Info::template FixedValue<paramIndex>::value); + } + }; + + // Recursive case: Chcek head parameter, then tail parameters. + template <int index, typename VarParam, typename... VarParams> + static bool CheckFixedParamsHelper(const VarParam& aParam, + const VarParams&... aParams) { + if (!CheckFixedParam<index>::Check(aParam)) { + return false; // didn't match a fixed parameter + } + return CheckFixedParamsHelper<index + 1>(aParams...); + } + + // Base case: All fixed parameters matched. + template <int> + static bool CheckFixedParamsHelper() { + return true; + } +}; + +// The default returns no parameters -- only the return value. +template <FunctionHookId functionId> +struct ResponseInfo { + template <int paramIndex> + struct HasFixedValue { + static const bool value = + RequestInfo<functionId>::template HasFixedValue<paramIndex>::value; + }; + + // Only the return value (index -1) is sent by default. + template <int paramIndex> + struct ShouldMarshal { + static const bool value = (paramIndex == -1); + }; + + // This is the condition on the function result that we use to determine if + // the windows thread-local error state should be sent to the client. The + // error is typically only relevant if the function did not succeed. + template <typename ResultType> + static bool ShouldTransmitError(const ResultType& aResult) { + return !static_cast<bool>(aResult); + } +}; + +/** + * Same rationale as for RequestHandlerBase. + */ +struct ResponseHandlerBase { + // Default to the namespace-level IPCTypeMap + template <typename OrigType> + struct IPCTypeMap { + typedef typename mozilla::plugins::IPCTypeMap<OrigType>::ipc_type ipc_type; + }; +}; + +#if defined(XP_WIN) + +// Response phase uses OpenFileNameRetIPC for an LPOPENFILENAMEW parameter. +template <> +struct ResponseHandlerBase::IPCTypeMap<LPOPENFILENAMEW> { + typedef OpenFileNameRetIPC ipc_type; +}; + +#endif + +template <FunctionHookId functionId, typename FunctionType, + typename EHContainer> +struct ResponseHandler; + +template <FunctionHookId functionId, typename EHContainerType, + typename ResultType, typename... ParamTypes> +struct ResponseHandler<functionId, ResultType HOOK_CALL(ParamTypes...), + EHContainerType> : public ResponseHandlerBase { + typedef ResultType(HOOK_CALL FunctionType)(ParamTypes...); + typedef ResponseHandler<functionId, FunctionType, EHContainerType> SelfType; + typedef ResponseInfo<functionId> Info; + typedef EHContainerType EHContainer; + + static void Marshal(IpdlTuple& aTuple, const ResultType& aResult, + const ParamTypes&... aParams) { + // Note that this "trick" means that the first parameter we marshal is + // considered to be parameter #-1 when checking the ResponseInfo. + // The parameters in the list therefore start at index 0. + RspMarshaler::template Marshal<-1>(aTuple, aResult, aParams...); + } + static bool Unmarshal(const IpdlTuple& aTuple, ResultType& aResult, + ParamTypes&... aParams) { + IpdlTupleContext cxt(&aTuple); + return RspUnmarshaler::template Unmarshal<-1>(cxt, aResult, aParams...); + } + + typedef Marshaler<SERVER, SelfType> RspMarshaler; + typedef Marshaler<CLIENT, SelfType> RspUnmarshaler; + + // Fixed parameters are not used in the response phase. + template <int tupleIndex, typename VarParam> + static void CopyFixedParam(VarParam& aParam) {} +}; + +/** + * Reference-counted monitor, used to synchronize communication between a + * thread using a brokered API and the FunctionDispatch thread. + */ +class FDMonitor : public Monitor { + public: + FDMonitor() : Monitor("FunctionDispatchThread lock") {} + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FDMonitor) + + private: + ~FDMonitor() = default; +}; + +/** + * Data for hooking a function that we automatically broker in a remote + * process. + */ +template <FunctionHookId functionId, typename FunctionType, + typename EHContainer = BaseEHContainer> +class FunctionBroker; + +template <FunctionHookId functionId, typename EHContainer, typename ResultType, + typename... ParamTypes> +class FunctionBroker<functionId, ResultType HOOK_CALL(ParamTypes...), + EHContainer> + : public BasicFunctionHook<functionId, + ResultType HOOK_CALL(ParamTypes...)> { + public: + typedef Tuple<ParamTypes...> TupleParamTypes; + typedef Tuple<mozilla::Maybe<ParamTypes>...> TupleMaybeParamTypes; + typedef Tuple<ParamTypes*...> TupleParamPtrTypes; + typedef Tuple<ParamTypes&...> TupleParamRefTypes; + static const size_t numParams = sizeof...(ParamTypes); + + typedef ResultType(HOOK_CALL FunctionType)(ParamTypes...); + typedef FunctionBroker<functionId, FunctionType, EHContainer> SelfType; + typedef BasicFunctionHook<functionId, FunctionType> FunctionHookInfoType; + typedef FunctionHookInfoType BaseType; + + typedef RequestHandler<functionId, FunctionType, EHContainer> Request; + typedef ResponseHandler<functionId, FunctionType, EHContainer> Response; + + template <typename DelegateFcnType> + using RequestDelegate = + RequestHandler<functionId, DelegateFcnType, EHContainer>; + template <typename DelegateFcnType> + using ResponseDelegate = + ResponseHandler<functionId, DelegateFcnType, EHContainer>; + + FunctionBroker(const char* aModuleName, const char* aMethodName, + FunctionType* aOriginalFunction) + : BasicFunctionHook<functionId, FunctionType>( + aModuleName, aMethodName, aOriginalFunction, InterceptorStub) {} + + // This is the function used to replace the original DLL-intercepted function. + static ResultType HOOK_CALL InterceptorStub(ParamTypes... aParams) { + MOZ_ASSERT(functionId < FunctionHook::GetHooks()->Length()); + FunctionHook* self = FunctionHook::GetHooks()->ElementAt(functionId); + MOZ_ASSERT(self && self->FunctionId() == functionId); + const SelfType* broker = static_cast<const SelfType*>(self); + return broker->MaybeBrokerCallClient(aParams...); + } + + /** + * Handle a call by running the original version or brokering, depending on + * ShouldBroker. All parameter types (including the result type) + * must have IPDL ParamTraits specializations or appear in this object's + * IPCTypeMap. If brokering fails for any reason then this falls back to + * calling the original version of the function. + */ + ResultType MaybeBrokerCallClient(ParamTypes&... aParameters) const; + + /** + * Called server-side to run the original function using aInTuple + * as parameter values. The return value and returned parameters + * (in that order) are added to aOutTuple. + */ + bool RunOriginalFunction(base::ProcessId aClientId, + const IPC::IpdlTuple& aInTuple, + IPC::IpdlTuple* aOutTuple) const override { + return BrokerCallServer(aClientId, aInTuple, aOutTuple); + } + + protected: + bool BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple) const { + return BrokerCallServer(aClientId, aInTuple, aOutTuple, + std::index_sequence_for<ParamTypes...>{}); + } + + bool BrokerCallClient(uint32_t& aWinError, ResultType& aResult, + ParamTypes&... aParameters) const; + bool PostToDispatchThread(uint32_t& aWinError, ResultType& aRet, + ParamTypes&... aParameters) const; + + static void PostToDispatchHelper(const SelfType* bmhi, + RefPtr<FDMonitor> monitor, bool* notified, + bool* ok, uint32_t* winErr, ResultType* r, + ParamTypes*... p) { + // Note: p is also non-null... its just hard to assert that. + MOZ_ASSERT(bmhi && monitor && notified && ok && winErr && r); + MOZ_ASSERT(*notified == false); + *ok = bmhi->BrokerCallClient(*winErr, *r, *p...); + + { + // We need to grab the lock to make sure that Wait() has been + // called in PostToDispatchThread. We need that since we wake it with + // Notify(). + MonitorAutoLock lock(*monitor); + *notified = true; + } + + monitor->Notify(); + }; + + template <typename... VarParams> + BROKER_DISABLE_CFGUARD ResultType RunFunction(FunctionType* aFunction, + base::ProcessId aClientId, + VarParams&... aParams) const { + return aFunction(aParams...); + }; + + bool BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple, ParamTypes&... aParams) const; + + template <size_t... Indices> + bool BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple, + std::index_sequence<Indices...>) const { + TupleParamTypes paramTuple; + return BrokerCallServer(aClientId, aInTuple, aOutTuple, + Get<Indices>(paramTuple)...); + } +}; + +template <FunctionHookId functionId, typename EHContainer, typename ResultType, + typename... ParamTypes> +ResultType FunctionBroker< + functionId, ResultType HOOK_CALL(ParamTypes...), + EHContainer>::MaybeBrokerCallClient(ParamTypes&... aParameters) const { + MOZ_ASSERT(FunctionBrokerChild::GetInstance()); + + // Broker the call if ShouldBroker says to. Otherwise, or if brokering + // fails, then call the original implementation. + if (!FunctionBrokerChild::GetInstance()) { + HOOK_LOG(LogLevel::Error, + ("[%s] Client attempted to broker call without actor.", + FunctionHookInfoType::mFunctionName.Data())); + } else if (Request::ShouldBroker(CLIENT, aParameters...)) { + HOOK_LOG(LogLevel::Debug, ("[%s] Client attempting to broker call.", + FunctionHookInfoType::mFunctionName.Data())); + uint32_t winError; + ResultType ret; + bool success = BrokerCallClient(winError, ret, aParameters...); + HOOK_LOG(LogLevel::Info, + ("[%s] Client brokering %s.", + FunctionHookInfoType::mFunctionName.Data(), SuccessMsg(success))); + if (success) { +#if defined(XP_WIN) + if (Response::Info::ShouldTransmitError(ret)) { + HOOK_LOG(LogLevel::Debug, + ("[%s] Client setting thread error code: %08x.", + FunctionHookInfoType::mFunctionName.Data(), winError)); + ::SetLastError(winError); + } +#endif + return ret; + } + } + + HOOK_LOG(LogLevel::Info, + ("[%s] Client could not broker. Running original version.", + FunctionHookInfoType::mFunctionName.Data())); + return FunctionHookInfoType::mOldFunction(aParameters...); +} + +template <FunctionHookId functionId, typename EHContainer, typename ResultType, + typename... ParamTypes> +bool FunctionBroker<functionId, ResultType HOOK_CALL(ParamTypes...), + EHContainer>::BrokerCallClient(uint32_t& aWinError, + ResultType& aResult, + ParamTypes&... aParameters) + const { + if (!FunctionBrokerChild::GetInstance()->IsDispatchThread()) { + return PostToDispatchThread(aWinError, aResult, aParameters...); + } + + if (FunctionBrokerChild::GetInstance()) { + IpdlTuple sending, returned; + HOOK_LOG(LogLevel::Debug, ("[%s] Client marshaling parameters.", + FunctionHookInfoType::mFunctionName.Data())); + Request::Marshal(sending, aParameters...); + HOOK_LOG(LogLevel::Info, ("[%s] Client sending broker message.", + FunctionHookInfoType::mFunctionName.Data())); + if (FunctionBrokerChild::GetInstance()->SendBrokerFunction( + FunctionHookInfoType::FunctionId(), sending, &returned)) { + HOOK_LOG(LogLevel::Debug, + ("[%s] Client received broker message response.", + FunctionHookInfoType::mFunctionName.Data())); + bool success = Response::Unmarshal(returned, aResult, aParameters...); + HOOK_LOG(LogLevel::Info, ("[%s] Client response unmarshaling: %s.", + FunctionHookInfoType::mFunctionName.Data(), + SuccessMsg(success))); +#if defined(XP_WIN) + if (success && Response::Info::ShouldTransmitError(aResult)) { + uint32_t* winError = + returned.Element<UINT32>(returned.NumElements() - 1); + if (!winError) { + HOOK_LOG(LogLevel::Error, + ("[%s] Client failed to unmarshal error code.", + FunctionHookInfoType::mFunctionName.Data())); + return false; + } + HOOK_LOG(LogLevel::Debug, + ("[%s] Client response unmarshaled error code: %08x.", + FunctionHookInfoType::mFunctionName.Data(), *winError)); + aWinError = *winError; + } +#endif + return success; + } + } + + HOOK_LOG(LogLevel::Error, ("[%s] Client failed to broker call.", + FunctionHookInfoType::mFunctionName.Data())); + return false; +} + +template <FunctionHookId functionId, typename EHContainer, typename ResultType, + typename... ParamTypes> +bool FunctionBroker<functionId, ResultType HOOK_CALL(ParamTypes...), + EHContainer>::BrokerCallServer(base::ProcessId aClientId, + const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple, + ParamTypes&... aParams) + const { + HOOK_LOG(LogLevel::Info, ("[%s] Server brokering function.", + FunctionHookInfoType::mFunctionName.Data())); + + ServerCallData scd; + if (!Request::Unmarshal(scd, aInTuple, aParams...)) { + HOOK_LOG(LogLevel::Info, ("[%s] Server failed to unmarshal.", + FunctionHookInfoType::mFunctionName.Data())); + return false; + } + + // Make sure that this call was legal -- do not execute a call that + // shouldn't have been brokered in the first place. + if (!Request::ShouldBroker(SERVER, aParams...)) { + HOOK_LOG(LogLevel::Error, ("[%s] Server rejected brokering request.", + FunctionHookInfoType::mFunctionName.Data())); + return false; + } + + // Run the function we are brokering. + HOOK_LOG(LogLevel::Info, ("[%s] Server broker running function.", + FunctionHookInfoType::mFunctionName.Data())); + ResultType ret = + RunFunction(FunctionHookInfoType::mOldFunction, aClientId, aParams...); + +#if defined(XP_WIN) + // Record the thread-local error state (before it is changed) if needed. + uint32_t err = UINT_MAX; + bool transmitError = Response::Info::ShouldTransmitError(ret); + if (transmitError) { + err = ::GetLastError(); + HOOK_LOG(LogLevel::Info, ("[%s] Server returning thread error code: %08x.", + FunctionHookInfoType::mFunctionName.Data(), err)); + } +#endif + + // Add the result, win thread error and any returned parameters to the + // returned tuple. + Response::Marshal(*aOutTuple, ret, aParams...); +#if defined(XP_WIN) + if (transmitError) { + aOutTuple->AddElement(err); + } +#endif + + return true; +} + +template <FunctionHookId functionId, typename EHContainer, typename ResultType, + typename... ParamTypes> +bool FunctionBroker< + functionId, ResultType HOOK_CALL(ParamTypes...), + EHContainer>::PostToDispatchThread(uint32_t& aWinError, ResultType& aRet, + ParamTypes&... aParameters) const { + MOZ_ASSERT(!FunctionBrokerChild::GetInstance()->IsDispatchThread()); + HOOK_LOG(LogLevel::Debug, ("Posting broker task '%s' to dispatch thread", + FunctionHookInfoType::mFunctionName.Data())); + + // Run PostToDispatchHelper on the dispatch thread. It will notify our + // waiting monitor when it is done. + RefPtr<FDMonitor> monitor(new FDMonitor()); + MonitorAutoLock lock(*monitor); + bool success = false; + bool notified = false; + FunctionBrokerChild::GetInstance()->PostToDispatchThread(NewRunnableFunction( + "FunctionDispatchThreadRunnable", &PostToDispatchHelper, this, monitor, + ¬ified, &success, &aWinError, &aRet, &aParameters...)); + + // We wait to be notified, testing that notified was actually set to make + // sure this isn't a spurious wakeup. + while (!notified) { + monitor->Wait(); + } + return success; +} + +void AddBrokeredFunctionHooks(FunctionHookArray& aHooks); + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_ipc_PluginHooksWin_h diff --git a/dom/plugins/ipc/FunctionBrokerChild.cpp b/dom/plugins/ipc/FunctionBrokerChild.cpp new file mode 100644 index 0000000000..a0780d853f --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerChild.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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 "FunctionBrokerChild.h" +#include "FunctionBrokerThread.h" + +#include "mozilla/ipc/Endpoint.h" + +namespace mozilla::plugins { + +FunctionBrokerChild* FunctionBrokerChild::sInstance = nullptr; + +bool FunctionBrokerChild::IsDispatchThread() { return mThread->IsOnThread(); } + +void FunctionBrokerChild::PostToDispatchThread( + already_AddRefed<nsIRunnable>&& runnable) { + mThread->Dispatch(std::move(runnable)); +} + +/* static */ +bool FunctionBrokerChild::Initialize( + Endpoint<PFunctionBrokerChild>&& aBrokerEndpoint) { + MOZ_RELEASE_ASSERT( + XRE_IsPluginProcess(), + "FunctionBrokerChild can only be used in plugin processes"); + + MOZ_ASSERT(!sInstance); + FunctionBrokerThread* thread = FunctionBrokerThread::Create(); + if (!thread) { + return false; + } + sInstance = new FunctionBrokerChild(thread, std::move(aBrokerEndpoint)); + return true; +} + +/* static */ +FunctionBrokerChild* FunctionBrokerChild::GetInstance() { + MOZ_RELEASE_ASSERT( + XRE_IsPluginProcess(), + "FunctionBrokerChild can only be used in plugin processes"); + + MOZ_ASSERT(sInstance, "Must initialize FunctionBrokerChild before using it"); + return sInstance; +} + +FunctionBrokerChild::FunctionBrokerChild( + FunctionBrokerThread* aThread, Endpoint<PFunctionBrokerChild>&& aEndpoint) + : mThread(aThread), + mShutdownDone(false), + mMonitor("FunctionBrokerChild Lock") { + MOZ_ASSERT(aThread); + PostToDispatchThread( + NewNonOwningRunnableMethod<Endpoint<PFunctionBrokerChild>&&>( + "FunctionBrokerChild::Bind", this, &FunctionBrokerChild::Bind, + std::move(aEndpoint))); +} + +void FunctionBrokerChild::Bind(Endpoint<PFunctionBrokerChild>&& aEndpoint) { + MOZ_RELEASE_ASSERT(mThread->IsOnThread()); + DebugOnly<bool> ok = aEndpoint.Bind(this); + MOZ_ASSERT(ok); +} + +void FunctionBrokerChild::ShutdownOnDispatchThread() { + MOZ_ASSERT(mThread->IsOnThread()); + + // Set mShutdownDone and notify waiting thread (if any) that we are done. + MonitorAutoLock lock(mMonitor); + mShutdownDone = true; + mMonitor.Notify(); +} + +void FunctionBrokerChild::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_ASSERT(mThread->IsOnThread()); + + // Queue up a task on the PD thread. When that task is executed then + // we know that anything queued before ActorDestroy has completed. + // At that point, we can set mShutdownDone and alert any waiting + // threads that it is safe to destroy us. + sInstance->PostToDispatchThread(NewNonOwningRunnableMethod( + "FunctionBrokerChild::ShutdownOnDispatchThread", sInstance, + &FunctionBrokerChild::ShutdownOnDispatchThread)); +} + +void FunctionBrokerChild::Destroy() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!sInstance) { + return; + } + + // mShutdownDone will tell us when ActorDestroy has been run and any tasks + // on the FunctionBrokerThread have completed. At that point, we can + // safely delete the actor. + { + MonitorAutoLock lock(sInstance->mMonitor); + while (!sInstance->mShutdownDone) { + // Release lock and wait. Regain lock when we are notified that + // we have ShutdownOnDispatchThread. + sInstance->mMonitor.Wait(); + } + } + + delete sInstance; + sInstance = nullptr; +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/FunctionBrokerChild.h b/dom/plugins/ipc/FunctionBrokerChild.h new file mode 100644 index 0000000000..767aaab170 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerChild.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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_functionbrokerchild_h +#define mozilla_plugins_functionbrokerchild_h + +#include "mozilla/plugins/PFunctionBrokerChild.h" + +namespace mozilla { +namespace plugins { + +class FunctionBrokerThread; + +/** + * Dispatches brokered methods to the Parent process to allow functionality + * that is otherwise blocked by the sandbox. + */ +class FunctionBrokerChild : public PFunctionBrokerChild { + public: + static bool Initialize(Endpoint<PFunctionBrokerChild>&& aBrokerEndpoint); + static FunctionBrokerChild* GetInstance(); + static void Destroy(); + + bool IsDispatchThread(); + void PostToDispatchThread(already_AddRefed<nsIRunnable>&& runnable); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + explicit FunctionBrokerChild(FunctionBrokerThread* aThread, + Endpoint<PFunctionBrokerChild>&& aEndpoint); + void ShutdownOnDispatchThread(); + void Bind(Endpoint<PFunctionBrokerChild>&& aEndpoint); + + UniquePtr<FunctionBrokerThread> mThread; + + // True if tasks on the FunctionBrokerThread have completed + bool mShutdownDone; + // This monitor guards mShutdownDone. + Monitor mMonitor; + + static FunctionBrokerChild* sInstance; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_functionbrokerchild_h diff --git a/dom/plugins/ipc/FunctionBrokerIPCUtils.cpp b/dom/plugins/ipc/FunctionBrokerIPCUtils.cpp new file mode 100644 index 0000000000..e0ee31e635 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerIPCUtils.cpp @@ -0,0 +1,334 @@ +/* -*- 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 "FunctionBrokerIPCUtils.h" + +#if defined(XP_WIN) + +# include <schannel.h> + +/* these defines are missing from mingw headers */ +# ifndef SP_PROT_TLS1_1_CLIENT +# define SP_PROT_TLS1_1_CLIENT 0x00000200 +# endif + +# ifndef SP_PROT_TLS1_2_CLIENT +# define SP_PROT_TLS1_2_CLIENT 0x00000800 +# endif + +namespace mozilla { +namespace plugins { + +mozilla::LazyLogModule sPluginHooksLog("PluginHooks"); + +static const DWORD SCHANNEL_SUPPORTED_PROTOCOLS = + SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT; + +static const DWORD SCHANNEL_SUPPORTED_FLAGS = + SCH_CRED_MANUAL_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS | + SCH_CRED_REVOCATION_CHECK_END_CERT; + +void OpenFileNameIPC::CopyFromOfn(LPOPENFILENAMEW aLpofn) { + mHwndOwner = nullptr; + + // Filter is double-NULL terminated. mFilter should include the double-NULL. + mHasFilter = aLpofn->lpstrFilter != nullptr; + if (mHasFilter) { + uint32_t dNullIdx = 0; + while (aLpofn->lpstrFilter[dNullIdx] != L'\0' || + aLpofn->lpstrFilter[dNullIdx + 1] != L'\0') { + dNullIdx++; + } + mFilter.assign(aLpofn->lpstrFilter, dNullIdx + 2); + } + mHasCustomFilter = aLpofn->lpstrCustomFilter != nullptr; + if (mHasCustomFilter) { + mCustomFilterIn = std::wstring(aLpofn->lpstrCustomFilter); + mNMaxCustFilterOut = + aLpofn->nMaxCustFilter - (wcslen(aLpofn->lpstrCustomFilter) + 1); + } else { + mNMaxCustFilterOut = 0; + } + mFilterIndex = aLpofn->nFilterIndex; + mFile = std::wstring(aLpofn->lpstrFile); + mNMaxFile = aLpofn->nMaxFile; + mNMaxFileTitle = + aLpofn->lpstrFileTitle != nullptr ? aLpofn->nMaxFileTitle : 0; + mHasInitialDir = aLpofn->lpstrInitialDir != nullptr; + if (mHasInitialDir) { + mInitialDir = std::wstring(aLpofn->lpstrInitialDir); + } + mHasTitle = aLpofn->lpstrTitle != nullptr; + if (mHasTitle) { + mTitle = std::wstring(aLpofn->lpstrTitle); + } + mHasDefExt = aLpofn->lpstrDefExt != nullptr; + if (mHasDefExt) { + mDefExt = std::wstring(aLpofn->lpstrDefExt); + } + + mFlags = aLpofn->Flags; + // If the user sets OFN_ALLOWMULTISELECT then we require OFN_EXPLORER + // as well. Without OFN_EXPLORER, the method has ancient legacy + // behavior that we don't support. + MOZ_ASSERT((mFlags & OFN_EXPLORER) || !(mFlags & OFN_ALLOWMULTISELECT)); + + // We ignore any visual customization and callbacks that the user set. + mFlags &= ~(OFN_ENABLEHOOK | OFN_ENABLETEMPLATEHANDLE | OFN_ENABLETEMPLATE); + + mFlagsEx = aLpofn->FlagsEx; +} + +void OpenFileNameIPC::AddToOfn(LPOPENFILENAMEW aLpofn) const { + aLpofn->lStructSize = sizeof(OPENFILENAMEW); + aLpofn->hwndOwner = mHwndOwner; + if (mHasFilter) { + memcpy(const_cast<LPWSTR>(aLpofn->lpstrFilter), mFilter.data(), + mFilter.size() * sizeof(wchar_t)); + } + if (mHasCustomFilter) { + aLpofn->nMaxCustFilter = mCustomFilterIn.size() + 1 + mNMaxCustFilterOut; + wcscpy(aLpofn->lpstrCustomFilter, mCustomFilterIn.c_str()); + memset(aLpofn->lpstrCustomFilter + mCustomFilterIn.size() + 1, 0, + mNMaxCustFilterOut * sizeof(wchar_t)); + } else { + aLpofn->nMaxCustFilter = 0; + } + aLpofn->nFilterIndex = mFilterIndex; + if (mNMaxFile > 0) { + wcsncpy(aLpofn->lpstrFile, mFile.c_str(), + std::min(static_cast<uint32_t>(mFile.size() + 1), mNMaxFile)); + aLpofn->lpstrFile[mNMaxFile - 1] = L'\0'; + } + aLpofn->nMaxFile = mNMaxFile; + aLpofn->nMaxFileTitle = mNMaxFileTitle; + if (mHasInitialDir) { + wcscpy(const_cast<LPWSTR>(aLpofn->lpstrInitialDir), mInitialDir.c_str()); + } + if (mHasTitle) { + wcscpy(const_cast<LPWSTR>(aLpofn->lpstrTitle), mTitle.c_str()); + } + aLpofn->Flags = mFlags; /* TODO: Consider adding OFN_NOCHANGEDIR */ + if (mHasDefExt) { + wcscpy(const_cast<LPWSTR>(aLpofn->lpstrDefExt), mDefExt.c_str()); + } + aLpofn->FlagsEx = mFlagsEx; +} + +void OpenFileNameIPC::AllocateOfnStrings(LPOPENFILENAMEW aLpofn) const { + if (mHasFilter) { + // mFilter is double-NULL terminated and it includes the double-NULL in its + // length. + aLpofn->lpstrFilter = + static_cast<LPCTSTR>(moz_xmalloc(sizeof(wchar_t) * (mFilter.size()))); + } + if (mHasCustomFilter) { + aLpofn->lpstrCustomFilter = static_cast<LPTSTR>(moz_xmalloc( + sizeof(wchar_t) * (mCustomFilterIn.size() + 1 + mNMaxCustFilterOut))); + } + aLpofn->lpstrFile = + static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * mNMaxFile)); + if (mNMaxFileTitle > 0) { + aLpofn->lpstrFileTitle = + static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * mNMaxFileTitle)); + } + if (mHasInitialDir) { + aLpofn->lpstrInitialDir = static_cast<LPCTSTR>( + moz_xmalloc(sizeof(wchar_t) * (mInitialDir.size() + 1))); + } + if (mHasTitle) { + aLpofn->lpstrTitle = static_cast<LPCTSTR>( + moz_xmalloc(sizeof(wchar_t) * (mTitle.size() + 1))); + } + if (mHasDefExt) { + aLpofn->lpstrDefExt = static_cast<LPCTSTR>( + moz_xmalloc(sizeof(wchar_t) * (mDefExt.size() + 1))); + } +} + +// static +void OpenFileNameIPC::FreeOfnStrings(LPOPENFILENAMEW aLpofn) { + if (aLpofn->lpstrFilter) { + free(const_cast<LPWSTR>(aLpofn->lpstrFilter)); + } + if (aLpofn->lpstrCustomFilter) { + free(aLpofn->lpstrCustomFilter); + } + if (aLpofn->lpstrFile) { + free(aLpofn->lpstrFile); + } + if (aLpofn->lpstrFileTitle) { + free(aLpofn->lpstrFileTitle); + } + if (aLpofn->lpstrInitialDir) { + free(const_cast<LPWSTR>(aLpofn->lpstrInitialDir)); + } + if (aLpofn->lpstrTitle) { + free(const_cast<LPWSTR>(aLpofn->lpstrTitle)); + } + if (aLpofn->lpstrDefExt) { + free(const_cast<LPWSTR>(aLpofn->lpstrDefExt)); + } +} + +void OpenFileNameRetIPC::CopyFromOfn(LPOPENFILENAMEW aLpofn) { + if (aLpofn->lpstrCustomFilter != nullptr) { + mCustomFilterOut = std::wstring(aLpofn->lpstrCustomFilter + + wcslen(aLpofn->lpstrCustomFilter) + 1); + } + mFile.assign(aLpofn->lpstrFile, aLpofn->nMaxFile); + if (aLpofn->lpstrFileTitle != nullptr) { + mFileTitle.assign(aLpofn->lpstrFileTitle, + wcslen(aLpofn->lpstrFileTitle) + 1); + } + mFileOffset = aLpofn->nFileOffset; + mFileExtension = aLpofn->nFileExtension; +} + +void OpenFileNameRetIPC::AddToOfn(LPOPENFILENAMEW aLpofn) const { + if (aLpofn->lpstrCustomFilter) { + LPWSTR secondString = + aLpofn->lpstrCustomFilter + wcslen(aLpofn->lpstrCustomFilter) + 1; + const wchar_t* customFilterOut = mCustomFilterOut.c_str(); + MOZ_ASSERT(wcslen(aLpofn->lpstrCustomFilter) + 1 + wcslen(customFilterOut) + + 1 + 1 <= + aLpofn->nMaxCustFilter); + wcscpy(secondString, customFilterOut); + secondString[wcslen(customFilterOut) + 1] = + L'\0'; // terminated with two NULLs + } + MOZ_ASSERT(mFile.size() <= aLpofn->nMaxFile); + memcpy(aLpofn->lpstrFile, mFile.data(), mFile.size() * sizeof(wchar_t)); + if (aLpofn->lpstrFileTitle != nullptr) { + MOZ_ASSERT(mFileTitle.size() + 1 < aLpofn->nMaxFileTitle); + wcscpy(aLpofn->lpstrFileTitle, mFileTitle.c_str()); + } + aLpofn->nFileOffset = mFileOffset; + aLpofn->nFileExtension = mFileExtension; +} + +void IPCSchannelCred::CopyFrom(const PSCHANNEL_CRED& aSCred) { + // We assert that the aSCred fields take supported values. + // If they do not then we ignore the values we were given. + MOZ_ASSERT(aSCred->dwVersion == SCHANNEL_CRED_VERSION); + MOZ_ASSERT(aSCred->cCreds == 0); + MOZ_ASSERT(aSCred->paCred == nullptr); + MOZ_ASSERT(aSCred->hRootStore == nullptr); + MOZ_ASSERT(aSCred->cMappers == 0); + MOZ_ASSERT(aSCred->aphMappers == nullptr); + MOZ_ASSERT(aSCred->cSupportedAlgs == 0); + MOZ_ASSERT(aSCred->palgSupportedAlgs == nullptr); + MOZ_ASSERT((aSCred->grbitEnabledProtocols & SCHANNEL_SUPPORTED_PROTOCOLS) == + aSCred->grbitEnabledProtocols); + mEnabledProtocols = + aSCred->grbitEnabledProtocols & SCHANNEL_SUPPORTED_PROTOCOLS; + mMinStrength = aSCred->dwMinimumCipherStrength; + mMaxStrength = aSCred->dwMaximumCipherStrength; + MOZ_ASSERT(aSCred->dwSessionLifespan == 0); + MOZ_ASSERT((aSCred->dwFlags & SCHANNEL_SUPPORTED_FLAGS) == aSCred->dwFlags); + mFlags = aSCred->dwFlags & SCHANNEL_SUPPORTED_FLAGS; + MOZ_ASSERT(aSCred->dwCredFormat == 0); +} + +void IPCSchannelCred::CopyTo(PSCHANNEL_CRED& aSCred) const { + // Validate values as they come from an untrusted process. + memset(aSCred, 0, sizeof(SCHANNEL_CRED)); + aSCred->dwVersion = SCHANNEL_CRED_VERSION; + aSCred->grbitEnabledProtocols = + mEnabledProtocols & SCHANNEL_SUPPORTED_PROTOCOLS; + aSCred->dwMinimumCipherStrength = mMinStrength; + aSCred->dwMaximumCipherStrength = mMaxStrength; + aSCred->dwFlags = mFlags & SCHANNEL_SUPPORTED_FLAGS; +} + +void IPCInternetBuffers::CopyFrom(const LPINTERNET_BUFFERSA& aBufs) { + mBuffers.Clear(); + + LPINTERNET_BUFFERSA inetBuf = aBufs; + while (inetBuf) { + MOZ_ASSERT(inetBuf->dwStructSize == sizeof(INTERNET_BUFFERSA)); + Buffer* ipcBuf = mBuffers.AppendElement(); + + ipcBuf->mHeader.SetIsVoid(inetBuf->lpcszHeader == nullptr); + if (inetBuf->lpcszHeader) { + ipcBuf->mHeader.Assign(inetBuf->lpcszHeader, inetBuf->dwHeadersLength); + } + ipcBuf->mHeaderTotal = inetBuf->dwHeadersTotal; + + ipcBuf->mBuffer.SetIsVoid(inetBuf->lpvBuffer == nullptr); + if (inetBuf->lpvBuffer) { + ipcBuf->mBuffer.Assign(static_cast<char*>(inetBuf->lpvBuffer), + inetBuf->dwBufferLength); + } + ipcBuf->mBufferTotal = inetBuf->dwBufferTotal; + inetBuf = inetBuf->Next; + } +} + +void IPCInternetBuffers::CopyTo(LPINTERNET_BUFFERSA& aBufs) const { + MOZ_ASSERT(!aBufs); + + LPINTERNET_BUFFERSA lastBuf = nullptr; + for (size_t idx = 0; idx < mBuffers.Length(); ++idx) { + const Buffer& ipcBuf = mBuffers[idx]; + LPINTERNET_BUFFERSA newBuf = static_cast<LPINTERNET_BUFFERSA>( + moz_xcalloc(1, sizeof(INTERNET_BUFFERSA))); + if (idx == 0) { + aBufs = newBuf; + } else { + MOZ_ASSERT(lastBuf); + lastBuf->Next = newBuf; + lastBuf = newBuf; + } + + newBuf->dwStructSize = sizeof(INTERNET_BUFFERSA); + + newBuf->dwHeadersTotal = ipcBuf.mHeaderTotal; + if (!ipcBuf.mHeader.IsVoid()) { + newBuf->lpcszHeader = + static_cast<LPCSTR>(moz_xmalloc(ipcBuf.mHeader.Length())); + memcpy(const_cast<char*>(newBuf->lpcszHeader), ipcBuf.mHeader.Data(), + ipcBuf.mHeader.Length()); + newBuf->dwHeadersLength = ipcBuf.mHeader.Length(); + } + + newBuf->dwBufferTotal = ipcBuf.mBufferTotal; + if (!ipcBuf.mBuffer.IsVoid()) { + newBuf->lpvBuffer = moz_xmalloc(ipcBuf.mBuffer.Length()); + memcpy(newBuf->lpvBuffer, ipcBuf.mBuffer.Data(), ipcBuf.mBuffer.Length()); + newBuf->dwBufferLength = ipcBuf.mBuffer.Length(); + } + } +} + +/* static */ +void IPCInternetBuffers::FreeBuffers(LPINTERNET_BUFFERSA& aBufs) { + if (!aBufs) { + return; + } + while (aBufs) { + LPINTERNET_BUFFERSA temp = aBufs->Next; + free(const_cast<char*>(aBufs->lpcszHeader)); + free(aBufs->lpvBuffer); + free(aBufs); + aBufs = temp; + } +} + +void IPCPrintDlg::CopyFrom(const LPPRINTDLGW& aDlg) { + // DLP: Trouble -- my prior impl "worked" but didn't return anything + // AFAIR. So... ??? But it printed a page!!! How?! + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); +} + +void IPCPrintDlg::CopyTo(LPPRINTDLGW& aDlg) const { + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); +} + +} // namespace plugins +} // namespace mozilla + +#endif // defined(XP_WIN) diff --git a/dom/plugins/ipc/FunctionBrokerIPCUtils.h b/dom/plugins/ipc/FunctionBrokerIPCUtils.h new file mode 100644 index 0000000000..c4b72dbe95 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerIPCUtils.h @@ -0,0 +1,436 @@ +/* -*- 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_functionbrokeripcutils_h +#define dom_plugins_ipc_functionbrokeripcutils_h 1 + +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "PluginMessageUtils.h" + +#if defined(XP_WIN) + +# define SECURITY_WIN32 +# include <security.h> +# include <wininet.h> +# include <schannel.h> +# include <commdlg.h> + +#endif // defined(XP_WIN) + +namespace mozilla { +namespace plugins { + +/** + * This enum represents all of the methods hooked by the main facility in + * BrokerClient. It is used to allow quick lookup in the sFunctionsToHook + * structure. + */ +enum FunctionHookId { +#if defined(XP_WIN) + ID_GetWindowInfo = 0, + ID_GetKeyState, + ID_SetCursorPos, + ID_GetSaveFileNameW, + ID_GetOpenFileNameW, + ID_InternetOpenA, + ID_InternetConnectA, + ID_InternetCloseHandle, + ID_InternetQueryDataAvailable, + ID_InternetReadFile, + ID_InternetWriteFile, + ID_InternetSetOptionA, + ID_HttpAddRequestHeadersA, + ID_HttpOpenRequestA, + ID_HttpQueryInfoA, + ID_HttpSendRequestA, + ID_HttpSendRequestExA, + ID_HttpEndRequestA, + ID_InternetQueryOptionA, + ID_InternetErrorDlg, + ID_AcquireCredentialsHandleA, + ID_QueryCredentialsAttributesA, + ID_FreeCredentialsHandle, + ID_PrintDlgW, + ID_CreateMutexW +# if defined(MOZ_SANDBOX) + , + ID_GetFileAttributesW +# endif // defined(MOZ_SANDBOX) + , + ID_FunctionHookCount +#else // defined(XP_WIN) + ID_FunctionHookCount +#endif // defined(XP_WIN) +}; + +// Max number of bytes to show when logging a blob of raw memory +static const uint32_t MAX_BLOB_CHARS_TO_LOG = 12; + +// Format strings for safe logging despite the fact that they are sometimes +// used as raw binary blobs. +inline nsCString FormatBlob(const nsACString& aParam) { + if (aParam.IsVoid() || aParam.IsEmpty()) { + return nsCString(aParam.IsVoid() ? "<void>" : "<empty>"); + } + + nsCString str; + uint32_t totalLen = std::min(MAX_BLOB_CHARS_TO_LOG, aParam.Length()); + // If we are printing only a portion of the string then follow it with + // ellipsis + const char* maybeEllipsis = + (MAX_BLOB_CHARS_TO_LOG < aParam.Length()) ? "..." : ""; + for (uint32_t idx = 0; idx < totalLen; ++idx) { + // Should be %02x but I've run into a AppendPrintf bug... + str.AppendPrintf("0x%2x ", aParam.Data()[idx] & 0xff); + } + str.AppendPrintf("%s | '", maybeEllipsis); + for (uint32_t idx = 0; idx < totalLen; ++idx) { + str.AppendPrintf("%c", (aParam.Data()[idx] > 0) ? aParam.Data()[idx] : '.'); + } + str.AppendPrintf("'%s", maybeEllipsis); + return str; +} + +#if defined(XP_WIN) + +// Values indicate GetOpenFileNameW and GetSaveFileNameW. +enum GetFileNameFunc { OPEN_FUNC, SAVE_FUNC }; + +typedef CopyableTArray<nsCString> StringArray; + +// IPC-capable version of the Windows OPENFILENAMEW struct. +typedef struct _OpenFileNameIPC { + // Allocates memory for the strings in this object. This should usually + // be used with a zeroed out OPENFILENAMEW structure. + void AllocateOfnStrings(LPOPENFILENAMEW aLpofn) const; + static void FreeOfnStrings(LPOPENFILENAMEW aLpofn); + void AddToOfn(LPOPENFILENAMEW aLpofn) const; + void CopyFromOfn(LPOPENFILENAMEW aLpofn); + bool operator==(const _OpenFileNameIPC& o) const { + return (o.mHwndOwner == mHwndOwner) && (o.mFilter == mFilter) && + (o.mHasFilter == mHasFilter) && + (o.mCustomFilterIn == mCustomFilterIn) && + (o.mHasCustomFilter == mHasCustomFilter) && + (o.mNMaxCustFilterOut == mNMaxCustFilterOut) && + (o.mFilterIndex == mFilterIndex) && (o.mFile == mFile) && + (o.mNMaxFile == mNMaxFile) && (o.mNMaxFileTitle == mNMaxFileTitle) && + (o.mInitialDir == mInitialDir) && + (o.mHasInitialDir == mHasInitialDir) && (o.mTitle == mTitle) && + (o.mHasTitle == mHasTitle) && (o.mFlags == mFlags) && + (o.mDefExt == mDefExt) && (o.mHasDefExt == mHasDefExt) && + (o.mFlagsEx == mFlagsEx); + } + + NativeWindowHandle mHwndOwner; + std::wstring + mFilter; // Double-NULL terminated (i.e. L"\0\0") if mHasFilter is true + bool mHasFilter; + std::wstring mCustomFilterIn; + bool mHasCustomFilter; + uint32_t mNMaxCustFilterOut; + uint32_t mFilterIndex; + std::wstring mFile; + uint32_t mNMaxFile; + uint32_t mNMaxFileTitle; + std::wstring mInitialDir; + bool mHasInitialDir; + std::wstring mTitle; + bool mHasTitle; + uint32_t mFlags; + std::wstring mDefExt; + bool mHasDefExt; + uint32_t mFlagsEx; +} OpenFileNameIPC; + +// GetOpenFileNameW and GetSaveFileNameW overwrite fields of their OPENFILENAMEW +// parameter. This represents those values so that they can be returned via +// IPC. +typedef struct _OpenFileNameRetIPC { + void CopyFromOfn(LPOPENFILENAMEW aLpofn); + void AddToOfn(LPOPENFILENAMEW aLpofn) const; + bool operator==(const _OpenFileNameRetIPC& o) const { + return (o.mCustomFilterOut == mCustomFilterOut) && (o.mFile == mFile) && + (o.mFileTitle == mFileTitle) && (o.mFileOffset == mFileOffset) && + (o.mFileExtension == mFileExtension); + } + + std::wstring mCustomFilterOut; + std::wstring mFile; // Double-NULL terminated (i.e. L"\0\0") + std::wstring mFileTitle; + uint16_t mFileOffset; + uint16_t mFileExtension; +} OpenFileNameRetIPC; + +typedef struct _IPCSchannelCred { + void CopyFrom(const PSCHANNEL_CRED& aSCred); + void CopyTo(PSCHANNEL_CRED& aSCred) const; + bool operator==(const _IPCSchannelCred& o) const { + return (o.mEnabledProtocols == mEnabledProtocols) && + (o.mMinStrength == mMinStrength) && + (o.mMaxStrength == mMaxStrength) && (o.mFlags == mFlags); + } + + DWORD mEnabledProtocols; + DWORD mMinStrength; + DWORD mMaxStrength; + DWORD mFlags; +} IPCSchannelCred; + +typedef struct _IPCInternetBuffers { + void CopyFrom(const LPINTERNET_BUFFERSA& aBufs); + void CopyTo(LPINTERNET_BUFFERSA& aBufs) const; + bool operator==(const _IPCInternetBuffers& o) const { + return o.mBuffers == mBuffers; + } + static void FreeBuffers(LPINTERNET_BUFFERSA& aBufs); + + struct Buffer { + nsCString mHeader; + uint32_t mHeaderTotal; + nsCString mBuffer; + uint32_t mBufferTotal; + bool operator==(const Buffer& o) const { + return (o.mHeader == mHeader) && (o.mHeaderTotal == mHeaderTotal) && + (o.mBuffer == mBuffer) && (o.mBufferTotal == mBufferTotal); + } + }; + CopyableTArray<Buffer> mBuffers; +} IPCInternetBuffers; + +typedef struct _IPCPrintDlg { + void CopyFrom(const LPPRINTDLGW& aDlg); + void CopyTo(LPPRINTDLGW& aDlg) const; + bool operator==(const _IPCPrintDlg& o) const { + MOZ_ASSERT_UNREACHABLE("DLP: TODO:"); + return false; + } +} IPCPrintDlg; + +#endif // defined(XP_WIN) + +} // namespace plugins +} // namespace mozilla + +namespace IPC { + +using mozilla::plugins::FunctionHookId; + +#if defined(XP_WIN) + +using mozilla::plugins::IPCInternetBuffers; +using mozilla::plugins::IPCPrintDlg; +using mozilla::plugins::IPCSchannelCred; +using mozilla::plugins::NativeWindowHandle; +using mozilla::plugins::OpenFileNameIPC; +using mozilla::plugins::OpenFileNameRetIPC; +using mozilla::plugins::StringArray; + +template <> +struct ParamTraits<OpenFileNameIPC> { + typedef OpenFileNameIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mHwndOwner); + WriteParam(aMsg, aParam.mFilter); + WriteParam(aMsg, aParam.mHasFilter); + WriteParam(aMsg, aParam.mCustomFilterIn); + WriteParam(aMsg, aParam.mHasCustomFilter); + WriteParam(aMsg, aParam.mNMaxCustFilterOut); + WriteParam(aMsg, aParam.mFilterIndex); + WriteParam(aMsg, aParam.mFile); + WriteParam(aMsg, aParam.mNMaxFile); + WriteParam(aMsg, aParam.mNMaxFileTitle); + WriteParam(aMsg, aParam.mInitialDir); + WriteParam(aMsg, aParam.mHasInitialDir); + WriteParam(aMsg, aParam.mTitle); + WriteParam(aMsg, aParam.mHasTitle); + WriteParam(aMsg, aParam.mFlags); + WriteParam(aMsg, aParam.mDefExt); + WriteParam(aMsg, aParam.mHasDefExt); + WriteParam(aMsg, aParam.mFlagsEx); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (ReadParam(aMsg, aIter, &aResult->mHwndOwner) && + ReadParam(aMsg, aIter, &aResult->mFilter) && + ReadParam(aMsg, aIter, &aResult->mHasFilter) && + ReadParam(aMsg, aIter, &aResult->mCustomFilterIn) && + ReadParam(aMsg, aIter, &aResult->mHasCustomFilter) && + ReadParam(aMsg, aIter, &aResult->mNMaxCustFilterOut) && + ReadParam(aMsg, aIter, &aResult->mFilterIndex) && + ReadParam(aMsg, aIter, &aResult->mFile) && + ReadParam(aMsg, aIter, &aResult->mNMaxFile) && + ReadParam(aMsg, aIter, &aResult->mNMaxFileTitle) && + ReadParam(aMsg, aIter, &aResult->mInitialDir) && + ReadParam(aMsg, aIter, &aResult->mHasInitialDir) && + ReadParam(aMsg, aIter, &aResult->mTitle) && + ReadParam(aMsg, aIter, &aResult->mHasTitle) && + ReadParam(aMsg, aIter, &aResult->mFlags) && + ReadParam(aMsg, aIter, &aResult->mDefExt) && + ReadParam(aMsg, aIter, &aResult->mHasDefExt) && + ReadParam(aMsg, aIter, &aResult->mFlagsEx)) { + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%ls, %ls, %ls, %ls]", aParam.mFilter.c_str(), + aParam.mCustomFilterIn.c_str(), + aParam.mFile.c_str(), aParam.mTitle.c_str())); + } +}; + +template <> +struct ParamTraits<OpenFileNameRetIPC> { + typedef OpenFileNameRetIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mCustomFilterOut); + WriteParam(aMsg, aParam.mFile); + WriteParam(aMsg, aParam.mFileTitle); + WriteParam(aMsg, aParam.mFileOffset); + WriteParam(aMsg, aParam.mFileExtension); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (ReadParam(aMsg, aIter, &aResult->mCustomFilterOut) && + ReadParam(aMsg, aIter, &aResult->mFile) && + ReadParam(aMsg, aIter, &aResult->mFileTitle) && + ReadParam(aMsg, aIter, &aResult->mFileOffset) && + ReadParam(aMsg, aIter, &aResult->mFileExtension)) { + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%ls, %ls, %ls, %d, %d]", + aParam.mCustomFilterOut.c_str(), + aParam.mFile.c_str(), aParam.mFileTitle.c_str(), + aParam.mFileOffset, aParam.mFileExtension)); + } +}; + +template <> +struct ParamTraits<mozilla::plugins::GetFileNameFunc> + : public ContiguousEnumSerializerInclusive< + mozilla::plugins::GetFileNameFunc, mozilla::plugins::OPEN_FUNC, + mozilla::plugins::SAVE_FUNC> {}; + +template <> +struct ParamTraits<IPCSchannelCred> { + typedef mozilla::plugins::IPCSchannelCred paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, static_cast<uint32_t>(aParam.mEnabledProtocols)); + WriteParam(aMsg, static_cast<uint32_t>(aParam.mMinStrength)); + WriteParam(aMsg, static_cast<uint32_t>(aParam.mMaxStrength)); + WriteParam(aMsg, static_cast<uint32_t>(aParam.mFlags)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t proto, minStr, maxStr, flags; + if (!ReadParam(aMsg, aIter, &proto) || !ReadParam(aMsg, aIter, &minStr) || + !ReadParam(aMsg, aIter, &maxStr) || !ReadParam(aMsg, aIter, &flags)) { + return false; + } + aResult->mEnabledProtocols = proto; + aResult->mMinStrength = minStr; + aResult->mMaxStrength = maxStr; + aResult->mFlags = flags; + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%d,%d,%d,%d]", aParam.mEnabledProtocols, + aParam.mMinStrength, aParam.mMaxStrength, + aParam.mFlags)); + } +}; + +template <> +struct ParamTraits<IPCInternetBuffers::Buffer> { + typedef mozilla::plugins::IPCInternetBuffers::Buffer paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mHeader); + WriteParam(aMsg, aParam.mHeaderTotal); + WriteParam(aMsg, aParam.mBuffer); + WriteParam(aMsg, aParam.mBufferTotal); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mHeader) && + ReadParam(aMsg, aIter, &aResult->mHeaderTotal) && + ReadParam(aMsg, aIter, &aResult->mBuffer) && + ReadParam(aMsg, aIter, &aResult->mBufferTotal); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + nsCString head = mozilla::plugins::FormatBlob(aParam.mHeader); + nsCString buffer = mozilla::plugins::FormatBlob(aParam.mBuffer); + std::string msg = + StringPrintf("[%s, %d, %s, %d]", head.Data(), aParam.mHeaderTotal, + buffer.Data(), aParam.mBufferTotal); + aLog->append(msg.begin(), msg.end()); + } +}; + +template <> +struct ParamTraits<IPCInternetBuffers> { + typedef mozilla::plugins::IPCInternetBuffers paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mBuffers); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mBuffers); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + ParamTraits<nsTArray<IPCInternetBuffers::Buffer>>::Log(aParam.mBuffers, + aLog); + } +}; + +template <> +struct ParamTraits<IPCPrintDlg> { + typedef mozilla::plugins::IPCPrintDlg paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); + } +}; + +#endif // defined(XP_WIN) + +template <> +struct ParamTraits<FunctionHookId> + : public ContiguousEnumSerializer<FunctionHookId, + static_cast<FunctionHookId>(0), + FunctionHookId::ID_FunctionHookCount> {}; + +} // namespace IPC + +#endif /* dom_plugins_ipc_functionbrokeripcutils_h */ diff --git a/dom/plugins/ipc/FunctionBrokerParent.cpp b/dom/plugins/ipc/FunctionBrokerParent.cpp new file mode 100644 index 0000000000..51e3b62899 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerParent.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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 "FunctionBrokerParent.h" +#include "FunctionBroker.h" +#include "FunctionBrokerThread.h" + +#include "mozilla/ipc/Endpoint.h" + +namespace mozilla::plugins { + +#if defined(XP_WIN) +UlongPairToIdMap sPairToIdMap; +IdToUlongPairMap sIdToPairMap; +PtrToIdMap sPtrToIdMap; +IdToPtrMap sIdToPtrMap; +#endif // defined(XP_WIN) + +/* static */ +FunctionBrokerParent* FunctionBrokerParent::Create( + Endpoint<PFunctionBrokerParent>&& aParentEnd) { + FunctionBrokerThread* thread = FunctionBrokerThread::Create(); + if (!thread) { + return nullptr; + } + + // We get the FunctionHooks so that they are created here, not on the + // message thread. + FunctionHook::GetHooks(); + + return new FunctionBrokerParent(thread, std::move(aParentEnd)); +} + +FunctionBrokerParent::FunctionBrokerParent( + FunctionBrokerThread* aThread, Endpoint<PFunctionBrokerParent>&& aParentEnd) + : mThread(aThread), + mMonitor("FunctionBrokerParent Lock"), + mShutdownDone(false) { + MOZ_ASSERT(mThread); + mThread->Dispatch( + NewNonOwningRunnableMethod<Endpoint<PFunctionBrokerParent>&&>( + "FunctionBrokerParent::Bind", this, &FunctionBrokerParent::Bind, + std::move(aParentEnd))); +} + +FunctionBrokerParent::~FunctionBrokerParent() { +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + // Clean up any file permissions that we granted to the child process. + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + RemovePermissionsForProcess(OtherPid()); +#endif +} + +void FunctionBrokerParent::Bind(Endpoint<PFunctionBrokerParent>&& aEnd) { + MOZ_RELEASE_ASSERT(mThread->IsOnThread()); + DebugOnly<bool> ok = aEnd.Bind(this); + MOZ_ASSERT(ok); +} + +void FunctionBrokerParent::ShutdownOnBrokerThread() { + MOZ_ASSERT(mThread->IsOnThread()); + Close(); + + // Notify waiting thread that we are done. + MonitorAutoLock lock(mMonitor); + mShutdownDone = true; + mMonitor.Notify(); +} + +void FunctionBrokerParent::Destroy(FunctionBrokerParent* aInst) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aInst); + + { + // Hold the lock while we destroy the actor on the broker thread. + MonitorAutoLock lock(aInst->mMonitor); + aInst->mThread->Dispatch(NewNonOwningRunnableMethod( + "FunctionBrokerParent::ShutdownOnBrokerThread", aInst, + &FunctionBrokerParent::ShutdownOnBrokerThread)); + + // Wait for broker thread to complete destruction. + while (!aInst->mShutdownDone) { + aInst->mMonitor.Wait(); + } + } + + delete aInst; +} + +void FunctionBrokerParent::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_RELEASE_ASSERT(mThread->IsOnThread()); +} + +mozilla::ipc::IPCResult FunctionBrokerParent::RecvBrokerFunction( + const FunctionHookId& aFunctionId, const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple) { +#if defined(XP_WIN) + MOZ_ASSERT(mThread->IsOnThread()); + if (RunBrokeredFunction(OtherPid(), aFunctionId, aInTuple, aOutTuple)) { + return IPC_OK(); + } + return IPC_FAIL_NO_REASON(this); +#else + MOZ_ASSERT_UNREACHABLE( + "BrokerFunction is currently only implemented on Windows."); + return IPC_FAIL_NO_REASON(this); +#endif +} + +// static +bool FunctionBrokerParent::RunBrokeredFunction( + base::ProcessId aClientId, const FunctionHookId& aFunctionId, + const IPC::IpdlTuple& aInTuple, IPC::IpdlTuple* aOutTuple) { + if ((size_t)aFunctionId >= FunctionHook::GetHooks()->Length()) { + MOZ_ASSERT_UNREACHABLE("Invalid function ID"); + return false; + } + + FunctionHook* hook = FunctionHook::GetHooks()->ElementAt(aFunctionId); + MOZ_ASSERT(hook->FunctionId() == aFunctionId); + return hook->RunOriginalFunction(aClientId, aInTuple, aOutTuple); +} + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + +mozilla::SandboxPermissions FunctionBrokerParent::sSandboxPermissions; + +// static +void FunctionBrokerParent::RemovePermissionsForProcess( + base::ProcessId aClientId) { + sSandboxPermissions.RemovePermissionsForProcess(aClientId); +} + +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/FunctionBrokerParent.h b/dom/plugins/ipc/FunctionBrokerParent.h new file mode 100644 index 0000000000..5ad26678b8 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerParent.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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_functionbrokerparent_h +#define mozilla_plugins_functionbrokerparent_h + +#include "mozilla/plugins/PFunctionBrokerParent.h" +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "sandboxPermissions.h" +#endif + +namespace mozilla { +namespace plugins { + +class FunctionBrokerThread; + +/** + * Top-level actor run on the process to which we broker calls from sandboxed + * plugin processes. + */ +class FunctionBrokerParent : public PFunctionBrokerParent { + public: + static FunctionBrokerParent* Create( + Endpoint<PFunctionBrokerParent>&& aParentEnd); + static void Destroy(FunctionBrokerParent* aInst); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult RecvBrokerFunction(const FunctionHookId& aFunctionId, + const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple) override; + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + static mozilla::SandboxPermissions* GetSandboxPermissions() { + return &sSandboxPermissions; + } +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + + private: + explicit FunctionBrokerParent(FunctionBrokerThread* aThread, + Endpoint<PFunctionBrokerParent>&& aParentEnd); + ~FunctionBrokerParent(); + void ShutdownOnBrokerThread(); + void Bind(Endpoint<PFunctionBrokerParent>&& aEnd); + + static bool RunBrokeredFunction(base::ProcessId aClientId, + const FunctionHookId& aFunctionId, + const IPC::IpdlTuple& aInTuple, + IPC::IpdlTuple* aOutTuple); + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + static void RemovePermissionsForProcess(base::ProcessId aClientId); + static mozilla::SandboxPermissions sSandboxPermissions; +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + + UniquePtr<FunctionBrokerThread> mThread; + Monitor mMonitor; + bool mShutdownDone; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_functionbrokerparent_hk diff --git a/dom/plugins/ipc/FunctionBrokerThread.h b/dom/plugins/ipc/FunctionBrokerThread.h new file mode 100644 index 0000000000..f5fe66e9ef --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerThread.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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_functionbrokerthread_h +#define mozilla_plugins_functionbrokerthread_h + +#include "nsThreadManager.h" + +namespace mozilla { +namespace plugins { + +class FunctionBrokerThread { + public: + void Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) { + mThread->Dispatch(std::move(aRunnable), nsIEventTarget::NS_DISPATCH_NORMAL); + } + + bool IsOnThread() { + bool on; + return NS_SUCCEEDED(mThread->IsOnCurrentThread(&on)) && on; + } + + static FunctionBrokerThread* Create() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIThread> thread; + if (NS_FAILED( + NS_NewNamedThread("Function Broker", getter_AddRefs(thread)))) { + return nullptr; + } + return new FunctionBrokerThread(thread); + } + + ~FunctionBrokerThread() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + mThread->Shutdown(); + } + + private: + explicit FunctionBrokerThread(nsIThread* aThread) : mThread(aThread) { + MOZ_ASSERT(mThread); + } + + nsCOMPtr<nsIThread> mThread; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_functionbrokerthread_h diff --git a/dom/plugins/ipc/FunctionHook.cpp b/dom/plugins/ipc/FunctionHook.cpp new file mode 100644 index 0000000000..b9a0ed9e3e --- /dev/null +++ b/dom/plugins/ipc/FunctionHook.cpp @@ -0,0 +1,359 @@ +/* -*- 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 "mozilla/TextUtils.h" + +#include "FunctionHook.h" +#include "FunctionBroker.h" +#include "nsClassHashtable.h" +#include "mozilla/ClearOnShutdown.h" + +#if defined(XP_WIN) +# include <shlobj.h> +# include "PluginModuleChild.h" +#endif + +namespace mozilla::plugins { + +StaticAutoPtr<FunctionHookArray> FunctionHook::sFunctionHooks; + +bool AlwaysHook(int) { return true; } + +FunctionHookArray* FunctionHook::GetHooks() { + if (sFunctionHooks) { + return sFunctionHooks; + } + + // sFunctionHooks is the StaticAutoPtr to the singleton array of FunctionHook + // objects. We free it by clearing the StaticAutoPtr on shutdown. + sFunctionHooks = new FunctionHookArray(); + ClearOnShutdown(&sFunctionHooks); + sFunctionHooks->SetLength(ID_FunctionHookCount); + + AddFunctionHooks(*sFunctionHooks); + AddBrokeredFunctionHooks(*sFunctionHooks); + return sFunctionHooks; +} + +void FunctionHook::HookFunctions(int aQuirks) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Plugin); + FunctionHookArray* hooks = FunctionHook::GetHooks(); + MOZ_ASSERT(hooks); + for (size_t i = 0; i < hooks->Length(); ++i) { + FunctionHook* mhb = hooks->ElementAt(i); + // Check that the FunctionHook array is in the same order as the + // FunctionHookId enum. + MOZ_ASSERT((size_t)mhb->FunctionId() == i); + mhb->Register(aQuirks); + } +} + +#if defined(XP_WIN) + +// This cache is created when a DLL is registered with a FunctionHook. +// It is cleared on a call to ClearDllInterceptorCache(). It +// must be freed before exit to avoid leaks. +typedef nsClassHashtable<nsStringHashKey, WindowsDllInterceptor> + DllInterceptors; +DllInterceptors* sDllInterceptorCache = nullptr; + +WindowsDllInterceptor* FunctionHook::GetDllInterceptorFor( + const char* aModuleName) { + if (!sDllInterceptorCache) { + sDllInterceptorCache = new DllInterceptors(); + } + + MOZ_ASSERT(IsAsciiNullTerminated(aModuleName), + "Non-ASCII module names are not supported"); + NS_ConvertASCIItoUTF16 moduleName(aModuleName); + + WindowsDllInterceptor* ret = sDllInterceptorCache->LookupOrAdd(moduleName); + MOZ_ASSERT(ret); + ret->Init(moduleName.get()); + return ret; +} + +void FunctionHook::ClearDllInterceptorCache() { + delete sDllInterceptorCache; + sDllInterceptorCache = nullptr; +} + +/* GetWindowInfo */ + +typedef BasicFunctionHook<ID_GetWindowInfo, decltype(GetWindowInfo)> + GetWindowInfoFH; + +template <> +ShouldHookFunc* const GetWindowInfoFH::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_GETWINDOWINFO>; + +static const wchar_t* kMozillaWindowClass = L"MozillaWindowClass"; +static HWND sBrowserHwnd = nullptr; + +INTERCEPTOR_DISABLE_CFGUARD BOOL WINAPI GetWindowInfoHook(HWND hWnd, + PWINDOWINFO pwi) { + if (!pwi) { + return FALSE; + } + + MOZ_ASSERT(ID_GetWindowInfo < FunctionHook::GetHooks()->Length()); + GetWindowInfoFH* functionHook = static_cast<GetWindowInfoFH*>( + FunctionHook::GetHooks()->ElementAt(ID_GetWindowInfo)); + if (!functionHook->OriginalFunction()) { + NS_ASSERTION(FALSE, "Something is horribly wrong in PHGetWindowInfoHook!"); + return FALSE; + } + + if (!sBrowserHwnd) { + wchar_t szClass[20]; + // GetClassNameW returns the length it copied w/o null terminator. + // Therefore, if the name and null-terminator fit then it returns a + // value less than the buffer's length. + int nameLen = GetClassNameW(hWnd, szClass, ArrayLength(szClass)); + if ((nameLen < (int)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? + typedef BOOL(WINAPI * GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi); + GetWindowInfoPtr gwiFunc = + static_cast<GetWindowInfoPtr>(functionHook->OriginalFunction()); + BOOL result = gwiFunc(hWnd, pwi); + if (sBrowserHwnd && sBrowserHwnd == hWnd) { + pwi->rcWindow = pwi->rcClient; + } + return result; +} + +/* PrintDlgW */ + +typedef BasicFunctionHook<ID_PrintDlgW, decltype(PrintDlgW)> PrintDlgWFH; + +template <> +ShouldHookFunc* const PrintDlgWFH::mShouldHook = + &CheckQuirks<QUIRK_FLASH_HOOK_PRINTDLGW>; + +INTERCEPTOR_DISABLE_CFGUARD BOOL WINAPI PrintDlgWHook(LPPRINTDLGW aDlg) { + // Zero out the HWND supplied by the plugin. We are sacrificing window + // parentage for the ability to run in the NPAPI sandbox. + HWND hwnd = aDlg->hwndOwner; + aDlg->hwndOwner = 0; + MOZ_ASSERT(ID_PrintDlgW < FunctionHook::GetHooks()->Length()); + PrintDlgWFH* functionHook = static_cast<PrintDlgWFH*>( + FunctionHook::GetHooks()->ElementAt(ID_PrintDlgW)); + MOZ_ASSERT(functionHook); + BOOL ret = functionHook->OriginalFunction()(aDlg); + aDlg->hwndOwner = hwnd; + return ret; +} + +// Hooking CreateFileW for protected-mode magic +static WindowsDllInterceptor sKernel32Intercept; +typedef HANDLE(WINAPI* CreateFileWPtr)(LPCWSTR aFname, DWORD aAccess, + DWORD aShare, + LPSECURITY_ATTRIBUTES aSecurity, + DWORD aCreation, DWORD aFlags, + HANDLE aFTemplate); +static WindowsDllInterceptor::FuncHookType<CreateFileWPtr> sCreateFileWStub; +typedef HANDLE(WINAPI* CreateFileAPtr)(LPCSTR aFname, DWORD aAccess, + DWORD aShare, + LPSECURITY_ATTRIBUTES aSecurity, + DWORD aCreation, DWORD aFlags, + HANDLE aFTemplate); +static WindowsDllInterceptor::FuncHookType<CreateFileAPtr> sCreateFileAStub; + +// 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 aFname, DWORD aAccess, + DWORD aShare, + LPSECURITY_ATTRIBUTES aSecurity, + DWORD aCreation, DWORD aFlags, + HANDLE aFTemplate) { + while (true) { // goto out + // Our hook is for mms.cfg into \Windows\System32\Macromed\Flash + // We don't require supporting too long path. + WCHAR unicodeName[MAX_PATH]; + size_t len = strlen(aFname); + + if (len >= MAX_PATH) { + break; + } + + // We call to CreateFileW for workaround of Windows 8 RTM + int newLen = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, aFname, len, + unicodeName, MAX_PATH); + if (newLen == 0 || newLen >= MAX_PATH) { + break; + } + unicodeName[newLen] = '\0'; + + return CreateFileW(unicodeName, aAccess, aShare, aSecurity, aCreation, + aFlags, aFTemplate); + } + + return sCreateFileAStub(aFname, aAccess, aShare, aSecurity, aCreation, aFlags, + aFTemplate); +} + +static bool GetLocalLowTempPath(size_t aLen, LPWSTR aPath) { + constexpr auto tempname = u"\\Temp"_ns; + LPWSTR path; + if (SUCCEEDED( + 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 aFname, DWORD aAccess, DWORD aShare, + LPSECURITY_ATTRIBUTES aSecurity, + DWORD aCreation, DWORD aFlags, + HANDLE aFTemplate) { + 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(aFname); + if (len < kConfigLength) { + break; + } + if (wcscmp(aFname + 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, aShare, aSecurity, + TRUNCATE_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + if (replacement == INVALID_HANDLE_VALUE) { + break; + } + + HANDLE original = sCreateFileWStub(aFname, aAccess, aShare, aSecurity, + aCreation, aFlags, aFTemplate); + if (original != INVALID_HANDLE_VALUE) { + // copy original to replacement + static const size_t kBufferSize = 1024; + char buffer[kBufferSize]; + DWORD bytes; + while (ReadFile(original, buffer, kBufferSize, &bytes, NULL)) { + if (bytes == 0) { + break; + } + DWORD wbytes; + WriteFile(replacement, buffer, bytes, &wbytes, NULL); + if (bytes < kBufferSize) { + break; + } + } + CloseHandle(original); + } + static const char kSettingString[] = "\nProtectedMode=0\n"; + DWORD wbytes; + WriteFile(replacement, static_cast<const void*>(kSettingString), + sizeof(kSettingString) - 1, &wbytes, NULL); + SetFilePointer(replacement, 0, NULL, FILE_BEGIN); + return replacement; + } + return sCreateFileWStub(aFname, aAccess, aShare, aSecurity, aCreation, aFlags, + aFTemplate); +} + +void FunctionHook::HookProtectedMode() { + // Legacy code. Uses the nsWindowsDLLInterceptor directly instead of + // using the FunctionHook + sKernel32Intercept.Init("kernel32.dll"); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Plugin); + sCreateFileWStub.Set(sKernel32Intercept, "CreateFileW", &CreateFileWHookFn); + sCreateFileAStub.Set(sKernel32Intercept, "CreateFileA", &CreateFileAHookFn); +} + +# if defined(MOZ_SANDBOX) + +/* GetFileAttributesW */ + +typedef BasicFunctionHook<ID_GetFileAttributesW, decltype(GetFileAttributesW)> + GetFileAttributesWFH; + +INTERCEPTOR_DISABLE_CFGUARD DWORD WINAPI +GetFileAttributesWHook(LPCWSTR aFilename) { + MOZ_ASSERT(ID_GetFileAttributesW < FunctionHook::GetHooks()->Length()); + GetFileAttributesWFH* functionHook = static_cast<GetFileAttributesWFH*>( + FunctionHook::GetHooks()->ElementAt(ID_GetFileAttributesW)); + if (!functionHook->OriginalFunction()) { + NS_ASSERTION(FALSE, + "Something is horribly wrong in GetFileAttributesWHook!"); + return FALSE; + } + + DWORD ret = functionHook->OriginalFunction()(aFilename); + if (ret != INVALID_FILE_ATTRIBUTES) { + return ret; + } + + // If aFilename is a parent of PluginModuleChild::GetFlashRoamingPath then + // assume it was blocked by the sandbox and just report it as a plain + // directory. + size_t len = wcslen(aFilename); + std::wstring roamingPath = PluginModuleChild::GetFlashRoamingPath(); + bool isParent = (len > 0) && (aFilename[len - 1] == L'\\') && + (_wcsnicmp(aFilename, roamingPath.c_str(), len) == 0); + if (!isParent) { + return ret; + } + return FILE_ATTRIBUTE_DIRECTORY; +} + +# endif // defined(MOZ_SANDBOX) + +#endif // defined(XP_WIN) + +#define FUN_HOOK(x) static_cast<FunctionHook*>(x) + +void FunctionHook::AddFunctionHooks(FunctionHookArray& aHooks) { + // We transfer ownership of the FunctionHook objects to the array. +#if defined(XP_WIN) + aHooks[ID_GetWindowInfo] = FUN_HOOK(new GetWindowInfoFH( + "user32.dll", "GetWindowInfo", &GetWindowInfo, &GetWindowInfoHook)); + aHooks[ID_PrintDlgW] = FUN_HOOK( + new PrintDlgWFH("comdlg32.dll", "PrintDlgW", &PrintDlgW, PrintDlgWHook)); +# if defined(MOZ_SANDBOX) + aHooks[ID_GetFileAttributesW] = FUN_HOOK( + new GetFileAttributesWFH("kernel32.dll", "GetFileAttributesW", + &GetFileAttributesW, &GetFileAttributesWHook)); +# endif // defined(MOZ_SANDBOX) +#endif // defined(XP_WIN) +} + +#undef FUN_HOOK + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/FunctionHook.h b/dom/plugins/ipc/FunctionHook.h new file mode 100644 index 0000000000..ac259d6bd3 --- /dev/null +++ b/dom/plugins/ipc/FunctionHook.h @@ -0,0 +1,206 @@ +/* -*- 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_functionhook_h +#define dom_plugins_ipc_functionhook_h 1 + +#include "IpdlTuple.h" +#include "base/process.h" +#include "mozilla/Atomics.h" + +#if defined(XP_WIN) +# include "nsWindowsDllInterceptor.h" +#endif + +namespace mozilla { + +template <class T> +class StaticAutoPtr; + +namespace plugins { + +// "PluginHooks" logging helpers +extern mozilla::LazyLogModule sPluginHooksLog; +#define HOOK_LOG(lvl, msg) MOZ_LOG(mozilla::plugins::sPluginHooksLog, lvl, msg); +inline const char* SuccessMsg(bool aVal) { + return aVal ? "succeeded" : "failed"; +} + +class FunctionHook; +class FunctionHookArray; + +class FunctionHook { + public: + virtual ~FunctionHook() = default; + + virtual FunctionHookId FunctionId() const = 0; + + /** + * Register to hook the function represented by this class. + * Returns false if we should have hooked but didn't. + */ + virtual bool Register(int aQuirks) = 0; + + /** + * Run the original function with parameters stored in a tuple. + * This is only supported on server-side and for auto-brokered methods. + */ + virtual bool RunOriginalFunction(base::ProcessId aClientId, + const IPC::IpdlTuple& aInTuple, + IPC::IpdlTuple* aOutTuple) const = 0; + + /** + * Hook the Win32 methods needed by the plugin process. + */ + static void HookFunctions(int aQuirks); + + static FunctionHookArray* GetHooks(); + +#if defined(XP_WIN) + /** + * Special handler for hooking some kernel32.dll methods that we use to + * disable Flash protected mode. + */ + static void HookProtectedMode(); + + /** + * Get the WindowsDllInterceptor for the given module. Creates a cache of + * WindowsDllInterceptors by name. + */ + static WindowsDllInterceptor* GetDllInterceptorFor(const char* aModuleName); + + /** + * Must be called to clear the cache created by calls to GetDllInterceptorFor. + */ + static void ClearDllInterceptorCache(); +#endif // defined(XP_WIN) + + private: + static StaticAutoPtr<FunctionHookArray> sFunctionHooks; + static void AddFunctionHooks(FunctionHookArray& aHooks); +}; + +// The FunctionHookArray deletes its FunctionHook objects when freed. +class FunctionHookArray : public nsTArray<FunctionHook*> { + public: + ~FunctionHookArray() { + for (uint32_t idx = 0; idx < Length(); ++idx) { + FunctionHook* elt = ElementAt(idx); + MOZ_ASSERT(elt); + delete elt; + } + } +}; + +// Type of function that returns true if a function should be hooked according +// to quirks. +typedef bool(ShouldHookFunc)(int aQuirks); + +template <FunctionHookId functionId, typename FunctionType> +class BasicFunctionHook : public FunctionHook { +#if defined(XP_WIN) + using FuncHookType = WindowsDllInterceptor::FuncHookType<FunctionType*>; +#endif // defined(XP_WIN) + + public: + BasicFunctionHook(const char* aModuleName, const char* aFunctionName, + FunctionType* aOldFunction, FunctionType* aNewFunction) + : mOldFunction(aOldFunction), + mRegistration(UNREGISTERED), + mModuleName(aModuleName), + mFunctionName(aFunctionName), + mNewFunction(aNewFunction) { + MOZ_ASSERT(mOldFunction); + MOZ_ASSERT(mNewFunction); + } + + /** + * Hooks the function if we haven't already and if ShouldHook() says to. + */ + bool Register(int aQuirks) override; + + /** + * Can be specialized to perform "extra" operations when running the + * function on the server side. + */ + bool RunOriginalFunction(base::ProcessId aClientId, + const IPC::IpdlTuple& aInTuple, + IPC::IpdlTuple* aOutTuple) const override { + return false; + } + + FunctionHookId FunctionId() const override { return functionId; } + + FunctionType* OriginalFunction() const { return mOldFunction; } + + protected: + // Once the function is hooked, this field will take the value of a pointer to + // a function that performs the old behavior. Before that, it is a pointer to + // the original function. + Atomic<FunctionType*> mOldFunction; +#if defined(XP_WIN) + FuncHookType mStub; +#endif // defined(XP_WIN) + + enum RegistrationStatus { UNREGISTERED, FAILED, SUCCEEDED }; + RegistrationStatus mRegistration; + + // The name of the module containing the function to hook. E.g. "user32.dll". + const nsCString mModuleName; + // The name of the function in the module. + const nsCString mFunctionName; + // The function that we should replace functionName with. The signature of + // newFunction must match that of functionName. + FunctionType* const mNewFunction; + static ShouldHookFunc* const mShouldHook; +}; + +// Default behavior is to hook every registered function. +extern bool AlwaysHook(int); +template <FunctionHookId functionId, typename FunctionType> +ShouldHookFunc* const BasicFunctionHook<functionId, FunctionType>::mShouldHook = + AlwaysHook; + +template <FunctionHookId functionId, typename FunctionType> +bool BasicFunctionHook<functionId, FunctionType>::Register(int aQuirks) { + MOZ_RELEASE_ASSERT(XRE_IsPluginProcess()); + + // If we have already attempted to hook this function or if quirks tell us + // not to then don't hook. + if (mRegistration != UNREGISTERED || !mShouldHook(aQuirks)) { + return true; + } + + bool isHooked = false; + mRegistration = FAILED; + +#if defined(XP_WIN) + WindowsDllInterceptor* dllInterceptor = + FunctionHook::GetDllInterceptorFor(mModuleName.Data()); + if (!dllInterceptor) { + return false; + } + + isHooked = mStub.Set(*dllInterceptor, mFunctionName.Data(), mNewFunction); +#endif + + if (isHooked) { +#if defined(XP_WIN) + mOldFunction = mStub.GetStub(); +#endif + mRegistration = SUCCEEDED; + } + + HOOK_LOG(LogLevel::Debug, ("Registering to intercept function '%s' : '%s'", + mFunctionName.Data(), SuccessMsg(isHooked))); + + return isHooked; +} + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_ipc_functionhook_h diff --git a/dom/plugins/ipc/IpdlTuple.h b/dom/plugins/ipc/IpdlTuple.h new file mode 100644 index 0000000000..06db9bfddb --- /dev/null +++ b/dom/plugins/ipc/IpdlTuple.h @@ -0,0 +1,186 @@ +/* -*- 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_ipdltuple_h +#define dom_plugins_ipc_ipdltuple_h + +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/plugins/FunctionBrokerIPCUtils.h" +#include "mozilla/Variant.h" + +namespace mozilla { +namespace plugins { + +// The stuff in this "internal" namespace used to be inside the IpdlTuple +// class, but that prevented the MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR +// that is needed on the IpdlTupleElement struct. Without this, nsTArray can end +// up using a move constructor on this struct, which is not memmovable on +// Windows. +namespace internal { + +struct InvalidType {}; + +// Like Variant but with a default constructor. +template <typename... Types> +struct MaybeVariant { + public: + MaybeVariant() : mValue(InvalidType()) {} + MaybeVariant(MaybeVariant&& o) : mValue(std::move(o.mValue)) {} + + template <typename Param> + void Set(const Param& aParam) { + mValue = mozilla::AsVariant(aParam); + } + + typedef mozilla::Variant<InvalidType, Types...> MaybeVariantType; + MaybeVariantType& GetVariant() { return mValue; } + const MaybeVariantType& GetVariant() const { return mValue; } + + private: + MaybeVariantType mValue; +}; + +#if defined(XP_WIN) +typedef MaybeVariant<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + int64_t, uint64_t, nsCString, nsString, bool, + OpenFileNameIPC, OpenFileNameRetIPC, NativeWindowHandle, + IPCSchannelCred, IPCInternetBuffers, StringArray, + IPCPrintDlg> + IpdlTupleElement; +#else +typedef MaybeVariant<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + int64_t, uint64_t, nsCString, nsString, bool> + IpdlTupleElement; +#endif // defined(XP_WIN) + +} // namespace internal +} // namespace plugins +} // namespace mozilla + +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::plugins::internal::IpdlTupleElement) + +namespace mozilla { +namespace plugins { + +/** + * IpdlTuple is used by automatic function brokering to pass parameter + * lists for brokered functions. It supports a limited set of types + * (see IpdlTuple::IpdlTupleElement). + */ +class IpdlTuple { + public: + uint32_t NumElements() const { return mTupleElements.Length(); } + + template <typename EltType> + EltType* Element(uint32_t index) { + if ((index >= mTupleElements.Length()) || + !mTupleElements[index].GetVariant().is<EltType>()) { + return nullptr; + } + return &mTupleElements[index].GetVariant().as<EltType>(); + } + + template <typename EltType> + const EltType* Element(uint32_t index) const { + return const_cast<IpdlTuple*>(this)->Element<EltType>(index); + } + + template <typename EltType> + void AddElement(const EltType& aElt) { + IpdlTupleElement* newEntry = mTupleElements.AppendElement(); + newEntry->Set(aElt); + } + + private: + typedef mozilla::plugins::internal::InvalidType InvalidType; + typedef mozilla::plugins::internal::IpdlTupleElement IpdlTupleElement; + + friend struct IPC::ParamTraits<IpdlTuple>; + friend struct IPC::ParamTraits<IpdlTuple::IpdlTupleElement>; + friend struct IPC::ParamTraits<IpdlTuple::InvalidType>; + + nsTArray<IpdlTupleElement> mTupleElements; +}; + +namespace internal { +template <> +template <> +inline void IpdlTupleElement::Set<nsDependentCSubstring>( + const nsDependentCSubstring& aParam) { + mValue = MaybeVariantType(mozilla::VariantType<nsCString>(), aParam); +} +} // namespace internal + +} // namespace plugins +} // namespace mozilla + +namespace IPC { + +using namespace mozilla::plugins; + +template <> +struct ParamTraits<IpdlTuple> { + typedef IpdlTuple paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mTupleElements); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aParam) { + return ReadParam(aMsg, aIter, &aParam->mTupleElements); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + LogParam(aParam.mTupleElements, aLog); + } +}; + +template <> +struct ParamTraits<IpdlTuple::IpdlTupleElement> { + typedef IpdlTuple::IpdlTupleElement paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + MOZ_RELEASE_ASSERT(!aParam.GetVariant().is<IpdlTuple::InvalidType>()); + WriteParam(aMsg, aParam.GetVariant()); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aParam) { + bool ret = ReadParam(aMsg, aIter, &aParam->GetVariant()); + MOZ_RELEASE_ASSERT(!aParam->GetVariant().is<IpdlTuple::InvalidType>()); + return ret; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aParam.GetVariant().match( + [aLog](const auto& aParam) { LogParam(aParam, aLog); }); + } +}; + +template <> +struct ParamTraits<IpdlTuple::InvalidType> { + typedef IpdlTuple::InvalidType paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + MOZ_ASSERT_UNREACHABLE("Attempt to serialize an invalid tuple element"); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aParam) { + MOZ_ASSERT_UNREACHABLE("Attempt to deserialize an invalid tuple element"); + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(L"<Invalid Tuple Entry>"); + } +}; + +} // namespace IPC + +#endif /* dom_plugins_ipc_ipdltuple_h */ diff --git a/dom/plugins/ipc/MiniShmParent.cpp b/dom/plugins/ipc/MiniShmParent.cpp new file mode 100644 index 0000000000..874b7fe339 --- /dev/null +++ b/dom/plugins/ipc/MiniShmParent.cpp @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MiniShmParent.h" + +#include "base/scoped_handle.h" + +#include <sstream> + +namespace mozilla { +namespace plugins { + +// static +const unsigned int MiniShmParent::kDefaultMiniShmSectionSize = 0x1000; + +MiniShmParent::MiniShmParent() + : mSectionSize(0), + mParentEvent(nullptr), + mParentGuard(nullptr), + mChildEvent(nullptr), + mChildGuard(nullptr), + mRegWait(nullptr), + mFileMapping(nullptr), + mView(nullptr), + mIsConnected(false), + mTimeout(INFINITE) {} + +MiniShmParent::~MiniShmParent() { CleanUp(); } + +void MiniShmParent::CleanUp() { + if (mRegWait) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = nullptr; + } + if (mParentEvent) { + ::CloseHandle(mParentEvent); + mParentEvent = nullptr; + } + if (mParentGuard) { + ::CloseHandle(mParentGuard); + mParentGuard = nullptr; + } + if (mChildEvent) { + ::CloseHandle(mChildEvent); + mChildEvent = nullptr; + } + if (mChildGuard) { + ::CloseHandle(mChildGuard); + mChildGuard = nullptr; + } + if (mView) { + ::UnmapViewOfFile(mView); + mView = nullptr; + } + if (mFileMapping) { + ::CloseHandle(mFileMapping); + mFileMapping = nullptr; + } +} + +nsresult MiniShmParent::Init(MiniShmObserver* aObserver, const DWORD aTimeout, + const unsigned int aSectionSize) { + if (!aObserver || !aSectionSize || (aSectionSize % 0x1000) || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (mFileMapping) { + return NS_ERROR_ALREADY_INITIALIZED; + } + SECURITY_ATTRIBUTES securityAttributes = {sizeof(securityAttributes), nullptr, + TRUE}; + ScopedHandle parentEvent( + ::CreateEvent(&securityAttributes, FALSE, FALSE, nullptr)); + if (!parentEvent.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle parentGuard( + ::CreateEvent(&securityAttributes, FALSE, TRUE, nullptr)); + if (!parentGuard.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle childEvent( + ::CreateEvent(&securityAttributes, FALSE, FALSE, nullptr)); + if (!childEvent.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle childGuard( + ::CreateEvent(&securityAttributes, FALSE, TRUE, nullptr)); + if (!childGuard.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle mapping(::CreateFileMapping(INVALID_HANDLE_VALUE, + &securityAttributes, PAGE_READWRITE, + 0, aSectionSize, nullptr)); + if (!mapping.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedMappedFileView view(::MapViewOfFile(mapping, FILE_MAP_WRITE, 0, 0, 0)); + if (!view.IsValid()) { + return NS_ERROR_FAILURE; + } + nsresult rv = SetView(view, aSectionSize, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetGuard(childGuard, aTimeout); + NS_ENSURE_SUCCESS(rv, rv); + + MiniShmInit* initStruct = nullptr; + rv = GetWritePtrInternal(initStruct); + NS_ENSURE_SUCCESS(rv, rv); + initStruct->mParentEvent = parentEvent; + initStruct->mParentGuard = parentGuard; + initStruct->mChildEvent = childEvent; + initStruct->mChildGuard = childGuard; + + if (!::RegisterWaitForSingleObject(&mRegWait, parentEvent, &SOnEvent, this, + INFINITE, WT_EXECUTEDEFAULT)) { + return NS_ERROR_FAILURE; + } + + mParentEvent = parentEvent.Take(); + mParentGuard = parentGuard.Take(); + mChildEvent = childEvent.Take(); + mChildGuard = childGuard.Take(); + mFileMapping = mapping.Take(); + mView = view.Take(); + mSectionSize = aSectionSize; + SetObserver(aObserver); + mTimeout = aTimeout; + return NS_OK; +} + +nsresult MiniShmParent::GetCookie(std::wstring& cookie) { + if (!mFileMapping) { + return NS_ERROR_NOT_INITIALIZED; + } + std::wostringstream oss; + oss << mFileMapping; + if (!oss) { + return NS_ERROR_FAILURE; + } + cookie = oss.str(); + return NS_OK; +} + +nsresult MiniShmParent::Send() { + if (!mChildEvent) { + return NS_ERROR_NOT_INITIALIZED; + } + if (!::SetEvent(mChildEvent)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +bool MiniShmParent::IsConnected() const { return mIsConnected; } + +void MiniShmParent::OnEvent() { + if (mIsConnected) { + MiniShmBase::OnEvent(); + } else { + FinalizeConnection(); + } + ::SetEvent(mParentGuard); +} + +void MiniShmParent::FinalizeConnection() { + const MiniShmInitComplete* initCompleteStruct = nullptr; + nsresult rv = GetReadPtr(initCompleteStruct); + mIsConnected = NS_SUCCEEDED(rv) && initCompleteStruct->mSucceeded; + if (mIsConnected) { + OnConnect(); + } +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/MiniShmParent.h b/dom/plugins/ipc/MiniShmParent.h new file mode 100644 index 0000000000..06eec62560 --- /dev/null +++ b/dom/plugins/ipc/MiniShmParent.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_MiniShmParent_h +#define mozilla_plugins_MiniShmParent_h + +#include "MiniShmBase.h" + +#include <string> + +namespace mozilla { +namespace plugins { + +/** + * This class provides a lightweight shared memory interface for a parent + * process in Win32. + * This code assumes that there is a parent-child relationship between + * processes, as it creates inheritable handles. + * Note that this class is *not* an IPDL actor. + * + * @see MiniShmChild + */ +class MiniShmParent : public MiniShmBase { + public: + MiniShmParent(); + virtual ~MiniShmParent(); + + static const unsigned int kDefaultMiniShmSectionSize; + + /** + * Initialize shared memory on the parent side. + * + * @param aObserver A MiniShmObserver object to receive event notifications. + * @param aTimeout Timeout in milliseconds. + * @param aSectionSize Desired size of the shared memory section. This is + * expected to be a multiple of 0x1000 (4KiB). + * @return nsresult error code + */ + nsresult Init(MiniShmObserver* aObserver, const DWORD aTimeout, + const unsigned int aSectionSize = kDefaultMiniShmSectionSize); + + /** + * Destroys the shared memory section. Useful to explicitly release + * resources if it is known that they won't be needed again. + */ + void CleanUp(); + + /** + * Provides a cookie string that should be passed to MiniShmChild + * during its initialization. + * + * @param aCookie A std::wstring variable to receive the cookie. + * @return nsresult error code + */ + nsresult GetCookie(std::wstring& aCookie); + + virtual nsresult Send() override; + + bool IsConnected() const; + + protected: + void OnEvent() override; + + private: + void FinalizeConnection(); + + unsigned int mSectionSize; + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + HANDLE mRegWait; + HANDLE mFileMapping; + LPVOID mView; + bool mIsConnected; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmParent); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmParent_h diff --git a/dom/plugins/ipc/NPEventAndroid.h b/dom/plugins/ipc/NPEventAndroid.h new file mode 100644 index 0000000000..0aa94a1bc1 --- /dev/null +++ b/dom/plugins/ipc/NPEventAndroid.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 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 plugins + +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteEvent> { + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->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 0000000000..9e24c426e5 --- /dev/null +++ b/dom/plugins/ipc/NPEventOSX.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_plugins_NPEventOSX_h +#define mozilla_dom_plugins_NPEventOSX_h 1 + +#include "npapi.h" + +namespace mozilla { + +namespace plugins { + +struct NPRemoteEvent { + NPCocoaEvent event; + double contentsScaleFactor; +}; + +} // namespace plugins + +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteEvent> { + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteInt(aParam.event.type); + aMsg->WriteUInt32(aParam.event.version); + switch (aParam.event.type) { + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseEntered: + case NPCocoaEventMouseExited: + case NPCocoaEventMouseDragged: + case NPCocoaEventScrollWheel: + aMsg->WriteUInt32(aParam.event.data.mouse.modifierFlags); + aMsg->WriteDouble(aParam.event.data.mouse.pluginX); + aMsg->WriteDouble(aParam.event.data.mouse.pluginY); + aMsg->WriteInt32(aParam.event.data.mouse.buttonNumber); + aMsg->WriteInt32(aParam.event.data.mouse.clickCount); + aMsg->WriteDouble(aParam.event.data.mouse.deltaX); + aMsg->WriteDouble(aParam.event.data.mouse.deltaY); + aMsg->WriteDouble(aParam.event.data.mouse.deltaZ); + break; + case NPCocoaEventKeyDown: + case NPCocoaEventKeyUp: + case NPCocoaEventFlagsChanged: + aMsg->WriteUInt32(aParam.event.data.key.modifierFlags); + WriteParam(aMsg, aParam.event.data.key.characters); + WriteParam(aMsg, aParam.event.data.key.charactersIgnoringModifiers); + aMsg->WriteUnsignedChar(aParam.event.data.key.isARepeat); + aMsg->WriteUInt16(aParam.event.data.key.keyCode); + break; + case NPCocoaEventFocusChanged: + case NPCocoaEventWindowFocusChanged: + aMsg->WriteUnsignedChar(aParam.event.data.focus.hasFocus); + break; + case NPCocoaEventDrawRect: + // We don't write out the context pointer, it would always be + // nullptr and is just filled in as such on the read. + aMsg->WriteDouble(aParam.event.data.draw.x); + aMsg->WriteDouble(aParam.event.data.draw.y); + aMsg->WriteDouble(aParam.event.data.draw.width); + aMsg->WriteDouble(aParam.event.data.draw.height); + break; + case NPCocoaEventTextInput: + WriteParam(aMsg, aParam.event.data.text.text); + break; + default: + MOZ_ASSERT_UNREACHABLE( + "Attempted to serialize unknown event " + "type."); + return; + } + aMsg->WriteDouble(aParam.contentsScaleFactor); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + int type = 0; + if (!aMsg->ReadInt(aIter, &type)) { + return false; + } + aResult->event.type = static_cast<NPCocoaEventType>(type); + + if (!aMsg->ReadUInt32(aIter, &aResult->event.version)) { + return false; + } + + switch (aResult->event.type) { + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseEntered: + case NPCocoaEventMouseExited: + case NPCocoaEventMouseDragged: + case NPCocoaEventScrollWheel: + if (!aMsg->ReadUInt32(aIter, + &aResult->event.data.mouse.modifierFlags)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.pluginX)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.pluginY)) { + return false; + } + if (!aMsg->ReadInt32(aIter, &aResult->event.data.mouse.buttonNumber)) { + return false; + } + if (!aMsg->ReadInt32(aIter, &aResult->event.data.mouse.clickCount)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaX)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaY)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaZ)) { + return false; + } + break; + case NPCocoaEventKeyDown: + case NPCocoaEventKeyUp: + case NPCocoaEventFlagsChanged: + if (!aMsg->ReadUInt32(aIter, &aResult->event.data.key.modifierFlags)) { + return false; + } + if (!ReadParam(aMsg, aIter, &aResult->event.data.key.characters)) { + return false; + } + if (!ReadParam(aMsg, aIter, + &aResult->event.data.key.charactersIgnoringModifiers)) { + return false; + } + if (!aMsg->ReadUnsignedChar(aIter, + &aResult->event.data.key.isARepeat)) { + return false; + } + if (!aMsg->ReadUInt16(aIter, &aResult->event.data.key.keyCode)) { + return false; + } + break; + case NPCocoaEventFocusChanged: + case NPCocoaEventWindowFocusChanged: + if (!aMsg->ReadUnsignedChar(aIter, + &aResult->event.data.focus.hasFocus)) { + return false; + } + break; + case NPCocoaEventDrawRect: + aResult->event.data.draw.context = nullptr; + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.x)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.y)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.width)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.height)) { + return false; + } + break; + case NPCocoaEventTextInput: + if (!ReadParam(aMsg, aIter, &aResult->event.data.text.text)) { + return false; + } + break; + default: + MOZ_ASSERT_UNREACHABLE( + "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 0000000000..55494b4d8c --- /dev/null +++ b/dom/plugins/ipc/NPEventUnix.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 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; +}; + +} // namespace plugins + +} // namespace mozilla + +// +// XEvent is defined as a union of all more specific X*Events. +// Luckily, as of xorg 1.6.0 / X protocol 11 rev 0, the only pointer +// field contained in any of these specific X*Event structs is a +// |Display*|. So to simplify serializing these XEvents, we make the +// +// ********** XXX ASSUMPTION XXX ********** +// +// that the process to which the event is forwarded shares the same +// display as the process on which the event originated. +// +// With this simplification, serialization becomes a simple memcpy to +// the output stream. Deserialization starts as just a memcpy from +// the input stream, BUT we then have to write the correct |Display*| +// into the right field of each X*Event that contains one. +// + +namespace IPC { + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteEvent> // synonym for XEvent +{ + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteBytes(&aParam, sizeof(paramType)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType))) { + return false; + } + +#ifdef MOZ_X11 + SetXDisplay(aResult->event); +#endif + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + // TODO + aLog->append(L"(XEvent)"); + } + +#ifdef MOZ_X11 + private: + static void SetXDisplay(XEvent& ev) { + Display* display = mozilla::DefaultXDisplay(); + if (ev.type >= KeyPress) { + ev.xany.display = display; + } else { + // XXX assuming that this is an error event + // (type == 0? not clear from Xlib.h) + ev.xerror.display = display; + } + } +#endif +}; + +} // namespace IPC + +#endif // ifndef mozilla_dom_plugins_NPEventX11_h diff --git a/dom/plugins/ipc/NPEventWindows.h b/dom/plugins/ipc/NPEventWindows.h new file mode 100644 index 0000000000..b2429883f8 --- /dev/null +++ b/dom/plugins/ipc/NPEventWindows.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 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 plugins + +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteEvent> { + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + // Make a non-const copy of aParam so that we can muck with + // its insides for tranport + paramType paramCopy; + + paramCopy.event = aParam.event; + + // We can't blindly ipc events because they may sometimes contain + // pointers to memory in the sending process. For example, the + // WM_IME_CONTROL with the IMC_GETCOMPOSITIONFONT message has lParam + // set to a pointer to a LOGFONT structure. + switch (paramCopy.event.event) { + case WM_WINDOWPOSCHANGED: + // The lParam parameter of WM_WINDOWPOSCHANGED holds a pointer to + // a WINDOWPOS structure that contains information about the + // window's new size and position + paramCopy.lParamData.windowpos = + *(reinterpret_cast<WINDOWPOS*>(paramCopy.event.lParam)); + break; + case WM_PAINT: + // The lParam parameter of WM_PAINT holds a pointer to an RECT + // structure specifying the bounding box of the update area. + paramCopy.lParamData.rect = + *(reinterpret_cast<RECT*>(paramCopy.event.lParam)); + break; + + // the white list of events that we will ipc to the client + case WM_CHAR: + case WM_SYSCHAR: + + case WM_KEYUP: + case WM_SYSKEYUP: + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + case WM_CONTEXTMENU: + + case WM_CUT: + case WM_COPY: + case WM_PASTE: + case WM_CLEAR: + case WM_UNDO: + + case WM_MOUSELEAVE: + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + + case WM_SETFOCUS: + case WM_KILLFOCUS: + + case WM_IME_STARTCOMPOSITION: + case WM_IME_COMPOSITION: + case WM_IME_ENDCOMPOSITION: + case WM_IME_CHAR: + case WM_IME_SETCONTEXT: + case WM_IME_COMPOSITIONFULL: + case WM_IME_KEYDOWN: + case WM_IME_KEYUP: + case WM_IME_SELECT: + case WM_INPUTLANGCHANGEREQUEST: + case WM_INPUTLANGCHANGE: + break; + + default: + // RegisterWindowMessage events should be passed. + if (paramCopy.event.event >= 0xC000) break; + + // FIXME/bug 567465: temporarily work around unhandled + // events by forwarding a "dummy event". The eventual + // fix will be to stop trying to send these events + // entirely. + paramCopy.event.event = WM_NULL; + break; + } + + aMsg->WriteBytes(¶mCopy, sizeof(paramType)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType))) { + return false; + } + + if (aResult->event.event == WM_PAINT) { + // restore the lParam to point at the RECT + aResult->event.lParam = + reinterpret_cast<LPARAM>(&aResult->lParamData.rect); + } else if (aResult->event.event == WM_WINDOWPOSCHANGED) { + // restore the lParam to point at the WINDOWPOS + aResult->event.lParam = + reinterpret_cast<LPARAM>(&aResult->lParamData.windowpos); + } + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(L"(WINEvent)"); + } +}; + +} // namespace IPC + +#endif // ifndef mozilla_dom_plugins_NPEventWindows_h diff --git a/dom/plugins/ipc/PBrowserStream.ipdl b/dom/plugins/ipc/PBrowserStream.ipdl new file mode 100644 index 0000000000..65548ca06f --- /dev/null +++ b/dom/plugins/ipc/PBrowserStream.ipdl @@ -0,0 +1,42 @@ +/* -*- 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); + + /** + * 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 StreamDestroyed(); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PFunctionBroker.ipdl b/dom/plugins/ipc/PFunctionBroker.ipdl new file mode 100644 index 0000000000..d8ecb76746 --- /dev/null +++ b/dom/plugins/ipc/PFunctionBroker.ipdl @@ -0,0 +1,23 @@ +/* -*- 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/. */ + +using mozilla::plugins::FunctionHookId from "mozilla/plugins/FunctionBrokerIPCUtils.h"; +using IPC::IpdlTuple from "mozilla/plugins/IpdlTuple.h"; + +namespace mozilla { +namespace plugins { + +/** + * Top-level actor that brokers functions for the client process. + */ +sync protocol PFunctionBroker +{ +parent: + sync BrokerFunction(FunctionHookId aFunctionId, IpdlTuple aFunctionParams) + returns (IpdlTuple aFunctionRet); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl b/dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl new file mode 100644 index 0000000000..d8c552fd86 --- /dev/null +++ b/dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl @@ -0,0 +1,34 @@ +/* -*- 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__(); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginInstance.ipdl b/dom/plugins/ipc/PPluginInstance.ipdl new file mode 100644 index 0000000000..4b54f61e3b --- /dev/null +++ b/dom/plugins/ipc/PPluginInstance.ipdl @@ -0,0 +1,294 @@ +/* -*- 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 PStreamNotify; +include protocol PPluginSurface; + +include "gfxipc/ShadowLayerUtils.h"; +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.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 "mozilla/ipc/IPCCore.h"; +using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h"; +using mozilla::plugins::WindowsSharedMemoryHandle from "mozilla/plugins/PluginMessageUtils.h"; +using mozilla::layers::SurfaceDescriptorX11 from "gfxipc/SurfaceDescriptor.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; +}; + +intr protocol PPluginInstance +{ + manager PPluginModule; + + manages PPluginBackgroundDestroyer; + manages PPluginScriptableObject; + manages PBrowserStream; + manages PStreamNotify; + manages PPluginSurface; + +child: + async __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); + + 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_ConvertPoint(double sourceX, bool ignoreDestX, double sourceY, bool ignoreDestY, NPCoordinateSpace sourceSpace, + NPCoordinateSpace destSpace) + returns (double destX, double destY, bool result); + + async RedrawPlugin(); + + // 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); + 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); + +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 0000000000..6d98408c2f --- /dev/null +++ b/dom/plugins/ipc/PPluginModule.ipdl @@ -0,0 +1,155 @@ +/* -*- 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 PContent; +include protocol PProfiler; +include protocol PFunctionBroker; + +using NPError from "npapi.h"; +using NPNVariable from "npapi.h"; +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.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"; +using struct mozilla::plugins::NPAudioDeviceStateChangedIPC 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 +{ + manages PPluginInstance; + +both: + // Window-specific message which instructs the interrupt mechanism to enter + // a nested event loop for the current interrupt call. + async ProcessNativeEventsInInterruptCall(); + +child: + async InitProfiler(Endpoint<PProfilerChild> aEndPoint); + + 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 PPluginInstance(nsCString aMimeType, + 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); + + 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 InitCrashReporter() + returns (NativeThreadId tid); + + async SettingChanged(PluginSettings settings); + + async NPP_SetValue_NPNVaudioDeviceChangeDetails(NPAudioDeviceChangeDetailsIPC changeDetails); + async NPP_SetValue_NPNVaudioDeviceStateChanged(NPAudioDeviceStateChangedIPC deviceState); + + async InitPluginModuleChild(Endpoint<PPluginModuleChild> endpoint); + + async InitPluginFunctionBroker(Endpoint<PFunctionBrokerChild> endpoint); + +parent: + /** + * 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, + double aWidth, double 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(); + + // Answers to request about site data + async ReturnClearSiteData(NPError aRv, uint64_t aCallbackId); + + async ReturnSitesWithData(nsCString[] aSites, uint64_t aCallbackId); + + 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 0000000000..8bee7b1997 --- /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 "mozilla/ipc/IPCCore.h"; +using struct mozilla::null_t from "mozilla/ipc/IPCCore.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/PPluginSurface.ipdl b/dom/plugins/ipc/PPluginSurface.ipdl new file mode 100644 index 0000000000..7be038c604 --- /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 0000000000..3e196acab8 --- /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/PluginBackgroundDestroyer.cpp b/dom/plugins/ipc/PluginBackgroundDestroyer.cpp new file mode 100644 index 0000000000..9c0b50d5e2 --- /dev/null +++ b/dom/plugins/ipc/PluginBackgroundDestroyer.cpp @@ -0,0 +1,35 @@ +/* -*- 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 "PluginBackgroundDestroyer.h" +#include "gfxSharedImageSurface.h" + +using namespace mozilla; +using namespace plugins; + +PluginBackgroundDestroyerParent::PluginBackgroundDestroyerParent( + gfxASurface* aDyingBackground) + : mDyingBackground(aDyingBackground) {} + +PluginBackgroundDestroyerParent::~PluginBackgroundDestroyerParent() = default; + +void PluginBackgroundDestroyerParent::ActorDestroy(ActorDestroyReason why) { + switch (why) { + case Deletion: + case AncestorDeletion: + if (gfxSharedImageSurface::IsSharedImage(mDyingBackground)) { + gfxSharedImageSurface* s = + static_cast<gfxSharedImageSurface*>(mDyingBackground.get()); + DeallocShmem(s->GetShmem()); + } + break; + default: + // We're shutting down or crashed, let automatic cleanup + // take care of our shmem, if we have one. + break; + } +} diff --git a/dom/plugins/ipc/PluginBackgroundDestroyer.h b/dom/plugins/ipc/PluginBackgroundDestroyer.h new file mode 100644 index 0000000000..627b09fdc4 --- /dev/null +++ b/dom/plugins/ipc/PluginBackgroundDestroyer.h @@ -0,0 +1,56 @@ +/* -*- 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 dom_plugins_PluginBackgroundDestroyer +#define dom_plugins_PluginBackgroundDestroyer + +#include "mozilla/plugins/PPluginBackgroundDestroyerChild.h" +#include "mozilla/plugins/PPluginBackgroundDestroyerParent.h" + +#include "gfxSharedImageSurface.h" + +class gfxASurface; + +namespace mozilla { +namespace plugins { + +/** + * When instances of this class are destroyed, the old background goes + * along with them, completing the destruction process (whether or not + * the plugin stayed alive long enough to ack). + */ +class PluginBackgroundDestroyerParent + : public PPluginBackgroundDestroyerParent { + public: + explicit PluginBackgroundDestroyerParent(gfxASurface* aDyingBackground); + + virtual ~PluginBackgroundDestroyerParent(); + + private: + virtual void ActorDestroy(ActorDestroyReason why) override; + + RefPtr<gfxASurface> mDyingBackground; +}; + +/** + * This class exists solely to instruct its instance to release its + * current background, a new one may be coming. + */ +class PluginBackgroundDestroyerChild : public PPluginBackgroundDestroyerChild { + public: + PluginBackgroundDestroyerChild() = default; + virtual ~PluginBackgroundDestroyerChild() = default; + + 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 0000000000..312b36f97d --- /dev/null +++ b/dom/plugins/ipc/PluginBridge.h @@ -0,0 +1,41 @@ +/* -*- 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 ipc { +template <class PFooSide> +class Endpoint; +} // namespace ipc + +namespace plugins { + +class PPluginModuleParent; + +bool SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent, + nsresult* aResult, uint32_t* aRunID, + ipc::Endpoint<PPluginModuleParent>* aEndpoint); + +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/PluginHangUIParent.cpp b/dom/plugins/ipc/PluginHangUIParent.cpp new file mode 100644 index 0000000000..4f0dab52ab --- /dev/null +++ b/dom/plugins/ipc/PluginHangUIParent.cpp @@ -0,0 +1,400 @@ +/* -*- 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 "base/command_line.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) + : Runnable("nsPluginHangUITelemetry"), + mResponseCode(aResponseCode), + mDontAskCode(aDontAskCode), + mResponseTimeMs(aResponseTimeMs), + mTimeoutMs(aTimeoutMs) {} + + NS_IMETHOD + Run() override { + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::PLUGIN_HANG_UI_USER_RESPONSE, mResponseCode); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLUGIN_HANG_UI_DONT_ASK, + mDontAskCode); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::PLUGIN_HANG_UI_RESPONSE_TIME, mResponseTimeMs); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLUGIN_HANG_TIME, + mTimeoutMs + mResponseTimeMs); + return NS_OK; + } + + private: + int mResponseCode; + int mDontAskCode; + uint32_t mResponseTimeMs; + uint32_t mTimeoutMs; +}; +} // namespace + +namespace mozilla { +namespace plugins { + +PluginHangUIParent::PluginHangUIParent(PluginModuleChromeParent* aModule, + const int32_t aHangUITimeoutPref, + const int32_t aChildTimeoutPref) + : mMutex("mozilla::plugins::PluginHangUIParent::mMutex"), + mModule(aModule), + mTimeoutPrefMs(static_cast<uint32_t>(aHangUITimeoutPref) * 1000U), + mIPCTimeoutMs(static_cast<uint32_t>(aChildTimeoutPref) * 1000U), + mMainThreadMessageLoop(MessageLoop::current()), + mIsShowing(false), + mLastUserResponse(0), + mHangUIProcessHandle(nullptr), + mMainWindowHandle(nullptr), + mRegWait(nullptr), + mShowEvent(nullptr), + mShowTicks(0), + mResponseTicks(0) {} + +PluginHangUIParent::~PluginHangUIParent() { + { // Scope for lock + MutexAutoLock lock(mMutex); + UnwatchHangUIChildProcess(true); + } + if (mShowEvent) { + ::CloseHandle(mShowEvent); + } + if (mHangUIProcessHandle) { + ::CloseHandle(mHangUIProcessHandle); + } +} + +bool PluginHangUIParent::DontShowAgain() const { + return (mLastUserResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); +} + +bool PluginHangUIParent::WasLastHangStopped() const { + return (mLastUserResponse & HANGUI_USER_RESPONSE_STOP); +} + +unsigned int PluginHangUIParent::LastShowDurationMs() const { + // We only return something if there was a user response + if (!mLastUserResponse) { + return 0; + } + return static_cast<unsigned int>(mResponseTicks - mShowTicks); +} + +bool PluginHangUIParent::Init(const nsString& aPluginName) { + if (mHangUIProcessHandle) { + return false; + } + + nsresult rv; + rv = mMiniShm.Init(this, ::IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIProperties> directoryService( + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + if (!directoryService) { + return false; + } + nsCOMPtr<nsIFile> greDir; + rv = directoryService->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + return false; + } + nsAutoString path; + greDir->GetPath(path); + + FilePath exePath(path.get()); + exePath = exePath.AppendASCII(MOZ_HANGUI_PROCESS_NAME); + CommandLine commandLine(exePath.value()); + + nsAutoString localizedStr; + rv = nsContentUtils::FormatLocalizedString( + localizedStr, nsContentUtils::eDOM_PROPERTIES, "PluginHangUIMessage", + aPluginName); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(localizedStr.get()); + + const char* keys[] = {"PluginHangUITitle", "PluginHangUIWaitButton", + "PluginHangUIStopButton", "DontAskAgain"}; + for (unsigned int i = 0; i < ArrayLength(keys); ++i) { + rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + keys[i], localizedStr); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(localizedStr.get()); + } + + rv = GetHangUIOwnerWindowHandle(mMainWindowHandle); + if (NS_FAILED(rv)) { + return false; + } + nsAutoString hwndStr; + hwndStr.AppendPrintf("%p", mMainWindowHandle); + commandLine.AppendLooseValue(hwndStr.get()); + + ScopedHandle procHandle( + ::OpenProcess(SYNCHRONIZE, TRUE, GetCurrentProcessId())); + if (!procHandle.IsValid()) { + return false; + } + nsAutoString procHandleStr; + procHandleStr.AppendPrintf("%p", procHandle.Get()); + commandLine.AppendLooseValue(procHandleStr.get()); + + // On Win7+, pass the application user model to the child, so it can + // register with it. This insures windows created by the Hang UI + // properly group with the parent app on the Win7 taskbar. + nsCOMPtr<nsIWinTaskbar> taskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID); + if (taskbarInfo) { + bool isSupported = false; + taskbarInfo->GetAvailable(&isSupported); + nsAutoString appId; + if (isSupported && NS_SUCCEEDED(taskbarInfo->GetDefaultGroupId(appId))) { + commandLine.AppendLooseValue(appId.get()); + } else { + commandLine.AppendLooseValue(L"-"); + } + } else { + commandLine.AppendLooseValue(L"-"); + } + + nsAutoString ipcTimeoutStr; + ipcTimeoutStr.AppendInt(mIPCTimeoutMs); + commandLine.AppendLooseValue(ipcTimeoutStr.get()); + + std::wstring ipcCookie; + rv = mMiniShm.GetCookie(ipcCookie); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(ipcCookie); + + ScopedHandle showEvent(::CreateEventW(nullptr, FALSE, FALSE, nullptr)); + if (!showEvent.IsValid()) { + return false; + } + mShowEvent = showEvent.Get(); + + MutexAutoLock lock(mMutex); + STARTUPINFO startupInfo = {sizeof(STARTUPINFO)}; + PROCESS_INFORMATION processInfo = {nullptr}; + BOOL isProcessCreated = ::CreateProcess( + exePath.value().c_str(), + const_cast<wchar_t*>(commandLine.command_line_string().c_str()), nullptr, + nullptr, TRUE, DETACHED_PROCESS, nullptr, nullptr, &startupInfo, + &processInfo); + if (isProcessCreated) { + ::CloseHandle(processInfo.hThread); + mHangUIProcessHandle = processInfo.hProcess; + ::RegisterWaitForSingleObject(&mRegWait, processInfo.hProcess, + &SOnHangUIProcessExit, this, INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + ::WaitForSingleObject(mShowEvent, + ::IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); + // Setting this to true even if we time out on mShowEvent. This timeout + // typically occurs when the machine is thrashing so badly that + // plugin-hang-ui.exe is taking a while to start. If we didn't set + // this to true, Firefox would keep spawning additional plugin-hang-ui + // processes, which is not what we want. + mIsShowing = true; + } + mShowEvent = nullptr; + return !(!isProcessCreated); +} + +// static +VOID CALLBACK PluginHangUIParent::SOnHangUIProcessExit(PVOID aContext, + BOOLEAN aIsTimer) { + PluginHangUIParent* object = static_cast<PluginHangUIParent*>(aContext); + MutexAutoLock lock(object->mMutex); + // If the Hang UI child process died unexpectedly, act as if the UI cancelled + if (object->IsShowing()) { + object->RecvUserResponse(HANGUI_USER_RESPONSE_CANCEL); + // Firefox window was disabled automatically when the Hang UI was shown. + // If plugin-hang-ui.exe was unexpectedly terminated, we need to re-enable. + ::EnableWindow(object->mMainWindowHandle, TRUE); + } +} + +// A precondition for this function is that the caller has locked mMutex +bool PluginHangUIParent::UnwatchHangUIChildProcess(bool aWait) { + mMutex.AssertCurrentThreadOwns(); + if (mRegWait) { + // If aWait is false then we want to pass a nullptr (i.e. default + // constructor) completionEvent + ScopedHandle completionEvent; + if (aWait) { + completionEvent.Set(::CreateEventW(nullptr, FALSE, FALSE, nullptr)); + if (!completionEvent.IsValid()) { + return false; + } + } + + // if aWait == false and UnregisterWaitEx fails with ERROR_IO_PENDING, + // it is okay to clear mRegWait; Windows is telling us that the wait's + // callback is running but will be cleaned up once the callback returns. + if (::UnregisterWaitEx(mRegWait, completionEvent) || + (!aWait && ::GetLastError() == ERROR_IO_PENDING)) { + mRegWait = nullptr; + if (aWait) { + // We must temporarily unlock mMutex while waiting for the registered + // wait callback to complete, or else we could deadlock. + MutexAutoUnlock unlock(mMutex); + ::WaitForSingleObject(completionEvent, INFINITE); + } + return true; + } + } + return false; +} + +bool PluginHangUIParent::Cancel() { + MutexAutoLock lock(mMutex); + bool result = mIsShowing && SendCancel(); + if (result) { + mIsShowing = false; + } + return result; +} + +bool PluginHangUIParent::SendCancel() { + PluginHangUICommand* cmd = nullptr; + nsresult rv = mMiniShm.GetWritePtr(cmd); + if (NS_FAILED(rv)) { + return false; + } + cmd->mCode = PluginHangUICommand::HANGUI_CMD_CANCEL; + return NS_SUCCEEDED(mMiniShm.Send()); +} + +// A precondition for this function is that the caller has locked mMutex +bool PluginHangUIParent::RecvUserResponse(const unsigned int& aResponse) { + mMutex.AssertCurrentThreadOwns(); + if (!mIsShowing && !(aResponse & HANGUI_USER_RESPONSE_CANCEL)) { + // Don't process a user response if a cancellation is already pending + return true; + } + mLastUserResponse = aResponse; + mResponseTicks = ::GetTickCount(); + mIsShowing = false; + // responseCode: 1 = Stop, 2 = Continue, 3 = Cancel + int responseCode; + if (aResponse & HANGUI_USER_RESPONSE_STOP) { + // User clicked Stop + mModule->TerminateChildProcess(mMainThreadMessageLoop, + mozilla::ipc::kInvalidProcessId, + "ModalHangUI"_ns, u""_ns); + responseCode = 1; + } else if (aResponse & HANGUI_USER_RESPONSE_CONTINUE) { + mModule->OnHangUIContinue(); + // User clicked Continue + responseCode = 2; + } else { + // Dialog was cancelled + responseCode = 3; + } + int dontAskCode = (aResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN) ? 1 : 0; + nsCOMPtr<nsIRunnable> workItem = new nsPluginHangUITelemetry( + responseCode, dontAskCode, LastShowDurationMs(), mTimeoutPrefMs); + NS_DispatchToMainThread(workItem); + return true; +} + +nsresult PluginHangUIParent::GetHangUIOwnerWindowHandle( + NativeWindowHandle& windowHandle) { + windowHandle = nullptr; + + nsresult rv; + nsCOMPtr<nsIWindowMediator> winMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIDOMWindowProxy> navWin; + rv = winMediator->GetMostRecentWindow(u"navigator:browser", + getter_AddRefs(navWin)); + NS_ENSURE_SUCCESS(rv, rv); + if (!navWin) { + return NS_ERROR_FAILURE; + } + + nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin); + nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(win); + if (!widget) { + return NS_ERROR_FAILURE; + } + + windowHandle = reinterpret_cast<NativeWindowHandle>( + widget->GetNativeData(NS_NATIVE_WINDOW)); + if (!windowHandle) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void PluginHangUIParent::OnMiniShmEvent(MiniShmBase* aMiniShmObj) { + const PluginHangUIResponse* response = nullptr; + nsresult rv = aMiniShmObj->GetReadPtr(response); + NS_ASSERTION(NS_SUCCEEDED(rv), "Couldn't obtain read pointer OnMiniShmEvent"); + if (NS_SUCCEEDED(rv)) { + // The child process has returned a response so we shouldn't worry about + // its state anymore. + MutexAutoLock lock(mMutex); + UnwatchHangUIChildProcess(false); + RecvUserResponse(response->mResponseBits); + } +} + +void PluginHangUIParent::OnMiniShmConnect(MiniShmBase* aMiniShmObj) { + PluginHangUICommand* cmd = nullptr; + nsresult rv = aMiniShmObj->GetWritePtr(cmd); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Couldn't obtain write pointer OnMiniShmConnect"); + if (NS_FAILED(rv)) { + return; + } + cmd->mCode = PluginHangUICommand::HANGUI_CMD_SHOW; + if (NS_SUCCEEDED(aMiniShmObj->Send())) { + mShowTicks = ::GetTickCount(); + } + ::SetEvent(mShowEvent); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginHangUIParent.h b/dom/plugins/ipc/PluginHangUIParent.h new file mode 100644 index 0000000000..8853d5c425 --- /dev/null +++ b/dom/plugins/ipc/PluginHangUIParent.h @@ -0,0 +1,142 @@ +/* -*- 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 0000000000..403a735c83 --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceChild.cpp @@ -0,0 +1,4045 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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 "StreamNotifyChild.h" +#include "PluginProcessChild.h" +#include "gfxASurface.h" +#include "gfxPlatform.h" +#include "gfx2DGlue.h" +#include "nsNPAPIPluginInstance.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.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 namespace mozilla::plugins; +using namespace mozilla::layers; +using namespace mozilla::gfx; +using namespace mozilla::widget; + +#ifdef MOZ_WIDGET_GTK + +# include <gtk/gtk.h> +# include <gdk/gdkx.h> +# include <gdk/gdk.h> + +#elif defined(OS_WIN) + +# include <windows.h> +# include <windowsx.h> + +# include "mozilla/widget/WinMessages.h" +# include "mozilla/widget/WinModifierKeyState.h" +# include "mozilla/widget/WinNativeEventData.h" +# include "nsWindowsDllInterceptor.h" +# include "X11UndefineNone.h" + +typedef BOOL(WINAPI* User32TrackPopupMenu)(HMENU hMenu, UINT uFlags, int x, + int y, int nReserved, HWND hWnd, + CONST RECT* prcRect); +static WindowsDllInterceptor sUser32Intercept; +static HWND sWinlessPopupSurrogateHWND = nullptr; +static WindowsDllInterceptor::FuncHookType<User32TrackPopupMenu> + sUser32TrackPopupMenuStub; + +static WindowsDllInterceptor sImm32Intercept; +static WindowsDllInterceptor::FuncHookType<decltype(&ImmGetContext)> + sImm32ImmGetContextStub; +static WindowsDllInterceptor::FuncHookType<decltype(&ImmGetCompositionStringW)> + sImm32ImmGetCompositionStringStub; +static WindowsDllInterceptor::FuncHookType<decltype(&ImmSetCandidateWindow)> + sImm32ImmSetCandidateWindowStub; +static WindowsDllInterceptor::FuncHookType<decltype(&ImmNotifyIME)> + sImm32ImmNotifyIME; +static WindowsDllInterceptor::FuncHookType<decltype(&ImmAssociateContextEx)> + sImm32ImmAssociateContextExStub; + +static PluginInstanceChild* sCurrentPluginInstance = nullptr; +static const HIMC sHookIMC = (const HIMC)0xefefefef; + +using mozilla::gfx::SharedDIB; + +// Flash WM_USER message delay time for PostDelayedTask. Borrowed +// from Chromium's web plugin delegate src. See 'flash msg throttling +// helpers' section for details. +const int kFlashWMUSERMessageThrottleDelayMs = 5; + +static const TCHAR kPluginIgnoreSubclassProperty[] = + TEXT("PluginIgnoreSubclassProperty"); + +#elif defined(XP_MACOSX) +# include <ApplicationServices/ApplicationServices.h> +# include "PluginUtilsOSX.h" +#endif // defined(XP_MACOSX) + +/** + * We can't use gfxPlatform::CreateDrawTargetForSurface() because calling + * gfxPlatform::GetPlatform() instantiates the prefs service, and that's not + * allowed from processes other than the main process. So we have our own + * version here. + */ +static RefPtr<DrawTarget> CreateDrawTargetForSurface(gfxASurface* aSurface) { + SurfaceFormat format = aSurface->GetSurfaceFormat(); + RefPtr<DrawTarget> drawTarget = Factory::CreateDrawTargetForCairoSurface( + aSurface->CairoSurface(), aSurface->GetSize(), &format); + if (!drawTarget) { + MOZ_CRASH("CreateDrawTargetForSurface failed in plugin"); + } + return drawTarget; +} + +bool PluginInstanceChild::sIsIMEComposing = false; + +PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface, + const nsCString& aMimeType, + const nsTArray<nsCString>& aNames, + const nsTArray<nsCString>& aValues) + : mPluginIface(aPluginIface), + mMimeType(aMimeType), + mNames(aNames.Clone()), + mValues(aValues.Clone()) +#if defined(XP_DARWIN) || defined(XP_WIN) + , + mContentsScaleFactor(1.0) +#endif + , + mCSSZoomFactor(0.0), + mPostingKeyEvents(0), + mPostingKeyEventsOutdated(0), + mDrawingModel(kDefaultDrawingModel), + mCurrentDirectSurface(nullptr), + mAsyncInvalidateMutex("PluginInstanceChild::mAsyncInvalidateMutex"), + mAsyncInvalidateTask(0), + mCachedWindowActor(nullptr), + mCachedElementActor(nullptr) +#if defined(OS_WIN) + , + mPluginWindowHWND(0), + mPluginWndProc(0), + mPluginParentHWND(0), + mCachedWinlessPluginHWND(0), + mWinlessPopupSurrogateHWND(0), + mWinlessThrottleOldWndProc(0), + mWinlessHiddenMsgHWND(0) +#endif // OS_WIN +#if defined(MOZ_WIDGET_COCOA) +# if defined(__i386__) + , + mEventModel(NPEventModelCarbon) +# endif + , + mShColorSpace(nullptr), + mShContext(nullptr), + mCGLayer(nullptr), + mCARefreshTimer(0), + 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), + mLastEnableIMEState(true) +#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; +# else + mWsInfo.display = DefaultXDisplay(); +# endif +#endif // MOZ_X11 && XP_UNIX && !XP_MACOSX +#if defined(OS_WIN) + InitPopupMenuHook(); + InitImm32Hook(); +#endif // OS_WIN +} + +PluginInstanceChild::~PluginInstanceChild() { +#if defined(OS_WIN) + NS_ASSERTION(!mPluginWindowHWND, + "Destroying PluginInstanceChild without NPP_Destroy?"); + // In the event that we registered for audio device changes, stop. + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome(); + if (chromeInstance) { + chromeInstance->PluginRequiresAudioDeviceChanges(this, false); + } +#endif +#if defined(MOZ_WIDGET_COCOA) + if (mShColorSpace) { + ::CGColorSpaceRelease(mShColorSpace); + } + if (mShContext) { + ::CGContextRelease(mShContext); + } + if (mCGLayer) { + PluginUtilsOSX::ReleaseCGLayer(mCGLayer); + } + if (mDrawingModel == NPDrawingModelCoreAnimation) { + UnscheduleTimer(mCARefreshTimer); + } +#endif +} + +NPError PluginInstanceChild::DoNPP_New() { + // unpack the arguments into a C format + int argc = mNames.Length(); + NS_ASSERTION(argc == (int)mValues.Length(), "argn.length != argv.length"); + + UniquePtr<char*[]> argn(new char*[1 + argc]); + UniquePtr<char*[]> argv(new char*[1 + argc]); + argn[argc] = 0; + argv[argc] = 0; + + for (int i = 0; i < argc; ++i) { + argn[i] = const_cast<char*>(NullableStringGet(mNames[i])); + argv[i] = const_cast<char*>(NullableStringGet(mValues[i])); + } + + NPP npp = GetNPP(); + + NPError rv = mPluginIface->newp((char*)NullableStringGet(mMimeType), npp, + NP_EMBED, argc, argn.get(), argv.get(), 0); + if (NPERR_NO_ERROR != rv) { + return rv; + } + + if (!Initialize()) { + rv = NPERR_MODULE_LOAD_FAILED_ERROR; + } + 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)) { + result = NPERR_GENERIC_ERROR; + PPluginScriptableObjectChild* actorProtocol; + if (CallNPN_GetValue_NPNVWindowNPObject(&actorProtocol, &result) && + (result == NPERR_NO_ERROR)) { + actor = mCachedWindowActor = + static_cast<PluginScriptableObjectChild*>(actorProtocol); + NS_ASSERTION(actor, "Null actor!"); + if (!actor->GetObject(false)) { + return NPERR_GENERIC_ERROR; + } + PluginModuleChild::sBrowserFuncs.retainobject( + actor->GetObject(false)); + } + } + break; + + case NPNVPluginElementNPObject: + if (!(actor = mCachedElementActor)) { + result = NPERR_GENERIC_ERROR; + PPluginScriptableObjectChild* actorProtocol; + if (CallNPN_GetValue_NPNVPluginElementNPObject(&actorProtocol, + &result) && + (result == NPERR_NO_ERROR)) { + actor = mCachedElementActor = + static_cast<PluginScriptableObjectChild*>(actorProtocol); + NS_ASSERTION(actor, "Null actor!"); + if (!actor->GetObject(false)) { + return NPERR_GENERIC_ERROR; + } + PluginModuleChild::sBrowserFuncs.retainobject( + actor->GetObject(false)); + } + } + break; + + default: + result = NPERR_GENERIC_ERROR; + MOZ_ASSERT_UNREACHABLE( + "Don't know what to do with this value " + "type!"); + } + +#ifdef DEBUG + { + NPError currentResult; + PPluginScriptableObjectChild* currentActor = nullptr; + + switch (aValue) { + case NPNVWindowNPObject: + CallNPN_GetValue_NPNVWindowNPObject(¤tActor, ¤tResult); + break; + case NPNVPluginElementNPObject: + CallNPN_GetValue_NPNVPluginElementNPObject(¤tActor, + ¤tResult); + break; + default: + MOZ_ASSERT(false); + } + + // Make sure that the current actor returned by the parent matches our + // cached actor! + NS_ASSERTION(!currentActor || static_cast<PluginScriptableObjectChild*>( + currentActor) == actor, + "Cached actor is out of date!"); + } +#endif + + if (result != NPERR_NO_ERROR) { + return result; + } + + NPObject* object; + if (!(object = actor->GetObject(false))) { + return NPERR_GENERIC_ERROR; + } + + *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. + if (!Initialize()) { + return NPERR_GENERIC_ERROR; + } + NS_ASSERTION(mWsInfo.display, "We should have a valid display!"); + } + *(void**)aValue = mWsInfo.display; + return NPERR_NO_ERROR; + +#elif defined(OS_WIN) + case NPNVToolkit: + return NPERR_GENERIC_ERROR; +#endif + case NPNVprivateModeBool: { + bool v = false; + NPError result; + if (!CallNPN_GetValue_NPNVprivateModeBool(&v, &result)) { + return NPERR_GENERIC_ERROR; + } + *static_cast<NPBool*>(aValue) = v; + return result; + } + + case NPNVdocumentOrigin: { + nsCString v; + NPError result; + if (!CallNPN_GetValue_NPNVdocumentOrigin(&v, &result)) { + return NPERR_GENERIC_ERROR; + } + if (result == NPERR_NO_ERROR || + (GetQuirks() & QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN)) { + *static_cast<char**>(aValue) = ToNewCString(v); + } + return result; + } + + case NPNVWindowNPObject: // Intentional fall-through + case NPNVPluginElementNPObject: { + NPObject* object; + *((NPObject**)aValue) = nullptr; + NPError result = InternalGetNPObjectForValue(aVar, &object); + if (result == NPERR_NO_ERROR) { + *((NPObject**)aValue) = object; + } + return result; + } + + case NPNVnetscapeWindow: { +#ifdef XP_WIN + if (mWindow.type == NPWindowTypeDrawable) { + if (mCachedWinlessPluginHWND) { + *static_cast<HWND*>(aValue) = mCachedWinlessPluginHWND; + return NPERR_NO_ERROR; + } + NPError result; + if (!CallNPN_GetValue_NPNVnetscapeWindow(&mCachedWinlessPluginHWND, + &result)) { + return NPERR_GENERIC_ERROR; + } + *static_cast<HWND*>(aValue) = mCachedWinlessPluginHWND; + return result; + } else { + *static_cast<HWND*>(aValue) = mPluginWindowHWND; + return NPERR_NO_ERROR; + } +#elif defined(MOZ_X11) + NPError result; + CallNPN_GetValue_NPNVnetscapeWindow(static_cast<XID*>(aValue), &result); + return result; +#else + return NPERR_GENERIC_ERROR; +#endif + } + + case NPNVsupportsAsyncBitmapSurfaceBool: { + bool value = false; + CallNPN_GetValue_SupportsAsyncBitmapSurface(&value); + *((NPBool*)aValue) = value; + return NPERR_NO_ERROR; + } + +#ifdef XP_WIN + case NPNVsupportsAsyncWindowsDXGISurfaceBool: { + bool value = false; + CallNPN_GetValue_SupportsAsyncDXGISurface(&value); + *((NPBool*)aValue) = value; + return NPERR_NO_ERROR; + } +#endif + +#ifdef XP_WIN + case NPNVpreferredDXGIAdapter: { + DxgiAdapterDesc desc; + if (!CallNPN_GetValue_PreferredDXGIAdapter(&desc)) { + return NPERR_GENERIC_ERROR; + } + *reinterpret_cast<DXGI_ADAPTER_DESC*>(aValue) = desc.ToDesc(); + return NPERR_NO_ERROR; + } +#endif + +#ifdef XP_MACOSX + case NPNVsupportsCoreGraphicsBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsCoreAnimationBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsInvalidatingCoreAnimationBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsCompositingCoreAnimationPluginsBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsCocoaBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + +# ifndef NP_NO_CARBON + case NPNVsupportsCarbonBool: { + *((NPBool*)aValue) = false; + return NPERR_NO_ERROR; + } +# endif + + case NPNVsupportsUpdatedCocoaTextInputBool: { + *static_cast<NPBool*>(aValue) = true; + return NPERR_NO_ERROR; + } + +# ifndef NP_NO_QUICKDRAW + case NPNVsupportsQuickDrawBool: { + *((NPBool*)aValue) = false; + return NPERR_NO_ERROR; + } +# endif /* NP_NO_QUICKDRAW */ +#endif /* XP_MACOSX */ + +#if defined(XP_MACOSX) || defined(XP_WIN) + case NPNVcontentsScaleFactor: { + *static_cast<double*>(aValue) = mContentsScaleFactor; + return NPERR_NO_ERROR; + } +#endif /* defined(XP_MACOSX) || defined(XP_WIN) */ + + case NPNVCSSZoomFactor: { + *static_cast<double*>(aValue) = mCSSZoomFactor; + return NPERR_NO_ERROR; + } + +#ifdef DEBUG + case NPNVjavascriptEnabledBool: + case NPNVasdEnabledBool: + case NPNVisOfflineBool: + case NPNVSupportsXEmbedBool: + case NPNVSupportsWindowless: + MOZ_FALLTHROUGH_ASSERT( + "NPNVariable should be handled in " + "PluginModuleChild."); +#endif + + default: + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceChild::NPN_GetValue: Unhandled NPNVariable %i " + "(%s)", + (int)aVar, NPNVariableToString(aVar))); + return NPERR_GENERIC_ERROR; + } +} + +#ifdef MOZ_WIDGET_COCOA +# define DEFAULT_REFRESH_MS 20 // CoreAnimation: 50 FPS + +void CAUpdate(NPP npp, uint32_t timerID) { + static_cast<PluginInstanceChild*>(npp->ndata)->Invalidate(); +} + +void PluginInstanceChild::Invalidate() { + NPRect windowRect = {0, 0, uint16_t(mWindow.height), uint16_t(mWindow.width)}; + + InvalidateRect(&windowRect); +} +#endif + +NPError PluginInstanceChild::NPN_SetValue(NPPVariable aVar, void* aValue) { + MOZ_LOG(GetPluginLog(), LogLevel::Debug, + ("%s (aVar=%i, aValue=%p)", FULLFUNCTION, (int)aVar, aValue)); + + AssertPluginThread(); + + AutoStackHelper guard(this); + + switch (aVar) { + case NPPVpluginWindowBool: { + NPError rv; + bool windowed = (NPBool)(intptr_t)aValue; + + if (windowed) { + return NPERR_GENERIC_ERROR; + } + + if (!CallNPN_SetValue_NPPVpluginWindow(windowed, &rv)) + return NPERR_GENERIC_ERROR; + + mWindow.type = NPWindowTypeDrawable; + return rv; + } + + case NPPVpluginTransparentBool: { + NPError rv; + mIsTransparent = (!!aValue); + + if (!CallNPN_SetValue_NPPVpluginTransparent(mIsTransparent, &rv)) + return NPERR_GENERIC_ERROR; + + return rv; + } + + case NPPVpluginUsesDOMForCursorBool: { + NPError rv = NPERR_GENERIC_ERROR; + if (!CallNPN_SetValue_NPPVpluginUsesDOMForCursor((NPBool)(intptr_t)aValue, + &rv)) { + return NPERR_GENERIC_ERROR; + } + return rv; + } + + case NPPVpluginDrawingModel: { + NPError rv; + int drawingModel = (int16_t)(intptr_t)aValue; + + if (!CallNPN_SetValue_NPPVpluginDrawingModel(drawingModel, &rv)) + return NPERR_GENERIC_ERROR; + + mDrawingModel = drawingModel; + +#ifdef XP_MACOSX + if (drawingModel == NPDrawingModelCoreAnimation) { + mCARefreshTimer = ScheduleTimer(DEFAULT_REFRESH_MS, true, CAUpdate); + } +#endif + + PLUGIN_LOG_DEBUG( + (" Plugin requested drawing model id #%i\n", mDrawingModel)); + + return rv; + } + +#ifdef XP_MACOSX + case NPPVpluginEventModel: { + NPError rv; + int eventModel = (int16_t)(intptr_t)aValue; + + if (!CallNPN_SetValue_NPPVpluginEventModel(eventModel, &rv)) + return NPERR_GENERIC_ERROR; +# if defined(__i386__) + mEventModel = static_cast<NPEventModel>(eventModel); +# endif + + PLUGIN_LOG_DEBUG( + (" Plugin requested event model id # %i\n", eventModel)); + + return rv; + } +#endif + + case NPPVpluginIsPlayingAudio: { + NPError rv = NPERR_GENERIC_ERROR; + if (!CallNPN_SetValue_NPPVpluginIsPlayingAudio((NPBool)(intptr_t)aValue, + &rv)) { + return NPERR_GENERIC_ERROR; + } + return rv; + } + +#ifdef XP_WIN + case NPPVpluginRequiresAudioDeviceChanges: { + // Many other NPN_SetValue variables are forwarded to our + // PluginInstanceParent, which runs on a content process. We + // instead forward this message to the PluginModuleParent, which runs + // on the chrome process. This is because our audio + // API calls should run the chrome proc, not content. + NPError rv = NPERR_GENERIC_ERROR; + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome(); + if (chromeInstance) { + rv = chromeInstance->PluginRequiresAudioDeviceChanges( + this, (NPBool)(intptr_t)aValue); + } + return rv; + } +#endif + + default: + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceChild::NPN_SetValue: Unhandled NPPVariable %i " + "(%s)", + (int)aVar, NPPVariableToString(aVar))); + return NPERR_GENERIC_ERROR; + } +} + +mozilla::ipc::IPCResult +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 IPC_OK(); +} + +mozilla::ipc::IPCResult +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 IPC_OK(); + } + + NS_ERROR("Failed to get actor!"); + result = NPERR_GENERIC_ERROR; + } else { + result = NPERR_GENERIC_ERROR; + } + + *aValue = nullptr; + *aResult = result; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +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 IPC_OK(); + +#else + + MOZ_CRASH("shouldn't be called on non-ATK platforms"); + +#endif +} + +mozilla::ipc::IPCResult +PluginInstanceChild::AnswerNPP_SetValue_NPNVprivateModeBool(const bool& value, + NPError* result) { + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return IPC_OK(); + } + + NPBool v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVprivateModeBool, &v); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceChild::AnswerNPP_SetValue_NPNVCSSZoomFactor(const double& value, + NPError* result) { + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return IPC_OK(); + } + + mCSSZoomFactor = value; + double v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVCSSZoomFactor, &v); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceChild::AnswerNPP_SetValue_NPNVmuteAudioBool(const bool& value, + NPError* result) { + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return IPC_OK(); + } + + NPBool v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVmuteAudioBool, &v); + return IPC_OK(); +} + +#if defined(XP_WIN) +NPError PluginInstanceChild::DefaultAudioDeviceChanged( + NPAudioDeviceChangeDetails& details) { + if (!mPluginIface->setvalue) { + return NPERR_GENERIC_ERROR; + } + return mPluginIface->setvalue(GetNPP(), NPNVaudioDeviceChangeDetails, + (void*)&details); +} + +NPError PluginInstanceChild::AudioDeviceStateChanged( + NPAudioDeviceStateChanged& aDeviceState) { + if (!mPluginIface->setvalue) { + return NPERR_GENERIC_ERROR; + } + return mPluginIface->setvalue(GetNPP(), NPNVaudioDeviceStateChanged, + (void*)&aDeviceState); +} + +void SetMouseEventWParam(NPEvent* aEvent) { + // Fill in potentially missing key state info. See + // nsPluginInstanceOwner::ProcessEvent for circumstances where this happens. + const auto kMouseMessages = mozilla::Array<int, 9>( + WM_LBUTTONDOWN, WM_MBUTTONDOWN, WM_RBUTTONDOWN, WM_LBUTTONUP, + WM_MBUTTONUP, WM_RBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOUSEHWHEEL); + + bool isInvalidWParam = + (aEvent->wParam == NPAPI_INVALID_WPARAM) && + (std::find(kMouseMessages.begin(), kMouseMessages.end(), + static_cast<int>(aEvent->event)) != kMouseMessages.end()); + + if (!isInvalidWParam) { + return; + } + + aEvent->wParam = (::GetKeyState(VK_CONTROL) ? MK_CONTROL : 0) | + (::GetKeyState(VK_SHIFT) ? MK_SHIFT : 0) | + (::GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) | + (::GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) | + (::GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0) | + (::GetKeyState(VK_XBUTTON1) ? MK_XBUTTON1 : 0) | + (::GetKeyState(VK_XBUTTON2) ? MK_XBUTTON2 : 0); +} +#endif + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent( + const NPRemoteEvent& event, int16_t* handled) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + AutoStackHelper guard(this); + +#if defined(MOZ_X11) && defined(DEBUG) + if (GraphicsExpose == event.event.type) + PLUGIN_LOG_DEBUG( + (" received drawable 0x%lx\n", event.event.xgraphicsexpose.drawable)); +#endif + +#ifdef XP_MACOSX + // Mac OS X does not define an NPEvent structure. It defines more specific + // types. + NPCocoaEvent evcopy = event.event; + + // Make sure we reset mCurrentEvent in case of an exception + AutoRestore<const NPCocoaEvent*> savePreviousEvent(mCurrentEvent); + + // Track the current event for NPN_PopUpContextMenu. + mCurrentEvent = &event.event; +#else + // Make a copy since we may modify values. + NPEvent evcopy = event.event; +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + // event.contentsScaleFactor <= 0 is a signal we shouldn't use it, + // for example when AnswerNPP_HandleEvent() is called from elsewhere + // in the child process (not via rpc code from the parent process). + if (event.contentsScaleFactor > 0) { + mContentsScaleFactor = event.contentsScaleFactor; + } +#endif + +#ifdef OS_WIN + // FIXME/bug 567645: temporarily drop the "dummy event" on the floor + if (WM_NULL == evcopy.event) return IPC_OK(); + + SetMouseEventWParam(&evcopy); + *handled = WinlessHandleEvent(evcopy); + return IPC_OK(); +#endif + + // XXX A previous call to mPluginIface->event might block, e.g. right click + // for context menu. Still, we might get here again, calling into the plugin + // a second time while it's in the previous call. + if (!mPluginIface->event) + *handled = false; + else + *handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy)); + +#ifdef XP_MACOSX + // Release any reference counted objects created in the child process. + if (evcopy.type == NPCocoaEventKeyDown || evcopy.type == NPCocoaEventKeyUp) { + ::CFRelease((CFStringRef)evcopy.data.key.characters); + ::CFRelease((CFStringRef)evcopy.data.key.charactersIgnoringModifiers); + } else if (evcopy.type == NPCocoaEventTextInput) { + ::CFRelease((CFStringRef)evcopy.data.text.text); + } +#endif + +#ifdef MOZ_X11 + if (GraphicsExpose == event.event.type) { + // Make sure the X server completes the drawing before the parent + // draws on top and destroys the Drawable. + // + // XSync() waits for the X server to complete. Really this child + // process does not need to wait; the parent is the process that needs + // to wait. A possibly-slightly-better alternative would be to send + // an X event to the parent that the parent would wait for. + XSync(mWsInfo.display, X11False); + } +#endif + + return IPC_OK(); +} + +#ifdef XP_MACOSX + +mozilla::ipc::IPCResult 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 IPC_OK(); + } + } + if (!mShContext) { + void* cgContextByte = mem.get<char>(); + mShContext = ::CGBitmapContextCreate( + cgContextByte, mWindow.width * scaleFactor, + mWindow.height * scaleFactor, 8, mWindow.width * 4 * scaleFactor, + mShColorSpace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host); + + if (!mShContext) { + PLUGIN_LOG_DEBUG(("Could not allocate CGBitmapContext.")); + *handled = false; + *rtnmem = mem; + return IPC_OK(); + } + } + 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 IPC_OK(); + } + + if (!mPluginIface->event) { + *handled = false; + } else { + ::CGContextSaveGState(evcopy.data.draw.context); + *handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy)); + ::CGContextRestoreGState(evcopy.data.draw.context); + } + + *rtnmem = mem; + return IPC_OK(); +} + +#else +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent_Shmem( + const NPRemoteEvent& event, Shmem&& mem, int16_t* handled, Shmem* rtnmem) { + MOZ_CRASH("not reached."); + *rtnmem = mem; + return IPC_OK(); +} +#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; +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent_IOSurface( + const NPRemoteEvent& event, const uint32_t& surfaceid, int16_t* handled) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + AutoStackHelper guard(this); + + PaintTracker pt; + + NPCocoaEvent evcopy = event.event; + mContentsScaleFactor = event.contentsScaleFactor; + RefPtr<MacIOSurface> surf = + MacIOSurface::LookupSurface(surfaceid, mContentsScaleFactor); + if (!surf) { + NS_ERROR("Invalid IOSurface."); + *handled = false; + return IPC_FAIL_NO_REASON(this); + } + + 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 IPC_FAIL_NO_REASON(this); + } + + 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 IPC_FAIL_NO_REASON(this); + } + + mCARenderer->Render(mWindow.width, mWindow.height, mContentsScaleFactor, + nullptr); + + return IPC_OK(); +} + +#else +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent_IOSurface( + const NPRemoteEvent& event, const uint32_t& surfaceid, int16_t* handled) { + MOZ_CRASH("NPP_HandleEvent_IOSurface is a OSX-only message"); +} +#endif + +mozilla::ipc::IPCResult 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 + MOZ_CRASH("WindowPosChanged is a windows-only message"); +#endif +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +#else + MOZ_CRASH("ContentsScaleFactorChanged is an Windows or OSX only message"); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerCreateChildPluginWindow( + NativeWindowHandle* aChildPluginWindow) { +#if defined(XP_WIN) + MOZ_ASSERT(!mPluginWindowHWND); + + if (!CreatePluginWindow()) { + return IPC_FAIL_NO_REASON(this); + } + + MOZ_ASSERT(mPluginWindowHWND); + + *aChildPluginWindow = mPluginWindowHWND; + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("CreateChildPluginWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvCreateChildPopupSurrogate( + const NativeWindowHandle& aNetscapeWindow) { +#if defined(XP_WIN) + mCachedWinlessPluginHWND = aNetscapeWindow; + CreateWinlessPopupSurrogate(); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("CreateChildPluginWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_SetWindow( + const NPRemoteWindow& aWindow) { + PLUGIN_LOG_DEBUG(("%s (aWindow=<window: 0x%" PRIx64 + ", x: %d, y: %d, width: %d, height: %d>)", + FULLFUNCTION, aWindow.window, aWindow.x, aWindow.y, + aWindow.width, aWindow.height)); + NS_ASSERTION(!mLayersRendering && !mPendingPluginCall, + "Shouldn't be receiving NPP_SetWindow with layer rendering"); + AssertPluginThread(); + AutoStackHelper guard(this); + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + NS_ASSERTION(mWsInfo.display, "We should have a valid display!"); + + // The minimum info is sent over IPC to allow this + // code to determine the rest. + + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.clipRect = aWindow.clipRect; + mWindow.type = aWindow.type; + + mWsInfo.colormap = aWindow.colormap; + int depth; + FindVisualAndDepth(mWsInfo.display, aWindow.visualID, &mWsInfo.visual, + &depth); + mWsInfo.depth = depth; + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Answer_SetWindow w=<x=%d,y=%d, w=%d,h=%d>, " + "clip=<l=%d,t=%d,r=%d,b=%d>", + this, mWindow.x, mWindow.y, mWindow.width, mWindow.height, + mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, + mWindow.clipRect.bottom)); + + if (mPluginIface->setwindow) (void)mPluginIface->setwindow(&mData, &mWindow); + +#elif defined(OS_WIN) + switch (aWindow.type) { + case NPWindowTypeWindow: { + MOZ_ASSERT(mPluginWindowHWND, + "Child plugin window must exist before call to SetWindow"); + + HWND parentHWND = reinterpret_cast<HWND>(aWindow.window); + if (mPluginWindowHWND != parentHWND) { + mPluginParentHWND = parentHWND; + ShowWindow(mPluginWindowHWND, SW_SHOWNA); + } + + SizePluginWindow(aWindow.width, aWindow.height); + + mWindow.window = (void*)mPluginWindowHWND; + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.type = aWindow.type; + mContentsScaleFactor = aWindow.contentsScaleFactor; + + if (mPluginIface->setwindow) { + SetProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty, (HANDLE)1); + (void)mPluginIface->setwindow(&mData, &mWindow); + WNDPROC wndProc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC)); + if (wndProc != PluginWindowProc) { + mPluginWndProc = reinterpret_cast<WNDPROC>( + SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(PluginWindowProc))); + NS_ASSERTION(mPluginWndProc != PluginWindowProc, "WTF?"); + } + RemoveProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty); + HookSetWindowLongPtr(); + } + } break; + + default: + MOZ_ASSERT_UNREACHABLE("Bad plugin window type."); + return IPC_FAIL_NO_REASON(this); + 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) || defined(MOZ_WAYLAND) + // Don't care +#else +# error Implement me for your OS +#endif + + return IPC_OK(); +} + +bool PluginInstanceChild::Initialize() { +#if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) + if (mWsInfo.display) { + // Already initialized + return true; + } + + // Request for windowless plugins is set in newp(), before this call. + if (mWindow.type == NPWindowTypeWindow) { + return false; + } + + mWsInfo.display = DefaultXDisplay(); +#endif + +#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) { + return false; + } +#endif + + return true; +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvHandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, const bool& aIsConsumed) { +#if defined(OS_WIN) + const WinNativeKeyEventData* eventData = + static_cast<const WinNativeKeyEventData*>(aKeyEventData); + switch (eventData->mMessage) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + mLastKeyEventConsumed = aIsConsumed; + break; + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + // If preceding keydown or keyup event is consumed by the chrome + // process, we should consume WM_*CHAR messages too. + if (mLastKeyEventConsumed) { + return IPC_OK(); + } + 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 IPC_OK(); + } + + // If there is outdated posting key events, we should consume the key + // events. + if (mPostingKeyEventsOutdated) { + mPostingKeyEventsOutdated--; + return IPC_OK(); + } + + 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 IPC_OK(); + } + +#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 IPC_OK(); +} + +#if defined(OS_WIN) + +static const TCHAR kWindowClassName[] = TEXT("GeckoPluginWindow"); +static const TCHAR kPluginInstanceChildProperty[] = + TEXT("PluginInstanceChildProperty"); +static const TCHAR kFlashThrottleProperty[] = + TEXT("MozillaFlashThrottleProperty"); + +// static +bool PluginInstanceChild::RegisterWindowClass() { + static bool alreadyRegistered = false; + if (alreadyRegistered) return true; + + alreadyRegistered = true; + + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = DummyWindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(nullptr); + wcex.hIcon = 0; + wcex.hCursor = 0; + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = kWindowClassName; + wcex.hIconSm = 0; + + return RegisterClassEx(&wcex); +} + +bool PluginInstanceChild::CreatePluginWindow() { + // already initialized + if (mPluginWindowHWND) return true; + + if (!RegisterWindowClass()) return false; + + mPluginWindowHWND = CreateWindowEx( + WS_EX_LEFT | WS_EX_LTRREADING | + WS_EX_NOPARENTNOTIFY | // XXXbent Get rid of this! + WS_EX_RIGHTSCROLLBAR, + kWindowClassName, 0, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, + 0, 0, nullptr, 0, GetModuleHandle(nullptr), 0); + if (!mPluginWindowHWND) return false; + if (!SetProp(mPluginWindowHWND, kPluginInstanceChildProperty, this)) + return false; + + // Apparently some plugins require an ASCII WndProc. + SetWindowLongPtrA(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(DefWindowProcA)); + + return true; +} + +void PluginInstanceChild::DestroyPluginWindow() { + if (mPluginWindowHWND) { + // Unsubclass the window. + WNDPROC wndProc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC)); + // Removed prior to SetWindowLongPtr, see HookSetWindowLongPtr. + RemoveProp(mPluginWindowHWND, kPluginInstanceChildProperty); + if (wndProc == PluginWindowProc) { + NS_ASSERTION(mPluginWndProc, "Should have old proc here!"); + SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(mPluginWndProc)); + mPluginWndProc = 0; + } + DestroyWindow(mPluginWindowHWND); + mPluginWindowHWND = 0; + } +} + +void PluginInstanceChild::SizePluginWindow(int width, int height) { + if (mPluginWindowHWND) { + mPluginSize.x = width; + mPluginSize.y = height; + SetWindowPos(mPluginWindowHWND, nullptr, 0, 0, width, height, + SWP_NOZORDER | SWP_NOREPOSITION); + } +} + +// See chromium's webplugin_delegate_impl.cc for explanation of this function. +// static +LRESULT CALLBACK PluginInstanceChild::DummyWindowProc(HWND hWnd, UINT message, + WPARAM wParam, + LPARAM lParam) { + return CallWindowProc(DefWindowProc, hWnd, message, wParam, lParam); +} + +// static +LRESULT CALLBACK PluginInstanceChild::PluginWindowProc(HWND hWnd, UINT message, + WPARAM wParam, + LPARAM lParam) { + return mozilla::CallWindowProcCrashProtected(PluginWindowProcInternal, hWnd, + message, wParam, lParam); +} + +// static +LRESULT CALLBACK PluginInstanceChild::PluginWindowProcInternal(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) { + NS_ASSERTION(!mozilla::ipc::MessageChannel::IsPumpingMessages(), + "Failed to prevent a nonqueued message from running!"); + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>( + GetProp(hWnd, kPluginInstanceChildProperty)); + if (!self) { + MOZ_ASSERT_UNREACHABLE("Badness!"); + return 0; + } + + NS_ASSERTION(self->mPluginWindowHWND == hWnd, "Wrong window!"); + NS_ASSERTION( + self->mPluginWndProc != PluginWindowProc, + "Self-referential windowproc. Infinite recursion will happen soon."); + + bool isIMECompositionMessage = false; + switch (message) { + // Adobe's shockwave positions the plugin window relative to the browser + // frame when it initializes. With oopp disabled, this wouldn't have an + // effect. With oopp, GeckoPluginWindow is a child of the parent plugin + // window, so the move offsets the child within the parent. Generally + // we don't want plugins moving or sizing our window, so we prevent + // these changes here. + case WM_WINDOWPOSCHANGING: { + WINDOWPOS* pos = reinterpret_cast<WINDOWPOS*>(lParam); + if (pos && (!(pos->flags & SWP_NOMOVE) || !(pos->flags & SWP_NOSIZE))) { + pos->x = pos->y = 0; + pos->cx = self->mPluginSize.x; + pos->cy = self->mPluginSize.y; + LRESULT res = + CallWindowProc(self->mPluginWndProc, hWnd, message, wParam, lParam); + pos->x = pos->y = 0; + pos->cx = self->mPluginSize.x; + pos->cy = self->mPluginSize.y; + return res; + } + break; + } + + case WM_SETFOCUS: + // If this gets focus, ensure that there is no pending key events. + // Even if there were, we should ignore them for performance reason. + // Although, such case shouldn't occur. + NS_WARNING_ASSERTION(self->mPostingKeyEvents == 0, "pending events"); + self->mPostingKeyEvents = 0; + self->mLastKeyEventConsumed = false; + break; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + if (self->MaybePostKeyMessage(message, wParam, lParam)) { + // If PreHandleKeyMessage() posts the message to the parent + // process, we need to wait RecvOnKeyEventHandledBeforePlugin() + // to be called. + return 0; // Consume current message temporarily. + } + break; + + case MOZ_WM_KEYDOWN: + message = WM_KEYDOWN; + break; + case MOZ_WM_SYSKEYDOWN: + message = WM_SYSKEYDOWN; + break; + case MOZ_WM_KEYUP: + message = WM_KEYUP; + break; + case MOZ_WM_SYSKEYUP: + message = WM_SYSKEYUP; + break; + case MOZ_WM_CHAR: + message = WM_CHAR; + break; + case MOZ_WM_SYSCHAR: + message = WM_SYSCHAR; + break; + case MOZ_WM_DEADCHAR: + message = WM_DEADCHAR; + break; + case MOZ_WM_SYSDEADCHAR: + message = WM_SYSDEADCHAR; + break; + + case WM_IME_STARTCOMPOSITION: + isIMECompositionMessage = true; + sIsIMEComposing = true; + break; + case WM_IME_ENDCOMPOSITION: + isIMECompositionMessage = true; + sIsIMEComposing = false; + break; + case WM_IME_COMPOSITION: + isIMECompositionMessage = true; + // XXX Some old IME may not send WM_IME_COMPOSITION_START or + // WM_IME_COMPSOITION_END properly. So, we need to check + // WM_IME_COMPSOITION and if it includes commit string. + sIsIMEComposing = !(lParam & GCS_RESULTSTR); + break; + + // The plugin received keyboard focus, let the parent know so the dom + // is up to date. + case WM_MOUSEACTIVATE: + self->CallPluginFocusChange(true); + break; + } + + // When a composition is committed, there may be pending key + // events which were posted to the parent process before starting + // the composition. Then, we shouldn't handle it since they are + // now outdated. + if (isIMECompositionMessage && !sIsIMEComposing) { + self->mPostingKeyEventsOutdated += self->mPostingKeyEvents; + self->mPostingKeyEvents = 0; + } + + // Prevent lockups due to plugins making rpc calls when the parent + // is making a synchronous SendMessage call to the child window. Add + // more messages as needed. + if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) { + switch (message) { + case WM_CHILDACTIVATE: + case WM_KILLFOCUS: + ReplyMessage(0); + break; + } + } + + if (message == WM_KILLFOCUS) { + self->CallPluginFocusChange(false); + } + + if (message == WM_USER + 1 && + (self->GetQuirks() & QUIRK_FLASH_THROTTLE_WMUSER_EVENTS)) { + self->FlashThrottleMessage(hWnd, message, wParam, lParam, true); + return 0; + } + + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, + "Self-referential windowproc happened inside our hook proc. " + "Infinite recursion will happen soon."); + + LRESULT res = + CallWindowProc(self->mPluginWndProc, hWnd, message, wParam, lParam); + + // Make sure capture is released by the child on mouse events. Fixes a + // problem with flash full screen mode mouse input. Appears to be + // caused by a bug in flash, since we are not setting the capture + // on the window. + if (message == WM_LBUTTONDOWN && + self->GetQuirks() & QUIRK_FLASH_FIXUP_MOUSE_CAPTURE) { + wchar_t szClass[26]; + HWND hwnd = GetForegroundWindow(); + if (hwnd && + GetClassNameW(hwnd, szClass, sizeof(szClass) / sizeof(char16_t)) && + !wcscmp(szClass, kFlashFullscreenClass)) { + ReleaseCapture(); + SetFocus(hwnd); + } + } + + if (message == WM_CLOSE) { + self->DestroyPluginWindow(); + } + + if (message == WM_NCDESTROY) { + RemoveProp(hWnd, kPluginInstanceChildProperty); + } + + return res; +} + +bool PluginInstanceChild::ShouldPostKeyMessage(UINT message, WPARAM wParam, + LPARAM lParam) { + // If there is a composition, we shouldn't post the key message to the + // parent process because we cannot handle IME messages asynchronously. + // Therefore, if we posted key events to the parent process, the event + // order of the posted key events and IME events are shuffled. + if (sIsIMEComposing) { + return false; + } + + // If there are some pending keyboard events which are not handled in + // the parent process, we should post the message for avoiding to shuffle + // the key event order. + if (mPostingKeyEvents) { + return true; + } + + // If we are not waiting calls of RecvOnKeyEventHandledBeforePlugin(), + // we don't need to post WM_*CHAR messages. + switch (message) { + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + return false; + } + + // Otherwise, we should post key message which might match with a + // shortcut key. + ModifierKeyState modifierState; + if (!modifierState.MaybeMatchShortcutKey()) { + // For better UX, we shouldn't use IPC when user tries to + // input character(s). + return false; + } + + // Ignore modifier key events and keys already handled by IME. + switch (wParam) { + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + case VK_LWIN: + case VK_RWIN: + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + // Following virtual keycodes shouldn't come with WM_(SYS)KEY* message + // but check it for avoiding unnecessary cross process communication. + case VK_LSHIFT: + case VK_RSHIFT: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_LMENU: + case VK_RMENU: + case VK_PROCESSKEY: + case VK_PACKET: + case 0xFF: // 0xFF could be sent with unidentified key by the layout. + return false; + default: + break; + } + return true; +} + +bool PluginInstanceChild::MaybePostKeyMessage(UINT message, WPARAM wParam, + LPARAM lParam) { + if (!ShouldPostKeyMessage(message, wParam, lParam)) { + return false; + } + + ModifierKeyState modifierState; + WinNativeKeyEventData winNativeKeyData(message, wParam, lParam, + modifierState); + NativeEventData nativeKeyData; + nativeKeyData.Copy(winNativeKeyData); + if (NS_WARN_IF(!SendOnWindowedPluginKeyEvent(nativeKeyData))) { + return false; + } + + mPostingKeyEvents++; + return true; +} + +/* set window long ptr hook for flash */ + +/* + * Flash will reset the subclass of our widget at various times. + * (Notably when entering and exiting full screen mode.) This + * occurs independent of the main plugin window event procedure. + * We trap these subclass calls to prevent our subclass hook from + * getting dropped. + * Note, ascii versions can be nixed once flash versions < 10.1 + * are considered obsolete. + */ + +# ifdef _WIN64 +typedef LONG_PTR(WINAPI* User32SetWindowLongPtrA)(HWND hWnd, int nIndex, + LONG_PTR dwNewLong); +typedef LONG_PTR(WINAPI* User32SetWindowLongPtrW)(HWND hWnd, int nIndex, + LONG_PTR dwNewLong); +static WindowsDllInterceptor::FuncHookType<User32SetWindowLongPtrA> + sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType<User32SetWindowLongPtrW> + sUser32SetWindowLongWHookStub; +# else +typedef LONG(WINAPI* User32SetWindowLongA)(HWND hWnd, int nIndex, + LONG dwNewLong); +typedef LONG(WINAPI* User32SetWindowLongW)(HWND hWnd, int nIndex, + LONG dwNewLong); +static WindowsDllInterceptor::FuncHookType<User32SetWindowLongA> + sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType<User32SetWindowLongW> + sUser32SetWindowLongWHookStub; +# endif + +extern LRESULT CALLBACK NeuteredWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, + LPARAM lParam); + +const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc"; + +// static +bool PluginInstanceChild::SetWindowLongHookCheck(HWND hWnd, int nIndex, + LONG_PTR newLong) { + // Let this go through if it's not a subclass + if (nIndex != GWLP_WNDPROC || + // if it's not a subclassed plugin window + !GetProp(hWnd, kPluginInstanceChildProperty) || + // if we're not disabled + GetProp(hWnd, kPluginIgnoreSubclassProperty) || + // if the subclass is set to a known procedure + newLong == reinterpret_cast<LONG_PTR>(PluginWindowProc) || + newLong == reinterpret_cast<LONG_PTR>(NeuteredWindowProc) || + newLong == reinterpret_cast<LONG_PTR>(DefWindowProcA) || + newLong == reinterpret_cast<LONG_PTR>(DefWindowProcW) || + // if the subclass is a WindowsMessageLoop subclass restore + GetProp(hWnd, kOldWndProcProp)) + return true; + // prevent the subclass + return false; +} + +# ifdef _WIN64 +LONG_PTR WINAPI PluginInstanceChild::SetWindowLongPtrAHook(HWND hWnd, + int nIndex, + LONG_PTR newLong) +# else +LONG WINAPI PluginInstanceChild::SetWindowLongAHook(HWND hWnd, int nIndex, + LONG newLong) +# endif +{ + if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) + return sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); + + // Set flash's new subclass to get the result. + LONG_PTR proc = sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); + + // We already checked this in SetWindowLongHookCheck + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>( + GetProp(hWnd, kPluginInstanceChildProperty)); + + // Hook our subclass back up, just like we do on setwindow. + WNDPROC currentProc = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (currentProc != PluginWindowProc) { + self->mPluginWndProc = + reinterpret_cast<WNDPROC>(sUser32SetWindowLongWHookStub( + hWnd, nIndex, reinterpret_cast<LONG_PTR>(PluginWindowProc))); + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, + "Infinite recursion coming up!"); + } + return proc; +} + +# ifdef _WIN64 +LONG_PTR WINAPI PluginInstanceChild::SetWindowLongPtrWHook(HWND hWnd, + int nIndex, + LONG_PTR newLong) +# else +LONG WINAPI PluginInstanceChild::SetWindowLongWHook(HWND hWnd, int nIndex, + LONG newLong) +# endif +{ + if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) + return sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); + + // Set flash's new subclass to get the result. + LONG_PTR proc = sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); + + // We already checked this in SetWindowLongHookCheck + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>( + GetProp(hWnd, kPluginInstanceChildProperty)); + + // Hook our subclass back up, just like we do on setwindow. + WNDPROC currentProc = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (currentProc != PluginWindowProc) { + self->mPluginWndProc = + reinterpret_cast<WNDPROC>(sUser32SetWindowLongWHookStub( + hWnd, nIndex, reinterpret_cast<LONG_PTR>(PluginWindowProc))); + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, + "Infinite recursion coming up!"); + } + return proc; +} + +void PluginInstanceChild::HookSetWindowLongPtr() { + if (!(GetQuirks() & QUIRK_FLASH_HOOK_SETLONGPTR)) { + return; + } + + sUser32Intercept.Init("user32.dll"); +# ifdef _WIN64 + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongPtrA", + &SetWindowLongPtrAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongPtrW", + &SetWindowLongPtrWHook); +# else + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongA", + &SetWindowLongAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongW", + &SetWindowLongWHook); +# endif +} + +/* 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)) { + 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. + sUser32Intercept.Init("user32.dll"); + sUser32TrackPopupMenuStub.Set(sUser32Intercept, "TrackPopupMenu", + &TrackPopupHookProc); +} + +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 +LONG PluginInstanceChild::ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex, + LPVOID aBuf, DWORD aLen) { + if (aIMC != sHookIMC) { + return sImm32ImmGetCompositionStringStub(aIMC, aIndex, aBuf, aLen); + } + if (!sCurrentPluginInstance) { + return IMM_ERROR_GENERAL; + } + AutoTArray<uint8_t, 16> dist; + int32_t length = 0; // IMM_ERROR_NODATA + sCurrentPluginInstance->SendGetCompositionString(aIndex, &dist, &length); + if (length == IMM_ERROR_NODATA || length == IMM_ERROR_GENERAL) { + return length; + } + + if (aBuf && aLen >= static_cast<DWORD>(length)) { + memcpy(aBuf, dist.Elements(), length); + } + return length; +} + +// staitc +BOOL PluginInstanceChild::ImmSetCandidateWindowProc(HIMC aIMC, + LPCANDIDATEFORM aForm) { + return FALSE; +} + +// 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; +} + +// static +BOOL PluginInstanceChild::ImmAssociateContextExProc(HWND hWND, HIMC hImc, + DWORD dwFlags) { + PluginInstanceChild* self = sCurrentPluginInstance; + if (!self) { + // If ImmAssociateContextEx calls unexpected window message, + // we can use child instance object from window property if available. + self = reinterpret_cast<PluginInstanceChild*>( + GetProp(hWND, kFlashThrottleProperty)); + NS_WARNING_ASSERTION(self, "Cannot find PluginInstanceChild"); + } + + // HIMC is always nullptr on Flash's windowless + if (!hImc && self) { + // Store the last IME state since Flash may call ImmAssociateContextEx + // before taking focus. + self->mLastEnableIMEState = !!(dwFlags & IACE_DEFAULT); + } + return sImm32ImmAssociateContextExStub(hWND, hImc, dwFlags); +} + +void PluginInstanceChild::InitImm32Hook() { + if (!(GetQuirks() & QUIRK_WINLESS_HOOK_IME)) { + return; + } + + // When using windowless plugin, IMM API won't work due to OOP. + // + // ImmReleaseContext on Windows 7+ just returns TRUE only, so we don't + // need to hook this. + + sImm32Intercept.Init("imm32.dll"); + sImm32ImmGetContextStub.Set(sImm32Intercept, "ImmGetContext", + &ImmGetContextProc); + sImm32ImmGetCompositionStringStub.Set(sImm32Intercept, + "ImmGetCompositionStringW", + &ImmGetCompositionStringProc); + sImm32ImmSetCandidateWindowStub.Set(sImm32Intercept, "ImmSetCandidateWindow", + &ImmSetCandidateWindowProc); + sImm32ImmNotifyIME.Set(sImm32Intercept, "ImmNotifyIME", &ImmNotifyIME); + sImm32ImmAssociateContextExStub.Set(sImm32Intercept, "ImmAssociateContextEx", + &ImmAssociateContextExProc); +} + +void PluginInstanceChild::DestroyWinlessPopupSurrogate() { + if (mWinlessPopupSurrogateHWND) DestroyWindow(mWinlessPopupSurrogateHWND); + mWinlessPopupSurrogateHWND = nullptr; +} + +int16_t PluginInstanceChild::WinlessHandleEvent(NPEvent& event) { + if (!mPluginIface->event) return false; + + // Events that might generate nested event dispatch loops need + // special handling during delivery. + int16_t handled; + + HWND focusHwnd = nullptr; + + // TrackPopupMenu will fail if the parent window is not associated with + // our ui thread. So we hook TrackPopupMenu so we can hand in a surrogate + // parent created in the child process. + if ((GetQuirks() & + QUIRK_WINLESS_TRACKPOPUP_HOOK) && // XXX turn on by default? + (event.event == WM_RBUTTONDOWN || // flash + event.event == WM_RBUTTONUP)) { // silverlight + sWinlessPopupSurrogateHWND = mWinlessPopupSurrogateHWND; + + // A little trick scrounged from chromium's code - set the focus + // to our surrogate parent so keyboard nav events go to the menu. + focusHwnd = SetFocus(mWinlessPopupSurrogateHWND); + } + + AutoRestore<PluginInstanceChild*> pluginInstance(sCurrentPluginInstance); + if (event.event == WM_IME_STARTCOMPOSITION || + event.event == WM_IME_COMPOSITION || event.event == WM_LBUTTONDOWN || + event.event == WM_KILLFOCUS) { + sCurrentPluginInstance = this; + } + + MessageLoop* loop = MessageLoop::current(); + AutoRestore<bool> modalLoop(loop->os_modal_loop()); + + handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&event)); + + sWinlessPopupSurrogateHWND = nullptr; + + if (IsWindow(focusHwnd)) { + SetFocus(focusHwnd); + } + + // This is hack of Flash's behaviour. + // + // When moving focus from chrome to plugin by mouse click, Gecko sends + // mouse message (such as WM_LBUTTONDOWN etc) at first, then sends + // WM_SETFOCUS. But Flash will call ImmAssociateContextEx on WM_LBUTTONDOWN + // even if it doesn't receive WM_SETFOCUS. + // + // In this situation, after sending mouse message, content process will be + // activated and set input context with PLUGIN. So after activating + // content process, we have to set current IME state again. + + if (event.event == WM_KILLFOCUS) { + // Flash always calls ImmAssociateContextEx by taking focus. + // Although this flag doesn't have to be reset, I add it for safety. + mLastEnableIMEState = true; + } + + return handled; +} + +/* flash msg throttling helpers */ + +// Flash has the unfortunate habit of flooding dispatch loops with custom +// windowing events they use for timing. We throttle these by dropping the +// delivery priority below any other event, including pending ipc io +// notifications. We do this for both windowed and windowless controls. +// Note flash's windowless msg window can last longer than our instance, +// so we try to unhook when the window is destroyed and in NPP_Destroy. + +void PluginInstanceChild::UnhookWinlessFlashThrottle() { + // We may have already unhooked + if (!mWinlessThrottleOldWndProc) return; + + WNDPROC tmpProc = mWinlessThrottleOldWndProc; + mWinlessThrottleOldWndProc = nullptr; + + NS_ASSERTION(mWinlessHiddenMsgHWND, + "Missing mWinlessHiddenMsgHWND w/subclass set??"); + + // reset the subclass + SetWindowLongPtr(mWinlessHiddenMsgHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(tmpProc)); + + // Remove our instance prop + RemoveProp(mWinlessHiddenMsgHWND, kFlashThrottleProperty); + mWinlessHiddenMsgHWND = nullptr; +} + +// static +LRESULT CALLBACK PluginInstanceChild::WinlessHiddenFlashWndProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) { + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>( + GetProp(hWnd, kFlashThrottleProperty)); + if (!self) { + MOZ_ASSERT_UNREACHABLE("Badness!"); + return 0; + } + + NS_ASSERTION(self->mWinlessThrottleOldWndProc, + "Missing subclass procedure!!"); + + // Throttle + if (message == WM_USER + 1) { + self->FlashThrottleMessage(hWnd, message, wParam, lParam, false); + return 0; + } + + // Unhook + if (message == WM_CLOSE || message == WM_NCDESTROY) { + WNDPROC tmpProc = self->mWinlessThrottleOldWndProc; + self->UnhookWinlessFlashThrottle(); + LRESULT res = CallWindowProc(tmpProc, hWnd, message, wParam, lParam); + return res; + } + + return CallWindowProc(self->mWinlessThrottleOldWndProc, hWnd, message, wParam, + lParam); +} + +// Enumerate all thread windows looking for flash's hidden message window. +// Once we find it, sub class it so we can throttle user msgs. +// static +BOOL CALLBACK PluginInstanceChild::EnumThreadWindowsCallback(HWND hWnd, + LPARAM aParam) { + PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>(aParam); + if (!self) { + MOZ_ASSERT_UNREACHABLE("Enum befuddled!"); + return FALSE; + } + + wchar_t className[64]; + if (!GetClassNameW(hWnd, className, sizeof(className) / sizeof(char16_t))) + return TRUE; + + if (!wcscmp(className, L"SWFlash_PlaceholderX")) { + WNDPROC oldWndProc = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + // Only set this if we haven't already. + if (oldWndProc != WinlessHiddenFlashWndProc) { + if (self->mWinlessThrottleOldWndProc) { + NS_WARNING("mWinlessThrottleWndProc already set???"); + return FALSE; + } + // Subsclass and store self as a property + self->mWinlessHiddenMsgHWND = hWnd; + self->mWinlessThrottleOldWndProc = + reinterpret_cast<WNDPROC>(SetWindowLongPtr( + hWnd, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(WinlessHiddenFlashWndProc))); + SetProp(hWnd, kFlashThrottleProperty, self); + NS_ASSERTION(self->mWinlessThrottleOldWndProc, + "SetWindowLongPtr failed?!"); + } + // Return no matter what once we find the right window. + return FALSE; + } + + return TRUE; +} + +void PluginInstanceChild::SetupFlashMsgThrottle() { + if (mWindow.type == NPWindowTypeDrawable) { + // Search for the flash hidden message window and subclass it. Only + // search for flash windows belonging to our ui thread! + if (mWinlessThrottleOldWndProc) return; + EnumThreadWindows(GetCurrentThreadId(), EnumThreadWindowsCallback, + reinterpret_cast<LPARAM>(this)); + } else { + // Already setup through quirks and the subclass. + return; + } +} + +WNDPROC +PluginInstanceChild::FlashThrottleMsg::GetProc() { + if (mInstance) { + return mWindowed ? mInstance->mPluginWndProc + : mInstance->mWinlessThrottleOldWndProc; + } + return nullptr; +} + +NS_IMETHODIMP +PluginInstanceChild::FlashThrottleMsg::Run() { + if (!mInstance) { + return NS_OK; + } + + mInstance->mPendingFlashThrottleMsgs.RemoveElement(this); + + // GetProc() checks mInstance, and pulls the procedure from + // PluginInstanceChild. We don't transport sub-class procedure + // ptrs around in FlashThrottleMsg msgs. + if (!GetProc()) return NS_OK; + + // deliver the event to flash + CallWindowProc(GetProc(), GetWnd(), GetMsg(), GetWParam(), GetLParam()); + return NS_OK; +} + +nsresult PluginInstanceChild::FlashThrottleMsg::Cancel() { + MOZ_ASSERT(mInstance); + mInstance = nullptr; + return NS_OK; +} + +void PluginInstanceChild::FlashThrottleMessage(HWND aWnd, UINT aMsg, + WPARAM aWParam, LPARAM aLParam, + bool isWindowed) { + // We save a reference to the FlashThrottleMsg so we can cancel it in + // Destroy if it's still alive. + RefPtr<FlashThrottleMsg> task = + new FlashThrottleMsg(this, aWnd, aMsg, aWParam, aLParam, isWindowed); + + mPendingFlashThrottleMsgs.AppendElement(task); + + MessageLoop::current()->PostDelayedTask(task.forget(), + kFlashWMUSERMessageThrottleDelayMs); +} + +#endif // OS_WIN + +mozilla::ipc::IPCResult 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) return IPC_OK(); + ::SetFocus(mPluginWindowHWND); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("AnswerSetPluginFocus not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("AnswerUpdateWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvNPP_DidComposite() { + if (mPluginIface->didComposite) { + mPluginIface->didComposite(GetNPP()); + } + return IPC_OK(); +} + +PPluginScriptableObjectChild* +PluginInstanceChild::AllocPPluginScriptableObjectChild() { + AssertPluginThread(); + return new PluginScriptableObjectChild(Proxy); +} + +bool PluginInstanceChild::DeallocPPluginScriptableObjectChild( + PPluginScriptableObjectChild* aObject) { + AssertPluginThread(); + delete aObject; + return true; +} + +mozilla::ipc::IPCResult +PluginInstanceChild::RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectChild* aActor) { + AssertPluginThread(); + + // This is only called in response to the parent process requesting the + // creation of an actor. This actor will represent an NPObject that is + // created by the browser and returned to the plugin. + PluginScriptableObjectChild* actor = + static_cast<PluginScriptableObjectChild*>(aActor); + NS_ASSERTION(!actor->GetObject(false), "Actor already has an object?!"); + + actor->InitializeProxy(); + NS_ASSERTION(actor->GetObject(false), "Actor should have an object!"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvPBrowserStreamConstructor( + PBrowserStreamChild* aActor, const nsCString& url, const uint32_t& length, + const uint32_t& lastmodified, PStreamNotifyChild* notifyData, + const nsCString& headers) { + return IPC_OK(); +} + +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; +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_NewStream( + PBrowserStreamChild* actor, const nsCString& mimeType, const bool& seekable, + NPError* rv, uint16_t* stype) { + *rv = DoNPP_NewStream(static_cast<BrowserStreamChild*>(actor), mimeType, + seekable, stype); + return IPC_OK(); +} + +PBrowserStreamChild* PluginInstanceChild::AllocPBrowserStreamChild( + const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, + PStreamNotifyChild* notifyData, const nsCString& headers) { + AssertPluginThread(); + return new BrowserStreamChild(this, url, length, lastmodified, + static_cast<StreamNotifyChild*>(notifyData), + headers); +} + +bool PluginInstanceChild::DeallocPBrowserStreamChild( + PBrowserStreamChild* stream) { + AssertPluginThread(); + delete stream; + return true; +} + +PStreamNotifyChild* PluginInstanceChild::AllocPStreamNotifyChild( + const nsCString& url, const nsCString& target, const bool& post, + const nsCString& buffer, const bool& file, NPError* result) { + AssertPluginThread(); + MOZ_CRASH("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; +} + +mozilla::ipc::IPCResult StreamNotifyChild::Recv__delete__( + const NPReason& reason) { + AssertPluginThread(); + + if (mBrowserStream) + mBrowserStream->NotifyPending(); + else + NPP_URLNotify(reason); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult StreamNotifyChild::RecvRedirectNotify( + const nsCString& url, const int32_t& status) { + // NPP_URLRedirectNotify requires a non-null closure. Since core logic + // assumes that all out-of-process notify streams have non-null closure + // data it will assume that the plugin was notified at this point and + // expect a response otherwise the redirect will hang indefinitely. + if (!mClosure) { + SendRedirectNotifyResponse(false); + } + + PluginInstanceChild* instance = static_cast<PluginInstanceChild*>(Manager()); + if (instance->mPluginIface->urlredirectnotify) + instance->mPluginIface->urlredirectnotify(instance->GetNPP(), url.get(), + status, mClosure); + + return IPC_OK(); +} + +void StreamNotifyChild::NPP_URLNotify(NPReason reason) { + PluginInstanceChild* instance = static_cast<PluginInstanceChild*>(Manager()); + + if (mClosure) + instance->mPluginIface->urlnotify(instance->GetNPP(), mURL.get(), reason, + mClosure); +} + +bool PluginInstanceChild::DeallocPStreamNotifyChild( + PStreamNotifyChild* notifyData) { + AssertPluginThread(); + + if (!static_cast<StreamNotifyChild*>(notifyData)->mBrowserStream) + delete notifyData; + return true; +} + +PluginScriptableObjectChild* PluginInstanceChild::GetActorForNPObject( + NPObject* aObject) { + AssertPluginThread(); + NS_ASSERTION(aObject, "Null pointer!"); + + if (aObject->_class == PluginScriptableObjectChild::GetClass()) { + // One of ours! It's a browser-provided object. + ChildNPObject* object = static_cast<ChildNPObject*>(aObject); + NS_ASSERTION(object->parent, "Null actor!"); + return object->parent; + } + + PluginScriptableObjectChild* actor = + PluginScriptableObjectChild::GetActorForNPObject(aObject); + if (actor) { + // Plugin-provided object that we've previously wrapped. + return actor; + } + + actor = new PluginScriptableObjectChild(LocalObject); + if (!SendPPluginScriptableObjectConstructor(actor)) { + NS_ERROR("Failed to send constructor message!"); + return nullptr; + } + + actor->InitializeLocal(aObject); + return actor; +} + +void PluginInstanceChild::NPN_URLRedirectResponse(void* notifyData, + NPBool allow) { + if (!notifyData) { + return; + } + + nsTArray<PStreamNotifyChild*> notifyStreams; + ManagedPStreamNotifyChild(notifyStreams); + uint32_t notifyStreamCount = notifyStreams.Length(); + for (uint32_t i = 0; i < notifyStreamCount; i++) { + StreamNotifyChild* sn = static_cast<StreamNotifyChild*>(notifyStreams[i]); + if (sn->mClosure == notifyData) { + sn->SendRedirectNotifyResponse(static_cast<bool>(allow)); + return; + } + } + NS_ASSERTION(false, "Couldn't find stream for redirect response!"); +} + +bool PluginInstanceChild::IsUsingDirectDrawing() { + return IsDrawingModelDirect(mDrawingModel); +} + +PluginInstanceChild::DirectBitmap::DirectBitmap(PluginInstanceChild* aOwner, + const Shmem& shmem, + const IntSize& size, + uint32_t stride, + SurfaceFormat format) + : mOwner(aOwner), + mShmem(shmem), + mFormat(format), + mSize(size), + mStride(stride) {} + +PluginInstanceChild::DirectBitmap::~DirectBitmap() { + mOwner->DeallocShmem(mShmem); +} + +static inline SurfaceFormat NPImageFormatToSurfaceFormat( + NPImageFormat aFormat) { + switch (aFormat) { + case NPImageFormatBGRA32: + return SurfaceFormat::B8G8R8A8; + case NPImageFormatBGRX32: + return SurfaceFormat::B8G8R8X8; + default: + MOZ_ASSERT_UNREACHABLE("unknown NPImageFormat"); + return SurfaceFormat::UNKNOWN; + } +} + +static inline gfx::IntRect NPRectToIntRect(const NPRect& in) { + return IntRect(in.left, in.top, in.right - in.left, in.bottom - in.top); +} + +NPError PluginInstanceChild::NPN_InitAsyncSurface(NPSize* size, + NPImageFormat format, + void* initData, + NPAsyncSurface* surface) { + AssertPluginThread(); + AutoStackHelper guard(this); + + if (!IsUsingDirectDrawing()) { + return NPERR_INVALID_PARAM; + } + if (format != NPImageFormatBGRA32 && format != NPImageFormatBGRX32) { + return NPERR_INVALID_PARAM; + } + + PodZero(surface); + + // NPAPI guarantees that the SetCurrentAsyncSurface call will release the + // previous surface if it was different. However, no functionality exists + // within content to synchronize a non-shadow-layers transaction with the + // compositor. + // + // To get around this, we allocate two surfaces: a child copy, which we + // hand off to the plugin, and a parent copy, which we will hand off to + // the compositor. Each call to SetCurrentAsyncSurface will copy the + // invalid region from the child surface to its parent. + switch (mDrawingModel) { + case NPDrawingModelAsyncBitmapSurface: { + // Validate that the caller does not expect initial data to be set. + if (initData) { + return NPERR_INVALID_PARAM; + } + + // Validate that we're not double-allocating a surface. + RefPtr<DirectBitmap> holder; + if (mDirectBitmaps.Get(surface, getter_AddRefs(holder))) { + return NPERR_INVALID_PARAM; + } + + SurfaceFormat mozformat = NPImageFormatToSurfaceFormat(format); + int32_t bytesPerPixel = BytesPerPixel(mozformat); + + if (size->width <= 0 || size->height <= 0) { + return NPERR_INVALID_PARAM; + } + + CheckedInt<uint32_t> nbytes = + SafeBytesForBitmap(size->width, size->height, bytesPerPixel); + if (!nbytes.isValid()) { + return NPERR_INVALID_PARAM; + } + + Shmem shmem; + if (!AllocUnsafeShmem(nbytes.value(), SharedMemory::TYPE_BASIC, &shmem)) { + return NPERR_OUT_OF_MEMORY_ERROR; + } + MOZ_ASSERT(shmem.Size<uint8_t>() == nbytes.value()); + + surface->version = 0; + surface->size = *size; + surface->format = format; + surface->bitmap.data = shmem.get<unsigned char>(); + surface->bitmap.stride = size->width * bytesPerPixel; + + // Hold the shmem alive until Finalize() is called or this actor dies. + holder = new DirectBitmap(this, shmem, IntSize(size->width, size->height), + surface->bitmap.stride, mozformat); + mDirectBitmaps.Put(surface, std::move(holder)); + return NPERR_NO_ERROR; + } +#if defined(XP_WIN) + case NPDrawingModelAsyncWindowsDXGISurface: { + // Validate that the caller does not expect initial data to be set. + if (initData) { + return NPERR_INVALID_PARAM; + } + + // Validate that we're not double-allocating a surface. + WindowsHandle handle = 0; + if (mDxgiSurfaces.Get(surface, &handle)) { + return NPERR_INVALID_PARAM; + } + + NPError error = NPERR_NO_ERROR; + SurfaceFormat mozformat = NPImageFormatToSurfaceFormat(format); + if (!SendInitDXGISurface(mozformat, IntSize(size->width, size->height), + &handle, &error)) { + return NPERR_GENERIC_ERROR; + } + if (error != NPERR_NO_ERROR) { + return error; + } + + surface->version = 0; + surface->size = *size; + surface->format = format; + surface->sharedHandle = reinterpret_cast<HANDLE>(handle); + + mDxgiSurfaces.Put(surface, handle); + return NPERR_NO_ERROR; + } +#endif + default: + MOZ_ASSERT_UNREACHABLE("unknown drawing model"); + } + + return NPERR_INVALID_PARAM; +} + +NPError PluginInstanceChild::NPN_FinalizeAsyncSurface(NPAsyncSurface* surface) { + AssertPluginThread(); + + if (!IsUsingDirectDrawing()) { + return NPERR_GENERIC_ERROR; + } + + switch (mDrawingModel) { + case NPDrawingModelAsyncBitmapSurface: { + RefPtr<DirectBitmap> bitmap; + if (!mDirectBitmaps.Get(surface, getter_AddRefs(bitmap))) { + return NPERR_INVALID_PARAM; + } + + PodZero(surface); + mDirectBitmaps.Remove(surface); + return NPERR_NO_ERROR; + } +#if defined(XP_WIN) + case NPDrawingModelAsyncWindowsDXGISurface: { + WindowsHandle handle; + if (!mDxgiSurfaces.Get(surface, &handle)) { + return NPERR_INVALID_PARAM; + } + + SendFinalizeDXGISurface(handle); + mDxgiSurfaces.Remove(surface); + return NPERR_NO_ERROR; + } +#endif + default: + MOZ_ASSERT_UNREACHABLE("unknown drawing model"); + } + + return NPERR_INVALID_PARAM; +} + +void PluginInstanceChild::NPN_SetCurrentAsyncSurface(NPAsyncSurface* surface, + NPRect* changed) { + AssertPluginThread(); + + if (!IsUsingDirectDrawing()) { + return; + } + + mCurrentDirectSurface = surface; + + if (!surface) { + SendRevokeCurrentDirectSurface(); + return; + } + + switch (mDrawingModel) { + case NPDrawingModelAsyncBitmapSurface: { + RefPtr<DirectBitmap> bitmap; + if (!mDirectBitmaps.Get(surface, getter_AddRefs(bitmap))) { + return; + } + + IntRect dirty = changed ? NPRectToIntRect(*changed) + : IntRect(IntPoint(0, 0), bitmap->mSize); + + // Need a holder since IPDL zaps the object for mysterious reasons. + Shmem shmemHolder = bitmap->mShmem; + SendShowDirectBitmap(std::move(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(); +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvAsyncSetWindow( + const gfxSurfaceType& aSurfaceType, const NPRemoteWindow& aWindow) { + AssertPluginThread(); + + AutoStackHelper guard(this); + NS_ASSERTION(!aWindow.window, "Remote window should be null."); + + if (mCurrentAsyncSetWindowTask) { + mCurrentAsyncSetWindowTask->Cancel(); + mCurrentAsyncSetWindowTask = nullptr; + } + + // We shouldn't process this now because it may be received within a nested + // RPC call, and both Flash and Java don't expect to receive setwindow calls + // at arbitrary times. + mCurrentAsyncSetWindowTask = + NewNonOwningCancelableRunnableMethod<gfxSurfaceType, NPRemoteWindow, + bool>( + "plugins::PluginInstanceChild::DoAsyncSetWindow", this, + &PluginInstanceChild::DoAsyncSetWindow, aSurfaceType, aWindow, true); + RefPtr<Runnable> addrefedTask = mCurrentAsyncSetWindowTask; + MessageLoop::current()->PostTask(addrefedTask.forget()); + + return IPC_OK(); +} + +void PluginInstanceChild::DoAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow, + bool aIsAsync) { + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] AsyncSetWindow to <x=%d,y=%d, w=%d,h=%d>", this, + aWindow.x, aWindow.y, aWindow.width, aWindow.height)); + + AssertPluginThread(); + NS_ASSERTION(!aWindow.window, "Remote window should be null."); + NS_ASSERTION(!mPendingPluginCall, "Can't do SetWindow during plugin call!"); + + if (aIsAsync) { + if (!mCurrentAsyncSetWindowTask) { + return; + } + mCurrentAsyncSetWindowTask = nullptr; + } + + mWindow.window = nullptr; + if (mWindow.width != aWindow.width || mWindow.height != aWindow.height || + mWindow.clipRect.top != aWindow.clipRect.top || + mWindow.clipRect.left != aWindow.clipRect.left || + mWindow.clipRect.bottom != aWindow.clipRect.bottom || + mWindow.clipRect.right != aWindow.clipRect.right) + mAccumulatedInvalidRect = nsIntRect(0, 0, aWindow.width, aWindow.height); + + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.clipRect = aWindow.clipRect; + mWindow.type = aWindow.type; +#if defined(XP_MACOSX) || defined(XP_WIN) + mContentsScaleFactor = aWindow.contentsScaleFactor; +#endif + + mLayersRendering = true; + mSurfaceType = aSurfaceType; + UpdateWindowAttributes(true); + +#ifdef XP_WIN + if (GetQuirks() & QUIRK_FLASH_THROTTLE_WMUSER_EVENTS) SetupFlashMsgThrottle(); +#endif + + if (!mAccumulatedInvalidRect.IsEmpty()) { + AsyncShowPluginFrame(); + } +} + +bool PluginInstanceChild::CreateOptSurface(void) { + MOZ_ASSERT(mSurfaceType != gfxSurfaceType::Max, + "Need a valid surface type here"); + NS_ASSERTION(!mCurrentSurface, "mCurrentSurfaceActor can get out of sync."); + + // Use an opaque surface unless we're transparent and *don't* have + // a background to source from. + gfxImageFormat format = (mIsTransparent && !mBackground) + ? SurfaceFormat::A8R8G8B8_UINT32 + : SurfaceFormat::X8R8G8B8_UINT32; + +#ifdef MOZ_X11 + Display* dpy = mWsInfo.display; + Screen* screen = DefaultScreenOfDisplay(dpy); + if (format == SurfaceFormat::X8R8G8B8_UINT32 && + DefaultDepth(dpy, DefaultScreen(dpy)) == 16) { + format = SurfaceFormat::R5G6B5_UINT16; + } + + if (mSurfaceType == gfxSurfaceType::Xlib) { + if (!mIsTransparent || mBackground) { + Visual* defaultVisual = DefaultVisualOfScreen(screen); + mCurrentSurface = gfxXlibSurface::Create( + screen, defaultVisual, IntSize(mWindow.width, mWindow.height)); + return mCurrentSurface != nullptr; + } + + XRenderPictFormat* xfmt = + XRenderFindStandardFormat(dpy, PictStandardARGB32); + if (!xfmt) { + NS_ERROR("Need X falback surface, but FindRenderFormat failed"); + return false; + } + mCurrentSurface = gfxXlibSurface::Create( + screen, xfmt, IntSize(mWindow.width, mWindow.height)); + return mCurrentSurface != nullptr; + } +#endif + +#ifdef XP_WIN + if (mSurfaceType == gfxSurfaceType::Win32) { + bool willHaveTransparentPixels = mIsTransparent && !mBackground; + + SharedDIBSurface* s = new SharedDIBSurface(); + if (!s->Create(reinterpret_cast<HDC>(mWindow.window), mWindow.width, + mWindow.height, willHaveTransparentPixels)) + return false; + + mCurrentSurface = s; + return true; + } + + MOZ_CRASH("Shared-memory drawing not expected on Windows."); +#endif + + // Make common shmem implementation working for any platform + mCurrentSurface = gfxSharedImageSurface::CreateUnsafe( + this, IntSize(mWindow.width, mWindow.height), format); + return !!mCurrentSurface; +} + +bool PluginInstanceChild::MaybeCreatePlatformHelperSurface(void) { + if (!mCurrentSurface) { + NS_ERROR("Cannot create helper surface without mCurrentSurface"); + return false; + } + +#ifdef MOZ_X11 + bool supportNonDefaultVisual = false; + Screen* screen = DefaultScreenOfDisplay(mWsInfo.display); + Visual* defaultVisual = DefaultVisualOfScreen(screen); + Visual* visual = nullptr; + Colormap colormap = 0; + mDoAlphaExtraction = false; + bool createHelperSurface = false; + + if (mCurrentSurface->GetType() == gfxSurfaceType::Xlib) { + static_cast<gfxXlibSurface*>(mCurrentSurface.get()) + ->GetColormapAndVisual(&colormap, &visual); + // Create helper surface if layer surface visual not same as default + // and we don't support non-default visual rendering + if (!visual || (defaultVisual != visual && !supportNonDefaultVisual)) { + createHelperSurface = true; + visual = defaultVisual; + mDoAlphaExtraction = mIsTransparent; + } + } else if (mCurrentSurface->GetType() == gfxSurfaceType::Image) { + // For image layer surface we should always create helper surface + createHelperSurface = true; + // Check if we can create helper surface with non-default visual + visual = gfxXlibSurface::FindVisual( + screen, static_cast<gfxImageSurface*>(mCurrentSurface.get())->Format()); + if (!visual || (defaultVisual != visual && !supportNonDefaultVisual)) { + visual = defaultVisual; + mDoAlphaExtraction = mIsTransparent; + } + } + + if (createHelperSurface) { + if (!visual) { + NS_ERROR("Need X falback surface, but visual failed"); + return false; + } + mHelperSurface = + gfxXlibSurface::Create(screen, visual, mCurrentSurface->GetSize()); + if (!mHelperSurface) { + NS_WARNING("Fail to create create helper surface"); + return false; + } + } +#elif defined(XP_WIN) + mDoAlphaExtraction = mIsTransparent && !mBackground; +#endif + + return true; +} + +bool PluginInstanceChild::EnsureCurrentBuffer(void) { +#ifndef XP_DARWIN + nsIntRect toInvalidate(0, 0, 0, 0); + IntSize winSize = IntSize(mWindow.width, mWindow.height); + + if (mBackground && mBackground->GetSize() != winSize) { + // It would be nice to keep the old background here, but doing + // so can lead to cases in which we permanently keep the old + // background size. + mBackground = nullptr; + toInvalidate.UnionRect(toInvalidate, + nsIntRect(0, 0, winSize.width, winSize.height)); + } + + if (mCurrentSurface) { + IntSize surfSize = mCurrentSurface->GetSize(); + if (winSize != surfSize || (mBackground && !CanPaintOnBackground()) || + (mBackground && + gfxContentType::COLOR != mCurrentSurface->GetContentType()) || + (!mBackground && mIsTransparent && + gfxContentType::COLOR == mCurrentSurface->GetContentType())) { + // Don't try to use an old, invalid DC. + mWindow.window = nullptr; + ClearCurrentSurface(); + toInvalidate.UnionRect(toInvalidate, + nsIntRect(0, 0, winSize.width, winSize.height)); + } + } + + mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect, toInvalidate); + + if (mCurrentSurface) { + return true; + } + + if (!CreateOptSurface()) { + NS_ERROR("Cannot create optimized surface"); + return false; + } + + if (!MaybeCreatePlatformHelperSurface()) { + NS_ERROR("Cannot create helper surface"); + return false; + } +#elif defined(XP_MACOSX) + + if (!mDoubleBufferCARenderer.HasCALayer()) { + void* caLayer = nullptr; + if (mDrawingModel == NPDrawingModelCoreGraphics) { + if (!mCGLayer) { + caLayer = mozilla::plugins::PluginUtilsOSX::GetCGLayer( + CallCGDraw, this, mContentsScaleFactor); + + if (!caLayer) { + PLUGIN_LOG_DEBUG(("GetCGLayer failed.")); + return false; + } + } + mCGLayer = caLayer; + } else { + NPError result = mPluginIface->getvalue( + GetNPP(), NPPVpluginCoreAnimationLayer, &caLayer); + if (result != NPERR_NO_ERROR || !caLayer) { + PLUGIN_LOG_DEBUG( + ("Plugin requested CoreAnimation but did not " + "provide CALayer.")); + return false; + } + } + mDoubleBufferCARenderer.SetCALayer(caLayer); + } + + if (mDoubleBufferCARenderer.HasFrontSurface() && + (mDoubleBufferCARenderer.GetFrontSurfaceWidth() != mWindow.width || + mDoubleBufferCARenderer.GetFrontSurfaceHeight() != mWindow.height || + mDoubleBufferCARenderer.GetContentsScaleFactor() != + mContentsScaleFactor)) { + mDoubleBufferCARenderer.ClearFrontSurface(); + } + + if (!mDoubleBufferCARenderer.HasFrontSurface()) { + bool allocSurface = mDoubleBufferCARenderer.InitFrontSurface( + mWindow.width, mWindow.height, mContentsScaleFactor, + GetQuirks() & QUIRK_ALLOW_OFFLINE_RENDERER ? ALLOW_OFFLINE_RENDERER + : DISALLOW_OFFLINE_RENDERER); + if (!allocSurface) { + PLUGIN_LOG_DEBUG(("Fail to allocate front IOSurface")); + return false; + } + + if (mPluginIface->setwindow) + (void)mPluginIface->setwindow(&mData, &mWindow); + + nsIntRect toInvalidate(0, 0, mWindow.width, mWindow.height); + mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect, toInvalidate); + } +#endif + + return true; +} + +void PluginInstanceChild::UpdateWindowAttributes(bool aForceSetWindow) { +#if defined(MOZ_X11) || defined(XP_WIN) + RefPtr<gfxASurface> curSurface = + mHelperSurface ? mHelperSurface : mCurrentSurface; +#endif // Only used within MOZ_X11 or XP_WIN blocks. Unused variable otherwise + bool needWindowUpdate = aForceSetWindow; +#ifdef MOZ_X11 + Visual* visual = nullptr; + Colormap colormap = 0; + if (curSurface && curSurface->GetType() == gfxSurfaceType::Xlib) { + static_cast<gfxXlibSurface*>(curSurface.get()) + ->GetColormapAndVisual(&colormap, &visual); + if (visual != mWsInfo.visual || colormap != mWsInfo.colormap) { + mWsInfo.visual = visual; + mWsInfo.colormap = colormap; + needWindowUpdate = true; + } + } +#endif // MOZ_X11 +#ifdef XP_WIN + HDC dc = nullptr; + + if (curSurface) { + if (!SharedDIBSurface::IsSharedDIBSurface(curSurface)) + MOZ_CRASH("Expected SharedDIBSurface!"); + + SharedDIBSurface* dibsurf = + static_cast<SharedDIBSurface*>(curSurface.get()); + dc = dibsurf->GetHDC(); + } + if (mWindow.window != dc) { + mWindow.window = dc; + needWindowUpdate = true; + } +#endif // XP_WIN + + if (!needWindowUpdate) { + return; + } + +#ifndef XP_MACOSX + // Adjusting the window isn't needed for OSX +# ifndef XP_WIN + // On Windows, we translate the device context, in order for the window + // origin to be correct. + mWindow.x = mWindow.y = 0; +# endif + + if (IsVisible()) { + // The clip rect is relative to drawable top-left. + nsIntRect clipRect; + + // Don't ask the plugin to draw outside the drawable. The clip rect + // is in plugin coordinates, not window coordinates. + // This also ensures that the unsigned clip rectangle offsets won't be -ve. + clipRect.SetRect(0, 0, mWindow.width, mWindow.height); + + mWindow.clipRect.left = 0; + mWindow.clipRect.top = 0; + mWindow.clipRect.right = clipRect.XMost(); + mWindow.clipRect.bottom = clipRect.YMost(); + } +#endif // XP_MACOSX + +#ifdef XP_WIN + // Windowless plugins on Windows need a WM_WINDOWPOSCHANGED event to update + // their location... or at least Flash does: Silverlight uses the + // window.x/y passed to NPP_SetWindow + + if (mPluginIface->event) { + // width and height are stored as units, but narrow to ints here + MOZ_RELEASE_ASSERT(mWindow.width <= INT_MAX); + MOZ_RELEASE_ASSERT(mWindow.height <= INT_MAX); + + WINDOWPOS winpos = {0, + 0, + mWindow.x, + mWindow.y, + (int32_t)mWindow.width, + (int32_t)mWindow.height, + 0}; + NPEvent pluginEvent = {WM_WINDOWPOSCHANGED, 0, (LPARAM)&winpos}; + mPluginIface->event(&mData, &pluginEvent); + } +#endif + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] UpdateWindow w=<x=%d,y=%d, w=%d,h=%d>, " + "clip=<l=%d,t=%d,r=%d,b=%d>", + this, mWindow.x, mWindow.y, mWindow.width, mWindow.height, + mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, + mWindow.clipRect.bottom)); + + if (mPluginIface->setwindow) { + mPluginIface->setwindow(&mData, &mWindow); + } +} + +void PluginInstanceChild::PaintRectToPlatformSurface(const nsIntRect& aRect, + gfxASurface* aSurface) { + UpdateWindowAttributes(); + + // We should not send an async surface if we're using direct rendering. + MOZ_ASSERT(!IsUsingDirectDrawing()); + +#ifdef MOZ_X11 + { + NS_ASSERTION(aSurface->GetType() == gfxSurfaceType::Xlib, + "Non supported platform surface type"); + + NPEvent pluginEvent; + XGraphicsExposeEvent& exposeEvent = pluginEvent.xgraphicsexpose; + exposeEvent.type = GraphicsExpose; + exposeEvent.display = mWsInfo.display; + exposeEvent.drawable = static_cast<gfxXlibSurface*>(aSurface)->XDrawable(); + exposeEvent.x = aRect.x; + exposeEvent.y = aRect.y; + exposeEvent.width = aRect.width; + exposeEvent.height = aRect.height; + exposeEvent.count = 0; + // information not set: + exposeEvent.serial = 0; + exposeEvent.send_event = X11False; + exposeEvent.major_code = 0; + exposeEvent.minor_code = 0; + mPluginIface->event(&mData, reinterpret_cast<void*>(&exposeEvent)); + } +#elif defined(XP_WIN) + NS_ASSERTION(SharedDIBSurface::IsSharedDIBSurface(aSurface), + "Expected (SharedDIB) image surface."); + + // This rect is in the window coordinate space. aRect is in the plugin + // coordinate space. + RECT rect = {mWindow.x + aRect.x, mWindow.y + aRect.y, + mWindow.x + aRect.XMost(), mWindow.y + aRect.YMost()}; + NPEvent paintEvent = {WM_PAINT, uintptr_t(mWindow.window), intptr_t(&rect)}; + + ::SetViewportOrgEx((HDC)mWindow.window, -mWindow.x, -mWindow.y, nullptr); + ::SelectClipRgn((HDC)mWindow.window, nullptr); + ::IntersectClipRect((HDC)mWindow.window, rect.left, rect.top, rect.right, + rect.bottom); + mPluginIface->event(&mData, reinterpret_cast<void*>(&paintEvent)); +#else + MOZ_CRASH("Surface type not implemented."); +#endif +} + +void PluginInstanceChild::PaintRectToSurface(const nsIntRect& aRect, + gfxASurface* aSurface, + const DeviceColor& aColor) { + // Render using temporary X surface, with copy to image surface + nsIntRect plPaintRect(aRect); + RefPtr<gfxASurface> renderSurface = aSurface; +#ifdef MOZ_X11 + if (mIsTransparent && (GetQuirks() & QUIRK_FLASH_EXPOSE_COORD_TRANSLATION)) { + // Work around a bug in Flash up to 10.1 d51 at least, where expose event + // top left coordinates within the plugin-rect and not at the drawable + // origin are misinterpreted. (We can move the top left coordinate + // provided it is within the clipRect.), see bug 574583 + plPaintRect.SetRect(0, 0, aRect.XMost(), aRect.YMost()); + } + if (mHelperSurface) { + // On X11 we can paint to non Xlib surface only with HelperSurface + renderSurface = mHelperSurface; + } +#endif + + if (mIsTransparent && !CanPaintOnBackground()) { + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(renderSurface); + gfx::Rect rect(plPaintRect.x, plPaintRect.y, plPaintRect.width, + plPaintRect.height); + // Moz2D treats OP_SOURCE operations as unbounded, so we need to + // clip to the rect that we want to fill: + dt->PushClipRect(rect); + dt->FillRect(rect, + ColorPattern(aColor), // aColor is already a device color + DrawOptions(1.f, CompositionOp::OP_SOURCE)); + dt->PopClip(); + dt->Flush(); + } + + PaintRectToPlatformSurface(plPaintRect, renderSurface); + + if (renderSurface != aSurface) { + RefPtr<DrawTarget> dt; + if (aSurface == mCurrentSurface && + aSurface->GetType() == gfxSurfaceType::Image && + aSurface->GetSurfaceFormat() == SurfaceFormat::B8G8R8X8) { + gfxImageSurface* imageSurface = static_cast<gfxImageSurface*>(aSurface); + // Bug 1196927 - Reinterpret target surface as BGRA to fill alpha with + // opaque. Certain backends (i.e. Skia) may not truly support BGRX + // formats, so they must be emulated by filling the alpha channel opaque + // as if it was BGRA data. Cairo leaves the alpha zeroed out for BGRX, so + // we cause Cairo to fill it as opaque by handling the copy target as a + // BGRA surface. + dt = Factory::CreateDrawTargetForData( + BackendType::CAIRO, imageSurface->Data(), imageSurface->GetSize(), + imageSurface->Stride(), SurfaceFormat::B8G8R8A8); + } else { + // Copy helper surface content to target + dt = CreateDrawTargetForSurface(aSurface); + } + if (dt && dt->IsValid()) { + RefPtr<SourceSurface> surface = + gfxPlatform::GetSourceSurfaceForSurface(dt, renderSurface); + dt->CopySurface(surface, aRect, aRect.TopLeft()); + } else { + gfxWarning() << "PluginInstanceChild::PaintRectToSurface failure"; + } + } +} + +void PluginInstanceChild::PaintRectWithAlphaExtraction(const nsIntRect& aRect, + gfxASurface* aSurface) { + MOZ_ASSERT(aSurface->GetContentType() == gfxContentType::COLOR_ALPHA, + "Refusing to pointlessly recover alpha"); + + nsIntRect rect(aRect); + // If |aSurface| can be used to paint and can have alpha values + // recovered directly to it, do that to save a tmp surface and + // copy. + bool useSurfaceSubimageForBlack = false; + if (gfxSurfaceType::Image == aSurface->GetType()) { + gfxImageSurface* surfaceAsImage = static_cast<gfxImageSurface*>(aSurface); + useSurfaceSubimageForBlack = + (surfaceAsImage->Format() == SurfaceFormat::A8R8G8B8_UINT32); + // If we're going to use a subimage, nudge the rect so that we + // can use optimal alpha recovery. If we're not using a + // subimage, the temporaries should automatically get + // fast-path alpha recovery so we don't need to do anything. + if (useSurfaceSubimageForBlack) { + rect = + gfxAlphaRecovery::AlignRectForSubimageRecovery(aRect, surfaceAsImage); + } + } + + RefPtr<gfxImageSurface> whiteImage; + RefPtr<gfxImageSurface> blackImage; + gfxRect targetRect(rect.x, rect.y, rect.width, rect.height); + IntSize targetSize(rect.width, rect.height); + + // 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)) + MOZ_CRASH("Expected SharedDIBSurface!"); + + // Paint the plugin directly onto the target, with a white + // background and copy the result + PaintRectToSurface(rect, aSurface, DeviceColor::MaskOpaqueWhite()); + { + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(whiteImage); + RefPtr<SourceSurface> surface = + gfxPlatform::GetSourceSurfaceForSurface(dt, aSurface); + dt->CopySurface(surface, rect, IntPoint()); + } + + // Paint the plugin directly onto the target, with a black + // background + PaintRectToSurface(rect, aSurface, DeviceColor::MaskOpaqueBlack()); + + // Don't copy the result, just extract a subimage so that we can + // recover alpha directly into the target + gfxImageSurface* image = static_cast<gfxImageSurface*>(aSurface); + blackImage = image->GetSubimage(targetRect); + +#else + gfxPoint deviceOffset = -targetRect.TopLeft(); + // Paint onto white background + whiteImage->SetDeviceOffset(deviceOffset); + PaintRectToSurface(rect, whiteImage, DeviceColor::MaskOpaqueWhite()); + + if (useSurfaceSubimageForBlack) { + gfxImageSurface* surface = static_cast<gfxImageSurface*>(aSurface); + blackImage = surface->GetSubimage(targetRect); + } else { + blackImage = + new gfxImageSurface(targetSize, SurfaceFormat::A8R8G8B8_UINT32); + } + + // Paint onto black background + blackImage->SetDeviceOffset(deviceOffset); + PaintRectToSurface(rect, blackImage, DeviceColor::MaskOpaqueBlack()); +#endif + + MOZ_ASSERT(whiteImage && blackImage, "Didn't paint enough!"); + + // Extract alpha from black and white image and store to black + // image + if (!gfxAlphaRecovery::RecoverAlpha(blackImage, whiteImage)) { + return; + } + + // If we had to use a temporary black surface, copy the pixels + // with alpha back to the target + if (!useSurfaceSubimageForBlack) { + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(aSurface); + RefPtr<SourceSurface> surface = + gfxPlatform::GetSourceSurfaceForSurface(dt, blackImage); + dt->CopySurface(surface, IntRect(0, 0, rect.width, rect.height), + rect.TopLeft()); + } +} + +bool PluginInstanceChild::CanPaintOnBackground() { + return (mBackground && mCurrentSurface && + mCurrentSurface->GetSize() == mBackground->GetSize()); +} + +bool PluginInstanceChild::ShowPluginFrame() { + // mLayersRendering can be false if we somehow get here without + // receiving AsyncSetWindow() first. mPendingPluginCall is our + // re-entrancy guard; we can't paint while nested inside another + // paint. + if (!mLayersRendering || mPendingPluginCall) { + return false; + } + + // We should not attempt to asynchronously show the plugin if we're using + // direct rendering. + MOZ_ASSERT(!IsUsingDirectDrawing()); + + AutoRestore<bool> pending(mPendingPluginCall); + mPendingPluginCall = true; + + bool temporarilyMakeVisible = !IsVisible() && !mHasPainted; + if (temporarilyMakeVisible && mWindow.width && mWindow.height) { + mWindow.clipRect.right = mWindow.width; + mWindow.clipRect.bottom = mWindow.height; + } else if (!IsVisible()) { + // If we're not visible, don't bother painting a <0,0,0,0> + // rect. If we're eventually made visible, the visibility + // change will invalidate our window. + ClearCurrentSurface(); + return true; + } + + if (!EnsureCurrentBuffer()) { + return false; + } + +#ifdef MOZ_WIDGET_COCOA + // We can't use the thebes code with CoreAnimation so we will + // take a different code path. + if (mDrawingModel == NPDrawingModelCoreAnimation || + mDrawingModel == NPDrawingModelInvalidatingCoreAnimation || + mDrawingModel == NPDrawingModelCoreGraphics) { + if (!IsVisible()) { + return true; + } + + if (!mDoubleBufferCARenderer.HasFrontSurface()) { + NS_ERROR("CARenderer not initialized for rendering"); + return false; + } + + // Clear accRect here to be able to pass + // test_invalidate_during_plugin_paint test + nsIntRect rect = mAccumulatedInvalidRect; + mAccumulatedInvalidRect.SetEmpty(); + + // Fix up old invalidations that might have been made when our + // surface was a different size + rect.IntersectRect( + rect, nsIntRect(0, 0, mDoubleBufferCARenderer.GetFrontSurfaceWidth(), + mDoubleBufferCARenderer.GetFrontSurfaceHeight())); + + if (mDrawingModel == NPDrawingModelCoreGraphics) { + mozilla::plugins::PluginUtilsOSX::Repaint(mCGLayer, rect); + } + + mDoubleBufferCARenderer.Render(); + + NPRect r = {(uint16_t)rect.y, (uint16_t)rect.x, (uint16_t)rect.YMost(), + (uint16_t)rect.XMost()}; + SurfaceDescriptor currSurf; + currSurf = + IOSurfaceDescriptor(mDoubleBufferCARenderer.GetFrontSurfaceID(), + mDoubleBufferCARenderer.GetContentsScaleFactor()); + + mHasPainted = true; + + SurfaceDescriptor returnSurf; + + if (!SendShow(r, currSurf, &returnSurf)) { + return false; + } + + SwapSurfaces(); + return true; + } else { + NS_ERROR("Unsupported drawing model for async layer rendering"); + return false; + } +#endif + + NS_ASSERTION(mWindow.width == uint32_t(mWindow.clipRect.right - + mWindow.clipRect.left) && + mWindow.height == + uint32_t(mWindow.clipRect.bottom - mWindow.clipRect.top), + "Clip rect should be same size as window when using layers"); + + // Clear accRect here to be able to pass + // test_invalidate_during_plugin_paint test + nsIntRect rect = mAccumulatedInvalidRect; + mAccumulatedInvalidRect.SetEmpty(); + + // Fix up old invalidations that might have been made when our + // surface was a different size + IntSize surfaceSize = mCurrentSurface->GetSize(); + rect.IntersectRect(rect, + nsIntRect(0, 0, surfaceSize.width, surfaceSize.height)); + + if (!ReadbackDifferenceRect(rect)) { + // We couldn't read back the pixels that differ between the + // current surface and last, so we have to invalidate the + // entire window. + rect.SetRect(0, 0, mWindow.width, mWindow.height); + } + + bool haveTransparentPixels = + gfxContentType::COLOR_ALPHA == mCurrentSurface->GetContentType(); + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Painting%s <x=%d,y=%d, w=%d,h=%d> on surface " + "<w=%d,h=%d>", + this, haveTransparentPixels ? " with alpha" : "", rect.x, rect.y, + rect.width, rect.height, mCurrentSurface->GetSize().width, + mCurrentSurface->GetSize().height)); + + if (CanPaintOnBackground()) { + PLUGIN_LOG_DEBUG((" (on background)")); + // Source the background pixels ... + { + RefPtr<gfxASurface> surface = + mHelperSurface ? mHelperSurface : mCurrentSurface; + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(surface); + RefPtr<SourceSurface> backgroundSurface = + gfxPlatform::GetSourceSurfaceForSurface(dt, mBackground); + dt->CopySurface(backgroundSurface, rect, rect.TopLeft()); + } + // ... and hand off to the plugin + // BEWARE: mBackground may die during this call + PaintRectToSurface(rect, mCurrentSurface, DeviceColor()); + } else if (!temporarilyMakeVisible && mDoAlphaExtraction) { + // We don't want to pay the expense of alpha extraction for + // phony paints. + PLUGIN_LOG_DEBUG((" (with alpha recovery)")); + PaintRectWithAlphaExtraction(rect, mCurrentSurface); + } else { + PLUGIN_LOG_DEBUG((" (onto opaque surface)")); + + // If we're on a platform that needs helper surfaces for + // plugins, and we're forcing a throwaway paint of a + // wmode=transparent plugin, then make sure to use the helper + // surface here. + RefPtr<gfxASurface> target = (temporarilyMakeVisible && mHelperSurface) + ? mHelperSurface + : mCurrentSurface; + + PaintRectToSurface(rect, target, DeviceColor()); + } + mHasPainted = true; + + if (temporarilyMakeVisible) { + mWindow.clipRect.right = mWindow.clipRect.bottom = 0; + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Undoing temporary clipping w=<x=%d,y=%d, " + "w=%d,h=%d>, clip=<l=%d,t=%d,r=%d,b=%d>", + this, mWindow.x, mWindow.y, mWindow.width, mWindow.height, + mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, + mWindow.clipRect.bottom)); + + if (mPluginIface->setwindow) { + mPluginIface->setwindow(&mData, &mWindow); + } + + // Skip forwarding the results of the phony paint to the + // browser. We may have painted a transparent plugin using + // the opaque-plugin path, which can result in wrong pixels. + // We also don't want to pay the expense of forwarding the + // surface for plugins that might really be invisible. + mAccumulatedInvalidRect.SetRect(0, 0, mWindow.width, mWindow.height); + return true; + } + + NPRect r = {(uint16_t)rect.y, (uint16_t)rect.x, (uint16_t)rect.YMost(), + (uint16_t)rect.XMost()}; + SurfaceDescriptor currSurf; +#ifdef MOZ_X11 + if (mCurrentSurface->GetType() == gfxSurfaceType::Xlib) { + gfxXlibSurface* xsurf = static_cast<gfxXlibSurface*>(mCurrentSurface.get()); + currSurf = SurfaceDescriptorX11(xsurf); + // Need to sync all pending x-paint requests + // before giving drawable to another process + XSync(mWsInfo.display, X11False); + } else +#endif +#ifdef XP_WIN + if (SharedDIBSurface::IsSharedDIBSurface(mCurrentSurface)) { + SharedDIBSurface* s = static_cast<SharedDIBSurface*>(mCurrentSurface.get()); + if (!mCurrentSurfaceActor) { + base::SharedMemoryHandle handle = nullptr; + s->ShareToProcess(OtherPid(), &handle); + + mCurrentSurfaceActor = SendPPluginSurfaceConstructor( + handle, mCurrentSurface->GetSize(), haveTransparentPixels); + } + currSurf = mCurrentSurfaceActor; + s->Flush(); + } else +#endif + if (gfxSharedImageSurface::IsSharedImage(mCurrentSurface)) { + currSurf = std::move( + static_cast<gfxSharedImageSurface*>(mCurrentSurface.get())->GetShmem()); + } else { + MOZ_CRASH("Surface type is not remotable"); + return false; + } + + // Unused, except to possibly return a shmem to us + SurfaceDescriptor returnSurf; + + if (!SendShow(r, currSurf, &returnSurf)) { + return false; + } + + SwapSurfaces(); + mSurfaceDifferenceRect = rect; + return true; +} + +bool PluginInstanceChild::ReadbackDifferenceRect(const nsIntRect& rect) { + if (!mBackSurface) return false; + + // We can read safely from XSurface,SharedDIBSurface and Unsafe + // SharedMemory, because PluginHost is not able to modify that surface +#if defined(MOZ_X11) + if (mBackSurface->GetType() != gfxSurfaceType::Xlib && + !gfxSharedImageSurface::IsSharedImage(mBackSurface)) + return false; +#elif defined(XP_WIN) + if (!SharedDIBSurface::IsSharedDIBSurface(mBackSurface)) return false; +#endif + +#if defined(MOZ_X11) || defined(XP_WIN) + if (mCurrentSurface->GetContentType() != mBackSurface->GetContentType()) + return false; + + if (mSurfaceDifferenceRect.IsEmpty()) return true; + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Reading back part of <x=%d,y=%d, w=%d,h=%d>", this, + mSurfaceDifferenceRect.x, mSurfaceDifferenceRect.y, + mSurfaceDifferenceRect.width, mSurfaceDifferenceRect.height)); + + // Read back previous content + RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(mCurrentSurface); + RefPtr<SourceSurface> source = + gfxPlatform::GetSourceSurfaceForSurface(dt, mBackSurface); + // Subtract from mSurfaceDifferenceRect area which is overlapping with rect + nsIntRegion result; + result.Sub(mSurfaceDifferenceRect, nsIntRegion(rect)); + for (auto iter = result.RectIter(); !iter.Done(); iter.Next()) { + const nsIntRect& r = iter.Get(); + dt->CopySurface(source, r, r.TopLeft()); + } + + return true; +#else + return false; +#endif +} + +void PluginInstanceChild::InvalidateRectDelayed(void) { + if (!mCurrentInvalidateTask) { + return; + } + + mCurrentInvalidateTask = nullptr; + + // When this method is run asynchronously, we can end up switching to + // direct drawing before while we wait to run. In that case, bail. + if (IsUsingDirectDrawing()) { + return; + } + + 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( + "plugins::PluginInstanceChild::InvalidateRectDelayed", this, + &PluginInstanceChild::InvalidateRectDelayed); + RefPtr<Runnable> addrefedTask = mCurrentInvalidateTask; + MessageLoop::current()->PostTask(addrefedTask.forget()); +} + +void PluginInstanceChild::InvalidateRect(NPRect* aInvalidRect) { + NS_ASSERTION(aInvalidRect, "Null pointer!"); + +#ifdef OS_WIN + // Invalidate and draw locally for windowed plugins. + if (mWindow.type == NPWindowTypeWindow) { + NS_ASSERTION(IsWindow(mPluginWindowHWND), "Bad window?!"); + RECT rect = {aInvalidRect->left, aInvalidRect->top, aInvalidRect->right, + aInvalidRect->bottom}; + ::InvalidateRect(mPluginWindowHWND, &rect, FALSE); + return; + } +#endif + + if (IsUsingDirectDrawing()) { + NS_ASSERTION(false, + "Should not call InvalidateRect() in direct surface mode!"); + return; + } + + if (mLayersRendering) { + nsIntRect r(aInvalidRect->left, aInvalidRect->top, + aInvalidRect->right - aInvalidRect->left, + aInvalidRect->bottom - aInvalidRect->top); + + mAccumulatedInvalidRect.UnionRect(r, mAccumulatedInvalidRect); + // If we are able to paint and invalidate sent, then reset + // accumulated rectangle + AsyncShowPluginFrame(); + return; + } + + // If we were going to use layers rendering but it's not set up + // yet, and the plugin happens to call this first, we'll forward + // the invalidation to the browser. It's unclear whether + // non-layers plugins need this rect forwarded when their window + // width or height is 0, which it would be for layers plugins + // before their first SetWindow(). + SendNPN_InvalidateRect(*aInvalidRect); +} + +mozilla::ipc::IPCResult 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: + MOZ_CRASH("Unexpected background surface descriptor"); + } + + if (!mBackground) { + return IPC_FAIL_NO_REASON(this); + } + + IntSize bgSize = mBackground->GetSize(); + mAccumulatedInvalidRect.UnionRect( + mAccumulatedInvalidRect, nsIntRect(0, 0, bgSize.width, bgSize.height)); + AsyncShowPluginFrame(); + return IPC_OK(); + } + + // 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 IPC_OK(); +} + +PPluginBackgroundDestroyerChild* +PluginInstanceChild::AllocPPluginBackgroundDestroyerChild() { + return new PluginBackgroundDestroyerChild(); +} + +mozilla::ipc::IPCResult +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(); + } + + if (!PPluginBackgroundDestroyerChild::Send__delete__(aActor)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool PluginInstanceChild::DeallocPPluginBackgroundDestroyerChild( + PPluginBackgroundDestroyerChild* aActor) { + delete aActor; + return true; +} + +uint32_t PluginInstanceChild::ScheduleTimer(uint32_t interval, bool repeat, + TimerFunc func) { + auto* 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::SwapSurfaces() { + RefPtr<gfxASurface> tmpsurf = mCurrentSurface; +#ifdef XP_WIN + PPluginSurfaceChild* tmpactor = mCurrentSurfaceActor; +#endif + + mCurrentSurface = mBackSurface; +#ifdef XP_WIN + mCurrentSurfaceActor = mBackSurfaceActor; +#endif + + mBackSurface = tmpsurf; +#ifdef XP_WIN + mBackSurfaceActor = tmpactor; +#endif + +#ifdef MOZ_WIDGET_COCOA + mDoubleBufferCARenderer.SwapSurfaces(); + + // Outdated back surface... not usable anymore due to changed plugin size. + // Dropping obsolete surface + if (mDoubleBufferCARenderer.HasFrontSurface() && + mDoubleBufferCARenderer.HasBackSurface() && + (mDoubleBufferCARenderer.GetFrontSurfaceWidth() != + mDoubleBufferCARenderer.GetBackSurfaceWidth() || + mDoubleBufferCARenderer.GetFrontSurfaceHeight() != + mDoubleBufferCARenderer.GetBackSurfaceHeight() || + mDoubleBufferCARenderer.GetFrontSurfaceContentsScaleFactor() != + mDoubleBufferCARenderer.GetBackSurfaceContentsScaleFactor())) { + mDoubleBufferCARenderer.ClearFrontSurface(); + } +#else + if (mCurrentSurface && mBackSurface && + (mCurrentSurface->GetSize() != mBackSurface->GetSize() || + mCurrentSurface->GetContentType() != mBackSurface->GetContentType())) { + ClearCurrentSurface(); + } +#endif +} + +void PluginInstanceChild::ClearCurrentSurface() { + mCurrentSurface = nullptr; +#ifdef MOZ_WIDGET_COCOA + if (mDoubleBufferCARenderer.HasFrontSurface()) { + mDoubleBufferCARenderer.ClearFrontSurface(); + } +#endif +#ifdef XP_WIN + if (mCurrentSurfaceActor) { + PPluginSurfaceChild::Send__delete__(mCurrentSurfaceActor); + mCurrentSurfaceActor = nullptr; + } +#endif + mHelperSurface = nullptr; +} + +void PluginInstanceChild::ClearAllSurfaces() { + if (mBackSurface) { + // Get last surface back, and drop it + SurfaceDescriptor temp = null_t(); + NPRect r = {0, 0, 1, 1}; + SendShow(r, temp, &temp); + } + + if (gfxSharedImageSurface::IsSharedImage(mCurrentSurface)) + DeallocShmem( + static_cast<gfxSharedImageSurface*>(mCurrentSurface.get())->GetShmem()); + if (gfxSharedImageSurface::IsSharedImage(mBackSurface)) + DeallocShmem( + static_cast<gfxSharedImageSurface*>(mBackSurface.get())->GetShmem()); + mCurrentSurface = nullptr; + mBackSurface = nullptr; + +#ifdef XP_WIN + if (mCurrentSurfaceActor) { + PPluginSurfaceChild::Send__delete__(mCurrentSurfaceActor); + mCurrentSurfaceActor = nullptr; + } + if (mBackSurfaceActor) { + PPluginSurfaceChild::Send__delete__(mBackSurfaceActor); + mBackSurfaceActor = nullptr; + } +#endif + +#ifdef MOZ_WIDGET_COCOA + if (mDoubleBufferCARenderer.HasBackSurface()) { + // Get last surface back, and drop it + SurfaceDescriptor temp = null_t(); + NPRect r = {0, 0, 1, 1}; + SendShow(r, temp, &temp); + } + + if (mCGLayer) { + mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(mCGLayer); + mCGLayer = nullptr; + } + + mDoubleBufferCARenderer.ClearFrontSurface(); + mDoubleBufferCARenderer.ClearBackSurface(); +#endif +} + +static void InvalidateObjects(nsTHashtable<DeletingObjectEntry>& aEntries) { + for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) { + DeletingObjectEntry* e = iter.Get(); + NPObject* o = e->GetKey(); + if (!e->mDeleted && o->_class && o->_class->invalidate) { + o->_class->invalidate(o); + } + } +} + +static void DeleteObjects(nsTHashtable<DeletingObjectEntry>& aEntries) { + for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) { + DeletingObjectEntry* e = iter.Get(); + NPObject* o = e->GetKey(); + if (!e->mDeleted) { + e->mDeleted = true; + +#ifdef NS_BUILD_REFCNT_LOGGING + { + int32_t refcnt = o->referenceCount; + while (refcnt) { + --refcnt; + NS_LOG_RELEASE(o, refcnt, "NPObject"); + } + } +#endif + + PluginModuleChild::DeallocNPObject(o); + } + } +} + +void PluginInstanceChild::Destroy() { + if (mDestroyed) { + return; + } + if (mStackDepth != 0) { + MOZ_CRASH("Destroying plugin instance on the stack."); + } + mDestroyed = true; + +#if defined(OS_WIN) + SetProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty, (HANDLE)1); +#endif + + nsTArray<PBrowserStreamChild*> streams; + ManagedPBrowserStreamChild(streams); + + // First make sure none of these streams become deleted + streams.RemoveElementsBy([](const auto& stream) { + return !static_cast<BrowserStreamChild*>(stream)->InstanceDying(); + }); + for (uint32_t i = 0; i < streams.Length(); ++i) + static_cast<BrowserStreamChild*>(streams[i])->FinishDelivery(); + + mTimers.Clear(); + + // NPP_Destroy() should be a synchronization point for plugin threads + // calling NPN_AsyncCall: after this function returns, they are no longer + // allowed to make async calls on this instance. + static_cast<PluginModuleChild*>(Manager())->NPP_Destroy(this); + mData.ndata = 0; + + if (mCurrentInvalidateTask) { + mCurrentInvalidateTask->Cancel(); + mCurrentInvalidateTask = nullptr; + } + if (mCurrentAsyncSetWindowTask) { + mCurrentAsyncSetWindowTask->Cancel(); + mCurrentAsyncSetWindowTask = nullptr; + } + { + MutexAutoLock autoLock(mAsyncInvalidateMutex); + if (mAsyncInvalidateTask) { + mAsyncInvalidateTask->Cancel(); + mAsyncInvalidateTask = nullptr; + } + } + + ClearAllSurfaces(); + mDirectBitmaps.Clear(); + + mDeletingHash = MakeUnique<nsTHashtable<DeletingObjectEntry>>(); + PluginScriptableObjectChild::NotifyOfInstanceShutdown(this); + + InvalidateObjects(*mDeletingHash); + DeleteObjects(*mDeletingHash); + + // Null out our cached actors as they should have been killed in the + // PluginInstanceDestroyed call above. + mCachedWindowActor = nullptr; + mCachedElementActor = nullptr; + +#if defined(OS_WIN) + DestroyWinlessPopupSurrogate(); + UnhookWinlessFlashThrottle(); + DestroyPluginWindow(); + + for (uint32_t i = 0; i < mPendingFlashThrottleMsgs.Length(); ++i) { + mPendingFlashThrottleMsgs[i]->Cancel(); + } + mPendingFlashThrottleMsgs.Clear(); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_Destroy( + NPError* aResult) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + *aResult = NPERR_NO_ERROR; + + Destroy(); + + return IPC_OK(); +} + +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 0000000000..479c060f91 --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceChild.h @@ -0,0 +1,613 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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 "mozilla/UniquePtr.h" +#include "nsTArray.h" +#include "ChildTimer.h" +#include "nsRect.h" +#include "nsTHashtable.h" +#include "mozilla/PaintTracker.h" +#include "mozilla/gfx/Types.h" + +#include <map> + +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; + friend class PPluginInstanceChild; + +#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: + mozilla::ipc::IPCResult AnswerCreateChildPluginWindow( + NativeWindowHandle* aChildPluginWindow); + + mozilla::ipc::IPCResult RecvCreateChildPopupSurrogate( + const NativeWindowHandle& aNetscapeWindow); + + mozilla::ipc::IPCResult AnswerNPP_SetWindow(const NPRemoteWindow& window); + + mozilla::ipc::IPCResult AnswerNPP_GetValue_NPPVpluginWantsAllNetworkStreams( + bool* wantsAllStreams, NPError* rv); + mozilla::ipc::IPCResult AnswerNPP_GetValue_NPPVpluginScriptableNPObject( + PPluginScriptableObjectChild** value, NPError* result); + mozilla::ipc::IPCResult + AnswerNPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId(nsCString* aPlugId, + NPError* aResult); + mozilla::ipc::IPCResult AnswerNPP_SetValue_NPNVprivateModeBool( + const bool& value, NPError* result); + mozilla::ipc::IPCResult AnswerNPP_SetValue_NPNVmuteAudioBool( + const bool& value, NPError* result); + mozilla::ipc::IPCResult AnswerNPP_SetValue_NPNVCSSZoomFactor( + const double& value, NPError* result); + + mozilla::ipc::IPCResult AnswerNPP_HandleEvent(const NPRemoteEvent& event, + int16_t* handled); + mozilla::ipc::IPCResult AnswerNPP_HandleEvent_Shmem( + const NPRemoteEvent& event, Shmem&& mem, int16_t* handled, Shmem* rtnmem); + mozilla::ipc::IPCResult AnswerNPP_HandleEvent_IOSurface( + const NPRemoteEvent& event, const uint32_t& surface, int16_t* handled); + + // Async rendering + mozilla::ipc::IPCResult RecvAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow); + + virtual void DoAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow, bool aIsAsync); + + PPluginSurfaceChild* AllocPPluginSurfaceChild( + const WindowsSharedMemoryHandle&, const gfx::IntSize&, const bool&) { + return new PPluginSurfaceChild(); + } + + bool DeallocPPluginSurfaceChild(PPluginSurfaceChild* s) { + delete s; + return true; + } + + mozilla::ipc::IPCResult AnswerPaint(const NPRemoteEvent& event, + int16_t* handled) { + PaintTracker pt; + if (!AnswerNPP_HandleEvent(event, handled)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); + } + + mozilla::ipc::IPCResult RecvWindowPosChanged(const NPRemoteEvent& event); + + mozilla::ipc::IPCResult RecvContentsScaleFactorChanged( + const double& aContentsScaleFactor); + + mozilla::ipc::IPCResult AnswerNPP_Destroy(NPError* result); + + PPluginScriptableObjectChild* AllocPPluginScriptableObjectChild(); + + bool DeallocPPluginScriptableObjectChild( + PPluginScriptableObjectChild* aObject); + + virtual mozilla::ipc::IPCResult RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectChild* aActor) override; + + virtual mozilla::ipc::IPCResult RecvPBrowserStreamConstructor( + PBrowserStreamChild* aActor, const nsCString& aURL, + const uint32_t& aLength, const uint32_t& aLastmodified, + PStreamNotifyChild* aNotifyData, const nsCString& aHeaders) override; + + mozilla::ipc::IPCResult AnswerNPP_NewStream(PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, NPError* rv, + uint16_t* stype); + + PBrowserStreamChild* AllocPBrowserStreamChild(const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + PStreamNotifyChild* notifyData, + const nsCString& headers); + + bool DeallocPBrowserStreamChild(PBrowserStreamChild* stream); + + PStreamNotifyChild* AllocPStreamNotifyChild( + const nsCString& url, const nsCString& target, const bool& post, + const nsCString& buffer, const bool& file, NPError* result); + + bool DeallocPStreamNotifyChild(PStreamNotifyChild* notifyData); + + mozilla::ipc::IPCResult AnswerSetPluginFocus(); + + mozilla::ipc::IPCResult AnswerUpdateWindow(); + + mozilla::ipc::IPCResult RecvNPP_DidComposite(); + + public: + PluginInstanceChild(const NPPluginFuncs* aPluginIface, + const nsCString& aMimeType, + const nsTArray<nsCString>& aNames, + const nsTArray<nsCString>& aValues); + + virtual ~PluginInstanceChild(); + + NPError DoNPP_New(); + + // Common sync+async implementation of NPP_NewStream + NPError DoNPP_NewStream(BrowserStreamChild* actor, const nsCString& mimeType, + const bool& seekable, uint16_t* stype); + + bool Initialize(); + + NPP GetNPP() { return &mData; } + + NPError NPN_GetValue(NPNVariable aVariable, void* aValue); + + NPError NPN_SetValue(NPPVariable aVariable, void* aValue); + + PluginScriptableObjectChild* GetActorForNPObject(NPObject* aObject); + + NPError NPN_NewStream(NPMIMEType aMIMEType, const char* aWindow, + NPStream** aStream); + + void InvalidateRect(NPRect* aInvalidRect); + +#ifdef MOZ_WIDGET_COCOA + void Invalidate(); +#endif // definied(MOZ_WIDGET_COCOA) + + uint32_t ScheduleTimer(uint32_t interval, bool repeat, TimerFunc func); + void UnscheduleTimer(uint32_t id); + + 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(); + + mozilla::ipc::IPCResult RecvHandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, const bool& aIsConsumed); + +#if defined(XP_WIN) + NPError DefaultAudioDeviceChanged(NPAudioDeviceChangeDetails& details); + NPError AudioDeviceStateChanged(NPAudioDeviceStateChanged& aDeviceState); +#endif + + private: + friend class PluginModuleChild; + + NPError InternalGetNPObjectForValue(NPNVariable aValue, NPObject** aObject); + + bool IsUsingDirectDrawing(); + + mozilla::ipc::IPCResult RecvUpdateBackground( + const SurfaceDescriptor& aBackground, const nsIntRect& aRect); + + PPluginBackgroundDestroyerChild* AllocPPluginBackgroundDestroyerChild(); + + mozilla::ipc::IPCResult RecvPPluginBackgroundDestroyerConstructor( + PPluginBackgroundDestroyerChild* aActor) override; + + bool DeallocPPluginBackgroundDestroyerChild( + PPluginBackgroundDestroyerChild* aActor); + +#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 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 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 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); + static BOOL WINAPI ImmAssociateContextExProc(HWND hWnd, HIMC aIMC, + DWORD dwFlags); + + class FlashThrottleMsg : public CancelableRunnable { + public: + FlashThrottleMsg(PluginInstanceChild* aInstance, HWND aWnd, UINT aMsg, + WPARAM aWParam, LPARAM aLParam, bool isWindowed) + : CancelableRunnable("FlashThrottleMsg"), + mInstance(aInstance), + mWnd(aWnd), + mMsg(aMsg), + mWParam(aWParam), + mLParam(aLParam), + mWindowed(isWindowed) {} + + NS_IMETHOD Run() override; + nsresult Cancel() override; + + WNDPROC GetProc(); + HWND GetWnd() { return mWnd; } + UINT GetMsg() { return mMsg; } + WPARAM GetWParam() { return mWParam; } + LPARAM GetLParam() { return mLParam; } + + private: + PluginInstanceChild* mInstance; + 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; + nsTArray<nsCString> mNames; + nsTArray<nsCString> mValues; + NPP_t mData; + NPWindow mWindow; +#if defined(XP_DARWIN) || defined(XP_WIN) + double mContentsScaleFactor; +#endif + double mCSSZoomFactor; + uint32_t mPostingKeyEvents; + uint32_t mPostingKeyEventsOutdated; + int16_t mDrawingModel; + + NPAsyncSurface* mCurrentDirectSurface; + + // The surface hashtables below serve a few purposes. They let us verify + // and retain extra information about plugin surfaces, and they let us + // free shared memory that the plugin might forget to release. + struct DirectBitmap { + DirectBitmap(PluginInstanceChild* aOwner, const Shmem& shmem, + const gfx::IntSize& size, uint32_t stride, + SurfaceFormat format); + + private: + ~DirectBitmap(); + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DirectBitmap); + + PluginInstanceChild* mOwner; + Shmem mShmem; + gfx::SurfaceFormat mFormat; + gfx::IntSize mSize; + uint32_t mStride; + }; + nsRefPtrHashtable<nsPtrHashKey<NPAsyncSurface>, DirectBitmap> mDirectBitmaps; + +#if defined(XP_WIN) + nsDataHashtable<nsPtrHashKey<NPAsyncSurface>, WindowsHandle> mDxgiSurfaces; +#endif + + mozilla::Mutex mAsyncInvalidateMutex; + CancelableRunnable* mAsyncInvalidateTask; + + // Cached scriptable actors to avoid IPC churn + PluginScriptableObjectChild* mCachedWindowActor; + PluginScriptableObjectChild* mCachedElementActor; + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + NPSetWindowCallbackStruct mWsInfo; +#elif defined(OS_WIN) + HWND mPluginWindowHWND; + WNDPROC mPluginWndProc; + HWND mPluginParentHWND; + int mNestedEventLevelDepth; + HWND mCachedWinlessPluginHWND; + HWND mWinlessPopupSurrogateHWND; + nsIntPoint mPluginSize; + WNDPROC mWinlessThrottleOldWndProc; + HWND mWinlessHiddenMsgHWND; +#endif + +#if defined(OS_WIN) + nsTArray<FlashThrottleMsg*> mPendingFlashThrottleMsgs; +#endif + nsTArray<UniquePtr<ChildTimer> > mTimers; + + /** + * During destruction we enumerate all remaining scriptable objects and + * invalidate/delete them. Enumeration can re-enter, so maintain a + * hash separate from PluginModuleChild.mObjectMap. + */ + UniquePtr<nsTHashtable<DeletingObjectEntry> > mDeletingHash; + +#if defined(MOZ_WIDGET_COCOA) + private: +# if defined(__i386__) + NPEventModel mEventModel; +# endif + CGColorSpaceRef mShColorSpace; + CGContextRef mShContext; + RefPtr<nsCARenderer> mCARenderer; + void* mCGLayer; + + // Core Animation drawing model requires a refresh timer. + uint32_t mCARefreshTimer; + + public: + const NPCocoaEvent* getCurrentEvent() { return mCurrentEvent; } + + bool CGDraw(CGContextRef ref, nsIntRect aUpdateRect); + +# if defined(__i386__) + NPEventModel EventModel() { return mEventModel; } +# endif + + private: + const NPCocoaEvent* mCurrentEvent; +#endif + + bool CanPaintOnBackground(); + + bool IsVisible() { +#ifdef XP_MACOSX + return mWindow.clipRect.top != mWindow.clipRect.bottom && + mWindow.clipRect.left != mWindow.clipRect.right; +#else + return mWindow.clipRect.top != 0 || mWindow.clipRect.left != 0 || + mWindow.clipRect.bottom != 0 || mWindow.clipRect.right != 0; +#endif + } + + // ShowPluginFrame - in general does four things: + // 1) Create mCurrentSurface optimized for rendering to parent process + // 2) Updated mCurrentSurface to be a complete copy of mBackSurface + // 3) Draw the invalidated plugin area into mCurrentSurface + // 4) Send it to parent process. + bool ShowPluginFrame(void); + + // If we can read back safely from mBackSurface, copy + // mSurfaceDifferenceRect from mBackSurface to mFrontSurface. + // @return Whether the back surface could be read. + bool ReadbackDifferenceRect(const nsIntRect& rect); + + // Post ShowPluginFrame task + void AsyncShowPluginFrame(void); + + // In the PaintRect functions, aSurface is the size of the full plugin + // window. Each PaintRect function renders into the subrectangle aRect of + // aSurface (possibly more if we're working around a Flash bug). + + // Paint plugin content rectangle to surface with bg color filling + void PaintRectToSurface(const nsIntRect& aRect, gfxASurface* aSurface, + const gfx::DeviceColor& aColor); + + // Render plugin content to surface using + // white/black image alpha extraction algorithm + void PaintRectWithAlphaExtraction(const nsIntRect& aRect, + gfxASurface* aSurface); + + // Call plugin NPAPI function to render plugin content to surface + // @param - aSurface - should be compatible with current platform plugin + // rendering + // @return - FALSE if plugin not painted to surface + void PaintRectToPlatformSurface(const nsIntRect& aRect, + gfxASurface* aSurface); + + // Update NPWindow platform attributes and call plugin "setwindow" + // @param - aForceSetWindow - call setwindow even if platform attributes are + // the same + void UpdateWindowAttributes(bool aForceSetWindow = false); + + // Create optimized mCurrentSurface for parent process rendering + // @return FALSE if optimized surface not created + bool CreateOptSurface(void); + + // Create mHelperSurface if mCurrentSurface non compatible with plugins + // @return TRUE if helper surface created successfully, or not needed + bool MaybeCreatePlatformHelperSurface(void); + + // Make sure that we have surface for rendering + bool EnsureCurrentBuffer(void); + + // Helper function for delayed InvalidateRect call + // non null mCurrentInvalidateTask will call this function + void InvalidateRectDelayed(void); + + // Clear mCurrentSurface/mCurrentSurfaceActor/mHelperSurface + void ClearCurrentSurface(); + + // Swap mCurrentSurface/mBackSurface and their associated actors + void SwapSurfaces(); + + // Clear all surfaces in response to NPP_Destroy + void ClearAllSurfaces(); + + void Destroy(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + // Set as true when SetupLayer called + // and go with different path in InvalidateRect function + bool mLayersRendering; + + // Current surface available for rendering + RefPtr<gfxASurface> mCurrentSurface; + + // Back surface, just keeping reference to + // surface which is on ParentProcess side + RefPtr<gfxASurface> mBackSurface; + +#ifdef XP_MACOSX + // Current IOSurface available for rendering + // We can't use thebes gfxASurface like other platforms. + PluginUtilsOSX::nsDoubleBufferCARenderer mDoubleBufferCARenderer; +#endif + + // (Not to be confused with mBackSurface). This is a recent copy + // of the opaque pixels under our object frame, if + // |mIsTransparent|. We ask the plugin render directly onto a + // copy of the background pixels if available, and fall back on + // alpha recovery otherwise. + RefPtr<gfxASurface> mBackground; + +#ifdef XP_WIN + // These actors mirror mCurrentSurface/mBackSurface + PPluginSurfaceChild* mCurrentSurfaceActor; + PPluginSurfaceChild* mBackSurfaceActor; +#endif + + // Accumulated invalidate rect, while back buffer is not accessible, + // in plugin coordinates. + nsIntRect mAccumulatedInvalidRect; + + // Plugin only call SetTransparent + // and does not remember their transparent state + // and p->getvalue return always false + bool mIsTransparent; + + // Surface type optimized of parent process + gfxSurfaceType mSurfaceType; + + // Keep InvalidateRect task pointer to be able Cancel it on Destroy + RefPtr<CancelableRunnable> mCurrentInvalidateTask; + + // Keep AsyncSetWindow task pointer to be able to Cancel it on Destroy + RefPtr<CancelableRunnable> mCurrentAsyncSetWindowTask; + + // True while plugin-child in plugin call + // Use to prevent plugin paint re-enter + bool mPendingPluginCall; + + // On some platforms, plugins may not support rendering to a surface with + // alpha, or not support rendering to an image surface. + // In those cases we need to draw to a temporary platform surface; we cache + // that surface here. + RefPtr<gfxASurface> mHelperSurface; + + // true when plugin does not support painting to ARGB32 + // surface this is false if plugin supports + // NPPVpluginTransparentAlphaBool (which is not part of + // NPAPI yet) + bool mDoAlphaExtraction; + + // true when the plugin has painted at least once. We use this to ensure + // that we ask a plugin to paint at least once even if it's invisible; + // some plugin (instances) rely on this in order to work properly. + bool mHasPainted; + + // Cached rectangle rendered to previous surface(mBackSurface) + // Used for reading back to current surface and syncing data, + // in plugin coordinates. + nsIntRect mSurfaceDifferenceRect; + + // Has this instance been destroyed, either by ActorDestroy or NPP_Destroy? + bool mDestroyed; + +#ifdef XP_WIN + // WM_*CHAR messages are never consumed by chrome process's widget. + // So, if preceding keydown or keyup event is consumed by reserved + // shortcut key in the chrome process, we shouldn't send the following + // WM_*CHAR messages to the plugin. + bool mLastKeyEventConsumed; + + // Store the last IME state by ImmAssociateContextEx. This will reset by + // WM_KILLFOCUS; + bool mLastEnableIMEState; +#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 0000000000..6e29171d91 --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -0,0 +1,2326 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" +#include <stdint.h> // for intptr_t + +#include "mozilla/BasicEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ToString.h" +#include "mozilla/dom/Element.h" +#include "PluginInstanceParent.h" +#include "BrowserStreamParent.h" +#include "PluginBackgroundDestroyer.h" +#include "PluginModuleParent.h" +#include "StreamNotifyParent.h" +#include "npfunctions.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" +#ifdef MOZ_X11 +# include "gfxXlibSurface.h" +#endif +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "Layers.h" +#include "ImageContainer.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "mozilla/layers/TextureWrapperImage.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/layers/ImageBridgeChild.h" +#if defined(XP_WIN) +# include "mozilla/layers/D3D11ShareHandleImage.h" +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/TextureD3D11.h" +#endif + +#ifdef XP_MACOSX +# include "MacIOSurfaceImage.h" +#endif + +#if defined(OS_WIN) +# include <windowsx.h> +# include "gfxWindowsPlatform.h" +# include "mozilla/plugins/PluginSurfaceParent.h" +# include "nsClassHashtable.h" +# include "nsHashKeys.h" +# include "nsIWidget.h" +# include "nsPluginNativeWindow.h" +# include "PluginQuirks.h" +# include "mozilla/layers/CompositorBridgeChild.h" +# include "GPUVideoImage.h" +# include "mozilla/layers/SynchronousTask.h" +extern const wchar_t* kFlashFullscreenClass; +#elif defined(MOZ_WIDGET_GTK) +# include "mozilla/dom/ContentChild.h" +# include <gdk/gdk.h> +#elif defined(XP_MACOSX) +# include <ApplicationServices/ApplicationServices.h> +#endif // defined(XP_MACOSX) + +using namespace mozilla::plugins; +using namespace mozilla::layers; +using namespace mozilla::gl; + +void StreamNotifyParent::ActorDestroy(ActorDestroyReason aWhy) { + // Implement me! Bug 1005162 +} + +mozilla::ipc::IPCResult StreamNotifyParent::RecvRedirectNotifyResponse( + const bool& allow) { + PluginInstanceParent* instance = + static_cast<PluginInstanceParent*>(Manager()); + instance->mNPNIface->urlredirectresponse(instance->mNPP, this, + static_cast<NPBool>(allow)); + return IPC_OK(); +} + +#if defined(XP_WIN) +namespace mozilla { +namespace plugins { +/** + * e10s specific, used in cross referencing hwnds with plugin instances so we + * can access methods here from PluginWidgetChild. + */ +static nsClassHashtable<nsVoidPtrHashKey, PluginInstanceParent>* + sPluginInstanceList; + +// static +PluginInstanceParent* PluginInstanceParent::LookupPluginInstanceByID( + uintptr_t aId) { + MOZ_ASSERT(NS_IsMainThread()); + if (sPluginInstanceList) { + return sPluginInstanceList->Get((void*)aId); + } + return nullptr; +} +} // namespace plugins +} // namespace mozilla +#endif + +PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent, NPP npp, + const nsCString& aMimeType, + const NPNetscapeFuncs* npniface) + : mParent(parent), + mNPP(npp), + mNPNIface(npniface), + mWindowType(NPWindowTypeWindow), + mDrawingModel(kDefaultDrawingModel), + mLastRecordedDrawingModel(-1), + mFrameID(0) +#if defined(OS_WIN) + , + mPluginHWND(nullptr), + mChildPluginHWND(nullptr), + mChildPluginsParentHWND(nullptr), + mPluginWndProc(nullptr) +#endif // defined(XP_WIN) +#if defined(XP_MACOSX) + , + mShWidth(0), + mShHeight(0), + mShColorSpace(nullptr) +#endif +{ +#if defined(OS_WIN) + if (!sPluginInstanceList) { + sPluginInstanceList = + new nsClassHashtable<nsVoidPtrHashKey, PluginInstanceParent>(); + } +#endif +} + +PluginInstanceParent::~PluginInstanceParent() { + if (mNPP) mNPP->pdata = nullptr; + +#if defined(OS_WIN) + NS_ASSERTION(!(mPluginHWND || mPluginWndProc), + "Subclass was not reset correctly before the dtor was reached!"); +#endif +#if defined(MOZ_WIDGET_COCOA) + if (mShWidth != 0 && mShHeight != 0) { + DeallocShmem(mShSurface); + } + if (mShColorSpace) ::CGColorSpaceRelease(mShColorSpace); +#endif +} + +bool PluginInstanceParent::InitMetadata(const nsACString& aMimeType, + const nsACString& aSrcAttribute) { + if (aSrcAttribute.IsEmpty()) { + return false; + } + // Ensure that the src attribute is absolute + RefPtr<nsPluginInstanceOwner> owner = GetOwner(); + if (!owner) { + return false; + } + return NS_SUCCEEDED( + NS_MakeAbsoluteURI(mSrcAttribute, aSrcAttribute, owner->GetBaseURI())); +} + +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; + 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) { + MOZ_CRASH("Not reachable"); + return nullptr; +} + +bool PluginInstanceParent::DeallocPBrowserStreamParent( + PBrowserStreamParent* stream) { + delete stream; + return true; +} + +mozilla::ipc::IPCResult +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) || defined(MOZ_WAYLAND) + // TODO: Need impl + int id; +#else +# warning Implement me +#endif + + *result = mNPNIface->getvalue(mNPP, NPNVnetscapeWindow, &id); + *value = id; + return IPC_OK(); +} + +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; +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_NPNVWindowNPObject( + PPluginScriptableObjectParent** aValue, NPError* aResult) { + if (!InternalGetValueForNPObject(NPNVWindowNPObject, aValue, aResult)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_NPNVPluginElementNPObject( + PPluginScriptableObjectParent** aValue, NPError* aResult) { + if (!InternalGetValueForNPObject(NPNVPluginElementNPObject, aValue, + aResult)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_NPNVprivateModeBool(bool* value, + NPError* result) { + NPBool v; + *result = mNPNIface->getvalue(mNPP, NPNVprivateModeBool, &v); + *value = v; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_DrawingModelSupport( + const NPNVariable& model, bool* value) { + *value = false; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_NPNVdocumentOrigin(nsCString* value, + NPError* result) { + void* v = nullptr; + *result = mNPNIface->getvalue(mNPP, NPNVdocumentOrigin, &v); + if (*result == NPERR_NO_ERROR && v) { + value->Adopt(static_cast<char*>(v)); + } + return IPC_OK(); +} + +static inline bool AllowDirectBitmapSurfaceDrawing() { + if (!mozilla::StaticPrefs::dom_ipc_plugins_asyncdrawing_enabled()) { + return false; + } + return gfxPlatform::GetPlatform()->SupportsPluginDirectBitmapDrawing(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_SupportsAsyncBitmapSurface( + bool* value) { + *value = AllowDirectBitmapSurfaceDrawing(); + return IPC_OK(); +} + +/* static */ +bool PluginInstanceParent::SupportsPluginDirectDXGISurfaceDrawing() { + bool value = false; +#if defined(XP_WIN) + // When WebRender does not use ANGLE, DXGISurface could not be used. + bool useAsyncDXGISurface = + StaticPrefs::dom_ipc_plugins_allow_dxgi_surface() && + !(gfx::gfxVars::UseWebRender() && !gfx::gfxVars::UseWebRenderANGLE()); + if (useAsyncDXGISurface) { + auto cbc = CompositorBridgeChild::Get(); + if (cbc) { + cbc->SendSupportsAsyncDXGISurface(&value); + } + } +#endif + return value; +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_PreferredDXGIAdapter( + DxgiAdapterDesc* aOutDesc) { + PodZero(aOutDesc); +#if defined(XP_WIN) + auto cbc = CompositorBridgeChild::Get(); + if (cbc) { + cbc->SendPreferredDXGIAdapter(aOutDesc); + } +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult +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 IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginTransparent( + const bool& transparent, NPError* result) { + *result = mNPNIface->setvalue(mNPP, NPPVpluginTransparentBool, + (void*)(intptr_t)transparent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginUsesDOMForCursor( + const bool& useDOMForCursor, NPError* result) { + *result = mNPNIface->setvalue(mNPP, NPPVpluginUsesDOMForCursorBool, + (void*)(intptr_t)useDOMForCursor); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +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 = SupportsPluginDirectDXGISurfaceDrawing(); + 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 IPC_OK(); + } + + 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 IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginEventModel( + const int& eventModel, NPError* result) { +#ifdef XP_MACOSX + *result = mNPNIface->setvalue(mNPP, NPPVpluginEventModel, + (void*)(intptr_t)eventModel); + return IPC_OK(); +#else + *result = NPERR_GENERIC_ERROR; + return IPC_OK(); +#endif +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginIsPlayingAudio( + const bool& isAudioPlaying, NPError* result) { + *result = mNPNIface->setvalue(mNPP, NPPVpluginIsPlayingAudio, + (void*)(intptr_t)isAudioPlaying); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerNPN_GetURL( + const nsCString& url, const nsCString& target, NPError* result) { + *result = mNPNIface->geturl(mNPP, NullableStringGet(url), + NullableStringGet(target)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +} + +PStreamNotifyParent* PluginInstanceParent::AllocPStreamNotifyParent( + const nsCString& url, const nsCString& target, const bool& post, + const nsCString& buffer, const bool& file, NPError* result) { + return new StreamNotifyParent(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerPStreamNotifyConstructor( + PStreamNotifyParent* actor, const nsCString& url, const nsCString& target, + const bool& post, const nsCString& buffer, const bool& file, + NPError* result) { + bool streamDestroyed = false; + static_cast<StreamNotifyParent*>(actor)->SetDestructionFlag(&streamDestroyed); + + if (!post) { + *result = mNPNIface->geturlnotify(mNPP, NullableStringGet(url), + NullableStringGet(target), actor); + } else { + *result = mNPNIface->posturlnotify( + mNPP, NullableStringGet(url), NullableStringGet(target), + buffer.Length(), NullableStringGet(buffer), file, actor); + } + + if (streamDestroyed) { + // If the stream was destroyed, we must return an error code in the + // constructor. + *result = NPERR_GENERIC_ERROR; + } else { + static_cast<StreamNotifyParent*>(actor)->ClearDestructionFlag(); + if (*result != NPERR_NO_ERROR) { + if (!PStreamNotifyParent::Send__delete__(actor, NPERR_GENERIC_ERROR)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); + } + } + + return IPC_OK(); +} + +bool PluginInstanceParent::DeallocPStreamNotifyParent( + PStreamNotifyParent* notifyData) { + delete notifyData; + return true; +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvNPN_InvalidateRect( + const NPRect& rect) { + mNPNIface->invalidaterect(mNPP, const_cast<NPRect*>(&rect)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvRevokeCurrentDirectSurface() { + ImageContainer* container = GetImageContainer(); + if (!container) { + return IPC_OK(); + } + + container->ClearAllImages(); + + PLUGIN_LOG_DEBUG((" (RecvRevokeCurrentDirectSurface)")); + return IPC_OK(); +} + +#if defined(XP_WIN) +// Uses the ImageBridge to perform IGPUVideoSurfaceManager operations +// in the GPU process. +class AsyncPluginSurfaceManager : public IGPUVideoSurfaceManager { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPluginSurfaceManager, override) + + already_AddRefed<gfx::SourceSurface> Readback( + const SurfaceDescriptorGPUVideo& aSD) override { + SurfaceDescriptorPlugin pluginSD = aSD; + if (!InImageBridgeChildThread()) { + SynchronousTask task("AsyncPluginSurfaceManager readback sync"); + RefPtr<gfx::SourceSurface> result; + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch( + NewRunnableFunction("AsyncPluginSurfaceManager readback", + &DoSyncReadback, &pluginSD, &result, &task)); + task.Wait(); + return result.forget(); + } + + return DoReadback(pluginSD); + } + + void DeallocateSurfaceDescriptor( + const SurfaceDescriptorGPUVideo& aSD) override { + SurfaceDescriptorPlugin pluginSD = aSD; + if (!InImageBridgeChildThread()) { + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch( + NewRunnableFunction("AsyncPluginSurfaceManager dealloc", &DoDealloc, + &pluginSD)); + return; + } + + return DoDealloc(&pluginSD); + } + + // Set of display surfaces for which the related plugin surface has been + // freed. They are freed when the AsyncPluginSurfaceManager is told it is + // safe. + static HashSet<WindowsHandle> sOrphanedDisplaySurfaces; + + private: + ~AsyncPluginSurfaceManager() {} + + struct SurfaceDescriptorUserData { + explicit SurfaceDescriptorUserData(layers::SurfaceDescriptor& aSD) + : mSD(aSD) {} + + ~SurfaceDescriptorUserData() { + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + DestroySurfaceDescriptor(ibc, &mSD); + } + + layers::SurfaceDescriptor mSD; + }; + + static void DeleteSurfaceDescriptorUserData(void* aClosure) { + SurfaceDescriptorUserData* sd = + reinterpret_cast<SurfaceDescriptorUserData*>(aClosure); + delete sd; + } + + static already_AddRefed<gfx::SourceSurface> DoReadback( + const SurfaceDescriptorPlugin& aSD) { + MOZ_ASSERT(InImageBridgeChildThread()); + + RefPtr<DataSourceSurface> source; + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return nullptr; + } + + layers::SurfaceDescriptor dataSD = null_t(); + ibc->SendReadbackAsyncPluginSurface(aSD, &dataSD); + if (!IsSurfaceDescriptorValid(dataSD)) { + NS_WARNING("Bad SurfaceDescriptor received in Readback"); + return nullptr; + } + + source = GetSurfaceForDescriptor(dataSD); + if (!source) { + DestroySurfaceDescriptor(ibc, &dataSD); + NS_WARNING("Failed to map SurfaceDescriptor in Readback"); + return nullptr; + } + + static UserDataKey sSurfaceDescriptor; + source->AddUserData(&sSurfaceDescriptor, + new SurfaceDescriptorUserData(dataSD), + DeleteSurfaceDescriptorUserData); + + return source.forget(); + } + + static void DoSyncReadback(const SurfaceDescriptorPlugin* aSD, + RefPtr<gfx::SourceSurface>* aResult, + SynchronousTask* aTask) { + AutoCompleteTask act(aTask); + *aResult = DoReadback(*aSD); + } + + static void DoDealloc(const SurfaceDescriptorPlugin* aSD) { + MOZ_ASSERT(InImageBridgeChildThread()); + + // If the plugin already Finalized and freed its surface then, since the + // compositor is now also done with the display surface, we can free + // it too. + WindowsHandle handle = aSD->displaySurf().handle(); + auto surfIt = sOrphanedDisplaySurfaces.lookup(handle); + if (!surfIt) { + // We wil continue to use the surfaces with future GPUVideoImages. + return; + } + + sOrphanedDisplaySurfaces.remove(surfIt); + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + ibc->SendRemoveAsyncPluginSurface(*aSD, true); + } +}; + +/* static */ HashSet<WindowsHandle> + AsyncPluginSurfaceManager::sOrphanedDisplaySurfaces; + +void InitDXGISurface(const gfx::SurfaceFormat& aFormat, + const gfx::IntSize& aSize, + SurfaceDescriptorPlugin* aSDPlugin, + SynchronousTask* aTask) { + MOZ_ASSERT(InImageBridgeChildThread()); + + AutoCompleteTask act(aTask); + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + + layers::SurfaceDescriptorPlugin sd; + if (!ibc->SendMakeAsyncPluginSurfaces(aFormat, aSize, &sd)) { + return; + } + *aSDPlugin = sd; +} + +void FinalizeDXGISurface(const SurfaceDescriptorPlugin& aSD) { + MOZ_ASSERT(InImageBridgeChildThread()); + + Unused << AsyncPluginSurfaceManager::sOrphanedDisplaySurfaces.put( + aSD.displaySurf().handle()); + + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + ibc->SendRemoveAsyncPluginSurface(aSD, false); +} + +void CopyDXGISurface(const SurfaceDescriptorPlugin& aSD, + SynchronousTask* aTask) { + MOZ_ASSERT(InImageBridgeChildThread()); + + AutoCompleteTask act(aTask); + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + ibc->SendUpdateAsyncPluginSurface(aSD); +} + +#endif // defined(XP_WIN) + +mozilla::ipc::IPCResult PluginInstanceParent::RecvInitDXGISurface( + const gfx::SurfaceFormat& format, const gfx::IntSize& size, + WindowsHandle* outHandle, NPError* outError) { + *outHandle = 0; + *outError = NPERR_GENERIC_ERROR; + +#if defined(XP_WIN) + MOZ_ASSERT(NS_IsMainThread()); + if (format != SurfaceFormat::B8G8R8A8 && format != SurfaceFormat::B8G8R8X8) { + *outError = NPERR_INVALID_PARAM; + return IPC_OK(); + } + if (size.width <= 0 || size.height <= 0) { + *outError = NPERR_INVALID_PARAM; + return IPC_OK(); + } + + if (!ImageBridgeChild::GetSingleton()) { + return IPC_OK(); + } + + // Ask the ImageBridge thread to generate two SurfaceDescriptorPlugins -- + // one for the GPU process to display and one for the Plugin process to + // render to. + SurfaceDescriptorPlugin sd; + SynchronousTask task("SendMakeAsyncPluginSurfaces sync"); + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch( + NewRunnableFunction("SendingMakeAsyncPluginSurfaces", &InitDXGISurface, + format, size, &sd, &task)); + task.Wait(); + + if (!sd.id()) { + NS_WARNING("SendMakeAsyncPluginSurfaces failed"); + return IPC_OK(); + } + + WindowsHandle pluginSurfHandle = sd.pluginSurf().handle(); + bool ok = mAsyncSurfaceMap.put(pluginSurfHandle, AsyncSurfaceInfo{sd, size}); + if (!ok) { + return IPC_OK(); + } + + *outHandle = pluginSurfHandle; + *outError = NPERR_NO_ERROR; +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvFinalizeDXGISurface( + const WindowsHandle& pluginSurfHandle) { +#if defined(XP_WIN) + MOZ_ASSERT(NS_IsMainThread()); + + if (!ImageBridgeChild::GetSingleton()) { + return IPC_OK(); + } + + auto asiIt = mAsyncSurfaceMap.lookup(pluginSurfHandle); + if (!asiIt) { + NS_WARNING("Plugin surface did not exist to finalize"); + return IPC_OK(); + } + + AsyncSurfaceInfo& asi = asiIt->value(); + + // Release the plugin surface but keep the display surface since it may + // still be displayed. Also let the display surface know that it should + // not receive further requests to copy from the plugin surface. + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch(NewRunnableFunction( + "SendingRemoveAsyncPluginSurface", &FinalizeDXGISurface, asi.mSD)); + + mAsyncSurfaceMap.remove(asiIt); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_FAIL_NO_REASON(this); + } + if (size.width <= 0 || size.height <= 0) { + MOZ_ASSERT_UNREACHABLE("bad image size"); + return IPC_FAIL_NO_REASON(this); + } + if (mDrawingModel != NPDrawingModelAsyncBitmapSurface) { + MOZ_ASSERT_UNREACHABLE("plugin did not set a bitmap drawing model"); + return IPC_FAIL_NO_REASON(this); + } + + // Validate buffer and size. + CheckedInt<uint32_t> nbytes = + CheckedInt<uint32_t>(uint32_t(size.height)) * stride; + if (!nbytes.isValid() || nbytes.value() != buffer.Size<uint8_t>()) { + MOZ_ASSERT_UNREACHABLE("bad shmem size"); + return IPC_FAIL_NO_REASON(this); + } + + ImageContainer* container = GetImageContainer(); + if (!container) { + return IPC_FAIL_NO_REASON(this); + } + + RefPtr<gfx::DataSourceSurface> source = + gfx::Factory::CreateWrappingDataSourceSurface(buffer.get<uint8_t>(), + stride, size, format); + if (!source) { + return IPC_FAIL_NO_REASON(this); + } + + // Allocate a texture for the compositor. + RefPtr<TextureClientRecycleAllocator> allocator = + mParent->EnsureTextureAllocatorForDirectBitmap(); + RefPtr<TextureClient> texture = allocator->CreateOrRecycle( + format, size, BackendSelector::Content, TextureFlags::NO_FLAGS, + TextureAllocationFlags(ALLOC_FOR_OUT_OF_BAND_CONTENT | + ALLOC_UPDATE_FROM_SURFACE)); + if (!texture) { + NS_WARNING("Could not allocate a TextureClient for plugin!"); + return IPC_FAIL_NO_REASON(this); + } + + // Upload the plugin buffer. + { + TextureClientAutoLock autoLock(texture, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return IPC_FAIL_NO_REASON(this); + } + texture->UpdateFromSurface(source); + } + + // Wrap the texture in an image and ship it off. + RefPtr<TextureWrapperImage> image = + new TextureWrapperImage(texture, gfx::IntRect(gfx::IntPoint(0, 0), size)); + SetCurrentImage(image); + + PLUGIN_LOG_DEBUG( + (" (RecvShowDirectBitmap received shmem=%p stride=%d size=%s dirty=%s)", + buffer.get<unsigned char>(), stride, ToString(size).c_str(), + ToString(dirty).c_str())); + return IPC_OK(); +} + +void PluginInstanceParent::SetCurrentImage(Image* aImage) { + MOZ_ASSERT(IsUsingDirectDrawing()); + ImageContainer::NonOwningImage holder(aImage); + holder.mFrameID = ++mFrameID; + + AutoTArray<ImageContainer::NonOwningImage, 1> imageList; + imageList.AppendElement(holder); + mImageContainer->SetCurrentImages(imageList); + + // Invalidate our area in the page so the image gets flushed. + gfx::IntRect rect = aImage->GetPictureRect(); + NPRect nprect = {uint16_t(rect.x), uint16_t(rect.y), uint16_t(rect.width), + uint16_t(rect.height)}; + RecvNPN_InvalidateRect(nprect); + + RecordDrawingModel(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvShowDirectDXGISurface( + const WindowsHandle& pluginSurfHandle, const gfx::IntRect& dirty) { +#if defined(XP_WIN) + MOZ_ASSERT(NS_IsMainThread()); + + if (!ImageBridgeChild::GetSingleton()) { + return IPC_OK(); + } + + auto asiIt = mAsyncSurfaceMap.lookup(pluginSurfHandle); + if (!asiIt) { + NS_WARNING("Plugin surface did not exist to finalize"); + return IPC_OK(); + } + + AsyncSurfaceInfo& asi = asiIt->value(); + + // Tell the ImageBridge to copy from the plugin surface to the display surface + SynchronousTask task("SendUpdateAsyncPluginSurface sync"); + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch(NewRunnableFunction( + "SendingUpdateAsyncPluginSurface", &CopyDXGISurface, asi.mSD, &task)); + task.Wait(); + + // Make sure we have an ImageContainer for SetCurrentImage. + ImageContainer* container = GetImageContainer(); + if (!container) { + return IPC_OK(); + } + + SetCurrentImage( + new GPUVideoImage(new AsyncPluginSurfaceManager(), asi.mSD, asi.mSize)); + + PLUGIN_LOG_DEBUG((" (RecvShowDirectDXGISurface received handle=%p rect=%s)", + reinterpret_cast<void*>(pluginSurfHandle), + ToString(dirty).c_str())); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvShow( + const NPRect& updatedRect, const SurfaceDescriptor& newSurface, + SurfaceDescriptor* prevSurface) { + PLUGIN_LOG_DEBUG(("[InstanceParent][%p] RecvShow for <x=%d,y=%d, w=%d,h=%d>", + this, updatedRect.left, updatedRect.top, + updatedRect.right - updatedRect.left, + updatedRect.bottom - updatedRect.top)); + + MOZ_ASSERT(!IsUsingDirectDrawing()); + + // XXXjwatt rewrite to use Moz2D + RefPtr<gfxASurface> surface; + if (newSurface.type() == SurfaceDescriptor::TShmem) { + if (!newSurface.get_Shmem().IsReadable()) { + NS_WARNING("back surface not readable"); + return IPC_FAIL_NO_REASON(this); + } + surface = gfxSharedImageSurface::Open(newSurface.get_Shmem()); + } +#ifdef XP_MACOSX + else if (newSurface.type() == SurfaceDescriptor::TIOSurfaceDescriptor) { + IOSurfaceDescriptor iodesc = newSurface.get_IOSurfaceDescriptor(); + + RefPtr<MacIOSurface> newIOSurface = MacIOSurface::LookupSurface( + iodesc.surfaceId(), iodesc.contentsScaleFactor()); + + if (!newIOSurface) { + NS_WARNING("Got bad IOSurfaceDescriptor in RecvShow"); + return IPC_FAIL_NO_REASON(this); + } + + 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 IPC_OK(); + } +#endif +#ifdef MOZ_X11 + else if (newSurface.type() == SurfaceDescriptor::TSurfaceDescriptorX11) { + surface = newSurface.get_SurfaceDescriptorX11().OpenForeign(); + } +#endif +#ifdef XP_WIN + else if (newSurface.type() == SurfaceDescriptor::TPPluginSurfaceParent) { + PluginSurfaceParent* s = static_cast<PluginSurfaceParent*>( + newSurface.get_PPluginSurfaceParent()); + surface = s->Surface(); + } +#endif + + if (mFrontSurface) { + // This is the "old front buffer" we're about to hand back to + // the plugin. We might still have drawing operations + // referencing it. +#ifdef MOZ_X11 + if (mFrontSurface->GetType() == gfxSurfaceType::Xlib) { + // Finish with the surface and XSync here to ensure the server has + // finished operations on the surface before the plugin starts + // scribbling on it again, or worse, destroys it. + mFrontSurface->Finish(); + FinishX(DefaultXDisplay()); + } else +#endif + { + mFrontSurface->Flush(); + } + } + + if (mFrontSurface && gfxSharedImageSurface::IsSharedImage(mFrontSurface)) + *prevSurface = std::move( + static_cast<gfxSharedImageSurface*>(mFrontSurface.get())->GetShmem()); + else + *prevSurface = null_t(); + + if (surface) { + // Notify the cairo backend that this surface has changed behind + // its back. + gfxRect ur(updatedRect.left, updatedRect.top, + updatedRect.right - updatedRect.left, + updatedRect.bottom - updatedRect.top); + surface->MarkDirty(ur); + + bool isPlugin = true; + RefPtr<gfx::SourceSurface> sourceSurface = + gfxPlatform::GetSourceSurfaceForSurface(nullptr, surface, isPlugin); + RefPtr<SourceSurfaceImage> image = + new SourceSurfaceImage(surface->GetSize(), sourceSurface); + + AutoTArray<ImageContainer::NonOwningImage, 1> imageList; + imageList.AppendElement(ImageContainer::NonOwningImage(image)); + + ImageContainer* container = GetImageContainer(); + container->SetCurrentImages(imageList); + } else if (mImageContainer) { + mImageContainer->ClearAllImages(); + } + + mFrontSurface = surface; + RecvNPN_InvalidateRect(updatedRect); + + PLUGIN_LOG_DEBUG( + (" (RecvShow invalidated for surface %p)", mFrontSurface.get())); + + RecordDrawingModel(); + return IPC_OK(); +} + +nsresult PluginInstanceParent::AsyncSetWindow(NPWindow* aWindow) { + NPRemoteWindow window; + mWindowType = aWindow->type; + window.window = reinterpret_cast<uint64_t>(aWindow->window); + window.x = aWindow->x; + window.y = aWindow->y; + window.width = aWindow->width; + window.height = aWindow->height; + window.clipRect = aWindow->clipRect; + window.type = aWindow->type; +#if defined(XP_MACOSX) || defined(XP_WIN) + double scaleFactor = 1.0; + mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &scaleFactor); + window.contentsScaleFactor = scaleFactor; +#endif + +#if defined(OS_WIN) + MaybeCreateChildPopupSurrogate(); +#endif + +#if defined(OS_WIN) + // Windows async surfaces must be Win32. In particular, it is incompatible + // with in-memory surface types. + gfxSurfaceType surfType = gfxSurfaceType::Win32; +#else + gfxSurfaceType surfType = + gfxPlatform::GetPlatform()->ScreenReferenceSurface()->GetType(); +#endif + + if (surfType == (gfxSurfaceType)-1) { + return NS_ERROR_FAILURE; + } + + if (!SendAsyncSetWindow(surfType, window)) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult PluginInstanceParent::GetImageContainer(ImageContainer** aContainer) { + if (IsUsingDirectDrawing()) { + // Use the image container created by the most recent direct surface + // call, if any. We don't create one if no surfaces were presented + // yet. + ImageContainer* container = mImageContainer; + NS_IF_ADDREF(container); + *aContainer = container; + return NS_OK; + } + +#ifdef XP_MACOSX + MacIOSurface* ioSurface = nullptr; + + if (mFrontIOSurface) { + ioSurface = mFrontIOSurface; + } else if (mIOSurface) { + ioSurface = mIOSurface; + } + + if (!mFrontSurface && !ioSurface) +#else + if (!mFrontSurface) +#endif + return NS_ERROR_NOT_AVAILABLE; + + ImageContainer* container = GetImageContainer(); + + if (!container) { + return NS_ERROR_FAILURE; + } + +#ifdef XP_MACOSX + if (ioSurface) { + RefPtr<Image> image = new MacIOSurfaceImage(ioSurface); + container->SetCurrentImageInTransaction(image); + + NS_IF_ADDREF(container); + *aContainer = container; + return NS_OK; + } +#endif + + NS_IF_ADDREF(container); + *aContainer = container; + return NS_OK; +} + +nsresult PluginInstanceParent::GetImageSize(nsIntSize* aSize) { + if (IsUsingDirectDrawing()) { + if (!mImageContainer) { + return NS_ERROR_NOT_AVAILABLE; + } + *aSize = mImageContainer->GetCurrentSize(); + return NS_OK; + } + + if (mFrontSurface) { + mozilla::gfx::IntSize size = mFrontSurface->GetSize(); + *aSize = nsIntSize(size.width, size.height); + return NS_OK; + } + +#ifdef XP_MACOSX + if (mFrontIOSurface) { + *aSize = + nsIntSize(mFrontIOSurface->GetWidth(), mFrontIOSurface->GetHeight()); + return NS_OK; + } else if (mIOSurface) { + *aSize = nsIntSize(mIOSurface->GetWidth(), mIOSurface->GetHeight()); + return NS_OK; + } +#endif + + return NS_ERROR_NOT_AVAILABLE; +} + +void PluginInstanceParent::DidComposite() { + if (!IsUsingDirectDrawing()) { + return; + } + Unused << SendNPP_DidComposite(); +} + +#ifdef XP_MACOSX +nsresult PluginInstanceParent::IsRemoteDrawingCoreAnimation(bool* aDrawing) { + *aDrawing = (NPDrawingModelCoreAnimation == (NPDrawingModel)mDrawingModel || + NPDrawingModelInvalidatingCoreAnimation == + (NPDrawingModel)mDrawingModel); + return NS_OK; +} +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +nsresult PluginInstanceParent::ContentsScaleFactorChanged( + double aContentsScaleFactor) { + bool rv = SendContentsScaleFactorChanged(aContentsScaleFactor); + return rv ? NS_OK : NS_ERROR_FAILURE; +} +#endif // #ifdef XP_MACOSX + +nsresult PluginInstanceParent::SetBackgroundUnknown() { + PLUGIN_LOG_DEBUG(("[InstanceParent][%p] SetBackgroundUnknown", this)); + + if (mBackground) { + DestroyBackground(); + MOZ_ASSERT(!mBackground, "Background not destroyed"); + } + + return NS_OK; +} + +nsresult PluginInstanceParent::BeginUpdateBackground(const nsIntRect& aRect, + DrawTarget** aDrawTarget) { + PLUGIN_LOG_DEBUG( + ("[InstanceParent][%p] BeginUpdateBackground for <x=%d,y=%d, w=%d,h=%d>", + this, aRect.x, aRect.y, aRect.width, aRect.height)); + + if (!mBackground) { + // XXX if we failed to create a background surface on one + // update, there's no guarantee that later updates will be for + // the entire background area until successful. We might want + // to fix that eventually. + MOZ_ASSERT(aRect.TopLeft() == nsIntPoint(0, 0), + "Expecting rect for whole frame"); + if (!CreateBackground(aRect.Size())) { + *aDrawTarget = nullptr; + return NS_OK; + } + } + + mozilla::gfx::IntSize sz = mBackground->GetSize(); +#ifdef DEBUG + MOZ_ASSERT(nsIntRect(0, 0, sz.width, sz.height).Contains(aRect), + "Update outside of background area"); +#endif + + RefPtr<gfx::DrawTarget> dt = gfxPlatform::CreateDrawTargetForSurface( + mBackground, gfx::IntSize(sz.width, sz.height)); + dt.forget(aDrawTarget); + + return NS_OK; +} + +nsresult PluginInstanceParent::EndUpdateBackground(const nsIntRect& aRect) { + PLUGIN_LOG_DEBUG( + ("[InstanceParent][%p] EndUpdateBackground for <x=%d,y=%d, w=%d,h=%d>", + this, aRect.x, aRect.y, aRect.width, aRect.height)); + +#ifdef MOZ_X11 + // Have to XSync here to avoid the plugin trying to draw with this + // surface racing with its creation in the X server. We also want + // to avoid the plugin drawing onto stale pixels, then handing us + // back a front surface from those pixels that we might + // recomposite for "a while" until the next update. This XSync + // still doesn't guarantee that the plugin draws onto a consistent + // view of its background, but it does mean that the plugin is + // drawing onto pixels no older than those in the latest + // EndUpdateBackground(). + XSync(DefaultXDisplay(), X11False); +#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(CompositableHandle(aScrollCaptureId)); + return NS_OK; +} + +nsresult PluginInstanceParent::GetScrollCaptureContainer( + ImageContainer** aContainer) { + if (!aContainer || !mImageContainer) { + return NS_ERROR_FAILURE; + } + + RefPtr<ImageContainer> container = GetImageContainer(); + container.forget(aContainer); + + return NS_OK; +} +#endif // XP_WIN + +bool PluginInstanceParent::CreateBackground(const nsIntSize& aSize) { + MOZ_ASSERT(!mBackground, "Already have a background"); + + // XXX refactor me + +#if defined(MOZ_X11) + Screen* screen = DefaultScreenOfDisplay(DefaultXDisplay()); + Visual* visual = DefaultVisualOfScreen(screen); + mBackground = gfxXlibSurface::Create( + screen, visual, mozilla::gfx::IntSize(aSize.width, aSize.height)); + return !!mBackground; + +#elif defined(XP_WIN) + // We have chosen to create an unsafe surface in which the plugin + // can read from the region while we're writing to it. + mBackground = gfxSharedImageSurface::CreateUnsafe( + this, mozilla::gfx::IntSize(aSize.width, aSize.height), + mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32); + return !!mBackground; +#else + return false; +#endif +} + +void PluginInstanceParent::DestroyBackground() { + if (!mBackground) { + return; + } + + // Relinquish ownership of |mBackground| to its destroyer + PPluginBackgroundDestroyerParent* pbd = + new PluginBackgroundDestroyerParent(mBackground); + mBackground = nullptr; + + // If this fails, there's no problem: |bd| will be destroyed along + // with the old background surface. + Unused << SendPPluginBackgroundDestroyerConstructor(pbd); +} + +mozilla::plugins::SurfaceDescriptor +PluginInstanceParent::BackgroundDescriptor() { + MOZ_ASSERT(mBackground, "Need a background here"); + + // XXX refactor me + +#ifdef MOZ_X11 + gfxXlibSurface* xsurf = static_cast<gfxXlibSurface*>(mBackground.get()); + return SurfaceDescriptorX11(xsurf); +#endif + +#ifdef XP_WIN + MOZ_ASSERT(gfxSharedImageSurface::IsSharedImage(mBackground), + "Expected shared image surface"); + gfxSharedImageSurface* shmem = + static_cast<gfxSharedImageSurface*>(mBackground.get()); + return mozilla::plugins::SurfaceDescriptor(std::move(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() { + MOZ_CRASH("'Power-user' ctor is used exclusively"); + return nullptr; +} + +bool PluginInstanceParent::DeallocPPluginBackgroundDestroyerParent( + PPluginBackgroundDestroyerParent* aActor) { + delete aActor; + return true; +} + +NPError PluginInstanceParent::NPP_SetWindow(const NPWindow* aWindow) { + PLUGIN_LOG_DEBUG(("%s (aWindow=%p)", FULLFUNCTION, (void*)aWindow)); + + NS_ENSURE_TRUE(aWindow, NPERR_GENERIC_ERROR); + + NPRemoteWindow window; + mWindowType = aWindow->type; + +#if defined(OS_WIN) + // On windowless controls, reset the shared memory surface as needed. + if (mWindowType == NPWindowTypeDrawable) { + MaybeCreateChildPopupSurrogate(); + } else { + SubclassPluginWindow(reinterpret_cast<HWND>(aWindow->window)); + + window.window = reinterpret_cast<uint64_t>(aWindow->window); + window.x = aWindow->x; + window.y = aWindow->y; + window.width = aWindow->width; + window.height = aWindow->height; + window.type = aWindow->type; + + // On Windows we need to create and set the parent before we set the + // window on the plugin, or keyboard interaction will not work. + if (!MaybeCreateAndParentChildPluginWindow()) { + return NPERR_GENERIC_ERROR; + } + } +#else + window.window = reinterpret_cast<uint64_t>(aWindow->window); + window.x = aWindow->x; + window.y = aWindow->y; + window.width = aWindow->width; + window.height = aWindow->height; + window.clipRect = aWindow->clipRect; // MacOS specific + window.type = aWindow->type; +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + double floatScaleFactor = 1.0; + mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &floatScaleFactor); + window.contentsScaleFactor = floatScaleFactor; +#endif +#if defined(XP_MACOSX) + int scaleFactor = ceil(floatScaleFactor); + if (mShWidth != window.width * scaleFactor || + mShHeight != window.height * scaleFactor) { + if (mDrawingModel == NPDrawingModelCoreAnimation || + mDrawingModel == NPDrawingModelInvalidatingCoreAnimation) { + mIOSurface = MacIOSurface::CreateIOSurface(window.width, window.height, + floatScaleFactor); + } else if (uint32_t(mShWidth * mShHeight) != + window.width * scaleFactor * window.height * scaleFactor) { + if (mShWidth != 0 && mShHeight != 0) { + DeallocShmem(mShSurface); + mShWidth = 0; + mShHeight = 0; + } + + if (window.width != 0 && window.height != 0) { + if (!AllocShmem( + window.width * scaleFactor * window.height * 4 * scaleFactor, + SharedMemory::TYPE_BASIC, &mShSurface)) { + PLUGIN_LOG_DEBUG(("Shared memory could not be allocated.")); + return NPERR_GENERIC_ERROR; + } + } + } + mShWidth = window.width * scaleFactor; + mShHeight = window.height * scaleFactor; + } +#endif + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + const NPSetWindowCallbackStruct* ws_info = + static_cast<NPSetWindowCallbackStruct*>(aWindow->ws_info); + window.visualID = ws_info->visual ? ws_info->visual->visualid : 0; + window.colormap = ws_info->colormap; +#endif + + if (!CallNPP_SetWindow(window)) { + return NPERR_GENERIC_ERROR; + } + + RecordDrawingModel(); + return NPERR_NO_ERROR; +} + +NPError PluginInstanceParent::NPP_GetValue(NPPVariable aVariable, + void* _retval) { + switch (aVariable) { + case NPPVpluginWantsAllNetworkStreams: { + bool wantsAllStreams; + NPError rv; + + if (!CallNPP_GetValue_NPPVpluginWantsAllNetworkStreams(&wantsAllStreams, + &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + (*(NPBool*)_retval) = wantsAllStreams; + return NPERR_NO_ERROR; + } + + case NPPVpluginScriptableNPObject: { + PPluginScriptableObjectParent* actor; + NPError rv; + if (!CallNPP_GetValue_NPPVpluginScriptableNPObject(&actor, &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + if (!actor) { + NS_ERROR("NPPVpluginScriptableNPObject succeeded but null."); + return NPERR_GENERIC_ERROR; + } + + const NPNetscapeFuncs* npn = mParent->GetNetscapeFuncs(); + if (!npn) { + NS_WARNING("No netscape functions?!"); + return NPERR_GENERIC_ERROR; + } + + NPObject* object = + static_cast<PluginScriptableObjectParent*>(actor)->GetObject(true); + NS_ASSERTION(object, "This shouldn't ever be null!"); + + (*(NPObject**)_retval) = npn->retainobject(object); + return NPERR_NO_ERROR; + } + +#ifdef MOZ_ACCESSIBILITY_ATK + case NPPVpluginNativeAccessibleAtkPlugId: { + nsCString plugId; + NPError rv; + if (!CallNPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId(&plugId, &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + (*(nsCString*)_retval) = plugId; + return NPERR_NO_ERROR; + } +#endif + + default: + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceParent::NPP_GetValue: Unhandled NPPVariable " + "%i (%s)", + (int)aVariable, NPPVariableToString(aVariable))); + return NPERR_GENERIC_ERROR; + } +} + +NPError PluginInstanceParent::NPP_SetValue(NPNVariable variable, void* value) { + NPError result; + switch (variable) { + case NPNVprivateModeBool: + if (!CallNPP_SetValue_NPNVprivateModeBool(*static_cast<NPBool*>(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + case NPNVmuteAudioBool: + if (!CallNPP_SetValue_NPNVmuteAudioBool(*static_cast<NPBool*>(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + case NPNVCSSZoomFactor: + if (!CallNPP_SetValue_NPNVCSSZoomFactor(*static_cast<double*>(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + default: + NS_ERROR("Unhandled NPNVariable in NPP_SetValue"); + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceParent::NPP_SetValue: Unhandled NPNVariable " + "%i (%s)", + (int)variable, NPNVariableToString(variable))); + return NPERR_GENERIC_ERROR; + } +} + +void PluginInstanceParent::NPP_URLRedirectNotify(const char* url, + int32_t status, + void* notifyData) { + if (!notifyData) return; + + PStreamNotifyParent* streamNotify = + static_cast<PStreamNotifyParent*>(notifyData); + Unused << streamNotify->SendRedirectNotify(NullableString(url), status); +} + +int16_t PluginInstanceParent::NPP_HandleEvent(void* event) { + PLUGIN_LOG_DEBUG_FUNCTION; + +#if defined(XP_MACOSX) + NPCocoaEvent* npevent = reinterpret_cast<NPCocoaEvent*>(event); +#else + NPEvent* npevent = reinterpret_cast<NPEvent*>(event); +#endif + NPRemoteEvent npremoteevent; + npremoteevent.event = *npevent; +#if defined(XP_MACOSX) || defined(XP_WIN) + double scaleFactor = 1.0; + mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &scaleFactor); + npremoteevent.contentsScaleFactor = scaleFactor; +#endif + int16_t handled = 0; + +#if defined(OS_WIN) + if (mWindowType == NPWindowTypeDrawable) { + switch (npevent->event) { + case WM_KILLFOCUS: { + // When the user selects fullscreen mode in Flash video players, + // WM_KILLFOCUS will be delayed by deferred event processing: + // WM_LBUTTONUP results in a call to CreateWindow within Flash, + // which fires WM_KILLFOCUS. Delayed delivery causes Flash to + // misinterpret the event, dropping back out of fullscreen. Trap + // this event and drop it. + // mPluginHWND is always NULL for non-windowed plugins. + if (mPluginHWND) { + 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, X11False); + 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, std::move(mShSurface), + &handled, &mShSurface)) + return false; // no good way to handle errors here... + + if (!mShSurface.IsReadable()) { + PLUGIN_LOG_DEBUG( + ("Shmem not returned. Either the plugin crashed " + "or we have a bug.")); + return false; + } + + char* shContextByte = mShSurface.get<char>(); + + if (!mShColorSpace) { + mShColorSpace = CreateSystemColorSpace(); + } + if (!mShColorSpace) { + PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace.")); + return false; + } + CGContextRef shContext = ::CGBitmapContextCreate( + shContextByte, mShWidth, mShHeight, 8, mShWidth * 4, mShColorSpace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host); + if (!shContext) { + PLUGIN_LOG_DEBUG(("Could not allocate CGBitmapContext.")); + return false; + } + + CGImageRef shImage = ::CGBitmapContextCreateImage(shContext); + if (shImage) { + CGContextRef cgContext = npevent->data.draw.context; + + ::CGContextDrawImage(cgContext, CGRectMake(0, 0, mShWidth, mShHeight), + shImage); + ::CGImageRelease(shImage); + } else { + ::CGContextRelease(shContext); + return false; + } + ::CGContextRelease(shContext); + return true; + } + } +#endif + + if (!CallNPP_HandleEvent(npremoteevent, &handled)) + return 0; // no good way to handle errors here... + + return handled; +} + +NPError PluginInstanceParent::NPP_NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype) { + PLUGIN_LOG_DEBUG(("%s (type=%s, stream=%p, seekable=%i)", FULLFUNCTION, + (char*)type, (void*)stream, (int)seekable)); + + BrowserStreamParent* bs = new BrowserStreamParent(this, stream); + + if (!SendPBrowserStreamConstructor( + bs, NullableString(stream->url), stream->end, stream->lastmodified, + static_cast<PStreamNotifyParent*>(stream->notifyData), + NullableString(stream->headers))) { + return NPERR_GENERIC_ERROR; + } + + NPError err = NPERR_NO_ERROR; + bs->SetAlive(); + if (!CallNPP_NewStream(bs, NullableString(type), seekable, &err, stype)) { + err = NPERR_GENERIC_ERROR; + } + if (NPERR_NO_ERROR != err) { + Unused << PBrowserStreamParent::Send__delete__(bs); + } + + return err; +} + +NPError PluginInstanceParent::NPP_DestroyStream(NPStream* stream, + NPReason reason) { + PLUGIN_LOG_DEBUG( + ("%s (stream=%p, reason=%i)", FULLFUNCTION, (void*)stream, (int)reason)); + + AStream* s = static_cast<AStream*>(stream->pdata); + if (!s) { + // The stream has already been deleted by other means. + // With async plugin init this could happen if async NPP_NewStream + // returns an error code. + return NPERR_NO_ERROR; + } + MOZ_ASSERT(s->IsBrowserStream()); + BrowserStreamParent* sp = static_cast<BrowserStreamParent*>(s); + if (sp->mNPP != this) MOZ_CRASH("Mismatched plugin data"); + sp->NPP_DestroyStream(reason); + return NPERR_NO_ERROR; +} + +void PluginInstanceParent::NPP_Print(NPPrint* platformPrint) { + // TODO: implement me + NS_ERROR("Not implemented"); +} + +PPluginScriptableObjectParent* +PluginInstanceParent::AllocPPluginScriptableObjectParent() { + return new PluginScriptableObjectParent(Proxy); +} + +bool PluginInstanceParent::DeallocPPluginScriptableObjectParent( + PPluginScriptableObjectParent* aObject) { + PluginScriptableObjectParent* actor = + static_cast<PluginScriptableObjectParent*>(aObject); + + NPObject* object = actor->GetObject(false); + if (object) { + NS_ASSERTION(mScriptableObjects.Get(object, nullptr), + "NPObject not in the hash!"); + mScriptableObjects.Remove(object); + } +#ifdef DEBUG + else { + for (auto iter = mScriptableObjects.Iter(); !iter.Done(); iter.Next()) { + NS_ASSERTION(actor != iter.UserData(), + "Actor in the hash with a null NPObject!"); + } + } +#endif + + delete actor; + return true; +} + +mozilla::ipc::IPCResult +PluginInstanceParent::RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectParent* aActor) { + // This is only called in response to the child process requesting the + // creation of an actor. This actor will represent an NPObject that is + // created by the plugin and returned to the browser. + PluginScriptableObjectParent* actor = + static_cast<PluginScriptableObjectParent*>(aActor); + NS_ASSERTION(!actor->GetObject(false), "Actor already has an object?!"); + + actor->InitializeProxy(); + NS_ASSERTION(actor->GetObject(false), "Actor should have an object!"); + + return IPC_OK(); +} + +void PluginInstanceParent::NPP_URLNotify(const char* url, NPReason reason, + void* notifyData) { + PLUGIN_LOG_DEBUG( + ("%s (%s, %i, %p)", FULLFUNCTION, url, (int)reason, notifyData)); + + PStreamNotifyParent* streamNotify = + static_cast<PStreamNotifyParent*>(notifyData); + Unused << PStreamNotifyParent::Send__delete__(streamNotify, reason); +} + +bool PluginInstanceParent::RegisterNPObjectForActor( + NPObject* aObject, PluginScriptableObjectParent* aActor) { + NS_ASSERTION(aObject && aActor, "Null pointers!"); + NS_ASSERTION(!mScriptableObjects.Get(aObject, nullptr), "Duplicate entry!"); + mScriptableObjects.Put(aObject, aActor); + return true; +} + +void PluginInstanceParent::UnregisterNPObject(NPObject* aObject) { + NS_ASSERTION(aObject, "Null pointer!"); + NS_ASSERTION(mScriptableObjects.Get(aObject, nullptr), "Unknown entry!"); + mScriptableObjects.Remove(aObject); +} + +PluginScriptableObjectParent* PluginInstanceParent::GetActorForNPObject( + NPObject* aObject) { + NS_ASSERTION(aObject, "Null pointer!"); + + if (aObject->_class == PluginScriptableObjectParent::GetClass()) { + // One of ours! + ParentNPObject* object = static_cast<ParentNPObject*>(aObject); + NS_ASSERTION(object->parent, "Null actor!"); + return object->parent; + } + + PluginScriptableObjectParent* actor; + if (mScriptableObjects.Get(aObject, &actor)) { + return actor; + } + + actor = new PluginScriptableObjectParent(LocalObject); + if (!SendPPluginScriptableObjectConstructor(actor)) { + NS_WARNING("Failed to send constructor message!"); + return nullptr; + } + + actor->InitializeLocal(aObject); + return actor; +} + +PPluginSurfaceParent* PluginInstanceParent::AllocPPluginSurfaceParent( + const WindowsSharedMemoryHandle& handle, const mozilla::gfx::IntSize& size, + const bool& transparent) { +#ifdef XP_WIN + return new PluginSurfaceParent(handle, size, transparent); +#else + NS_ERROR("This shouldn't be called!"); + return nullptr; +#endif +} + +bool PluginInstanceParent::DeallocPPluginSurfaceParent( + PPluginSurfaceParent* s) { +#ifdef XP_WIN + delete s; + return true; +#else + return false; +#endif +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerNPN_PushPopupsEnabledState( + const bool& aState) { + mNPNIface->pushpopupsenabledstate(mNPP, aState ? 1 : 0); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_PopPopupsEnabledState() { + mNPNIface->poppopupsenabledstate(mNPP); + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvRedrawPlugin() { + nsNPAPIPluginInstance* inst = + static_cast<nsNPAPIPluginInstance*>(mNPP->ndata); + if (!inst) { + return IPC_FAIL_NO_REASON(this); + } + + inst->RedrawPlugin(); + return IPC_OK(); +} + +nsPluginInstanceOwner* PluginInstanceParent::GetOwner() { + nsNPAPIPluginInstance* inst = + static_cast<nsNPAPIPluginInstance*>(mNPP->ndata); + if (!inst) { + return nullptr; + } + return inst->GetOwner(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("RecvSetNetscapeWindowAsParent not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +#if defined(OS_WIN) + +/* + plugin focus changes between processes + + focus from dom -> child: + Focus manager calls on widget to set the focus on the window. + We pick up the resulting wm_setfocus event here, and forward + that over ipc to the child which calls set focus on itself. + + focus from child -> focus manager: + Child picks up the local wm_setfocus and sends it via ipc over + here. We then post a custom event to widget/windows/nswindow + which fires off a gui event letting the browser know. +*/ + +static const wchar_t kPluginInstanceParentProperty[] = + L"PluginInstanceParentProperty"; + +// static +LRESULT CALLBACK PluginInstanceParent::PluginWindowHookProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) { + PluginInstanceParent* self = reinterpret_cast<PluginInstanceParent*>( + ::GetPropW(hWnd, kPluginInstanceParentProperty)); + if (!self) { + MOZ_ASSERT_UNREACHABLE( + "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) { + MOZ_ASSERT_UNREACHABLE( + "PluginWindowHookProc invoking mPluginWndProc w/" + "mPluginWndProc == PluginWindowHookProc????"); + return DefWindowProc(hWnd, message, wParam, lParam); + } + return ::CallWindowProc(self->mPluginWndProc, hWnd, message, wParam, lParam); +} + +void PluginInstanceParent::SubclassPluginWindow(HWND aWnd) { + if ((aWnd && mPluginHWND == aWnd) || (!aWnd && mPluginHWND)) { + return; + } + + if (XRE_IsContentProcess()) { + if (!aWnd) { + NS_WARNING( + "PluginInstanceParent::SubclassPluginWindow unexpected null window"); + return; + } + mPluginHWND = aWnd; // now a remote window, we can't subclass this + mPluginWndProc = nullptr; + // Note sPluginInstanceList wil delete 'this' if we do not remove + // it on shutdown. + sPluginInstanceList->Put((void*)mPluginHWND, this); + return; + } + + NS_ASSERTION( + !(mPluginHWND && aWnd != mPluginHWND), + "PluginInstanceParent::SubclassPluginWindow hwnd is not our window!"); + + mPluginHWND = aWnd; + mPluginWndProc = (WNDPROC)::SetWindowLongPtrA( + mPluginHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(PluginWindowHookProc)); + DebugOnly<bool> bRes = + ::SetPropW(mPluginHWND, kPluginInstanceParentProperty, this); + NS_ASSERTION( + mPluginWndProc, + "PluginInstanceParent::SubclassPluginWindow failed to set subclass!"); + NS_ASSERTION( + bRes, "PluginInstanceParent::SubclassPluginWindow failed to set prop!"); +} + +void PluginInstanceParent::UnsubclassPluginWindow() { + if (XRE_IsContentProcess()) { + if (mPluginHWND) { + // Remove 'this' from the plugin list safely + mozilla::UniquePtr<PluginInstanceParent> tmp; + MOZ_ASSERT(sPluginInstanceList); + sPluginInstanceList->Remove((void*)mPluginHWND, &tmp); + mozilla::Unused << tmp.release(); + if (!sPluginInstanceList->Count()) { + delete sPluginInstanceList; + sPluginInstanceList = nullptr; + } + } + mPluginHWND = nullptr; + return; + } + + if (mPluginHWND && mPluginWndProc) { + ::SetWindowLongPtrA(mPluginHWND, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(mPluginWndProc)); + + ::RemovePropW(mPluginHWND, kPluginInstanceParentProperty); + + mPluginWndProc = nullptr; + mPluginHWND = nullptr; + } +} + +/* windowless drawing helpers */ + +/* + * Origin info: + * + * windowless, offscreen: + * + * WM_WINDOWPOSCHANGED: origin is relative to container + * setwindow: origin is 0,0 + * WM_PAINT: origin is 0,0 + * + * windowless, native: + * + * WM_WINDOWPOSCHANGED: origin is relative to container + * setwindow: origin is relative to container + * WM_PAINT: origin is relative to container + * + * PluginInstanceParent: + * + * painting: mPluginPort (nsIntRect, saved in SetWindow) + */ + +bool PluginInstanceParent::MaybeCreateAndParentChildPluginWindow() { + // On Windows we need to create and set the parent before we set the + // window on the plugin, or keyboard interaction will not work. + if (!mChildPluginHWND) { + if (!CallCreateChildPluginWindow(&mChildPluginHWND) || !mChildPluginHWND) { + return false; + } + } + + // It's not clear if the parent window would ever change, but when this + // was done in the NPAPI child it used to allow for this. + if (mPluginHWND == mChildPluginsParentHWND) { + return true; + } + + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner) { + // We can't reparent without an owner, the plugin is probably shutting + // down, just return true to allow any calls to continue. + return true; + } + + // Note that this call will probably cause a sync native message to the + // process that owns the child window. + owner->SetWidgetWindowAsParent(mChildPluginHWND); + mChildPluginsParentHWND = mPluginHWND; + return true; +} + +void PluginInstanceParent::MaybeCreateChildPopupSurrogate() { + // Already created or not required for this plugin. + if (mChildPluginHWND || mWindowType != NPWindowTypeDrawable || + !(mParent->GetQuirks() & QUIRK_WINLESS_TRACKPOPUP_HOOK)) { + return; + } + + // We need to pass the netscape window down to be cached as part of the call + // to create the surrogate, because the reparenting of the surrogate in the + // main process can cause sync Windows messages to the plugin process, which + // then cause sync messages from the plugin child for the netscape window + // which causes a deadlock. + NativeWindowHandle netscapeWindow; + NPError result = + mNPNIface->getvalue(mNPP, NPNVnetscapeWindow, &netscapeWindow); + if (NPERR_NO_ERROR != result) { + NS_WARNING("Can't get netscape window to pass to plugin child."); + return; + } + + if (!SendCreateChildPopupSurrogate(netscapeWindow)) { + NS_WARNING("Failed to create popup surrogate in child."); + } +} + +#endif // defined(OS_WIN) + +mozilla::ipc::IPCResult 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) { + RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager(); + RefPtr<dom::Element> element; + owner->GetDOMElement(getter_AddRefs(element)); + if (fm && element) { + fm->SetFocus(element, 0); + } + } + } + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("AnswerPluginFocusChange not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +PluginInstanceParent* PluginInstanceParent::Cast(NPP aInstance) { + auto ip = static_cast<PluginInstanceParent*>(aInstance->pdata); + + // If the plugin crashed and the PluginInstanceParent was deleted, + // aInstance->pdata will be nullptr. + if (!ip) { + return nullptr; + } + + if (aInstance != ip->mNPP) { + MOZ_CRASH("Corrupted plugin data."); + } + + return ip; +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvGetCompositionString( + const uint32_t& aIndex, nsTArray<uint8_t>* aDist, int32_t* aLength) { +#if defined(OS_WIN) + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner) { + *aLength = IMM_ERROR_GENERAL; + return IPC_OK(); + } + + if (!owner->GetCompositionString(aIndex, aDist, aLength)) { + *aLength = IMM_ERROR_NODATA; + } +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvRequestCommitOrCancel( + const bool& aCommitted) { +#if defined(OS_WIN) + nsPluginInstanceOwner* owner = GetOwner(); + if (owner) { + owner->RequestCommitOrCancel(aCommitted); + } +#endif + return IPC_OK(); +} + +nsresult PluginInstanceParent::HandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, bool aIsConsumed) { + if (NS_WARN_IF( + !SendHandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +mozilla::ipc::IPCResult 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 IPC_OK(); + } + owner->OnWindowedPluginKeyEvent(aKeyEventData); + return IPC_OK(); +} + +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 0000000000..bef637b21f --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceParent.h @@ -0,0 +1,403 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginInstanceParent_h +#define dom_plugins_PluginInstanceParent_h 1 + +#include "mozilla/plugins/PPluginInstanceParent.h" +#include "mozilla/plugins/PluginScriptableObjectParent.h" +#if defined(OS_WIN) +# include "mozilla/gfx/SharedDIBWin.h" +# include <d3d10_1.h> +# include "nsRefPtrHashtable.h" +# include "mozilla/layers/LayersSurfaces.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 "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 { + friend class PluginModuleParent; + friend class BrowserStreamParent; + friend class StreamNotifyParent; + friend class PPluginInstanceParent; + +#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; + + PPluginScriptableObjectParent* AllocPPluginScriptableObjectParent(); + + virtual mozilla::ipc::IPCResult RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectParent* aActor) override; + + bool DeallocPPluginScriptableObjectParent( + PPluginScriptableObjectParent* aObject); + PBrowserStreamParent* AllocPBrowserStreamParent( + const nsCString& url, const uint32_t& length, + const uint32_t& lastmodified, PStreamNotifyParent* notifyData, + const nsCString& headers); + bool DeallocPBrowserStreamParent(PBrowserStreamParent* stream); + + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVnetscapeWindow( + NativeWindowHandle* value, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVWindowNPObject( + PPluginScriptableObjectParent** value, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVPluginElementNPObject( + PPluginScriptableObjectParent** value, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVprivateModeBool( + bool* value, NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_GetValue_DrawingModelSupport( + const NPNVariable& model, bool* value); + + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVdocumentOrigin( + nsCString* value, NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_GetValue_SupportsAsyncBitmapSurface( + bool* value); + + static bool SupportsPluginDirectDXGISurfaceDrawing(); + mozilla::ipc::IPCResult AnswerNPN_GetValue_SupportsAsyncDXGISurface( + bool* value) { + *value = SupportsPluginDirectDXGISurfaceDrawing(); + return IPC_OK(); + } + + mozilla::ipc::IPCResult AnswerNPN_GetValue_PreferredDXGIAdapter( + DxgiAdapterDesc* desc); + + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginWindow( + const bool& windowed, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginTransparent( + const bool& transparent, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginUsesDOMForCursor( + const bool& useDOMForCursor, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginDrawingModel( + const int& drawingModel, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginEventModel( + const int& eventModel, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginIsPlayingAudio( + const bool& isAudioPlaying, NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_GetURL(const nsCString& url, + const nsCString& target, + NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_PostURL(const nsCString& url, + const nsCString& target, + const nsCString& buffer, + const bool& file, NPError* result); + + PStreamNotifyParent* AllocPStreamNotifyParent( + const nsCString& url, const nsCString& target, const bool& post, + const nsCString& buffer, const bool& file, NPError* result); + + virtual mozilla::ipc::IPCResult AnswerPStreamNotifyConstructor( + PStreamNotifyParent* actor, const nsCString& url, const nsCString& target, + const bool& post, const nsCString& buffer, const bool& file, + NPError* result) override; + + bool DeallocPStreamNotifyParent(PStreamNotifyParent* notifyData); + + mozilla::ipc::IPCResult RecvNPN_InvalidateRect(const NPRect& rect); + + mozilla::ipc::IPCResult RecvRevokeCurrentDirectSurface(); + + /** + * Windows async plugin rendering uses DXGI surface objects, entirely + * maintained by the compositor process, to back the rendering of plugins. + * The expected mechanics are: + * - The PluginInstanceChild (PIC) in the plugin process sends + * InitDXGISurface to us, the content process' PluginInstanceParent (PIP). + * - The PIP uses the ImageBridge to tell the compositor to create 2 + * surfaces -- one to be written to by the plugin process and another + * to be displayed by the compositor. The PIP returns the plugin + * surface to the PIC. + * - The PIC repeatedly issues ShowDirectDXGISurface calls to tell the + * PIP to blit the plugin surface to the display surface. These + * requests are forwarded to the compositor via the ImageBridge. + * - The PIC sends FinalizeDXGISurface tio the PIP when it no longer needs + * the surface. The PIP then tells the compositor to destroy the + * plugin surface. After this, the PIP will tell the compositor to + * also destroy the display surface as soon as the ImageBridge says it + * no longer needs it. + */ + mozilla::ipc::IPCResult RecvInitDXGISurface(const gfx::SurfaceFormat& format, + const gfx::IntSize& size, + WindowsHandle* outHandle, + NPError* outError); + mozilla::ipc::IPCResult RecvShowDirectDXGISurface(const WindowsHandle& handle, + const gfx::IntRect& rect); + + mozilla::ipc::IPCResult RecvFinalizeDXGISurface(const WindowsHandle& handle); + + // -- + + mozilla::ipc::IPCResult RecvShowDirectBitmap(Shmem&& buffer, + const gfx::SurfaceFormat& format, + const uint32_t& stride, + const gfx::IntSize& size, + const gfx::IntRect& dirty); + + mozilla::ipc::IPCResult RecvShow(const NPRect& updatedRect, + const SurfaceDescriptor& newSurface, + SurfaceDescriptor* prevSurface); + + PPluginSurfaceParent* AllocPPluginSurfaceParent( + const WindowsSharedMemoryHandle& handle, + const mozilla::gfx::IntSize& size, const bool& transparent); + + bool DeallocPPluginSurfaceParent(PPluginSurfaceParent* s); + + mozilla::ipc::IPCResult AnswerNPN_PushPopupsEnabledState(const bool& aState); + + mozilla::ipc::IPCResult AnswerNPN_PopPopupsEnabledState(); + + mozilla::ipc::IPCResult AnswerNPN_GetValueForURL( + const NPNURLVariable& variable, const nsCString& url, nsCString* value, + NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_SetValueForURL( + const NPNURLVariable& variable, const nsCString& url, + const nsCString& value, NPError* result); + + mozilla::ipc::IPCResult 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); + + mozilla::ipc::IPCResult RecvRedrawPlugin(); + + mozilla::ipc::IPCResult RecvSetNetscapeWindowAsParent( + const NativeWindowHandle& childWindow); + + 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; } + + void GetSrcAttribute(nsACString& aOutput) const { aOutput = mSrcAttribute; } + + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult AnswerPluginFocusChange( + const bool& gotFocus); + + 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(); + + static PluginInstanceParent* Cast(NPP instance); + + // for IME hook + mozilla::ipc::IPCResult RecvGetCompositionString(const uint32_t& aIndex, + nsTArray<uint8_t>* aBuffer, + int32_t* aLength); + mozilla::ipc::IPCResult RecvRequestCommitOrCancel(const bool& aCommitted); + + // for reserved shortcut key handling with windowed plugin on Windows + nsresult HandledWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, bool aIsConsumed); + mozilla::ipc::IPCResult RecvOnWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData); + + 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(); + + PPluginBackgroundDestroyerParent* AllocPPluginBackgroundDestroyerParent(); + + bool DeallocPPluginBackgroundDestroyerParent( + PPluginBackgroundDestroyerParent* aActor); + + 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; + NPP mNPP; + const NPNetscapeFuncs* mNPNIface; + nsCString mSrcAttribute; + NPWindowType mWindowType; + int16_t mDrawingModel; + + // Since plugins may request different drawing models to find a compatible + // one, we only record the drawing model after a SetWindow call and if the + // drawing model has changed. + int mLastRecordedDrawingModel; + + nsDataHashtable<nsPtrHashKey<NPObject>, PluginScriptableObjectParent*> + mScriptableObjects; + + // This is used to tell the compositor that it should invalidate the + // ImageLayer. + uint32_t mFrameID; + +#if defined(XP_WIN) + // Note: DXGI 1.1 surface handles are global across all processes, and are not + // marshaled. As long as we haven't freed a texture its handle should be valid + // as a unique cross-process identifier for the texture. + nsRefPtrHashtable<nsPtrHashKey<void>, D3D11SurfaceHolder> mD3D11Surfaces; +#endif + +#if defined(OS_WIN) + private: + // Used in handling parent/child forwarding of events. + static LRESULT CALLBACK PluginWindowHookProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam); + void SubclassPluginWindow(HWND aWnd); + void UnsubclassPluginWindow(); + + bool MaybeCreateAndParentChildPluginWindow(); + void MaybeCreateChildPopupSurrogate(); + + private: + nsIntRect mPluginPort; + nsIntRect mSharedSize; + HWND mPluginHWND; + // This is used for the normal child plugin HWND for windowed plugins and, + // if needed, also the child popup surrogate HWND for windowless plugins. + HWND mChildPluginHWND; + HWND mChildPluginsParentHWND; + WNDPROC mPluginWndProc; + + struct AsyncSurfaceInfo { + layers::SurfaceDescriptorPlugin mSD; + gfx::IntSize mSize; + }; + // Key is plugin surface's texture's handle + HashMap<WindowsHandle, AsyncSurfaceInfo> mAsyncSurfaceMap; +#endif // defined(OS_WIN) + +#if defined(MOZ_WIDGET_COCOA) + private: + Shmem mShSurface; + uint16_t mShWidth; + uint16_t mShHeight; + CGColorSpaceRef mShColorSpace; + RefPtr<MacIOSurface> mIOSurface; + RefPtr<MacIOSurface> mFrontIOSurface; +#endif // definied(MOZ_WIDGET_COCOA) + + // ObjectFrame layer wrapper + RefPtr<gfxASurface> mFrontSurface; + // For windowless+transparent instances, this surface contains a + // "pretty recent" copy of the pixels under its <object> frame. + // On the plugin side, we use this surface to avoid doing alpha + // recovery when possible. This surface is created and owned by + // the browser, but a "read-only" reference is sent to the plugin. + // + // We have explicitly chosen not to provide any guarantees about + // the consistency of the pixels in |mBackground|. A plugin may + // be able to observe partial updates to the background. + RefPtr<gfxASurface> mBackground; + + RefPtr<ImageContainer> mImageContainer; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // ifndef dom_plugins_PluginInstanceParent_h diff --git a/dom/plugins/ipc/PluginInterposeOSX.h b/dom/plugins/ipc/PluginInterposeOSX.h new file mode 100644 index 0000000000..2f10dbd6c7 --- /dev/null +++ b/dom/plugins/ipc/PluginInterposeOSX.h @@ -0,0 +1,137 @@ +// vim:set ts=2 sts=2 sw=2 et cin: +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H +#define DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H + +#include "base/basictypes.h" +#include "nsPoint.h" +#include "npapi.h" + +// Make this includable from non-Objective-C code. +#ifndef __OBJC__ +class NSCursor; +#else +# import <Cocoa/Cocoa.h> +#endif + +// The header file QuickdrawAPI.h is missing on OS X 10.7 and up (though the +// QuickDraw APIs defined in it are still present) -- so we need to supply the +// relevant parts of its contents here. It's likely that Apple will eventually +// remove the APIs themselves (probably in OS X 10.8), so we need to make them +// weak imports, and test for their presence before using them. +#if !defined(__QUICKDRAWAPI__) + +typedef short Bits16[16]; +struct Cursor { + Bits16 data; + Bits16 mask; + Point hotSpot; +}; +typedef struct Cursor Cursor; + +#endif /* __QUICKDRAWAPI__ */ + +namespace mac_plugin_interposing { + +// Class used to serialize NSCursor objects over IPC between processes. +class NSCursorInfo { + public: + enum Type { + TypeCustom, + TypeArrow, + TypeClosedHand, + TypeContextualMenu, // Only supported on OS X 10.6 and up + TypeCrosshair, + TypeDisappearingItem, + TypeDragCopy, // Only supported on OS X 10.6 and up + TypeDragLink, // Only supported on OS X 10.6 and up + TypeIBeam, + TypeNotAllowed, // Only supported on OS X 10.6 and up + TypeOpenHand, + TypePointingHand, + TypeResizeDown, + TypeResizeLeft, + TypeResizeLeftRight, + TypeResizeRight, + TypeResizeUp, + TypeResizeUpDown, + TypeTransparent // Special type + }; + + NSCursorInfo(); + explicit NSCursorInfo(NSCursor* aCursor); + explicit NSCursorInfo(const Cursor* aCursor); + ~NSCursorInfo(); + + NSCursor* GetNSCursor() const; + Type GetType() const; + const char* GetTypeName() const; + nsPoint GetHotSpot() const; + uint8_t* GetCustomImageData() const; + uint32_t GetCustomImageDataLength() const; + + void SetType(Type aType); + void SetHotSpot(nsPoint aHotSpot); + void SetCustomImageData(uint8_t* aData, uint32_t aDataLength); + + static bool GetNativeCursorsSupported(); + + private: + NSCursor* GetTransparentCursor() const; + + Type mType; + // The hot spot's coordinate system is the cursor's coordinate system, and + // has an upper-left origin (in both Cocoa and pre-Cocoa systems). + nsPoint mHotSpot; + uint8_t* mCustomImageData; + uint32_t mCustomImageDataLength; + static int32_t mNativeCursorsSupported; +}; + +namespace parent { + +void OnPluginShowWindow(uint32_t window_id, CGRect window_bounds, bool modal); +void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid); +void OnSetCursor(const NSCursorInfo& cursorInfo); +void OnShowCursor(bool show); +void OnPushCursor(const NSCursorInfo& cursorInfo); +void OnPopCursor(); + +} // namespace parent + +namespace child { + +void SetUpCocoaInterposing(); + +} // namespace child + +} // namespace mac_plugin_interposing + +#endif /* DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H */ diff --git a/dom/plugins/ipc/PluginInterposeOSX.mm b/dom/plugins/ipc/PluginInterposeOSX.mm new file mode 100644 index 0000000000..3f9f3ff747 --- /dev/null +++ b/dom/plugins/ipc/PluginInterposeOSX.mm @@ -0,0 +1,1040 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/basictypes.h" +#include "nsCocoaUtils.h" +#include "PluginModuleChild.h" +#include "nsDebug.h" +#include "PluginInterposeOSX.h" +#include <set> +#import <AppKit/AppKit.h> +#import <objc/runtime.h> +#import <Carbon/Carbon.h> + +using namespace mozilla::plugins; + +namespace mac_plugin_interposing { + +int32_t NSCursorInfo::mNativeCursorsSupported = -1; + +// This constructor may be called from the browser process or the plugin +// process. +NSCursorInfo::NSCursorInfo() + : mType(TypeArrow), + mHotSpot(nsPoint(0, 0)), + mCustomImageData(NULL), + mCustomImageDataLength(0) {} + +NSCursorInfo::NSCursorInfo(NSCursor* aCursor) + : mType(TypeArrow), mHotSpot(nsPoint(0, 0)), mCustomImageData(NULL), mCustomImageDataLength(0) { + // This constructor is only ever called from the plugin process, so the + // following is safe. + if (!GetNativeCursorsSupported()) { + return; + } + + NSPoint hotSpotCocoa = [aCursor hotSpot]; + mHotSpot = nsPoint(hotSpotCocoa.x, hotSpotCocoa.y); + + Class nsCursorClass = [NSCursor class]; + if ([aCursor isEqual:[NSCursor arrowCursor]]) { + mType = TypeArrow; + } else if ([aCursor isEqual:[NSCursor closedHandCursor]]) { + mType = TypeClosedHand; + } else if ([aCursor isEqual:[NSCursor crosshairCursor]]) { + mType = TypeCrosshair; + } else if ([aCursor isEqual:[NSCursor disappearingItemCursor]]) { + mType = TypeDisappearingItem; + } else if ([aCursor isEqual:[NSCursor IBeamCursor]]) { + mType = TypeIBeam; + } else if ([aCursor isEqual:[NSCursor openHandCursor]]) { + mType = TypeOpenHand; + } else if ([aCursor isEqual:[NSCursor pointingHandCursor]]) { + mType = TypePointingHand; + } else if ([aCursor isEqual:[NSCursor resizeDownCursor]]) { + mType = TypeResizeDown; + } else if ([aCursor isEqual:[NSCursor resizeLeftCursor]]) { + mType = TypeResizeLeft; + } else if ([aCursor isEqual:[NSCursor resizeLeftRightCursor]]) { + mType = TypeResizeLeftRight; + } else if ([aCursor isEqual:[NSCursor resizeRightCursor]]) { + mType = TypeResizeRight; + } else if ([aCursor isEqual:[NSCursor resizeUpCursor]]) { + mType = TypeResizeUp; + } else if ([aCursor isEqual:[NSCursor resizeUpDownCursor]]) { + mType = TypeResizeUpDown; + // The following cursor types are only supported on OS X 10.6 and up. + } else if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(contextualMenuCursor)]]) { + mType = TypeContextualMenu; + } else if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(dragCopyCursor)]]) { + mType = TypeDragCopy; + } else if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(dragLinkCursor)]]) { + mType = TypeDragLink; + } else if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)] && + [aCursor + isEqual:[nsCursorClass performSelector:@selector(operationNotAllowedCursor)]]) { + mType = TypeNotAllowed; + } else { + NSImage* image = [aCursor image]; + NSArray* reps = image ? [image representations] : nil; + NSUInteger repsCount = reps ? [reps count] : 0; + if (!repsCount) { + // If we have a custom cursor with no image representations, assume we + // need a transparent cursor. + mType = TypeTransparent; + } else { + CGImageRef cgImage = nil; + // XXX We don't know how to deal with a cursor that doesn't have a + // bitmap image representation. For now we fall back to an arrow + // cursor. + for (NSUInteger i = 0; i < repsCount; ++i) { + id rep = [reps objectAtIndex:i]; + if ([rep isKindOfClass:[NSBitmapImageRep class]]) { + cgImage = [(NSBitmapImageRep*)rep CGImage]; + break; + } + } + if (cgImage) { + CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); + if (data) { + CGImageDestinationRef dest = + ::CGImageDestinationCreateWithData(data, kUTTypePNG, 1, NULL); + if (dest) { + ::CGImageDestinationAddImage(dest, cgImage, NULL); + if (::CGImageDestinationFinalize(dest)) { + uint32_t dataLength = (uint32_t)::CFDataGetLength(data); + mCustomImageData = (uint8_t*)moz_xmalloc(dataLength); + ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData); + mCustomImageDataLength = dataLength; + mType = TypeCustom; + } + ::CFRelease(dest); + } + ::CFRelease(data); + } + } + if (!mCustomImageData) { + mType = TypeArrow; + } + } + } +} + +NSCursorInfo::NSCursorInfo(const Cursor* aCursor) + : mType(TypeArrow), mHotSpot(nsPoint(0, 0)), mCustomImageData(NULL), mCustomImageDataLength(0) { + // This constructor is only ever called from the plugin process, so the + // following is safe. + if (!GetNativeCursorsSupported()) { + return; + } + + mHotSpot = nsPoint(aCursor->hotSpot.h, aCursor->hotSpot.v); + + int width = 16, height = 16; + int bytesPerPixel = 4; + int rowBytes = width * bytesPerPixel; + int bitmapSize = height * rowBytes; + + bool isTransparent = true; + + uint8_t* bitmap = (uint8_t*)moz_xmalloc(bitmapSize); + // The way we create 'bitmap' is largely "borrowed" from Chrome's + // WebCursor::InitFromCursor(). + for (int y = 0; y < height; ++y) { + unsigned short data = aCursor->data[y]; + unsigned short mask = aCursor->mask[y]; + // Change 'data' and 'mask' from big-endian to little-endian, but output + // big-endian data below. + data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF); + mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF); + // It'd be nice to use a gray-scale bitmap. But + // CGBitmapContextCreateImage() (used below) won't work with one that also + // has alpha values. + for (int x = 0; x < width; ++x) { + int offset = (y * rowBytes) + (x * bytesPerPixel); + // Color value + if (data & 0x8000) { + bitmap[offset] = 0x0; + bitmap[offset + 1] = 0x0; + bitmap[offset + 2] = 0x0; + } else { + bitmap[offset] = 0xFF; + bitmap[offset + 1] = 0xFF; + bitmap[offset + 2] = 0xFF; + } + // Mask value + if (mask & 0x8000) { + bitmap[offset + 3] = 0xFF; + isTransparent = false; + } else { + bitmap[offset + 3] = 0x0; + } + data <<= 1; + mask <<= 1; + } + } + + if (isTransparent) { + // If aCursor is transparent, we don't need to serialize custom cursor + // data over IPC. + mType = TypeTransparent; + } else { + CGColorSpaceRef color = ::CGColorSpaceCreateDeviceRGB(); + if (color) { + CGContextRef context = + ::CGBitmapContextCreate(bitmap, width, height, 8, rowBytes, color, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + if (context) { + CGImageRef image = ::CGBitmapContextCreateImage(context); + if (image) { + ::CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); + if (data) { + CGImageDestinationRef dest = + ::CGImageDestinationCreateWithData(data, kUTTypePNG, 1, NULL); + if (dest) { + ::CGImageDestinationAddImage(dest, image, NULL); + if (::CGImageDestinationFinalize(dest)) { + uint32_t dataLength = (uint32_t)::CFDataGetLength(data); + mCustomImageData = (uint8_t*)moz_xmalloc(dataLength); + ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData); + mCustomImageDataLength = dataLength; + mType = TypeCustom; + } + ::CFRelease(dest); + } + ::CFRelease(data); + } + ::CGImageRelease(image); + } + ::CGContextRelease(context); + } + ::CGColorSpaceRelease(color); + } + } + + free(bitmap); +} + +NSCursorInfo::~NSCursorInfo() { + if (mCustomImageData) { + free(mCustomImageData); + } +} + +NSCursor* NSCursorInfo::GetNSCursor() const { + NSCursor* retval = nil; + + Class nsCursorClass = [NSCursor class]; + switch (mType) { + case TypeArrow: + retval = [NSCursor arrowCursor]; + break; + case TypeClosedHand: + retval = [NSCursor closedHandCursor]; + break; + case TypeCrosshair: + retval = [NSCursor crosshairCursor]; + break; + case TypeDisappearingItem: + retval = [NSCursor disappearingItemCursor]; + break; + case TypeIBeam: + retval = [NSCursor IBeamCursor]; + break; + case TypeOpenHand: + retval = [NSCursor openHandCursor]; + break; + case TypePointingHand: + retval = [NSCursor pointingHandCursor]; + break; + case TypeResizeDown: + retval = [NSCursor resizeDownCursor]; + break; + case TypeResizeLeft: + retval = [NSCursor resizeLeftCursor]; + break; + case TypeResizeLeftRight: + retval = [NSCursor resizeLeftRightCursor]; + break; + case TypeResizeRight: + retval = [NSCursor resizeRightCursor]; + break; + case TypeResizeUp: + retval = [NSCursor resizeUpCursor]; + break; + case TypeResizeUpDown: + retval = [NSCursor resizeUpDownCursor]; + break; + // The following four cursor types are only supported on OS X 10.6 and up. + case TypeContextualMenu: { + if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)]) { + retval = [nsCursorClass performSelector:@selector(contextualMenuCursor)]; + } + break; + } + case TypeDragCopy: { + if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)]) { + retval = [nsCursorClass performSelector:@selector(dragCopyCursor)]; + } + break; + } + case TypeDragLink: { + if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)]) { + retval = [nsCursorClass performSelector:@selector(dragLinkCursor)]; + } + break; + } + case TypeNotAllowed: { + if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)]) { + retval = [nsCursorClass performSelector:@selector(operationNotAllowedCursor)]; + } + break; + } + case TypeTransparent: + retval = GetTransparentCursor(); + break; + default: + break; + } + + if (!retval && mCustomImageData && mCustomImageDataLength) { + CGDataProviderRef provider = ::CGDataProviderCreateWithData(NULL, (const void*)mCustomImageData, + mCustomImageDataLength, NULL); + if (provider) { + CGImageRef cgImage = + ::CGImageCreateWithPNGDataProvider(provider, NULL, false, kCGRenderingIntentDefault); + if (cgImage) { + NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; + if (rep) { + NSImage* image = [[NSImage alloc] init]; + if (image) { + [image addRepresentation:rep]; + retval = + [[[NSCursor alloc] initWithImage:image + hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] autorelease]; + [image release]; + } + [rep release]; + } + ::CGImageRelease(cgImage); + } + ::CFRelease(provider); + } + } + + // Fall back to an arrow cursor if need be. + if (!retval) { + retval = [NSCursor arrowCursor]; + } + + return retval; +} + +// Get a transparent cursor with the appropriate hot spot. We need one if +// (for example) we have a custom cursor with no image data. +NSCursor* NSCursorInfo::GetTransparentCursor() const { + NSCursor* retval = nil; + + int width = 16, height = 16; + int bytesPerPixel = 2; + int rowBytes = width * bytesPerPixel; + int dataSize = height * rowBytes; + + uint8_t* data = (uint8_t*)moz_xmalloc(dataSize); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + int offset = (y * rowBytes) + (x * bytesPerPixel); + data[offset] = 0x7E; // Arbitrary gray-scale value + data[offset + 1] = 0; // Alpha value to make us transparent + } + } + + NSBitmapImageRep* imageRep = + [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:2 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedWhiteColorSpace + bytesPerRow:rowBytes + bitsPerPixel:16] autorelease]; + if (imageRep) { + uint8_t* repDataPtr = [imageRep bitmapData]; + if (repDataPtr) { + memcpy(repDataPtr, data, dataSize); + NSImage* image = [[[NSImage alloc] initWithSize:NSMakeSize(width, height)] autorelease]; + if (image) { + [image addRepresentation:imageRep]; + retval = [[[NSCursor alloc] initWithImage:image + hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] autorelease]; + } + } + } + + free(data); + + // Fall back to an arrow cursor if (for some reason) the above code failed. + if (!retval) { + retval = [NSCursor arrowCursor]; + } + + return retval; +} + +NSCursorInfo::Type NSCursorInfo::GetType() const { return mType; } + +const char* NSCursorInfo::GetTypeName() const { + switch (mType) { + case TypeCustom: + return "TypeCustom"; + case TypeArrow: + return "TypeArrow"; + case TypeClosedHand: + return "TypeClosedHand"; + case TypeContextualMenu: + return "TypeContextualMenu"; + case TypeCrosshair: + return "TypeCrosshair"; + case TypeDisappearingItem: + return "TypeDisappearingItem"; + case TypeDragCopy: + return "TypeDragCopy"; + case TypeDragLink: + return "TypeDragLink"; + case TypeIBeam: + return "TypeIBeam"; + case TypeNotAllowed: + return "TypeNotAllowed"; + case TypeOpenHand: + return "TypeOpenHand"; + case TypePointingHand: + return "TypePointingHand"; + case TypeResizeDown: + return "TypeResizeDown"; + case TypeResizeLeft: + return "TypeResizeLeft"; + case TypeResizeLeftRight: + return "TypeResizeLeftRight"; + case TypeResizeRight: + return "TypeResizeRight"; + case TypeResizeUp: + return "TypeResizeUp"; + case TypeResizeUpDown: + return "TypeResizeUpDown"; + case TypeTransparent: + return "TypeTransparent"; + default: + break; + } + return "TypeUnknown"; +} + +nsPoint NSCursorInfo::GetHotSpot() const { return mHotSpot; } + +uint8_t* NSCursorInfo::GetCustomImageData() const { return mCustomImageData; } + +uint32_t NSCursorInfo::GetCustomImageDataLength() const { return mCustomImageDataLength; } + +void NSCursorInfo::SetType(Type aType) { mType = aType; } + +void NSCursorInfo::SetHotSpot(nsPoint aHotSpot) { mHotSpot = aHotSpot; } + +void NSCursorInfo::SetCustomImageData(uint8_t* aData, uint32_t aDataLength) { + if (mCustomImageData) { + free(mCustomImageData); + } + if (aDataLength) { + mCustomImageData = (uint8_t*)moz_xmalloc(aDataLength); + memcpy(mCustomImageData, aData, aDataLength); + } else { + mCustomImageData = NULL; + } + mCustomImageDataLength = aDataLength; +} + +// This should never be called from the browser process -- only from the +// plugin process. +bool NSCursorInfo::GetNativeCursorsSupported() { + if (mNativeCursorsSupported == -1) { + ENSURE_PLUGIN_THREAD(false); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + bool result = pmc->GetNativeCursorsSupported(); + if (result) { + mNativeCursorsSupported = 1; + } else { + mNativeCursorsSupported = 0; + } + } + } + return (mNativeCursorsSupported == 1); +} + +} // namespace mac_plugin_interposing + +namespace mac_plugin_interposing { +namespace parent { + +// Tracks plugin windows currently visible. +std::set<uint32_t> plugin_visible_windows_set_; +// Tracks full screen windows currently visible. +std::set<uint32_t> plugin_fullscreen_windows_set_; +// Tracks modal windows currently visible. +std::set<uint32_t> plugin_modal_windows_set_; + +void OnPluginShowWindow(uint32_t window_id, CGRect window_bounds, bool modal) { + plugin_visible_windows_set_.insert(window_id); + + if (modal) plugin_modal_windows_set_.insert(window_id); + + CGRect main_display_bounds = ::CGDisplayBounds(CGMainDisplayID()); + + if (CGRectEqualToRect(window_bounds, main_display_bounds) && + (plugin_fullscreen_windows_set_.find(window_id) == plugin_fullscreen_windows_set_.end())) { + plugin_fullscreen_windows_set_.insert(window_id); + + nsCocoaUtils::HideOSChromeOnScreen(true); + } +} + +static void ActivateProcess(pid_t pid) { + ProcessSerialNumber process; + OSStatus status = ::GetProcessForPID(pid, &process); + + if (status == noErr) { + SetFrontProcess(&process); + } else { + NS_WARNING("Unable to get process for pid."); + } +} + +// Must be called on the UI thread. +// If plugin_pid is -1, the browser will be the active process on return, +// otherwise that process will be given focus back before this function returns. +static void ReleasePluginFullScreen(pid_t plugin_pid) { + // Releasing full screen only works if we are the frontmost process; grab + // focus, but give it back to the plugin process if requested. + ActivateProcess(base::GetCurrentProcId()); + + nsCocoaUtils::HideOSChromeOnScreen(false); + + if (plugin_pid != -1) { + ActivateProcess(plugin_pid); + } +} + +void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid) { + bool had_windows = !plugin_visible_windows_set_.empty(); + plugin_visible_windows_set_.erase(window_id); + bool browser_needs_activation = had_windows && plugin_visible_windows_set_.empty(); + + plugin_modal_windows_set_.erase(window_id); + if (plugin_fullscreen_windows_set_.find(window_id) != plugin_fullscreen_windows_set_.end()) { + plugin_fullscreen_windows_set_.erase(window_id); + pid_t plugin_pid = browser_needs_activation ? -1 : aPluginPid; + browser_needs_activation = false; + ReleasePluginFullScreen(plugin_pid); + } + + if (browser_needs_activation) { + ActivateProcess(getpid()); + } +} + +void OnSetCursor(const NSCursorInfo& cursorInfo) { + NSCursor* aCursor = cursorInfo.GetNSCursor(); + if (aCursor) { + [aCursor set]; + } +} + +void OnShowCursor(bool show) { + if (show) { + [NSCursor unhide]; + } else { + [NSCursor hide]; + } +} + +void OnPushCursor(const NSCursorInfo& cursorInfo) { + NSCursor* aCursor = cursorInfo.GetNSCursor(); + if (aCursor) { + [aCursor push]; + } +} + +void OnPopCursor() { [NSCursor pop]; } + +} // namespace parent +} // namespace mac_plugin_interposing + +namespace mac_plugin_interposing { +namespace child { + +// TODO(stuartmorgan): Make this an IPC to order the plugin process above the +// browser process only if the browser is current frontmost. +void FocusPluginProcess() { + ProcessSerialNumber this_process, front_process; + if ((GetCurrentProcess(&this_process) != noErr) || (GetFrontProcess(&front_process) != noErr)) { + return; + } + + Boolean matched = false; + if ((SameProcess(&this_process, &front_process, &matched) == noErr) && !matched) { + SetFrontProcess(&this_process); + } +} + +void NotifyBrowserOfPluginShowWindow(uint32_t window_id, CGRect bounds, bool modal) { + ENSURE_PLUGIN_THREAD_VOID(); + + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) pmc->PluginShowWindow(window_id, modal, bounds); +} + +void NotifyBrowserOfPluginHideWindow(uint32_t window_id, CGRect bounds) { + ENSURE_PLUGIN_THREAD_VOID(); + + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) pmc->PluginHideWindow(window_id); +} + +void NotifyBrowserOfSetCursor(NSCursorInfo& aCursorInfo) { + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->SetCursor(aCursorInfo); + } +} + +void NotifyBrowserOfShowCursor(bool show) { + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->ShowCursor(show); + } +} + +void NotifyBrowserOfPushCursor(NSCursorInfo& aCursorInfo) { + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->PushCursor(aCursorInfo); + } +} + +void NotifyBrowserOfPopCursor() { + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->PopCursor(); + } +} + +struct WindowInfo { + uint32_t window_id; + CGRect bounds; + explicit WindowInfo(NSWindow* aWindow) { + NSInteger window_num = [aWindow windowNumber]; + window_id = window_num > 0 ? window_num : 0; + bounds = NSRectToCGRect([aWindow frame]); + } +}; + +static void OnPluginWindowClosed(const WindowInfo& window_info) { + if (window_info.window_id == 0) return; + mac_plugin_interposing::child::NotifyBrowserOfPluginHideWindow(window_info.window_id, + window_info.bounds); +} + +static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) { + // The window id is 0 if it has never been shown (including while it is the + // process of being shown for the first time); when that happens, we'll catch + // it in _setWindowNumber instead. + static BOOL s_pending_display_is_modal = NO; + if (window_info.window_id == 0) { + if (is_modal) s_pending_display_is_modal = YES; + return; + } + if (s_pending_display_is_modal) { + is_modal = YES; + s_pending_display_is_modal = NO; + } + mac_plugin_interposing::child::NotifyBrowserOfPluginShowWindow(window_info.window_id, + window_info.bounds, is_modal); +} + +static BOOL OnSetCursor(NSCursorInfo& aInfo) { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfSetCursor(aInfo); + return YES; + } + return NO; +} + +static BOOL OnHideCursor() { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfShowCursor(NO); + return YES; + } + return NO; +} + +static BOOL OnUnhideCursor() { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfShowCursor(YES); + return YES; + } + return NO; +} + +static BOOL OnPushCursor(NSCursorInfo& aInfo) { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfPushCursor(aInfo); + return YES; + } + return NO; +} + +static BOOL OnPopCursor() { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfPopCursor(); + return YES; + } + return NO; +} + +} // namespace child +} // namespace mac_plugin_interposing + +using namespace mac_plugin_interposing::child; + +@interface NSWindow (PluginInterposing) +- (void)pluginInterpose_orderOut:(id)sender; +- (void)pluginInterpose_orderFront:(id)sender; +- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender; +- (void)pluginInterpose_setWindowNumber:(NSInteger)num; +@end + +@implementation NSWindow (PluginInterposing) + +- (void)pluginInterpose_orderOut:(id)sender { + WindowInfo window_info(self); + [self pluginInterpose_orderOut:sender]; + OnPluginWindowClosed(window_info); +} + +- (void)pluginInterpose_orderFront:(id)sender { + mac_plugin_interposing::child::FocusPluginProcess(); + [self pluginInterpose_orderFront:sender]; + OnPluginWindowShown(WindowInfo(self), NO); +} + +- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender { + mac_plugin_interposing::child::FocusPluginProcess(); + [self pluginInterpose_makeKeyAndOrderFront:sender]; + OnPluginWindowShown(WindowInfo(self), NO); +} + +- (void)pluginInterpose_setWindowNumber:(NSInteger)num { + if (num > 0) mac_plugin_interposing::child::FocusPluginProcess(); + [self pluginInterpose_setWindowNumber:num]; + if (num > 0) OnPluginWindowShown(WindowInfo(self), NO); +} + +@end + +@interface NSApplication (PluginInterposing) +- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window; +@end + +@implementation NSApplication (PluginInterposing) + +- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window { + mac_plugin_interposing::child::FocusPluginProcess(); + // This is out-of-order relative to the other calls, but runModalForWindow: + // won't return until the window closes, and the order only matters for + // full-screen windows. + OnPluginWindowShown(WindowInfo(window), YES); + return [self pluginInterpose_runModalForWindow:window]; +} + +@end + +// Hook commands to manipulate the current cursor, so that they can be passed +// from the child process to the parent process. These commands have no +// effect unless they're performed in the parent process. +@interface NSCursor (PluginInterposing) +- (void)pluginInterpose_set; +- (void)pluginInterpose_push; +- (void)pluginInterpose_pop; ++ (NSCursor*)pluginInterpose_currentCursor; ++ (void)pluginInterpose_hide; ++ (void)pluginInterpose_unhide; ++ (void)pluginInterpose_pop; +@end + +// Cache the results of [NSCursor set], [NSCursor push] and [NSCursor pop]. +// The last element is always the current cursor. +static NSMutableArray* gCursorStack = nil; + +static BOOL initCursorStack() { + if (!gCursorStack) { + gCursorStack = [[NSMutableArray arrayWithCapacity:5] retain]; + } + return (gCursorStack != NULL); +} + +static NSCursor* currentCursorFromCache() { + if (!initCursorStack()) return nil; + return (NSCursor*)[gCursorStack lastObject]; +} + +static void setCursorInCache(NSCursor* aCursor) { + if (!initCursorStack() || !aCursor) return; + NSUInteger count = [gCursorStack count]; + if (count) { + [gCursorStack replaceObjectAtIndex:count - 1 withObject:aCursor]; + } else { + [gCursorStack addObject:aCursor]; + } +} + +static void pushCursorInCache(NSCursor* aCursor) { + if (!initCursorStack() || !aCursor) return; + [gCursorStack addObject:aCursor]; +} + +static void popCursorInCache() { + if (!initCursorStack()) return; + // Apple's doc on the +[NSCursor pop] method says: "If the current cursor + // is the only cursor on the stack, this method does nothing." + if ([gCursorStack count] > 1) { + [gCursorStack removeLastObject]; + } +} + +@implementation NSCursor (PluginInterposing) + +- (void)pluginInterpose_set { + NSCursorInfo info(self); + OnSetCursor(info); + setCursorInCache(self); + [self pluginInterpose_set]; +} + +- (void)pluginInterpose_push { + NSCursorInfo info(self); + OnPushCursor(info); + pushCursorInCache(self); + [self pluginInterpose_push]; +} + +- (void)pluginInterpose_pop { + OnPopCursor(); + popCursorInCache(); + [self pluginInterpose_pop]; +} + +// The currentCursor method always returns nil when running in a background +// process. But this may confuse plugins (notably Flash, see bug 621117). So +// if we get a nil return from the "call to super", we return a cursor that's +// been cached by previous calls to set or push. According to Apple's docs, +// currentCursor "only returns the cursor set by your application using +// NSCursor methods". So we don't need to worry about changes to the cursor +// made by other methods like SetThemeCursor(). ++ (NSCursor*)pluginInterpose_currentCursor { + NSCursor* retval = [self pluginInterpose_currentCursor]; + if (!retval) { + retval = currentCursorFromCache(); + } + return retval; +} + ++ (void)pluginInterpose_hide { + OnHideCursor(); + [self pluginInterpose_hide]; +} + ++ (void)pluginInterpose_unhide { + OnUnhideCursor(); + [self pluginInterpose_unhide]; +} + ++ (void)pluginInterpose_pop { + OnPopCursor(); + popCursorInCache(); + [self pluginInterpose_pop]; +} + +@end + +static void ExchangeMethods(Class target_class, BOOL class_method, SEL original, SEL replacement) { + Method m1; + Method m2; + if (class_method) { + m1 = class_getClassMethod(target_class, original); + m2 = class_getClassMethod(target_class, replacement); + } else { + m1 = class_getInstanceMethod(target_class, original); + m2 = class_getInstanceMethod(target_class, replacement); + } + + if (m1 == m2) return; + + if (m1 && m2) + method_exchangeImplementations(m1, m2); + else + MOZ_ASSERT_UNREACHABLE("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 0000000000..ce8cc3af17 --- /dev/null +++ b/dom/plugins/ipc/PluginLibrary.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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 "nsStringFwd.h" +#include "nsTArray.h" +#include "nsError.h" +#include "mozilla/EventForwards.h" +#include "nsSize.h" +#include "nsRect.h" + +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(nsTArray<nsCString>& result) = 0; + NS_DECLARE_STATIC_IID_ACCESSOR(nsIGetSitesWithDataCallback_CID) +}; +NS_DEFINE_STATIC_IID_ACCESSOR(nsIGetSitesWithDataCallback, + nsIGetSitesWithDataCallback_CID) + +namespace mozilla { + +class PluginLibrary { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + virtual ~PluginLibrary() = default; + + /** + * 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) + 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, int16_t argc, + char* argn[], char* argv[], NPSavedData* saved, + NPError* error) = 0; + + virtual nsresult NPP_ClearSiteData( + const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr<nsIClearSiteDataCallback> callback) = 0; + virtual nsresult NPP_GetSitesWithData( + nsCOMPtr<nsIGetSitesWithDataCallback> callback) = 0; + + virtual nsresult AsyncSetWindow(NPP instance, NPWindow* window) = 0; + virtual nsresult GetImageContainer( + NPP instance, mozilla::layers::ImageContainer** aContainer) = 0; + virtual nsresult GetImageSize(NPP instance, nsIntSize* aSize) = 0; + virtual void DidComposite(NPP instance) = 0; + virtual bool IsOOP() = 0; +#if defined(XP_MACOSX) + virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, + bool* aDrawing) = 0; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + virtual nsresult ContentsScaleFactorChanged(NPP instance, + double aContentsScaleFactor) = 0; +#endif +#if defined(XP_WIN) + virtual nsresult GetScrollCaptureContainer( + NPP aInstance, mozilla::layers::ImageContainer** aContainer) = 0; +#endif + virtual nsresult HandledWindowedPluginKeyEvent( + NPP aInstance, const mozilla::NativeEventData& aNativeKeyData, + bool aIsCOnsumed) = 0; + + /** + * The next three methods are the third leg in the trip to + * PluginInstanceParent. They approximately follow the ReadbackSink + * API. + */ + virtual nsresult SetBackgroundUnknown(NPP instance) = 0; + virtual nsresult BeginUpdateBackground(NPP instance, const nsIntRect&, + DrawTarget**) = 0; + virtual nsresult EndUpdateBackground(NPP instance, const nsIntRect&) = 0; + virtual nsresult GetRunID(uint32_t* aRunID) = 0; + virtual void SetHasLocalInstance() = 0; +}; + +} // namespace mozilla + +#endif // ifndef mozilla_PluginLibrary_h diff --git a/dom/plugins/ipc/PluginMessageUtils.cpp b/dom/plugins/ipc/PluginMessageUtils.cpp new file mode 100644 index 0000000000..4d639b6f39 --- /dev/null +++ b/dom/plugins/ipc/PluginMessageUtils.cpp @@ -0,0 +1,141 @@ +/* -*- 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 "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) + : Runnable("DeferNPObjectReleaseRunnable"), mFuncs(f), mObject(o) { + NS_ASSERTION(o, "no release null objects"); + } + + NS_IMETHOD Run() override; + + private: + const NPNetscapeFuncs* mFuncs; + NPObject* mObject; +}; + +NS_IMETHODIMP +DeferNPObjectReleaseRunnable::Run() { + mFuncs->releaseobject(mObject); + return NS_OK; +} + +} // namespace + +namespace mozilla::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) || defined(OS_SOLARIS) +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) || defined(OS_SOLARIS) + // 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) || defined(OS_SOLARIS) + 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 mozilla::plugins diff --git a/dom/plugins/ipc/PluginMessageUtils.h b/dom/plugins/ipc/PluginMessageUtils.h new file mode 100644 index 0000000000..c21aa10966 --- /dev/null +++ b/dom/plugins/ipc/PluginMessageUtils.h @@ -0,0 +1,662 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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/EnumSerializer.h" +#include "base/message_loop.h" +#include "base/shared_memory.h" + +#include "mozilla/ipc/CrossProcessMutex.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/UniquePtr.h" +#include "gfxipc/SurfaceDescriptor.h" + +#include "npapi.h" +#include "npruntime.h" +#include "npfunctions.h" +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/Logging.h" +#include "nsHashKeys.h" + +#ifdef XP_MACOSX +# include "PluginInterposeOSX.h" +#else +namespace mac_plugin_interposing { +class NSCursorInfo {}; +} // namespace mac_plugin_interposing +#endif +using mac_plugin_interposing::NSCursorInfo; + +namespace mozilla { +namespace plugins { + +using layers::SurfaceDescriptorX11; + +enum ScriptableObjectType { LocalObject, Proxy }; + +mozilla::ipc::RacyInterruptPolicy MediateRace( + const mozilla::ipc::MessageChannel::MessageInfo& parent, + const mozilla::ipc::MessageChannel::MessageInfo& child); + +std::string MungePluginDsoPath(const std::string& path); +std::string UnmungePluginDsoPath(const std::string& munged); + +extern mozilla::LogModule* GetPluginLog(); + +#if defined(_MSC_VER) +# define FULLFUNCTION __FUNCSIG__ +#elif defined(__GNUC__) +# define FULLFUNCTION __PRETTY_FUNCTION__ +#else +# define FULLFUNCTION __FUNCTION__ +#endif + +#define PLUGIN_LOG_DEBUG(args) \ + MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, args) +#define PLUGIN_LOG_DEBUG_FUNCTION \ + MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, ("%s", FULLFUNCTION)) +#define PLUGIN_LOG_DEBUG_METHOD \ + MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, \ + ("%s [%p]", FULLFUNCTION, (void*)this)) + +/** + * This is NPByteRange without the linked list. + */ +struct IPCByteRange { + int32_t offset; + uint32_t length; +}; + +typedef nsTArray<IPCByteRange> IPCByteRanges; + +typedef nsCString Buffer; + +struct NPRemoteWindow { + NPRemoteWindow(); + uint64_t window; + int32_t x; + int32_t y; + uint32_t width; + uint32_t height; + NPRect clipRect; + NPWindowType type; +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + VisualID visualID; + Colormap colormap; +#endif /* XP_UNIX */ +#if defined(XP_MACOSX) || defined(XP_WIN) + double contentsScaleFactor; +#endif +}; + +// This struct is like NPAudioDeviceChangeDetails, only it uses a +// std::wstring instead of a const wchar_t* for the defaultDevice. +// This gives us the necessary memory-ownership semantics without +// requiring C++ objects in npapi.h. +struct NPAudioDeviceChangeDetailsIPC { + int32_t flow; + int32_t role; + std::wstring defaultDevice; +}; + +struct NPAudioDeviceStateChangedIPC { + std::wstring device; + uint32_t state; +}; + +#ifdef XP_WIN +typedef HWND NativeWindowHandle; +#elif defined(MOZ_X11) +typedef XID NativeWindowHandle; +#elif defined(XP_DARWIN) || defined(ANDROID) || defined(MOZ_WAYLAND) +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 // XP_WIN +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 VoidCString(); + } + return nsCString(aString); +} + +inline const char* NullableStringGet(const nsCString& str) { + if (str.IsVoid()) return nullptr; + + return str.get(); +} + +struct DeletingObjectEntry : public nsPtrHashKey<NPObject> { + explicit DeletingObjectEntry(const NPObject* key) + : nsPtrHashKey<NPObject>(key), mDeleted(false) {} + + bool mDeleted; +}; + +} /* namespace plugins */ + +} /* namespace mozilla */ + +namespace IPC { + +template <> +struct ParamTraits<NPRect> { + typedef NPRect paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.top); + WriteParam(aMsg, aParam.left); + WriteParam(aMsg, aParam.bottom); + WriteParam(aMsg, aParam.right); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint16_t top, left, bottom, right; + if (ReadParam(aMsg, aIter, &top) && ReadParam(aMsg, aIter, &left) && + ReadParam(aMsg, aIter, &bottom) && ReadParam(aMsg, aIter, &right)) { + aResult->top = top; + aResult->left = left; + aResult->bottom = bottom; + aResult->right = right; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%u, %u, %u, %u]", aParam.top, aParam.left, + aParam.bottom, aParam.right)); + } +}; + +template <> +struct ParamTraits<NPWindowType> + : public ContiguousEnumSerializerInclusive< + NPWindowType, NPWindowType::NPWindowTypeWindow, + NPWindowType::NPWindowTypeDrawable> {}; + +template <> +struct ParamTraits<mozilla::plugins::NPRemoteWindow> { + typedef mozilla::plugins::NPRemoteWindow paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteUInt64(aParam.window); + WriteParam(aMsg, aParam.x); + WriteParam(aMsg, aParam.y); + WriteParam(aMsg, aParam.width); + WriteParam(aMsg, aParam.height); + WriteParam(aMsg, aParam.clipRect); + WriteParam(aMsg, aParam.type); +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + aMsg->WriteULong(aParam.visualID); + aMsg->WriteULong(aParam.colormap); +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + aMsg->WriteDouble(aParam.contentsScaleFactor); +#endif + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint64_t window; + int32_t x, y; + uint32_t width, height; + NPRect clipRect; + NPWindowType type; + if (!(aMsg->ReadUInt64(aIter, &window) && ReadParam(aMsg, aIter, &x) && + ReadParam(aMsg, aIter, &y) && ReadParam(aMsg, aIter, &width) && + ReadParam(aMsg, aIter, &height) && + ReadParam(aMsg, aIter, &clipRect) && ReadParam(aMsg, aIter, &type))) + return false; + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + unsigned long visualID; + unsigned long colormap; + if (!(aMsg->ReadULong(aIter, &visualID) && + aMsg->ReadULong(aIter, &colormap))) + return false; +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + double contentsScaleFactor; + if (!aMsg->ReadDouble(aIter, &contentsScaleFactor)) return false; +#endif + + aResult->window = window; + aResult->x = x; + aResult->y = y; + aResult->width = width; + aResult->height = height; + aResult->clipRect = clipRect; + aResult->type = type; +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + aResult->visualID = visualID; + aResult->colormap = colormap; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + aResult->contentsScaleFactor = contentsScaleFactor; +#endif + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%u, %d, %d, %u, %u, %d", + (unsigned long)aParam.window, aParam.x, aParam.y, + aParam.width, aParam.height, (long)aParam.type)); + } +}; + +#ifdef XP_MACOSX +template <> +struct ParamTraits<NPNSString*> { + // 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, NPNSString* 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, + NPNSString** aResult) { + bool haveString = false; + if (!aMsg->ReadBool(aIter, &haveString)) { + return false; + } + if (!haveString) { + *aResult = nullptr; + return true; + } + + long length; + if (!ReadParam(aMsg, aIter, &length)) { + return false; + } + + // Avoid integer multiplication overflow. + if (length > INT_MAX / static_cast<long>(sizeof(UniChar))) { + return false; + } + + auto chars = mozilla::MakeUnique<UniChar[]>(length); + if (length != 0) { + if (!aMsg->ReadBytesInto(aIter, chars.get(), length * sizeof(UniChar))) { + return false; + } + } + + *aResult = (NPNSString*)::CFStringCreateWithBytes( + kCFAllocatorDefault, (UInt8*)chars.get(), length * sizeof(UniChar), + kCFStringEncodingUTF16, false); + if (!*aResult) { + return false; + } + + return true; + } +}; +#endif + +#ifdef XP_MACOSX +template <> +struct ParamTraits<NSCursorInfo> { + typedef NSCursorInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + NSCursorInfo::Type type = aParam.GetType(); + + aMsg->WriteInt(type); + + nsPoint hotSpot = aParam.GetHotSpot(); + WriteParam(aMsg, hotSpot.x); + WriteParam(aMsg, hotSpot.y); + + uint32_t dataLength = aParam.GetCustomImageDataLength(); + WriteParam(aMsg, dataLength); + if (dataLength == 0) { + return; + } + + uint8_t* buffer = (uint8_t*)moz_xmalloc(dataLength); + memcpy(buffer, aParam.GetCustomImageData(), dataLength); + aMsg->WriteBytes(buffer, dataLength); + free(buffer); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + NSCursorInfo::Type type; + if (!aMsg->ReadInt(aIter, (int*)&type)) { + return false; + } + + nscoord hotSpotX, hotSpotY; + if (!ReadParam(aMsg, aIter, &hotSpotX) || + !ReadParam(aMsg, aIter, &hotSpotY)) { + return false; + } + + uint32_t dataLength; + if (!ReadParam(aMsg, aIter, &dataLength)) { + return false; + } + + auto data = mozilla::MakeUnique<uint8_t[]>(dataLength); + if (dataLength != 0) { + if (!aMsg->ReadBytesInto(aIter, data.get(), dataLength)) { + return false; + } + } + + aResult->SetType(type); + aResult->SetHotSpot(nsPoint(hotSpotX, hotSpotY)); + aResult->SetCustomImageData(data.get(), dataLength); + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + const char* typeName = aParam.GetTypeName(); + nsPoint hotSpot = aParam.GetHotSpot(); + int hotSpotX, hotSpotY; +# ifdef NS_COORD_IS_FLOAT + hotSpotX = rint(hotSpot.x); + hotSpotY = rint(hotSpot.y); +# else + hotSpotX = hotSpot.x; + hotSpotY = hotSpot.y; +# endif + uint32_t dataLength = aParam.GetCustomImageDataLength(); + uint8_t* data = aParam.GetCustomImageData(); + + aLog->append(StringPrintf(L"[%s, (%i %i), %u, %p]", typeName, hotSpotX, + hotSpotY, dataLength, data)); + } +}; +#else +template <> +struct ParamTraits<NSCursorInfo> { + typedef NSCursorInfo paramType; + static void Write(Message* aMsg, const paramType& aParam) { + MOZ_CRASH("NSCursorInfo isn't meaningful on this platform"); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + MOZ_CRASH("NSCursorInfo isn't meaningful on this platform"); + return false; + } +}; +#endif // #ifdef XP_MACOSX + +template <> +struct ParamTraits<mozilla::plugins::IPCByteRange> { + typedef mozilla::plugins::IPCByteRange paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.offset); + WriteParam(aMsg, aParam.length); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + paramType p; + if (ReadParam(aMsg, aIter, &p.offset) && + ReadParam(aMsg, aIter, &p.length)) { + *aResult = p; + return true; + } + return false; + } +}; + +template <> +struct ParamTraits<NPNVariable> + : public ContiguousEnumSerializer<NPNVariable, NPNVariable::NPNVxDisplay, + NPNVariable::NPNVLast> {}; + +// The only accepted value is NPNURLVariable::NPNURLVProxy +template <> +struct ParamTraits<NPNURLVariable> + : public ContiguousEnumSerializerInclusive<NPNURLVariable, + NPNURLVariable::NPNURLVProxy, + NPNURLVariable::NPNURLVProxy> {}; + +template <> +struct ParamTraits<NPCoordinateSpace> + : public ContiguousEnumSerializerInclusive< + NPCoordinateSpace, NPCoordinateSpace::NPCoordinateSpacePlugin, + NPCoordinateSpace::NPCoordinateSpaceFlippedScreen> {}; + +template <> +struct ParamTraits<mozilla::plugins::NPAudioDeviceChangeDetailsIPC> { + typedef mozilla::plugins::NPAudioDeviceChangeDetailsIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.flow); + WriteParam(aMsg, aParam.role); + WriteParam(aMsg, aParam.defaultDevice); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + int32_t flow, role; + std::wstring defaultDevice; + if (ReadParam(aMsg, aIter, &flow) && ReadParam(aMsg, aIter, &role) && + ReadParam(aMsg, aIter, &defaultDevice)) { + aResult->flow = flow; + aResult->role = role; + aResult->defaultDevice = defaultDevice; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%d, %d, %S]", aParam.flow, aParam.role, + aParam.defaultDevice.c_str())); + } +}; + +template <> +struct ParamTraits<mozilla::plugins::NPAudioDeviceStateChangedIPC> { + typedef mozilla::plugins::NPAudioDeviceStateChangedIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.device); + WriteParam(aMsg, aParam.state); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + int32_t state; + std::wstring device; + if (ReadParam(aMsg, aIter, &device) && ReadParam(aMsg, aIter, &state)) { + aResult->device = device; + aResult->state = state; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%S,%d]", aParam.device.c_str(), aParam.state)); + } +}; +} /* 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 0000000000..81eb8467d4 --- /dev/null +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -0,0 +1,1984 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=4 et : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/plugins/PluginModuleChild.h" + +/* This must occur *after* plugins/PluginModuleChild.h to avoid typedefs + * conflicts. */ +#include "mozilla/ArrayUtils.h" + +#include "mozilla/ipc/MessageChannel.h" + +#ifdef MOZ_WIDGET_GTK +# include <gtk/gtk.h> +# include <gdk/gdkx.h> +#endif + +#include "nsIFile.h" + +#include "pratom.h" +#include "nsDebug.h" +#include "nsCOMPtr.h" +#include "nsPluginsDir.h" +#include "nsXULAppAPI.h" + +#ifdef MOZ_X11 +# include "nsX11ErrorHandler.h" +# include "mozilla/X11Util.h" +#endif + +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/plugins/PluginInstanceChild.h" +#include "mozilla/plugins/StreamNotifyChild.h" +#include "mozilla/plugins/BrowserStreamChild.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" + +#include "nsNPAPIPlugin.h" +#include "FunctionHook.h" +#include "FunctionBrokerChild.h" + +#ifdef XP_WIN +# include "mozilla/widget/AudioSession.h" +# include <knownfolders.h> +#endif + +#ifdef MOZ_WIDGET_COCOA +# include "PluginInterposeOSX.h" +# include "PluginUtilsOSX.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "ChildProfilerController.h" +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +using namespace mozilla; +using namespace mozilla::ipc; +using namespace mozilla::plugins; +using namespace mozilla::widget; + +#if defined(XP_WIN) +const wchar_t* kFlashFullscreenClass = L"ShockwaveFlashFullScreen"; +# if defined(MOZ_SANDBOX) +std::wstring sRoamingPath; +# endif +#endif + +namespace { +// see PluginModuleChild::GetChrome() +PluginModuleChild* gChromeInstance = nullptr; +} // namespace + +#ifdef XP_WIN +// Used with fix for flash fullscreen window loosing focus. +static bool gDelayFlashFocusReplyUntilEval = false; +#endif + +/* static */ +bool PluginModuleChild::CreateForContentProcess( + Endpoint<PPluginModuleChild>&& aEndpoint) { + auto* child = new PluginModuleChild(false); + return child->InitForContent(std::move(aEndpoint)); +} + +PluginModuleChild::PluginModuleChild(bool aIsChrome) + : mLibrary(0), + mPluginFilename(""), + mQuirks(QUIRKS_NOT_INITIALIZED), + mIsChrome(aIsChrome), + mHasShutdown(false), + 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 +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + , + mFlashSandboxLevel(0), + mEnableFlashSandboxLogging(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; +} + +void PluginModuleChild::CommonInit() { + 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); + + memset((void*)&mFunctions, 0, sizeof(mFunctions)); + mFunctions.size = sizeof(mFunctions); + mFunctions.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; +} + +bool PluginModuleChild::InitForContent( + Endpoint<PPluginModuleChild>&& aEndpoint) { + CommonInit(); + + if (!aEndpoint.Bind(this)) { + return false; + } + + mLibrary = GetChrome()->mLibrary; + mFunctions = GetChrome()->mFunctions; + + return true; +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvInitProfiler( + Endpoint<mozilla::PProfilerChild>&& aEndpoint) { +#ifdef MOZ_GECKO_PROFILER + mProfilerController = ChildProfilerController::Create(std::move(aEndpoint)); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvDisableFlashProtectedMode() { + MOZ_ASSERT(mIsChrome); +#ifdef XP_WIN + FunctionHook::HookProtectedMode(); +#else + MOZ_ASSERT(false, "Should not be called"); +#endif + return IPC_OK(); +} + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +void PluginModuleChild::EnableFlashSandbox(int aLevel, + bool aShouldEnableLogging) { + mFlashSandboxLevel = aLevel; + mEnableFlashSandboxLogging = aShouldEnableLogging; +} +#endif + +#if defined(OS_WIN) && defined(MOZ_SANDBOX) +/* static */ +void PluginModuleChild::SetFlashRoamingPath(const std::wstring& aRoamingPath) { + MOZ_ASSERT(sRoamingPath.empty()); + sRoamingPath = aRoamingPath; +} + +/* static */ std::wstring PluginModuleChild::GetFlashRoamingPath() { + return sRoamingPath; +} +#endif + +bool PluginModuleChild::InitForChrome(const std::string& aPluginFilename, + base::ProcessId aParentPid, + MessageLoop* aIOLoop, + UniquePtr<IPC::Channel> aChannel) { + NS_ASSERTION(aChannel, "need a channel"); + +#if defined(OS_WIN) && defined(MOZ_SANDBOX) + MOZ_ASSERT(!sRoamingPath.empty(), + "Should have already called SetFlashRoamingPath"); +#endif + + if (!InitGraphics()) return false; + + mPluginFilename = aPluginFilename.c_str(); + nsCOMPtr<nsIFile> localFile; + NS_NewLocalFile(NS_ConvertUTF8toUTF16(mPluginFilename), true, + getter_AddRefs(localFile)); + + if (!localFile) return false; + + bool exists; + localFile->Exists(&exists); + NS_ASSERTION(exists, "plugin file ain't there"); + + nsPluginFile pluginFile(localFile); + + nsPluginInfo info = nsPluginInfo(); + if (NS_FAILED(pluginFile.GetPluginInfo(info, &mLibrary))) { + return false; + } + +#if defined(XP_WIN) + // XXX quirks isn't initialized yet + mAsyncRenderSupport = info.fSupportsAsyncRender; +#endif +#if defined(MOZ_X11) + constexpr auto flash10Head = "Shockwave Flash 10."_ns; + 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"); + + CommonInit(); + + if (!Open(std::move(aChannel), aParentPid, aIOLoop)) { + return false; + } + + GetIPCChannel()->SetAbortOnError(true); + +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + 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 + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (mFlashSandboxLevel > 0) { + MacSandboxInfo flashSandboxInfo; + flashSandboxInfo.type = MacSandboxType_Flash; + flashSandboxInfo.pluginBinaryPath = aPluginFilename; + flashSandboxInfo.level = mFlashSandboxLevel; + flashSandboxInfo.shouldLog = mEnableFlashSandboxLogging; + + std::string sbError; + if (!mozilla::StartMacSandbox(flashSandboxInfo, sbError)) { + fprintf(stderr, "Failed to start sandbox:\n%s\n", sbError.c_str()); + return false; + } + } +#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 = X11True; + + gdk_error_trap_push(); + + XSendEvent(dpy, xevent.xbutton.window, X11True, ButtonPressMask, &xevent); + + xevent.xbutton.type = ButtonRelease; + xevent.xbutton.state |= button_mask; + XSendEvent(dpy, xevent.xbutton.window, X11True, ButtonReleaseMask, &xevent); + + gdk_display_sync(gdk_screen_get_display(screen)); + gdk_error_trap_pop(); + + return TRUE; // event handled +} + +static void wrap_gtk_plug_embedded(GtkPlug* plug) { + GdkWindow* socket_window = gtk_plug_get_socket_window(plug); + if (socket_window) { + if (gtk_check_version(2, 18, 7) != nullptr // older + && g_object_get_data(G_OBJECT(socket_window), + "moz-existed-before-set-window")) { + // Add missing reference for + // https://bugzilla.gnome.org/show_bug.cgi?id=607061 + g_object_ref(socket_window); + } + + // Ensure the window exists to make this GtkPlug behave like an + // in-process GtkPlug for Flash Player. (Bugs 561308 and 539138). + gtk_widget_realize(GTK_WIDGET(plug)); + } + + if (*real_gtk_plug_embedded) { + (*real_gtk_plug_embedded)(plug); + } +} + +// +// The next four constants are knobs that can be tuned. They trade +// off potential UI lag from delayed event processing with CPU time. +// +static const gint kNestedLoopDetectorPriority = G_PRIORITY_HIGH_IDLE; +// 90ms so that we can hopefully break livelocks before the user +// notices UI lag (100ms) +static const guint kNestedLoopDetectorIntervalMs = 90; + +static const gint kBrowserEventPriority = G_PRIORITY_HIGH_IDLE; +static const guint kBrowserEventIntervalMs = 10; + +// static +gboolean PluginModuleChild::DetectNestedEventLoop(gpointer data) { + PluginModuleChild* pmc = static_cast<PluginModuleChild*>(data); + + MOZ_ASSERT(0 != pmc->mNestedLoopTimerId, "callback after descheduling"); + MOZ_ASSERT(pmc->mTopLoopDepth < g_main_depth(), + "not canceled before returning to main event loop!"); + + PLUGIN_LOG_DEBUG(("Detected nested glib event loop")); + + // just detected a nested loop; start a timer that will + // periodically rpc-call back into the browser and process some + // events + pmc->mNestedLoopTimerId = g_timeout_add_full( + kBrowserEventPriority, kBrowserEventIntervalMs, + PluginModuleChild::ProcessBrowserEvents, data, nullptr); + // cancel the nested-loop detection timer + return FALSE; +} + +// static +gboolean PluginModuleChild::ProcessBrowserEvents(gpointer data) { + PluginModuleChild* pmc = static_cast<PluginModuleChild*>(data); + + MOZ_ASSERT(pmc->mTopLoopDepth < g_main_depth(), + "not canceled before returning to main event loop!"); + + pmc->CallProcessSomeEvents(); + + return TRUE; +} + +void PluginModuleChild::EnteredCxxStack() { + MOZ_ASSERT(0 == mNestedLoopTimerId, "previous timer not descheduled"); + + mNestedLoopTimerId = g_timeout_add_full( + kNestedLoopDetectorPriority, kNestedLoopDetectorIntervalMs, + PluginModuleChild::DetectNestedEventLoop, this, nullptr); + +# ifdef DEBUG + mTopLoopDepth = g_main_depth(); +# endif +} + +void PluginModuleChild::ExitedCxxStack() { + MOZ_ASSERT(0 < mNestedLoopTimerId, "nested loop timeout not scheduled"); + + g_source_remove(mNestedLoopTimerId); + mNestedLoopTimerId = 0; +} + +#endif + +mozilla::ipc::IPCResult PluginModuleChild::RecvSetParentHangTimeout( + const uint32_t& aSeconds) { +#ifdef XP_WIN + SetReplyTimeoutMs(((aSeconds > 0) ? (1000 * aSeconds) : 0)); +#endif + return IPC_OK(); +} + +bool PluginModuleChild::ShouldContinueFromReplyTimeout() { +#ifdef XP_WIN + MOZ_CRASH("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; +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerNP_Shutdown(NPError* rv) { + *rv = NP_Shutdown(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerOptionalFunctionsSupported( + bool* aURLRedirectNotify, bool* aClearSiteData, bool* aGetSitesWithData) { + *aURLRedirectNotify = !!mFunctions.urlredirectnotify; + *aClearSiteData = !!mFunctions.clearsitedata; + *aGetSitesWithData = !!mFunctions.getsiteswithdata; + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvNPP_GetSitesWithData( + const uint64_t& aCallbackId) { + char** result = mFunctions.getsiteswithdata(); + nsTArray<nsCString> array; + if (!result) { + SendReturnSitesWithData(array, aCallbackId); + return IPC_OK(); + } + char** iterator = result; + while (*iterator) { + array.AppendElement(*iterator); + free(*iterator); + ++iterator; + } + SendReturnSitesWithData(array, aCallbackId); + free(result); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvSetAudioSessionData( + const nsID& aId, const nsString& aDisplayName, const nsString& aIconPath) { +#if !defined XP_WIN + MOZ_CRASH("Not Reached!"); +#else + nsresult rv = + mozilla::widget::RecvAudioSessionData(aId, aDisplayName, aIconPath); + NS_ENSURE_SUCCESS(rv, IPC_OK()); // Bail early if this fails + + // Ignore failures here; we can't really do anything about them + mozilla::widget::StartAudioSession(); + return IPC_OK(); +#endif +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvInitPluginModuleChild( + Endpoint<PPluginModuleChild>&& aEndpoint) { + if (!CreateForContentProcess(std::move(aEndpoint))) { + return IPC_FAIL(this, "CreateForContentProcess failed"); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvInitPluginFunctionBroker( + Endpoint<PFunctionBrokerChild>&& aEndpoint) { +#if defined(XP_WIN) + MOZ_ASSERT(mIsChrome); + if (!FunctionBrokerChild::Initialize(std::move(aEndpoint))) { + return IPC_FAIL( + this, "InitPluginFunctionBroker failed to initialize broker child."); + } + return IPC_OK(); +#else + return IPC_FAIL(this, + "InitPluginFunctionBroker not supported on this platform."); +#endif +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerInitCrashReporter( + mozilla::dom::NativeThreadId* aOutId) { + CrashReporterClient::InitSingleton(); + *aOutId = CrashReporter::CurrentThreadId(); + + return IPC_OK(); +} + +void PluginModuleChild::ActorDestroy(ActorDestroyReason why) { +#ifdef MOZ_GECKO_PROFILER + if (mProfilerController) { + mProfilerController->Shutdown(); + mProfilerController = nullptr; + } +#endif + + if (!mIsChrome) { + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome(); + if (chromeInstance) { + chromeInstance->SendNotifyContentModuleDestroyed(); + } + + // Destroy ourselves once we finish other teardown activities. + RefPtr<DeleteTask<PluginModuleChild>> task = + new DeleteTask<PluginModuleChild>(this); + MessageLoop::current()->PostTask(task.forget()); + return; + } + + if (AbnormalShutdown == why) { + NS_WARNING("shutting down early because of crash!"); + ProcessChild::QuickExit(); + } + + if (!mHasShutdown) { + MOZ_ASSERT(gChromeInstance == this); + NP_Shutdown(); + } + +#if defined(XP_WIN) + FunctionBrokerChild::Destroy(); + FunctionHook::ClearDllInterceptorCache(); +#endif + + // doesn't matter why we're being destroyed; it's up to us to + // initiate (clean) shutdown + CrashReporterClient::DestroySingleton(); + + 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::plugins::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 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 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 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 mozilla::plugins::child + +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, + nullptr, // _newstream, unimplemented + nullptr, // _write, unimplemented + nullptr, // _destroystream, unimplemented + 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, + nullptr, // pluginthreadasynccall, not used + mozilla::plugins::child::_construct, + mozilla::plugins::child::_getvalueforurl, + mozilla::plugins::child::_setvalueforurl, + nullptr, // NPN GetAuthenticationInfo, not supported + mozilla::plugins::child::_scheduletimer, + mozilla::plugins::child::_unscheduletimer, + mozilla::plugins::child::_popupcontextmenu, + mozilla::plugins::child::_convertpoint, + nullptr, // handleevent, unimplemented + nullptr, // unfocusinstance, unimplemented + mozilla::plugins::child::_urlredirectresponse, + mozilla::plugins::child::_initasyncsurface, + mozilla::plugins::child::_finalizeasyncsurface, + mozilla::plugins::child::_setcurrentasyncsurface, +}; + +PluginInstanceChild* InstCast(NPP aNPP) { + MOZ_ASSERT(!!(aNPP->ndata), "nil instance"); + return static_cast<PluginInstanceChild*>(aNPP->ndata); +} + +namespace mozilla::plugins::child { + +NPError _requestread(NPStream* aStream, NPByteRange* aRangeList) { + return NPERR_STREAM_NOT_SEEKABLE; +} + +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); + auto* sn = new StreamNotifyChild(url); + + NPError err; + if (!InstCast(aNPP)->CallPStreamNotifyConstructor( + sn, url, NullableString(aTarget), false, nsCString(), false, &err)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR == err) { + // If NPN_PostURLNotify fails, the parent will immediately send us + // a PStreamNotifyDestructor, which should not call NPP_URLNotify. + sn->SetValid(aNotifyData); + } + + return err; +} + +NPError _getvalue(NPP aNPP, NPNVariable aVariable, void* aValue) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + switch (aVariable) { + // Copied from nsNPAPIPlugin.cpp + case NPNVToolkit: +#if defined(MOZ_WIDGET_GTK) + *static_cast<NPNToolkitType*>(aValue) = NPNVGtk2; + return NPERR_NO_ERROR; +#endif + return NPERR_GENERIC_ERROR; + + case NPNVjavascriptEnabledBool: + *(NPBool*)aValue = + PluginModuleChild::GetChrome()->Settings().javascriptEnabled(); + return NPERR_NO_ERROR; + case NPNVasdEnabledBool: + *(NPBool*)aValue = + PluginModuleChild::GetChrome()->Settings().asdEnabled(); + return NPERR_NO_ERROR; + case NPNVisOfflineBool: + *(NPBool*)aValue = PluginModuleChild::GetChrome()->Settings().isOffline(); + return NPERR_NO_ERROR; + case NPNVSupportsXEmbedBool: + // We don't support windowed xembed any more. But we still deliver + // events based on X/GTK, not Xt, so we continue to return true + // (and Flash requires that we return true). + *(NPBool*)aValue = true; + return NPERR_NO_ERROR; + case NPNVSupportsWindowless: + *(NPBool*)aValue = true; + return NPERR_NO_ERROR; +#if defined(MOZ_WIDGET_GTK) + case NPNVxDisplay: { + if (!aNPP) { + return NPERR_INVALID_INSTANCE_ERROR; + } + return InstCast(aNPP)->NPN_GetValue(aVariable, aValue); + } + 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; + } + } + + MOZ_ASSERT_UNREACHABLE("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; + + if (aIsFile) { + PLUGIN_LOG_DEBUG( + ("NPN_PostURLNotify with file=true is no longer supported")); + return NPERR_GENERIC_ERROR; + } + + nsCString url = NullableString(aRelativeURL); + auto* sn = new StreamNotifyChild(url); + + NPError err; + if (!InstCast(aNPP)->CallPStreamNotifyConstructor( + sn, url, NullableString(aTarget), true, nsCString(aBuffer, aLength), + aIsFile, &err)) { + return NPERR_GENERIC_ERROR; + } + + 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); + + if (aIsFile) { + PLUGIN_LOG_DEBUG(("NPN_PostURL with file=true is no longer supported")); + return NPERR_GENERIC_ERROR; + } + 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; +} + +void _status(NPP aNPP, const char* aMessage) { + // NPN_Status is no longer supported. +} + +void _memfree(void* aPtr) { + PLUGIN_LOG_DEBUG_FUNCTION; + free(aPtr); +} + +uint32_t _memflush(uint32_t aSize) { return 0; } + +void _reloadplugins(NPBool aReloadPages) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + // Send the reload message to all modules. Chrome will need to reload from + // disk and content will need to request a new list of plugin tags from + // chrome. + PluginModuleChild::GetChrome()->SendNPN_ReloadPlugins(!!aReloadPages); +} + +void _invalidaterect(NPP aNPP, NPRect* aInvalidRect) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + // nullptr check for nspluginwrapper (bug 548434) + if (aNPP) { + InstCast(aNPP)->InvalidateRect(aInvalidRect); + } +} + +void _invalidateregion(NPP aNPP, NPRegion aInvalidRegion) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + NS_WARNING("Not yet implemented!"); +} + +void _forceredraw(NPP aNPP) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + // We ignore calls to NPN_ForceRedraw. Such calls should + // never be necessary. +} + +const char* _useragent(NPP aNPP) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(nullptr); + return PluginModuleChild::GetChrome()->GetUserAgent(); +} + +void* _memalloc(uint32_t aSize) { + PLUGIN_LOG_DEBUG_FUNCTION; + return moz_xmalloc(aSize); +} + +// Deprecated entry points for the old Java plugin. +void* /* OJI type: JRIEnv* */ +_getjavaenv(void) { + PLUGIN_LOG_DEBUG_FUNCTION; + return 0; +} + +void* /* OJI type: jref */ +_getjavapeer(NPP aNPP) { + PLUGIN_LOG_DEBUG_FUNCTION; + return 0; +} + +bool _invoke(NPP aNPP, NPObject* aNPObj, NPIdentifier aMethod, + const NPVariant* aArgs, uint32_t aArgCount, NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->invoke) + return false; + + return aNPObj->_class->invoke(aNPObj, aMethod, aArgs, aArgCount, aResult); +} + +bool _invokedefault(NPP aNPP, NPObject* aNPObj, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->invokeDefault) + return false; + + return aNPObj->_class->invokeDefault(aNPObj, aArgs, aArgCount, aResult); +} + +bool _evaluate(NPP aNPP, NPObject* aObject, NPString* aScript, + NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!(aNPP && aObject && aScript && aResult)) { + NS_ERROR("Bad arguments!"); + return false; + } + + PluginScriptableObjectChild* actor = + InstCast(aNPP)->GetActorForNPObject(aObject); + if (!actor) { + NS_ERROR("Failed to create actor?!"); + return false; + } + +#ifdef XP_WIN + if (gDelayFlashFocusReplyUntilEval) { + ReplyMessage(0); + gDelayFlashFocusReplyUntilEval = false; + } +#endif + + return actor->Evaluate(aScript, aResult); +} + +bool _getproperty(NPP aNPP, NPObject* aNPObj, NPIdentifier aPropertyName, + NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->getProperty) + return false; + + return aNPObj->_class->getProperty(aNPObj, aPropertyName, aResult); +} + +bool _setproperty(NPP aNPP, NPObject* aNPObj, NPIdentifier aPropertyName, + const NPVariant* aValue) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->setProperty) + return false; + + return aNPObj->_class->setProperty(aNPObj, aPropertyName, aValue); +} + +bool _removeproperty(NPP aNPP, NPObject* aNPObj, NPIdentifier aPropertyName) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->removeProperty) + return false; + + return aNPObj->_class->removeProperty(aNPObj, aPropertyName); +} + +bool _hasproperty(NPP aNPP, NPObject* aNPObj, NPIdentifier aPropertyName) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->hasProperty) + return false; + + return aNPObj->_class->hasProperty(aNPObj, aPropertyName); +} + +bool _hasmethod(NPP aNPP, NPObject* aNPObj, NPIdentifier aMethodName) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->hasMethod) + return false; + + return aNPObj->_class->hasMethod(aNPObj, aMethodName); +} + +bool _enumerate(NPP aNPP, NPObject* aNPObj, NPIdentifier** aIdentifiers, + uint32_t* aCount) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class) return false; + + if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(aNPObj->_class) || + !aNPObj->_class->enumerate) { + *aIdentifiers = 0; + *aCount = 0; + return true; + } + + return aNPObj->_class->enumerate(aNPObj, aIdentifiers, aCount); +} + +bool _construct(NPP aNPP, NPObject* aNPObj, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || + !NP_CLASS_STRUCT_VERSION_HAS_CTOR(aNPObj->_class) || + !aNPObj->_class->construct) { + return false; + } + + return aNPObj->_class->construct(aNPObj, aArgs, aArgCount, aResult); +} + +void _releasevariantvalue(NPVariant* aVariant) { + PLUGIN_LOG_DEBUG_FUNCTION; + // Only assert plugin thread here for consistency with in-process plugins. + AssertPluginThread(); + + if (NPVARIANT_IS_STRING(*aVariant)) { + NPString str = NPVARIANT_TO_STRING(*aVariant); + free(const_cast<NPUTF8*>(str.UTF8Characters)); + } else if (NPVARIANT_IS_OBJECT(*aVariant)) { + NPObject* object = NPVARIANT_TO_OBJECT(*aVariant); + if (object) { + PluginModuleChild::NPN_ReleaseObject(object); + } + } + VOID_TO_NPVARIANT(*aVariant); +} + +void _setexception(NPObject* aNPObj, const NPUTF8* aMessage) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + // Do nothing. We no longer support this API. +} + +void _pushpopupsenabledstate(NPP aNPP, NPBool aEnabled) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + InstCast(aNPP)->CallNPN_PushPopupsEnabledState(aEnabled ? true : false); +} + +void _poppopupsenabledstate(NPP aNPP) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + InstCast(aNPP)->CallNPN_PopPopupsEnabledState(); +} + +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; + + if (variable == 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; + + if (variable == NPNURLVProxy) { + NPError result; + InstCast(npp)->CallNPN_SetValueForURL( + variable, nsCString(url), nsDependentCString(value, len), &result); + return result; + } + + return NPERR_INVALID_PARAM; +} + +uint32_t _scheduletimer(NPP npp, uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + return InstCast(npp)->ScheduleTimer(interval, repeat, timerFunc); +} + +void _unscheduletimer(NPP npp, uint32_t timerID) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + InstCast(npp)->UnscheduleTimer(timerID); +} + +#ifdef OS_MACOSX +static void ProcessBrowserEvents(void* pluginModule) { + PluginModuleChild* pmc = static_cast<PluginModuleChild*>(pluginModule); + + if (!pmc) return; + + pmc->CallProcessSomeEvents(); +} +#endif + +NPError _popupcontextmenu(NPP instance, NPMenu* menu) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + +#ifdef MOZ_WIDGET_COCOA + double pluginX, pluginY; + double screenX, screenY; + + const NPCocoaEvent* currentEvent = InstCast(instance)->getCurrentEvent(); + if (!currentEvent) { + return NPERR_GENERIC_ERROR; + } + + // Ensure that the events has an x/y value. + if (currentEvent->type != NPCocoaEventMouseDown && + currentEvent->type != NPCocoaEventMouseUp && + currentEvent->type != NPCocoaEventMouseMoved && + currentEvent->type != NPCocoaEventMouseEntered && + currentEvent->type != NPCocoaEventMouseExited && + currentEvent->type != NPCocoaEventMouseDragged) { + return NPERR_GENERIC_ERROR; + } + + pluginX = currentEvent->data.mouse.pluginX; + pluginY = currentEvent->data.mouse.pluginY; + + if ((pluginX < 0.0) || (pluginY < 0.0)) return NPERR_GENERIC_ERROR; + + NPBool success = + _convertpoint(instance, pluginX, pluginY, NPCoordinateSpacePlugin, + &screenX, &screenY, NPCoordinateSpaceScreen); + + if (success) { + return mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu( + menu, screenX, screenY, InstCast(instance)->Manager(), + ProcessBrowserEvents); + } else { + NS_WARNING("Convertpoint failed, could not created contextmenu."); + return NPERR_GENERIC_ERROR; + } + +#else + NS_WARNING("Not supported on this platform!"); + return NPERR_GENERIC_ERROR; +#endif +} + +NPBool _convertpoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace) { + PLUGIN_LOG_DEBUG_FUNCTION; + if (!IsPluginThread()) { + NS_WARNING("Not running on the plugin's main thread!"); + return false; + } + + double rDestX = 0; + bool ignoreDestX = !destX; + double rDestY = 0; + bool ignoreDestY = !destY; + bool result = false; + InstCast(instance)->CallNPN_ConvertPoint(sourceX, ignoreDestX, sourceY, + ignoreDestY, sourceSpace, destSpace, + &rDestX, &rDestY, &result); + if (result) { + if (destX) *destX = rDestX; + if (destY) *destY = rDestY; + } + + return result; +} + +void _urlredirectresponse(NPP instance, void* notifyData, NPBool allow) { + InstCast(instance)->NPN_URLRedirectResponse(notifyData, allow); +} + +NPError _initasyncsurface(NPP instance, NPSize* size, NPImageFormat format, + void* initData, NPAsyncSurface* surface) { + return InstCast(instance)->NPN_InitAsyncSurface(size, format, initData, + surface); +} + +NPError _finalizeasyncsurface(NPP instance, NPAsyncSurface* surface) { + return InstCast(instance)->NPN_FinalizeAsyncSurface(surface); +} + +void _setcurrentasyncsurface(NPP instance, NPAsyncSurface* surface, + NPRect* changed) { + InstCast(instance)->NPN_SetCurrentAsyncSurface(surface, changed); +} + +} // namespace mozilla::plugins::child + +//----------------------------------------------------------------------------- + +mozilla::ipc::IPCResult PluginModuleChild::RecvSettingChanged( + const PluginSettings& aSettings) { + mCachedSettings = aSettings; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerNP_GetEntryPoints( + NPError* _retval) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + MOZ_ASSERT(mIsChrome); + +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + return IPC_OK(); +#elif defined(OS_WIN) || defined(OS_MACOSX) + *_retval = mGetEntryPointsFunc(&mFunctions); + return IPC_OK(); +#else +# error Please implement me for your platform +#endif +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerNP_Initialize( + const PluginSettings& aSettings, NPError* rv) { + *rv = DoNP_Initialize(aSettings); + return IPC_OK(); +} + +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 +# ifdef MOZ_WIDGET_GTK + if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) { + // We don't support NPAPI plugins on Wayland. + return NPERR_GENERIC_ERROR; + } +# endif + // 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) || defined(OS_SOLARIS) + 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; +} + +PPluginInstanceChild* PluginModuleChild::AllocPPluginInstanceChild( + const nsCString& aMimeType, const nsTArray<nsCString>& aNames, + const nsTArray<nsCString>& aValues) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + + // In e10s, gChromeInstance hands out quirks to instances, but never + // allocates an instance on its own. Make sure it gets the latest copy + // of quirks once we have them. Also note, with process-per-tab, we may + // have multiple PluginModuleChilds in the same plugin process, so only + // initialize this once in gChromeInstance, which is a singleton. + GetChrome()->InitQuirksModes(aMimeType); + mQuirks = GetChrome()->mQuirks; + +#ifdef XP_WIN + FunctionHook::HookFunctions(mQuirks); +#endif + + return new PluginInstanceChild(&mFunctions, aMimeType, aNames, aValues); +} + +void PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) { + if (mQuirks != QUIRKS_NOT_INITIALIZED) { + return; + } + + mQuirks = GetQuirksFromMimeTypeAndFilename(aMimeType, mPluginFilename); +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerModuleSupportsAsyncRender( + bool* aResult) { +#if defined(XP_WIN) + *aResult = gChromeInstance->mAsyncRenderSupport; + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("Shouldn't get here!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvPPluginInstanceConstructor( + PPluginInstanceChild* aActor, const nsCString& aMimeType, + nsTArray<nsCString>&& aNames, nsTArray<nsCString>&& aValues) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + + NS_ASSERTION(aActor, "Null actor!"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerSyncNPP_New( + PPluginInstanceChild* aActor, NPError* rv) { + PLUGIN_LOG_DEBUG_METHOD; + PluginInstanceChild* childInstance = + reinterpret_cast<PluginInstanceChild*>(aActor); + AssertPluginThread(); + *rv = childInstance->DoNPP_New(); + return IPC_OK(); +} + +bool PluginModuleChild::DeallocPPluginInstanceChild( + PPluginInstanceChild* aActor) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + + delete aActor; + + return true; +} + +NPObject* PluginModuleChild::NPN_CreateObject(NPP aNPP, NPClass* aClass) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(nullptr); + + PluginInstanceChild* i = InstCast(aNPP); + if (i->mDeletingHash) { + NS_ERROR("Plugin used NPP after NPP_Destroy"); + return nullptr; + } + + NPObject* newObject; + if (aClass && aClass->allocate) { + newObject = aClass->allocate(aNPP, aClass); + } else { + newObject = reinterpret_cast<NPObject*>(child::_memalloc(sizeof(NPObject))); + } + + if (newObject) { + newObject->_class = aClass; + newObject->referenceCount = 1; + NS_LOG_ADDREF(newObject, 1, "NPObject", sizeof(NPObject)); + } + + PluginScriptableObjectChild::RegisterObject(newObject, i); + + return newObject; +} + +NPObject* PluginModuleChild::NPN_RetainObject(NPObject* aNPObj) { + AssertPluginThread(); + + if (NS_WARN_IF(!aNPObj)) { + return nullptr; + } + +#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) { + // The PluginInstanceChild was destroyed + 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; + } +} + +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)) { + MOZ_CRASH("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"); + const IncallFrame& f = mIncallPumpingStack.LastElement(); + if (f._spinning) + MessageLoop::current()->SetNestableTasksAllowed( + f._savedNestableTasksAllowed); + + // XXX Is RemoveLastElement intentionally called only after calling + // SetNestableTasksAllowed? Otherwise, PopLastElement could be used above. + mIncallPumpingStack.RemoveLastElement(); +} + +LRESULT CALLBACK PluginModuleChild::CallWindowProcHook(int nCode, WPARAM wParam, + LPARAM lParam) { + // Trap and reply to anything we recognize as the source of a + // potential send message deadlock. + if (nCode >= 0 && + (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) { + CWPSTRUCT* pCwp = reinterpret_cast<CWPSTRUCT*>(lParam); + if (pCwp->message == WM_KILLFOCUS) { + // Fix for flash fullscreen window loosing focus. On single + // core systems, sync killfocus events need to be handled + // after the flash fullscreen window procedure processes this + // message, otherwise fullscreen focus will not work correctly. + wchar_t szClass[26]; + if (GetClassNameW(pCwp->hwnd, szClass, + sizeof(szClass) / sizeof(char16_t)) && + !wcscmp(szClass, kFlashFullscreenClass)) { + gDelayFlashFocusReplyUntilEval = true; + } + } + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +LRESULT CALLBACK PluginModuleChild::NestedInputEventHook(int nCode, + WPARAM wParam, + LPARAM lParam) { + PluginModuleChild* self = GetChrome(); + uint32_t len = self->mIncallPumpingStack.Length(); + if (nCode >= 0 && len && !self->mIncallPumpingStack[len - 1]._spinning) { + MessageLoop* loop = MessageLoop::current(); + self->SendProcessNativeEventsInInterruptCall(); + IncallFrame& f = self->mIncallPumpingStack[len - 1]; + f._spinning = true; + f._savedNestableTasksAllowed = loop->NestableTasksAllowed(); + loop->SetNestableTasksAllowed(true); + loop->set_os_modal_loop(true); + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +void PluginModuleChild::SetEventHooks() { + NS_ASSERTION( + !mNestedEventHook, + "mNestedEventHook already setup in call to SetNestedInputEventHook?"); + NS_ASSERTION( + !mGlobalCallWndProcHook, + "mGlobalCallWndProcHook already setup in call to CallWindowProcHook?"); + + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + // WH_MSGFILTER event hook for detecting modal loops in the child. + mNestedEventHook = SetWindowsHookEx(WH_MSGFILTER, NestedInputEventHook, + nullptr, GetCurrentThreadId()); + + // WH_CALLWNDPROC event hook for trapping sync messages sent from + // parent that can cause deadlocks. + mGlobalCallWndProcHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcHook, + nullptr, GetCurrentThreadId()); +} + +void PluginModuleChild::ResetEventHooks() { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + if (mNestedEventHook) UnhookWindowsHookEx(mNestedEventHook); + mNestedEventHook = nullptr; + if (mGlobalCallWndProcHook) UnhookWindowsHookEx(mGlobalCallWndProcHook); + mGlobalCallWndProcHook = nullptr; +} +#endif + +mozilla::ipc::IPCResult +PluginModuleChild::RecvProcessNativeEventsInInterruptCall() { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(OS_WIN) + ProcessNativeEventsInInterruptCall(); + return IPC_OK(); +#else + MOZ_CRASH( + "PluginModuleChild::RecvProcessNativeEventsInInterruptCall not " + "implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +#ifdef MOZ_WIDGET_COCOA +void PluginModuleChild::ProcessNativeEvents() { CallProcessSomeEvents(); } +#endif + +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 + MOZ_CRASH( + "PluginRequiresAudioDeviceChanges is not available on this platform."); +#endif // XP_WIN +} + +mozilla::ipc::IPCResult +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 IPC_OK(); +#else + MOZ_CRASH( + "NPP_SetValue_NPNVaudioDeviceChangeDetails is a Windows-only message"); +#endif +} + +mozilla::ipc::IPCResult +PluginModuleChild::RecvNPP_SetValue_NPNVaudioDeviceStateChanged( + const NPAudioDeviceStateChangedIPC& aDeviceStateIPC) { +#if defined(XP_WIN) + NPAudioDeviceStateChanged stateChange; + stateChange.newState = aDeviceStateIPC.state; + stateChange.device = aDeviceStateIPC.device.c_str(); + for (auto iter = mAudioNotificationSet.ConstIter(); !iter.Done(); + iter.Next()) { + PluginInstanceChild* pluginInst = iter.Get()->GetKey(); + pluginInst->AudioDeviceStateChanged(stateChange); + } + return IPC_OK(); +#else + MOZ_CRASH("NPP_SetValue_NPNVaudioDeviceRemoved is a Windows-only message"); +#endif +} diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h new file mode 100644 index 0000000000..31d4eafb8f --- /dev/null +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -0,0 +1,345 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginModuleChild_h +#define dom_plugins_PluginModuleChild_h 1 + +#include "mozilla/Attributes.h" + +#include <string> +#include <vector> + +#include "base/basictypes.h" + +#include "prlink.h" + +#include "npapi.h" +#include "npfunctions.h" + +#include "nsDataHashtable.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" + +#ifdef MOZ_WIDGET_COCOA +# include "PluginInterposeOSX.h" +#endif + +#include "mozilla/plugins/PPluginModuleChild.h" +#include "mozilla/plugins/PluginInstanceChild.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/plugins/PluginQuirks.h" + +#if defined(MOZ_WIDGET_GTK) +# include <glib.h> +#endif + +namespace mozilla { + +class ChildProfilerController; + +namespace plugins { + +class PluginInstanceChild; + +class PluginModuleChild : public PPluginModuleChild { + friend class PPluginModuleChild; + + protected: + virtual mozilla::ipc::RacyInterruptPolicy MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) override { + return MediateRace(parent, child); + } + + virtual bool ShouldContinueFromReplyTimeout() override; + + mozilla::ipc::IPCResult RecvSettingChanged(const PluginSettings& aSettings); + + // Implement the PPluginModuleChild interface + mozilla::ipc::IPCResult RecvInitProfiler( + Endpoint<mozilla::PProfilerChild>&& aEndpoint); + mozilla::ipc::IPCResult RecvDisableFlashProtectedMode(); + mozilla::ipc::IPCResult AnswerNP_GetEntryPoints(NPError* rv); + mozilla::ipc::IPCResult AnswerNP_Initialize(const PluginSettings& aSettings, + NPError* rv); + mozilla::ipc::IPCResult AnswerSyncNPP_New(PPluginInstanceChild* aActor, + NPError* rv); + + mozilla::ipc::IPCResult RecvInitPluginModuleChild( + Endpoint<PPluginModuleChild>&& endpoint); + + mozilla::ipc::IPCResult RecvInitPluginFunctionBroker( + Endpoint<PFunctionBrokerChild>&& endpoint); + + PPluginInstanceChild* AllocPPluginInstanceChild( + const nsCString& aMimeType, const nsTArray<nsCString>& aNames, + const nsTArray<nsCString>& aValues); + + bool DeallocPPluginInstanceChild(PPluginInstanceChild* aActor); + + mozilla::ipc::IPCResult RecvPPluginInstanceConstructor( + PPluginInstanceChild* aActor, const nsCString& aMimeType, + nsTArray<nsCString>&& aNames, nsTArray<nsCString>&& aValues) override; + mozilla::ipc::IPCResult AnswerNP_Shutdown(NPError* rv); + + mozilla::ipc::IPCResult AnswerOptionalFunctionsSupported( + bool* aURLRedirectNotify, bool* aClearSiteData, bool* aGetSitesWithData); + + mozilla::ipc::IPCResult RecvNPP_ClearSiteData(const nsCString& aSite, + const uint64_t& aFlags, + const uint64_t& aMaxAge, + const uint64_t& aCallbackId); + + mozilla::ipc::IPCResult RecvNPP_GetSitesWithData(const uint64_t& aCallbackId); + + mozilla::ipc::IPCResult RecvSetAudioSessionData(const nsID& aId, + const nsString& aDisplayName, + const nsString& aIconPath); + + mozilla::ipc::IPCResult RecvSetParentHangTimeout(const uint32_t& aSeconds); + + mozilla::ipc::IPCResult AnswerInitCrashReporter( + mozilla::dom::NativeThreadId* aId); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + mozilla::ipc::IPCResult RecvProcessNativeEventsInInterruptCall(); + + mozilla::ipc::IPCResult AnswerModuleSupportsAsyncRender(bool* aResult); + + public: + explicit PluginModuleChild(bool aIsChrome); + virtual ~PluginModuleChild(); + + void CommonInit(); + +#if defined(OS_WIN) && defined(MOZ_SANDBOX) + // Path to the roaming Flash Player folder. This is used to restore some + // behavior blocked by the sandbox. + static void SetFlashRoamingPath(const std::wstring& aRoamingPath); + static std::wstring GetFlashRoamingPath(); +#endif + + // aPluginFilename is UTF8, not native-charset! + bool InitForChrome(const std::string& aPluginFilename, + base::ProcessId aParentPid, MessageLoop* aIOLoop, + UniquePtr<IPC::Channel> aChannel); + + bool InitForContent(Endpoint<PPluginModuleChild>&& aEndpoint); + + static bool CreateForContentProcess(Endpoint<PPluginModuleChild>&& aEndpoint); + + 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); + mozilla::ipc::IPCResult RecvNPP_SetValue_NPNVaudioDeviceChangeDetails( + const NPAudioDeviceChangeDetailsIPC& detailsIPC); + mozilla::ipc::IPCResult RecvNPP_SetValue_NPNVaudioDeviceStateChanged( + const NPAudioDeviceStateChangedIPC& aDeviceStateIPC); + + 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(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 + +#ifdef MOZ_GECKO_PROFILER + RefPtr<ChildProfilerController> mProfilerController; +#endif + + // we get this from the plugin + NP_PLUGINSHUTDOWN mShutdownFunc; +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + NP_PLUGINUNIXINIT mInitializeFunc; +#elif defined(OS_WIN) || defined(OS_MACOSX) + NP_PLUGININIT mInitializeFunc; + NP_GETENTRYPOINTS mGetEntryPointsFunc; +#endif + + NPPluginFuncs mFunctions; + + PluginSettings mCachedSettings; + +#if defined(MOZ_WIDGET_GTK) + // If a plugin spins a nested glib event loop in response to a + // synchronous IPC message from the browser, the loop might break + // only after the browser responds to a request sent by the + // plugin. This can happen if a plugin uses gtk's synchronous + // copy/paste, for example. But because the browser is blocked on + // a condvar, it can't respond to the request. This situation + // isn't technically a deadlock, but the symptoms are basically + // the same from the user's perspective. + // + // We take two steps to prevent this + // + // (1) Detect nested event loops spun by the plugin. This is + // done by scheduling a glib timer event in the plugin + // process whenever the browser might block on the plugin. + // If the plugin indeed spins a nested loop, this timer event + // will fire "soon" thereafter. + // + // (2) When a nested loop is detected, deschedule the + // nested-loop-detection timer and in its place, schedule + // another timer that periodically calls back into the + // browser and spins a mini event loop. This mini event loop + // processes a handful of pending native events. + // + // Because only timer (1) or (2) (or neither) may be active at any + // point in time, we use the same member variable + // |mNestedLoopTimerId| to refer to both. + // + // When the browser no longer might be blocked on a plugin's IPC + // response, we deschedule whichever of (1) or (2) is active. + guint mNestedLoopTimerId; +# ifdef DEBUG + // Depth of the stack of calls to g_main_context_dispatch before any + // nested loops are run. This is 1 when IPC calls are dispatched from + // g_main_context_iteration, or 0 when dispatched directly from + // MessagePumpForUI. + int mTopLoopDepth; +# endif +#endif + +#if defined(XP_WIN) + typedef nsTHashtable<nsPtrHashKey<PluginInstanceChild>> PluginInstanceSet; + // Set of plugins that have registered to be notified when the audio device + // changes. + PluginInstanceSet mAudioNotificationSet; +#endif + + public: // called by PluginInstanceChild + /** + * Dealloc an NPObject after last-release or when the associated instance + * is destroyed. This function will remove the object from mObjectMap. + */ + static void DeallocNPObject(NPObject* o); + + NPError NPP_Destroy(PluginInstanceChild* instance) { + return mFunctions.destroy(instance->GetNPP(), 0); + } + +#if defined(OS_MACOSX) && defined(MOZ_SANDBOX) + void EnableFlashSandbox(int aLevel, bool aShouldEnableLogging); +#endif + + private: +#if defined(OS_MACOSX) && defined(MOZ_SANDBOX) + int mFlashSandboxLevel; + bool mEnableFlashSandboxLogging; +#endif + +#if defined(OS_WIN) + virtual void EnteredCall() override; + virtual void ExitedCall() override; + + // Entered/ExitedCall notifications keep track of whether the plugin has + // entered a nested event loop within this interrupt call. + struct IncallFrame { + IncallFrame() : _spinning(false), _savedNestableTasksAllowed(false) {} + + bool _spinning; + bool _savedNestableTasksAllowed; + }; + + AutoTArray<IncallFrame, 8> mIncallPumpingStack; + + static LRESULT CALLBACK NestedInputEventHook(int code, WPARAM wParam, + LPARAM lParam); + static LRESULT CALLBACK CallWindowProcHook(int code, WPARAM wParam, + LPARAM lParam); + void SetEventHooks(); + void ResetEventHooks(); + HHOOK mNestedEventHook; + HHOOK mGlobalCallWndProcHook; + + public: + bool mAsyncRenderSupport; +#endif +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif // ifndef dom_plugins_PluginModuleChild_h diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp new file mode 100644 index 0000000000..6743ed1893 --- /dev/null +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -0,0 +1,2541 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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/AutoRestore.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/Document.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/CrashReporterHost.h" +#include "mozilla/dom/Element.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/plugins/BrowserStreamParent.h" +#include "mozilla/plugins/PluginBridge.h" +#include "mozilla/plugins/PluginInstanceParent.h" +#include "mozilla/plugins/PPluginBackgroundDestroyerParent.h" +#include "mozilla/plugins/PStreamNotifyParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/UniquePtr.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsICrashService.h" +#include "nsIObserverService.h" +#include "nsNPAPIPlugin.h" +#include "nsPluginInstanceOwner.h" +#include "nsPrintfCString.h" +#include "prsystem.h" +#include "prclist.h" +#include "PluginQuirks.h" +#include "gfxPlatform.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 "FunctionBrokerParent.h" +# include "PluginUtilsWin.h" +#endif + +#ifdef MOZ_WIDGET_GTK +# include <glib.h> +#elif XP_MACOSX +# include "PluginInterposeOSX.h" +# include "PluginUtilsOSX.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "ProfilerParent.h" +#endif + +using base::KillProcess; + +using mozilla::PluginLibrary; +using mozilla::ipc::GeckoChildProcessHost; +using mozilla::ipc::MessageChannel; + +using namespace mozilla; +using namespace mozilla::plugins; +using namespace mozilla::plugins::parent; + +using namespace CrashReporter; + +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"; +#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, nsresult* rv, + uint32_t* runID, ipc::Endpoint<PPluginModuleParent>* aEndpoint) { + AUTO_PROFILER_LABEL("plugins::SetupBridge", OTHER); + if (NS_WARN_IF(!rv) || NS_WARN_IF(!runID)) { + return false; + } + + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + RefPtr<nsNPAPIPlugin> plugin; + *rv = host->GetPluginForContentProcess(aPluginId, getter_AddRefs(plugin)); + if (NS_FAILED(*rv)) { + return true; + } + PluginModuleChromeParent* chromeParent = + static_cast<PluginModuleChromeParent*>(plugin->GetLibrary()); + *rv = chromeParent->GetRunID(runID); + if (NS_FAILED(*rv)) { + return true; + } + + ipc::Endpoint<PPluginModuleParent> parent; + ipc::Endpoint<PPluginModuleChild> child; + + *rv = PPluginModule::CreateEndpoints( + aContentParent->OtherPid(), chromeParent->OtherPid(), &parent, &child); + if (NS_FAILED(*rv)) { + return true; + } + + *aEndpoint = std::move(parent); + + if (!chromeParent->SendInitPluginModuleChild(std::move(child))) { + *rv = NS_ERROR_BRIDGE_OPEN_CHILD; + return true; + } + + return true; +} + +#ifdef MOZ_CRASHREPORTER_INJECTOR + +/** + * Use for executing CreateToolhelp32Snapshot off main thread + */ +class mozilla::plugins::FinishInjectorInitTask + : public mozilla::CancelableRunnable { + public: + FinishInjectorInitTask() + : CancelableRunnable("FinishInjectorInitTask"), + mMutex("FlashInjectorInitTask::mMutex"), + mParent(nullptr), + mMainThreadMsgLoop(MessageLoop::current()) { + MOZ_ASSERT(NS_IsMainThread()); + } + + void Init(PluginModuleChromeParent* aParent) { + MOZ_ASSERT(aParent); + mParent = aParent; + } + + void PostToMainThread() { + RefPtr<Runnable> self = this; + mSnapshot.own(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); + { // Scope for lock + mozilla::MutexAutoLock lock(mMutex); + if (mMainThreadMsgLoop) { + mMainThreadMsgLoop->PostTask(self.forget()); + } + } + } + + NS_IMETHOD Run() override { + mParent->DoInjection(mSnapshot); + // We don't need to hold this lock during DoInjection, but we do need + // to obtain it before returning from Run() to ensure that + // PostToMainThread has completed before we return. + mozilla::MutexAutoLock lock(mMutex); + return NS_OK; + } + + nsresult Cancel() override { + mozilla::MutexAutoLock lock(mMutex); + mMainThreadMsgLoop = nullptr; + return NS_OK; + } + + private: + mozilla::Mutex mMutex; + nsAutoHandle mSnapshot; + PluginModuleChromeParent* mParent; + MessageLoop* mMainThreadMsgLoop; +}; + +#endif // MOZ_CRASHREPORTER_INJECTOR + +namespace { + +/** + * Objects of this class remain linked until an error occurs in the + * plugin initialization sequence. + */ +class PluginModuleMapping : public PRCList { + public: + explicit PluginModuleMapping(uint32_t aPluginId) + : mPluginId(aPluginId), + mProcessIdValid(false), + mProcessId(0), + 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(); + } + return mModule; + } + + static PluginModuleMapping* AssociateWithProcessId( + uint32_t aPluginId, base::ProcessId aProcessId) { + PluginModuleMapping* mapping = + static_cast<PluginModuleMapping*>(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + mapping->AssociateWithProcessId(aProcessId); + return mapping; + } + mapping = static_cast<PluginModuleMapping*>(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static PluginModuleMapping* Resolve(base::ProcessId aProcessId) { + PluginModuleMapping* mapping = nullptr; + + if (sIsLoadModuleOnStack) { + // Special case: If loading synchronously, we just need to access + // the tail entry of the list. + mapping = + static_cast<PluginModuleMapping*>(PR_LIST_TAIL(&sModuleListHead)); + MOZ_ASSERT(mapping); + return mapping; + } + + mapping = static_cast<PluginModuleMapping*>(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mProcessIdValid && mapping->mProcessId == aProcessId) { + return mapping; + } + mapping = static_cast<PluginModuleMapping*>(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static PluginModuleMapping* FindModuleByPluginId(uint32_t aPluginId) { + PluginModuleMapping* mapping = + static_cast<PluginModuleMapping*>(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + return mapping; + } + mapping = static_cast<PluginModuleMapping*>(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + class MOZ_RAII NotifyLoadingModule { + public: + explicit NotifyLoadingModule() { + PluginModuleMapping::sIsLoadModuleOnStack = true; + } + + ~NotifyLoadingModule() { + PluginModuleMapping::sIsLoadModuleOnStack = false; + } + + private: + }; + + private: + void AssociateWithProcessId(base::ProcessId aProcessId) { + MOZ_ASSERT(!mProcessIdValid); + mProcessId = aProcessId; + mProcessIdValid = true; + } + + uint32_t mPluginId; + bool mProcessIdValid; + base::ProcessId mProcessId; + PluginModuleContentParent* mModule; + bool mChannelOpened; + + friend class NotifyLoadingModule; + + static PRCList sModuleListHead; + static bool sIsLoadModuleOnStack; +}; + +PRCList PluginModuleMapping::sModuleListHead = + PR_INIT_STATIC_CLIST(&PluginModuleMapping::sModuleListHead); + +bool PluginModuleMapping::sIsLoadModuleOnStack = false; + +} // namespace + +static PluginModuleChromeParent* PluginModuleChromeParentForId( + const uint32_t aPluginId) { + MOZ_ASSERT(XRE_IsParentProcess()); + + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + nsPluginTag* pluginTag = host->PluginWithId(aPluginId); + if (!pluginTag || !pluginTag->mPlugin) { + return nullptr; + } + RefPtr<nsNPAPIPlugin> plugin = pluginTag->mPlugin; + + return static_cast<PluginModuleChromeParent*>(plugin->GetLibrary()); +} + +void mozilla::plugins::TakeFullMinidump(uint32_t aPluginId, + base::ProcessId aContentProcessId, + const nsAString& aBrowserDumpId, + nsString& aDumpId) { + PluginModuleChromeParent* chromeParent = + PluginModuleChromeParentForId(aPluginId); + + if (chromeParent) { + chromeParent->TakeFullMinidump(aContentProcessId, aBrowserDumpId, aDumpId); + } +} + +void mozilla::plugins::TerminatePlugin(uint32_t aPluginId, + base::ProcessId aContentProcessId, + const nsCString& aMonitorDescription, + const nsAString& aDumpId) { + PluginModuleChromeParent* chromeParent = + PluginModuleChromeParentForId(aPluginId); + + if (chromeParent) { + chromeParent->TerminateChildProcess(MessageLoop::current(), + aContentProcessId, aMonitorDescription, + aDumpId); + } +} + +/* static */ +PluginLibrary* PluginModuleContentParent::LoadModule(uint32_t aPluginId, + nsPluginTag* aPluginTag) { + PluginModuleMapping::NotifyLoadingModule loadingModule; + UniquePtr<PluginModuleMapping> mapping(new PluginModuleMapping(aPluginId)); + + 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; + Endpoint<PPluginModuleParent> endpoint; + if (!cp->SendLoadPlugin(aPluginId, &rv, &runID, &endpoint) || NS_FAILED(rv)) { + return nullptr; + } + Initialize(std::move(endpoint)); + + PluginModuleContentParent* parent = mapping->GetModule(); + MOZ_ASSERT(parent); + + if (!mapping->IsChannelOpened()) { + // mapping is linked into PluginModuleMapping::sModuleListHead and is + // needed later, so since this function is returning successfully we + // forget it here. + Unused << mapping.release(); + } + + parent->mPluginId = aPluginId; + parent->mRunID = runID; + + return parent; +} + +/* static */ +void PluginModuleContentParent::Initialize( + Endpoint<PPluginModuleParent>&& aEndpoint) { + UniquePtr<PluginModuleMapping> moduleMapping( + PluginModuleMapping::Resolve(aEndpoint.OtherPid())); + MOZ_ASSERT(moduleMapping); + PluginModuleContentParent* parent = moduleMapping->GetModule(); + MOZ_ASSERT(parent); + + DebugOnly<bool> ok = aEndpoint.Bind(parent); + MOZ_ASSERT(ok); + + moduleMapping->SetChannelOpened(); + + if (XRE_UseNativeEventProcessing()) { + // If we're processing native events in our message pump, 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. + Unused << moduleMapping.release(); +} + +// static +PluginLibrary* PluginModuleChromeParent::LoadModule(const char* aFilePath, + uint32_t aPluginId, + nsPluginTag* aPluginTag) { + PLUGIN_LOG_DEBUG_FUNCTION; + + UniquePtr<PluginModuleChromeParent> parent(new PluginModuleChromeParent( + aFilePath, aPluginId, aPluginTag->mSandboxLevel)); + UniquePtr<LaunchCompleteTask> onLaunchedRunnable( + new LaunchedTask(parent.get())); + bool launched = parent->mSubprocess->Launch( + std::move(onLaunchedRunnable), aPluginTag->mSandboxLevel, + aPluginTag->mIsSandboxLoggingEnabled); + 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; + int32_t launchTimeoutSecs = Preferences::GetInt(kLaunchTimeoutPref, 0); + if (!parent->mSubprocess->WaitUntilConnected(launchTimeoutSecs * 1000)) { + parent->mShutdown = true; + return nullptr; + } + +#if defined(XP_WIN) + Endpoint<PFunctionBrokerParent> brokerParentEnd; + Endpoint<PFunctionBrokerChild> brokerChildEnd; + rv = PFunctionBroker::CreateEndpoints(base::GetCurrentProcId(), + parent->OtherPid(), &brokerParentEnd, + &brokerChildEnd); + if (NS_FAILED(rv)) { + parent->mShutdown = true; + return nullptr; + } + + parent->mBrokerParent = + FunctionBrokerParent::Create(std::move(brokerParentEnd)); + if (parent->mBrokerParent) { + Unused << parent->SendInitPluginFunctionBroker(std::move(brokerChildEnd)); + } +#endif + return parent.release(); +} + +static const char* gCallbackPrefs[] = { + kChildTimeoutPref, + kParentTimeoutPref, +#ifdef XP_WIN + kHangUITimeoutPref, + kHangUIMinDisplayPref, +#endif + nullptr, +}; + +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 (mShutdown) { + return; + } + + Open(mSubprocess->TakeChannel(), + 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::RegisterCallbacks(TimeoutChanged, gCallbackPrefs, + static_cast<PluginModuleParent*>(this)); + + RegisterSettingsCallbacks(); + + // If this fails, we're having IPC troubles, and we're doomed anyways. + if (!InitCrashReporter()) { + mShutdown = true; + Close(); + OnInitFailure(); + return; + } + +#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)) { + Unused << SendDisableFlashProtectedMode(); + } +#endif + +#ifdef MOZ_GECKO_PROFILER + Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid())); +#endif +} + +bool PluginModuleChromeParent::InitCrashReporter() { + NativeThreadId threadId; + if (!CallInitCrashReporter(&threadId)) { + return false; + } + + { + mozilla::MutexAutoLock lock(mCrashReporterMutex); + mCrashReporter = + MakeUnique<ipc::CrashReporterHost>(GeckoProcessType_Plugin, threadId); + } + + return true; +} + +PluginModuleParent::PluginModuleParent(bool aIsChrome) + : 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), + mRunID(0), + mCrashReporterMutex("PluginModuleChromeParent::mCrashReporterMutex") {} + +PluginModuleParent::~PluginModuleParent() { + if (!OkToCleanup()) { + MOZ_CRASH("unsafe destruction"); + } + + if (!mShutdown) { + NS_WARNING("Plugin host deleted the module without shutting down."); + NPError err; + NP_Shutdown(&err); + } +} + +PluginModuleContentParent::PluginModuleContentParent() + : PluginModuleParent(false), mPluginId(0) { + Preferences::RegisterCallback(TimeoutChanged, kContentTimeoutPref, + static_cast<PluginModuleParent*>(this)); +} + +PluginModuleContentParent::~PluginModuleContentParent() { + Preferences::UnregisterCallback(TimeoutChanged, kContentTimeoutPref, + static_cast<PluginModuleParent*>(this)); +} + +PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, + uint32_t aPluginId, + int32_t aSandboxLevel) + : PluginModuleParent(true), + mSubprocess(new PluginProcessParent(aFilePath)), + mPluginId(aPluginId), + mChromeTaskFactory(this), + mHangAnnotationFlags(0) +#ifdef XP_WIN + , + mPluginCpuUsageOnHang(), + mHangUIParent(nullptr), + mHangUIEnabled(true), + mIsTimerReset(true), + mBrokerParent(nullptr) +#endif +#ifdef MOZ_CRASHREPORTER_INJECTOR + , + mFlashProcess1(0), + mFlashProcess2(0), + mFinishInitTask(nullptr) +#endif + , + mIsBlocklisted(false), + mIsCleaningFromTimeout(false) { + NS_ASSERTION(mSubprocess, "Out of memory!"); + mSandboxLevel = aSandboxLevel; + mRunID = GeckoChildProcessHost::GetUniqueID(); + + mozilla::BackgroundHangMonitor::RegisterAnnotator(*this); +} + +PluginModuleChromeParent::~PluginModuleChromeParent() { + if (!OkToCleanup()) { + MOZ_CRASH("unsafe destruction"); + } + +#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->Destroy(); + 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::UnregisterCallbacks(TimeoutChanged, gCallbackPrefs, + static_cast<PluginModuleParent*>(this)); + +#ifdef XP_WIN + if (mHangUIParent) { + delete mHangUIParent; + mHangUIParent = nullptr; + } +#endif + + mozilla::BackgroundHangMonitor::UnregisterAnnotator(*this); +} + +void PluginModuleChromeParent::AddCrashAnnotations() { + // mCrashReporterMutex is already held by the caller + mCrashReporterMutex.AssertCurrentThreadOwns(); + + typedef nsDependentCString cstring; + + // 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++; + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginFilename, + cstring(pluginFile.substr(filePos).c_str())); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginName, + mPluginName); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginVersion, + mPluginVersion); + + if (mCrashReporter) { +#ifdef XP_WIN + if (mPluginCpuUsageOnHang.Length() > 0) { + nsCString cpuUsageStr; + cpuUsageStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[0] * 100) / 100); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginCpuUsage, + cpuUsageStr); + +# ifdef MOZ_CRASHREPORTER_INJECTOR + for (uint32_t i = 1; i < mPluginCpuUsageOnHang.Length(); ++i) { + nsCString tempStr; + tempStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[i] * 100) / 100); + // HACK: There can only be at most two flash processes hence + // the hardcoded annotations + CrashReporter::Annotation annotation = + (i == 1) ? CrashReporter::Annotation::CpuUsageFlashProcess1 + : CrashReporter::Annotation::CpuUsageFlashProcess2; + mCrashReporter->AddAnnotation(annotation, tempStr); + } +# endif + } +#endif + } +} + +void PluginModuleParent::SetChildTimeout(const int32_t aChildTimeout) { + int32_t timeoutMs = + (aChildTimeout > 0) ? (1000 * aChildTimeout) : MessageChannel::kNoTimeout; + SetReplyTimeoutMs(timeoutMs); +} + +void PluginModuleParent::TimeoutChanged(const char* aPref, void* aModule) { + auto module = static_cast<PluginModuleParent*>(aModule); + + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); +#ifndef XP_WIN + if (!strcmp(aPref, kChildTimeoutPref)) { + MOZ_ASSERT(module->IsChrome()); + // The timeout value used by the parent for children + int32_t timeoutSecs = Preferences::GetInt(kChildTimeoutPref, 0); + module->SetChildTimeout(timeoutSecs); +#else + if (!strcmp(aPref, kChildTimeoutPref) || + !strcmp(aPref, kHangUIMinDisplayPref) || + !strcmp(aPref, kHangUITimeoutPref)) { + MOZ_ASSERT(module->IsChrome()); + static_cast<PluginModuleChromeParent*>(module)->EvaluateHangUIState(true); +#endif // XP_WIN + } else if (!strcmp(aPref, kParentTimeoutPref)) { + // The timeout value used by the child for its parent + MOZ_ASSERT(module->IsChrome()); + int32_t timeoutSecs = Preferences::GetInt(kParentTimeoutPref, 0); + Unused << static_cast<PluginModuleChromeParent*>(module) + ->SendSetParentHangTimeout(timeoutSecs); + } else if (!strcmp(aPref, kContentTimeoutPref)) { + MOZ_ASSERT(!module->IsChrome()); + int32_t timeoutSecs = Preferences::GetInt(kContentTimeoutPref, 0); + module->SetChildTimeout(timeoutSecs); + } +} + +void PluginModuleChromeParent::CleanupFromTimeout(const bool aFromHangUI) { + if (mShutdown) { + return; + } + + if (!OkToCleanup()) { + // there's still plugin code on the C++ stack, try again + MessageLoop::current()->PostDelayedTask( + mChromeTaskFactory.NewRunnableMethod( + &PluginModuleChromeParent::CleanupFromTimeout, aFromHangUI), + 10); + return; + } + + // Avoid recursively calling this method. MessageChannel::Close() can + // cause this task to be re-launched. + if (mIsCleaningFromTimeout) { + return; + } + + AutoRestore<bool> resetCleaningFlag(mIsCleaningFromTimeout); + mIsCleaningFromTimeout = true; + + /* 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 nsTArray<base::ProcessHandle>& processHandles, + nsTArray<float>& cpuUsage) { + nsTArray<CpuUsageSamples> samples(processHandles.Length()); + FILETIME creationTime, exitTime, kernelTime, userTime, currentTime; + BOOL res; + + for (uint32_t i = 0; i < processHandles.Length(); ++i) { + ::GetSystemTimeAsFileTime(¤tTime); + res = ::GetProcessTimes(processHandles[i], &creationTime, &exitTime, + &kernelTime, &userTime); + if (!res) { + NS_WARNING("failed to get process times"); + return false; + } + + CpuUsageSamples s; + s.sampleTimes[0] = FileTimeToUTC(currentTime); + s.cpuTimes[0] = FileTimeToUTC(kernelTime) + FileTimeToUTC(userTime); + samples.AppendElement(s); + } + + // we already hung for a while, a little bit longer won't matter + ::Sleep(50); + + const int32_t numberOfProcessors = PR_GetNumberOfProcessors(); + + for (uint32_t i = 0; i < processHandles.Length(); ++i) { + ::GetSystemTimeAsFileTime(¤tTime); + res = ::GetProcessTimes(processHandles[i], &creationTime, &exitTime, + &kernelTime, &userTime); + if (!res) { + NS_WARNING("failed to get process times"); + return false; + } + + samples[i].sampleTimes[1] = FileTimeToUTC(currentTime); + samples[i].cpuTimes[1] = + FileTimeToUTC(kernelTime) + FileTimeToUTC(userTime); + + const uint64_t deltaSampleTime = + samples[i].sampleTimes[1] - samples[i].sampleTimes[0]; + const uint64_t deltaCpuTime = + samples[i].cpuTimes[1] - samples[i].cpuTimes[0]; + const float usage = + 100.f * (float(deltaCpuTime) / deltaSampleTime) / numberOfProcessors; + cpuUsage.AppendElement(usage); + } + + return true; +} + +} // namespace + +#endif // #ifdef XP_WIN + +/** + * This function converts the topmost routing id on the call stack (as recorded + * by the MessageChannel) into a pointer to a IProtocol object. + */ +mozilla::ipc::IProtocol* PluginModuleChromeParent::GetInvokingProtocol() { + int32_t routingId = GetIPCChannel()->GetTopmostMessageRoutingId(); + // Nothing being routed. No protocol. Just return nullptr. + if (routingId == MSG_ROUTING_NONE) { + return nullptr; + } + // If routingId is MSG_ROUTING_CONTROL then we're dealing with control + // messages that were initiated by the topmost managing protocol, ie. this. + if (routingId == MSG_ROUTING_CONTROL) { + return this; + } + // Otherwise we can look up the protocol object by the routing id. + mozilla::ipc::IProtocol* protocol = Lookup(routingId); + return protocol; +} + +/** + * This function examines the IProtocol object parameter and converts it into + * the PluginInstanceParent object that is associated with that protocol, if + * any. Since PluginInstanceParent manages subprotocols, this function needs + * to determine whether |aProtocol| is a subprotocol, and if so it needs to + * obtain the protocol's manager. + * + * This function needs to be updated if the subprotocols are modified in + * PPluginInstance.ipdl. + */ +PluginInstanceParent* PluginModuleChromeParent::GetManagingInstance( + mozilla::ipc::IProtocol* aProtocol) { + MOZ_ASSERT(aProtocol); + mozilla::ipc::IProtocol* listener = aProtocol; + switch (listener->GetProtocolId()) { + case PPluginInstanceMsgStart: + // In this case, aProtocol is the instance itself. Just cast it. + return static_cast<PluginInstanceParent*>(aProtocol); + case PPluginBackgroundDestroyerMsgStart: { + PPluginBackgroundDestroyerParent* actor = + static_cast<PPluginBackgroundDestroyerParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } + case PPluginScriptableObjectMsgStart: { + PPluginScriptableObjectParent* actor = + static_cast<PPluginScriptableObjectParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } + case PBrowserStreamMsgStart: { + PBrowserStreamParent* actor = + static_cast<PBrowserStreamParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } + case PStreamNotifyMsgStart: { + PStreamNotifyParent* actor = static_cast<PStreamNotifyParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } +#ifdef XP_WIN + case PPluginSurfaceMsgStart: { + PPluginSurfaceParent* actor = + static_cast<PPluginSurfaceParent*>(aProtocol); + return static_cast<PluginInstanceParent*>(actor->Manager()); + } +#endif + default: + return nullptr; + } +} + +void PluginModuleChromeParent::EnteredCxxStack() { + mHangAnnotationFlags |= kInPluginCall; +} + +void PluginModuleChromeParent::ExitedCxxStack() { + mHangAnnotationFlags = 0; +#ifdef XP_WIN + FinishHangUI(); +#endif +} + +/** + * This function is always called by the BackgroundHangMonitor thread. + */ +void PluginModuleChromeParent::AnnotateHang( + mozilla::BackgroundHangAnnotations& 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 hang stack that we're in a plugin + call when the hang occurred. */ + if (flags & kHangUIShown) { + aAnnotations.AddAnnotation(u"HangUIShown"_ns, true); + } + if (flags & kHangUIContinued) { + aAnnotations.AddAnnotation(u"HangUIContinued"_ns, true); + } + if (flags & kHangUIDontShow) { + aAnnotations.AddAnnotation(u"HangUIDontShow"_ns, true); + } + aAnnotations.AddAnnotation(u"pluginName"_ns, mPluginName); + aAnnotations.AddAnnotation(u"pluginVersion"_ns, mPluginVersion); + } +} + +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); +} + +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, + "ModalHangUI"_ns, u""_ns); + GetIPCChannel()->CloseWithTimeout(); + return false; +} + +bool PluginModuleContentParent::ShouldContinueFromReplyTimeout() { + RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get(); + if (!monitor) { + return true; + } + monitor->NotifyPluginHang(mPluginId); + return true; +} + +void PluginModuleContentParent::OnExitedSyncSend() { + ProcessHangMonitor::ClearHang(); +} + +void PluginModuleChromeParent::TakeFullMinidump(base::ProcessId aContentPid, + const nsAString& aBrowserDumpId, + nsString& aDumpId) { + mozilla::MutexAutoLock lock(mCrashReporterMutex); + + if (!mCrashReporter) { + 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. + nsCOMPtr<nsIFile> browserDumpFile; + if (CrashReporter::GetMinidumpForID(aBrowserDumpId, + getter_AddRefs(browserDumpFile))) { + // We have a single browser report, generate a new plugin process parent + // report and pair it up with the browser report handed in. + reportsReady = mCrashReporter->GenerateMinidumpAndPair( + this, browserDumpFile, "browser"_ns); + + 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 = mCrashReporter->GenerateMinidumpAndPair( + this, + nullptr, // Pair with a dump of this process and thread. + "browser"_ns); + } + + if (reportsReady) { + aDumpId = mCrashReporter->MinidumpID(); + PLUGIN_LOG_DEBUG(("generated paired browser/plugin minidumps: %s)", + NS_ConvertUTF16toUTF8(aDumpId).get())); + nsAutoCString additionalDumps("browser"); + nsCOMPtr<nsIFile> pluginDumpFile; + if (GetMinidumpForID(aDumpId, getter_AddRefs(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, + "flash1"_ns)) { + additionalDumps.AppendLiteral(",flash1"); + } + if (CreatePluginMinidump(mFlashProcess2, 0, pluginDumpFile, + "flash2"_ns)) { + additionalDumps.AppendLiteral(",flash2"); + } +#endif // MOZ_CRASHREPORTER_INJECTOR + if (aContentPid != mozilla::ipc::kInvalidProcessId) { + // Include the content process minidump + if (CreatePluginMinidump(aContentPid, 0, pluginDumpFile, + "content"_ns)) { + additionalDumps.AppendLiteral(",content"); + } + } + } + mCrashReporter->AddAnnotation(Annotation::additional_minidumps, + additionalDumps); + } else { + NS_WARNING("failed to capture paired minidumps from hang"); + } +} + +void PluginModuleChromeParent::TerminateChildProcess( + MessageLoop* aMsgLoop, base::ProcessId aContentPid, + const nsCString& aMonitorDescription, const nsAString& aDumpId) { + // 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 recursive locking. + nsAutoString dumpId; + if (aDumpId.IsEmpty()) { + TakeFullMinidump(aContentPid, u""_ns, dumpId); + } + + mozilla::MutexAutoLock lock(mCrashReporterMutex); + if (!mCrashReporter) { + // If mCrashReporter is null then the hang has ended, the plugin module + // is shutting down. There's nothing to do here. + return; + } + mCrashReporter->AddAnnotation(Annotation::PluginHang, true); + mCrashReporter->AddAnnotation(Annotation::HangMonitorDescription, + aMonitorDescription); +#ifdef XP_WIN + if (mHangUIParent) { + unsigned int hangUIDuration = mHangUIParent->LastShowDurationMs(); + if (hangUIDuration) { + mCrashReporter->AddAnnotation(Annotation::PluginHangUIDuration, + hangUIDuration); + } + } +#endif // XP_WIN + + mozilla::ipc::ScopedProcessHandle geckoChildProcess; + bool childOpened = + base::OpenProcessHandle(OtherPid(), &geckoChildProcess.rwget()); + +#ifdef XP_WIN + // collect cpu usage for plugin processes + + nsTArray<base::ProcessHandle> processHandles; + + if (childOpened) { + processHandles.AppendElement(geckoChildProcess); + } + +# ifdef MOZ_CRASHREPORTER_INJECTOR + mozilla::ipc::ScopedProcessHandle flashBrokerProcess; + if (mFlashProcess1 && + base::OpenProcessHandle(mFlashProcess1, &flashBrokerProcess.rwget())) { + processHandles.AppendElement(flashBrokerProcess); + } + mozilla::ipc::ScopedProcessHandle flashSandboxProcess; + if (mFlashProcess2 && + base::OpenProcessHandle(mFlashProcess2, &flashSandboxProcess.rwget())) { + processHandles.AppendElement(flashSandboxProcess); + } +# endif + + if (!GetProcessCpuUsage(processHandles, mPluginCpuUsageOnHang)) { + mPluginCpuUsageOnHang.Clear(); + } +#endif + + // this must run before the error notification from the channel, + // or not at all + bool isFromHangUI = aMsgLoop != MessageLoop::current(); + aMsgLoop->PostTask(mChromeTaskFactory.NewRunnableMethod( + &PluginModuleChromeParent::CleanupFromTimeout, isFromHangUI)); + + if (!childOpened || !KillProcess(geckoChildProcess, 1, false)) { + NS_WARNING("failed to kill subprocess!"); + } +} + +bool PluginModuleParent::GetPluginDetails() { + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + if (!host) { + return false; + } + nsPluginTag* pluginTag = host->TagForPlugin(mPlugin); + if (!pluginTag) { + return false; + } + mPluginName = pluginTag->Name(); + mPluginVersion = pluginTag->Version(); + mPluginFilename = pluginTag->FileName(); + mIsFlashPlugin = pluginTag->mIsFlashPlugin; + mSandboxLevel = pluginTag->mSandboxLevel; + return true; +} + +void PluginModuleParent::InitQuirksModes(const nsCString& aMimeType) { + if (mQuirks != QUIRKS_NOT_INITIALIZED) { + return; + } + + mQuirks = GetQuirksFromMimeTypeAndFilename(aMimeType, mPluginFilename); +} + +#ifdef XP_WIN +void PluginModuleChromeParent::EvaluateHangUIState(const bool aReset) { + int32_t minDispSecs = Preferences::GetInt(kHangUIMinDisplayPref, 10); + int32_t autoStopSecs = Preferences::GetInt(kChildTimeoutPref, 0); + int32_t timeoutSecs = 0; + if (autoStopSecs > 0 && autoStopSecs < minDispSecs) { + /* If we're going to automatically terminate the plugin within a + time frame shorter than minDispSecs, there's no point in + showing the hang UI; it would just flash briefly on the screen. */ + mHangUIEnabled = false; + } else { + timeoutSecs = Preferences::GetInt(kHangUITimeoutPref, 0); + mHangUIEnabled = timeoutSecs > 0; + } + if (mHangUIEnabled) { + if (aReset) { + mIsTimerReset = true; + SetChildTimeout(timeoutSecs); + return; + } else if (mIsTimerReset) { + /* The Hang UI is being shown, so now we're setting the + timeout to kChildTimeoutPref while we wait for a user + response. ShouldContinueFromReplyTimeout will fire + after (reply timeout / 2) seconds, which is not what + we want. Doubling the timeout value here so that we get + the right result. */ + autoStopSecs *= 2; + } + } + mIsTimerReset = false; + SetChildTimeout(autoStopSecs); +} + +bool PluginModuleChromeParent::LaunchHangUI() { + if (!mHangUIEnabled) { + return false; + } + if (mHangUIParent) { + if (mHangUIParent->IsShowing()) { + // We've already shown the UI but the timeout has expired again. + return false; + } + if (mHangUIParent->DontShowAgain()) { + mHangAnnotationFlags |= kHangUIDontShow; + bool wasLastHangStopped = mHangUIParent->WasLastHangStopped(); + if (!wasLastHangStopped) { + mHangAnnotationFlags |= kHangUIContinued; + } + return !wasLastHangStopped; + } + delete mHangUIParent; + mHangUIParent = nullptr; + } + mHangUIParent = + new PluginHangUIParent(this, Preferences::GetInt(kHangUITimeoutPref, 0), + Preferences::GetInt(kChildTimeoutPref, 0)); + bool retval = mHangUIParent->Init(NS_ConvertUTF8toUTF16(mPluginName)); + if (retval) { + mHangAnnotationFlags |= kHangUIShown; + /* Once the UI is shown we switch the timeout over to use + kChildTimeoutPref, allowing us to terminate a hung plugin + after kChildTimeoutPref seconds if the user doesn't respond to + the hang UI. */ + EvaluateHangUIState(false); + } + return retval; +} + +void PluginModuleChromeParent::FinishHangUI() { + if (mHangUIEnabled && mHangUIParent) { + bool needsCancel = mHangUIParent->IsShowing(); + // If we're still showing, send a Cancel notification + if (needsCancel) { + mHangUIParent->Cancel(); + } + /* If we cancelled the UI or if the user issued a response, + we need to reset the child process timeout. */ + if (needsCancel || (!mIsTimerReset && mHangUIParent->WasShown())) { + /* We changed the timeout to kChildTimeoutPref when the plugin hang + UI was displayed. Now that we're finishing the UI, we need to + switch it back to kHangUITimeoutPref. */ + EvaluateHangUIState(true); + } + } +} + +void PluginModuleChromeParent::OnHangUIContinue() { + mHangAnnotationFlags |= kHangUIContinued; +} +#endif // XP_WIN + +#ifdef MOZ_CRASHREPORTER_INJECTOR +static void RemoveMinidump(nsIFile* minidump) { + if (!minidump) return; + + minidump->Remove(false); + nsCOMPtr<nsIFile> extraFile; + if (GetExtraFileForMinidump(minidump, getter_AddRefs(extraFile))) { + extraFile->Remove(true); + } +} +#endif // MOZ_CRASHREPORTER_INJECTOR + +void PluginModuleChromeParent::ProcessFirstMinidump() { + mozilla::MutexAutoLock lock(mCrashReporterMutex); + + if (!mCrashReporter) { + HandleOrphanedMinidump(); + return; + } + + AddCrashAnnotations(); + + if (mCrashReporter->HasMinidump()) { + // A minidump 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. + mCrashReporter->FinalizeCrashReport(); + return; + } + + AnnotationTable annotations; + uint32_t sequence = UINT32_MAX; + nsAutoCString flashProcessType; + RefPtr<nsIFile> dumpFile = + mCrashReporter->TakeCrashedChildMinidump(OtherPid(), &sequence); + +#ifdef MOZ_CRASHREPORTER_INJECTOR + nsCOMPtr<nsIFile> childDumpFile; + uint32_t childSequence; + + if (mFlashProcess1 && + TakeMinidumpForChild(mFlashProcess1, getter_AddRefs(childDumpFile), + annotations, &childSequence)) { + if (childSequence < sequence && + mCrashReporter->AdoptMinidump(childDumpFile, annotations)) { + RemoveMinidump(dumpFile); + dumpFile = childDumpFile; + sequence = childSequence; + flashProcessType.AssignLiteral("Broker"); + } else { + RemoveMinidump(childDumpFile); + } + } + if (mFlashProcess2 && + TakeMinidumpForChild(mFlashProcess2, getter_AddRefs(childDumpFile), + annotations, &childSequence)) { + if (childSequence < sequence && + mCrashReporter->AdoptMinidump(childDumpFile, annotations)) { + 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(mCrashReporter->MinidumpID()).get())); + + if (!flashProcessType.IsEmpty()) { + mCrashReporter->AddAnnotation(Annotation::FlashProcessDump, + flashProcessType); + } + mCrashReporter->FinalizeCrashReport(); +} + +void PluginModuleChromeParent::HandleOrphanedMinidump() { + if (CrashReporter::FinalizeOrphanedMinidump( + OtherPid(), GeckoProcessType_Plugin, &mOrphanedDumpId)) { + ipc::CrashReporterHost::RecordCrash(GeckoProcessType_Plugin, + nsICrashService::CRASH_TYPE_CRASH, + mOrphanedDumpId); + } else { + NS_WARNING(nsPrintfCString("plugin process pid = %d crashed without " + "leaving a minidump behind", + OtherPid()) + .get()); + } +} + +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: + MOZ_CRASH("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) { + ProcessFirstMinidump(); + Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, "plugin"_ns, 1); + } + + // We can't broadcast settings changes anymore. + UnregisterSettingsCallbacks(); + +#if defined(XP_WIN) + if (mBrokerParent) { + FunctionBrokerParent::Destroy(mBrokerParent); + mBrokerParent = nullptr; + } +#endif + + PluginModuleParent::ActorDestroy(why); +} + +void PluginModuleParent::NotifyFlashHang() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "flash-plugin-hang", nullptr); + } +} + +void PluginModuleParent::NotifyPluginCrashed() { + if (!OkToCleanup()) { + // there's still plugin code on the C++ stack. try again + MessageLoop::current()->PostDelayedTask( + mTaskFactory.NewRunnableMethod( + &PluginModuleParent::NotifyPluginCrashed), + 10); + return; + } + + if (!mPlugin) { + return; + } + + nsString dumpID; + nsCString additionalMinidumps; + + if (mCrashReporter && mCrashReporter->HasMinidump()) { + dumpID = mCrashReporter->MinidumpID(); + additionalMinidumps = mCrashReporter->AdditionalMinidumps(); + } else { + dumpID = mOrphanedDumpId; + } + + mPlugin->PluginCrashed(dumpID, additionalMinidumps); +} + +PPluginInstanceParent* PluginModuleParent::AllocPPluginInstanceParent( + const nsCString& aMimeType, const nsTArray<nsCString>& aNames, + const nsTArray<nsCString>& aValues) { + NS_ERROR("Not reachable!"); + return nullptr; +} + +bool PluginModuleParent::DeallocPPluginInstanceParent( + PPluginInstanceParent* aActor) { + PLUGIN_LOG_DEBUG_METHOD; + delete aActor; + return true; +} + +void PluginModuleParent::SetPluginFuncs(NPPluginFuncs* aFuncs) { + MOZ_ASSERT(aFuncs); + + aFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + aFuncs->javaClass = nullptr; + + // Gecko should always call these functions through a PluginLibrary object. + aFuncs->newp = nullptr; + aFuncs->clearsitedata = nullptr; + aFuncs->getsiteswithdata = nullptr; + + aFuncs->destroy = NPP_Destroy; + aFuncs->setwindow = NPP_SetWindow; + aFuncs->newstream = NPP_NewStream; + aFuncs->destroystream = NPP_DestroyStream; + aFuncs->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; + } +} + +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; + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + if (!pip) return NPERR_NO_ERROR; + + NPError retval = pip->Destroy(); + instance->pdata = nullptr; + + Unused << PluginInstanceParent::Send__delete__(pip); + return retval; +} + +NPError PluginModuleParent::NPP_NewStream(NPP instance, NPMIMEType type, + NPStream* stream, NPBool seekable, + uint16_t* stype) { + AUTO_PROFILER_LABEL("PluginModuleParent::NPP_NewStream", OTHER); + + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_NewStream(type, stream, seekable, stype) + : NPERR_GENERIC_ERROR; +} + +NPError PluginModuleParent::NPP_SetWindow(NPP instance, NPWindow* window) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_SetWindow(window) : NPERR_GENERIC_ERROR; +} + +NPError PluginModuleParent::NPP_DestroyStream(NPP instance, NPStream* stream, + NPReason reason) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_DestroyStream(stream, reason) : NPERR_GENERIC_ERROR; +} + +int32_t PluginModuleParent::NPP_WriteReady(NPP instance, NPStream* stream) { + BrowserStreamParent* s = StreamCast(instance, stream); + return s ? s->WriteReady() : -1; +} + +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_Print(NPP instance, NPPrint* platformPrint) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_Print(platformPrint) : (void)0; +} + +int16_t PluginModuleParent::NPP_HandleEvent(NPP instance, void* event) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_HandleEvent(event) : NPERR_GENERIC_ERROR; +} + +void PluginModuleParent::NPP_URLNotify(NPP instance, const char* url, + NPReason reason, void* notifyData) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_URLNotify(url, reason, notifyData) : (void)0; +} + +NPError PluginModuleParent::NPP_GetValue(NPP instance, NPPVariable variable, + void* ret_value) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_GetValue(variable, ret_value) : NPERR_GENERIC_ERROR; +} + +NPError PluginModuleParent::NPP_SetValue(NPP instance, NPNVariable variable, + void* value) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_SetValue(variable, value) : NPERR_GENERIC_ERROR; +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +#else + MOZ_CRASH( + "NPPVpluginRequiresAudioDeviceChanges is not valid on this platform."); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvBackUpXResources( + const FileDescriptor& aXSocketFd) { +#ifndef MOZ_X11 + MOZ_CRASH("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 IPC_OK(); +} + +void PluginModuleParent::NPP_URLRedirectNotify(NPP instance, const char* url, + int32_t status, + void* notifyData) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_URLRedirectNotify(url, status, notifyData) : (void)0; +} + +BrowserStreamParent* PluginModuleParent::StreamCast(NPP instance, NPStream* s) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + if (!pip) { + return nullptr; + } + + BrowserStreamParent* sp = + static_cast<BrowserStreamParent*>(static_cast<AStream*>(s->pdata)); + if (sp && (sp->mNPP != pip || s != sp->mStream)) { + MOZ_CRASH("Corrupted plugin stream data."); + } + return sp; +} + +bool PluginModuleParent::HasRequiredFunctions() { return true; } + +nsresult PluginModuleParent::AsyncSetWindow(NPP instance, NPWindow* window) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->AsyncSetWindow(window) : NS_ERROR_FAILURE; +} + +nsresult PluginModuleParent::GetImageContainer( + NPP instance, mozilla::layers::ImageContainer** aContainer) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->GetImageContainer(aContainer) : NS_ERROR_FAILURE; +} + +nsresult PluginModuleParent::GetImageSize(NPP instance, nsIntSize* aSize) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->GetImageSize(aSize) : NS_ERROR_FAILURE; +} + +void PluginModuleParent::DidComposite(NPP aInstance) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(aInstance); + return pip ? pip->DidComposite() : (void)0; +} + +nsresult PluginModuleParent::SetBackgroundUnknown(NPP instance) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->SetBackgroundUnknown() : NS_ERROR_FAILURE; +} + +nsresult PluginModuleParent::BeginUpdateBackground(NPP instance, + const nsIntRect& aRect, + DrawTarget** aDrawTarget) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->BeginUpdateBackground(aRect, aDrawTarget) + : NS_ERROR_FAILURE; +} + +nsresult PluginModuleParent::EndUpdateBackground(NPP instance, + const nsIntRect& aRect) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->EndUpdateBackground(aRect) : NS_ERROR_FAILURE; +} + +#if defined(XP_WIN) +nsresult PluginModuleParent::GetScrollCaptureContainer( + NPP aInstance, mozilla::layers::ImageContainer** aContainer) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(aInstance); + return pip ? pip->GetScrollCaptureContainer(aContainer) : NS_ERROR_FAILURE; +} +#endif + +nsresult PluginModuleParent::HandledWindowedPluginKeyEvent( + NPP aInstance, const NativeEventData& aNativeKeyData, bool aIsConsumed) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(aInstance); + return pip ? pip->HandledWindowedPluginKeyEvent(aNativeKeyData, aIsConsumed) + : NS_ERROR_FAILURE; +} + +void PluginModuleParent::OnInitFailure() { + if (GetIPCChannel()->CanSend()) { + Close(); + } + + mShutdown = true; +} + +class PluginOfflineObserver final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit PluginOfflineObserver(PluginModuleChromeParent* pmp) : mPmp(pmp) {} + + private: + ~PluginOfflineObserver() = default; + 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; +} + +void PluginModuleChromeParent::RegisterSettingsCallbacks() { + Preferences::RegisterCallback(CachedSettingChanged, "javascript.enabled", + this); + Preferences::RegisterCallback(CachedSettingChanged, + "dom.ipc.plugins.nativeCursorSupport", this); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + mPluginOfflineObserver = new PluginOfflineObserver(this); + observerService->AddObserver(mPluginOfflineObserver, + "ipc:network:set-offline", false); + } +} + +void PluginModuleChromeParent::UnregisterSettingsCallbacks() { + Preferences::UnregisterCallback(CachedSettingChanged, "javascript.enabled", + this); + Preferences::UnregisterCallback(CachedSettingChanged, + "dom.ipc.plugins.nativeCursorSupport", this); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(mPluginOfflineObserver, + "ipc:network:set-offline"); + mPluginOfflineObserver = nullptr; + } +} + +bool PluginModuleParent::GetSetting(NPNVariable aVariable) { + NPBool boolVal = false; + mozilla::plugins::parent::_getvalue(nullptr, aVariable, &boolVal); + return boolVal; +} + +void PluginModuleParent::GetSettings(PluginSettings* aSettings) { + aSettings->javascriptEnabled() = GetSetting(NPNVjavascriptEnabledBool); + aSettings->asdEnabled() = GetSetting(NPNVasdEnabledBool); + aSettings->isOffline() = GetSetting(NPNVisOfflineBool); + aSettings->supportsXembed() = GetSetting(NPNVSupportsXEmbedBool); + aSettings->supportsWindowless() = GetSetting(NPNVSupportsWindowless); + aSettings->userAgent() = NullableString(mNPNIface->uagent(nullptr)); + +#if defined(XP_MACOSX) + aSettings->nativeCursorsSupported() = + Preferences::GetBool("dom.ipc.plugins.nativeCursorSupport", false); +#else + // Need to initialize this to satisfy IPDL. + aSettings->nativeCursorsSupported() = false; +#endif +} + +void PluginModuleChromeParent::CachedSettingChanged() { + PluginSettings settings; + GetSettings(&settings); + Unused << SendSettingChanged(settings); +} + +/* static */ +void PluginModuleChromeParent::CachedSettingChanged(const char* aPref, + void* aModule) { + auto module = static_cast<PluginModuleChromeParent*>(aModule); + module->CachedSettingChanged(); +} + +#if defined(XP_UNIX) && !defined(XP_MACOSX) +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; + 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; + + PluginSettings settings; + GetSettings(&settings); + + if (!CallNP_Initialize(settings, error)) { + Close(); + return NS_ERROR_FAILURE; + } else if (*error != NPERR_NO_ERROR) { + Close(); + return NS_ERROR_FAILURE; + } + + if (*error != NPERR_NO_ERROR) { + OnInitFailure(); + return NS_OK; + } + + SetPluginFuncs(mNPPIface); + + return NS_OK; +} + +#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; + return PluginModuleParent::NP_Initialize(bFuncs, error); +} + +# endif + +nsresult PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, + NPError* error) { + nsresult rv = PluginModuleParent::NP_Initialize(bFuncs, error); + if (NS_FAILED(rv)) return rv; + + PluginSettings settings; + GetSettings(&settings); + + if (!CallNP_Initialize(settings, error)) { + Close(); + return NS_ERROR_FAILURE; + } + + bool ok = true; + if (*error == NPERR_NO_ERROR) { + // Initialization steps for (e10s && !asyncInit) || !e10s +# if defined XP_WIN + // 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 + } + + if (!ok) { + return NS_ERROR_FAILURE; + } + + if (*error != NPERR_NO_ERROR) { + OnInitFailure(); + return NS_OK; + } + + return NS_OK; +} + +#endif + +nsresult PluginModuleParent::NP_Shutdown(NPError* error) { + PLUGIN_LOG_DEBUG_METHOD; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + 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; + SetPluginFuncs(pFuncs); + + return NS_OK; +} + +nsresult PluginModuleChromeParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, + NPError* error) { +# if !defined(XP_MACOSX) + if (!mSubprocess->IsConnected()) { + mNPPIface = pFuncs; + *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, + 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; + } + + // create the instance on the other side + nsTArray<nsCString> names; + nsTArray<nsCString> values; + + for (int i = 0; i < argc; ++i) { + names.AppendElement(NullableString(argn[i])); + values.AppendElement(NullableString(argv[i])); + } + + return NPP_NewInternal(pluginType, instance, names, values, saved, error); +} + +class nsCaseInsensitiveUTF8StringArrayComparator { + public: + template <class A, class B> + bool Equals(const A& a, const B& b) const { + return a.Equals(b.get(), nsCaseInsensitiveUTF8StringComparator); + } +}; + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) +static void ForceWindowless(nsTArray<nsCString>& names, + nsTArray<nsCString>& values) { + nsCaseInsensitiveUTF8StringArrayComparator comparator; + constexpr auto wmodeAttributeName = "wmode"_ns; + constexpr auto opaqueAttributeValue = "opaque"_ns; + 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 // windows or linux +#if defined(XP_WIN) +static void ForceDirect(nsTArray<nsCString>& names, + nsTArray<nsCString>& values) { + nsCaseInsensitiveUTF8StringArrayComparator comparator; + constexpr auto wmodeAttributeName = "wmode"_ns; + constexpr auto directAttributeValue = "direct"_ns; + auto wmodeAttributeIndex = names.IndexOf(wmodeAttributeName, 0, comparator); + if (wmodeAttributeIndex != names.NoIndex) { + if ((!values[wmodeAttributeIndex].EqualsLiteral("transparent")) && + (!values[wmodeAttributeIndex].EqualsLiteral("opaque"))) { + values[wmodeAttributeIndex].Assign(directAttributeValue); + } + } else { + names.AppendElement(wmodeAttributeName); + values.AppendElement(directAttributeValue); + } +} +#endif // windows + +nsresult PluginModuleParent::NPP_NewInternal( + NPMIMEType pluginType, NPP instance, nsTArray<nsCString>& names, + nsTArray<nsCString>& values, NPSavedData* saved, NPError* error) { + MOZ_ASSERT(names.Length() == values.Length()); + if (mPluginName.IsEmpty()) { + GetPluginDetails(); + InitQuirksModes(nsDependentCString(pluginType)); + } + + nsCaseInsensitiveUTF8StringArrayComparator comparator; + constexpr auto srcAttributeName = "src"_ns; + 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); + bool supportsForceDirect = + Preferences::GetBool("dom.ipc.plugins.forcedirect.enabled", false); + if (supportsAsyncRender) { + // Prefs indicates we want async plugin rendering, make sure + // the flash module has support. + if (!CallModuleSupportsAsyncRender(&supportsAsyncRender)) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + } +# 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 + ForceWindowless(names, values); + } +#elif defined(MOZ_WIDGET_GTK) + // We no longer support windowed mode on Linux. + ForceWindowless(names, values); +#endif +#ifdef XP_WIN + // For all builds that use async rendering force use of the accelerated + // direct path for flash objects that have wmode=window or no wmode + // specified. + if (supportsAsyncRender && supportsForceDirect && + PluginInstanceParent::SupportsPluginDirectDXGISurfaceDrawing()) { + ForceDirect(names, values); + } +#endif + } + + instance->pdata = parentInstance; + + // Any IPC messages for the PluginInstance actor should be dispatched to the + // DocGroup for the plugin's document. + RefPtr<nsPluginInstanceOwner> owner = parentInstance->GetOwner(); + RefPtr<dom::Element> elt; + owner->GetDOMElement(getter_AddRefs(elt)); + if (elt) { + RefPtr<dom::Document> doc = elt->OwnerDoc(); + nsCOMPtr<nsISerialEventTarget> eventTarget = + doc->EventTargetFor(TaskCategory::Other); + SetEventTargetForActor(parentInstance, eventTarget); + } + + if (!SendPPluginInstanceConstructor( + parentInstance, nsDependentCString(pluginType), names, values)) { + // |parentInstance| is automatically deleted. + instance->pdata = nullptr; + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + 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) { + NPP_Destroy(instance, 0); + return NS_ERROR_FAILURE; + } + + Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_PLUGIN_INSTANTIATED, + 1); + + UpdatePluginTimeout(); + + return NS_OK; +} + +void PluginModuleChromeParent::UpdatePluginTimeout() { + TimeoutChanged(kParentTimeoutPref, this); +} + +nsresult PluginModuleParent::NPP_ClearSiteData( + const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr<nsIClearSiteDataCallback> callback) { + if (!mClearSiteDataSupported) return NS_ERROR_NOT_AVAILABLE; + + static uint64_t callbackId = 0; + callbackId++; + mClearSiteDataCallbacks[callbackId] = callback; + + if (!SendNPP_ClearSiteData(NullableString(site), flags, maxAge, callbackId)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult PluginModuleParent::NPP_GetSitesWithData( + nsCOMPtr<nsIGetSitesWithDataCallback> callback) { + if (!mGetSitesWithDataSupported) return NS_ERROR_NOT_AVAILABLE; + + static uint64_t callbackId = 0; + callbackId++; + mSitesWithDataCallbacks[callbackId] = callback; + + if (!SendNPP_GetSitesWithData(callbackId)) return NS_ERROR_FAILURE; + + return NS_OK; +} + +#if defined(XP_MACOSX) +nsresult PluginModuleParent::IsRemoteDrawingCoreAnimation(NPP instance, + bool* aDrawing) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->IsRemoteDrawingCoreAnimation(aDrawing) : NS_ERROR_FAILURE; +} +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +nsresult PluginModuleParent::ContentsScaleFactorChanged( + NPP instance, double aContentsScaleFactor) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->ContentsScaleFactorChanged(aContentsScaleFactor) + : NS_ERROR_FAILURE; +} +#endif // #if defined(XP_MACOSX) + +#if defined(XP_MACOSX) +mozilla::ipc::IPCResult PluginModuleParent::AnswerProcessSomeEvents() { + mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop(); + return IPC_OK(); +} + +#elif !defined(MOZ_WIDGET_GTK) +mozilla::ipc::IPCResult PluginModuleParent::AnswerProcessSomeEvents() { + MOZ_CRASH("unreached"); +} + +#else +static const int kMaxChancesToProcessEvents = 20; + +mozilla::ipc::IPCResult 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 IPC_OK(); +} +#endif + +mozilla::ipc::IPCResult +PluginModuleParent::RecvProcessNativeEventsInInterruptCall() { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(OS_WIN) + ProcessNativeEventsInInterruptCall(); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginModuleParent::RecvProcessNativeEventsInInterruptCall not " + "implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +void PluginModuleParent::ProcessRemoteNativeEventsInInterruptCall() { +#if defined(OS_WIN) + Unused << SendProcessNativeEventsInInterruptCall(); + return; +#endif + MOZ_ASSERT_UNREACHABLE( + "PluginModuleParent::ProcessRemoteNativeEventsInInterruptCall not " + "implemented!"); +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvPluginShowWindow( + const uint32_t& aWindowId, const bool& aModal, const int32_t& aX, + const int32_t& aY, const double& aWidth, const double& 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 IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvPluginShowWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvPluginHideWindow( + const uint32_t& aWindowId) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnPluginHideWindow(aWindowId, OtherPid()); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvPluginHideWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvSetCursor( + const NSCursorInfo& aCursorInfo) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnSetCursor(aCursorInfo); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvSetCursor not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvShowCursor(const bool& aShow) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnShowCursor(aShow); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvShowCursor not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvPushCursor( + const NSCursorInfo& aCursorInfo) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnPushCursor(aCursorInfo); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvPushCursor not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvPopCursor() { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnPopCursor(); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvPopCursor not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult 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 IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvNPN_ReloadPlugins( + const bool& aReloadPages) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + mozilla::plugins::parent::_reloadplugins(aReloadPages); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginModuleChromeParent::RecvNotifyContentModuleDestroyed() { + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + if (host) { + host->NotifyContentModuleDestroyed(mPluginId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvReturnClearSiteData( + const NPError& aRv, const uint64_t& aCallbackId) { + if (mClearSiteDataCallbacks.find(aCallbackId) == + mClearSiteDataCallbacks.end()) { + return IPC_OK(); + } + 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 IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvReturnSitesWithData( + nsTArray<nsCString>&& aSites, const uint64_t& aCallbackId) { + if (mSitesWithDataCallbacks.find(aCallbackId) == + mSitesWithDataCallbacks.end()) { + return IPC_OK(); + } + + if (!!mSitesWithDataCallbacks[aCallbackId]) { + mSitesWithDataCallbacks[aCallbackId]->SitesWithData(aSites); + } + mSitesWithDataCallbacks.erase(aCallbackId); + return IPC_OK(); +} + +layers::TextureClientRecycleAllocator* +PluginModuleParent::EnsureTextureAllocatorForDirectBitmap() { + if (!mTextureAllocatorForDirectBitmap) { + mTextureAllocatorForDirectBitmap = + new layers::TextureClientRecycleAllocator( + layers::ImageBridgeChild::GetSingleton().get()); + } + return mTextureAllocatorForDirectBitmap; +} + +layers::TextureClientRecycleAllocator* +PluginModuleParent::EnsureTextureAllocatorForDXGISurface() { + if (!mTextureAllocatorForDXGISurface) { + mTextureAllocatorForDXGISurface = new layers::TextureClientRecycleAllocator( + layers::ImageBridgeChild::GetSingleton().get()); + } + return mTextureAllocatorForDXGISurface; +} + +mozilla::ipc::IPCResult +PluginModuleParent::AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, NPError* result) { + MOZ_CRASH( + "SetValue_NPPVpluginRequiresAudioDeviceChanges is only valid " + "with PluginModuleChromeParent"); +} + +#ifdef MOZ_CRASHREPORTER_INJECTOR + +// We only add the crash reporter to subprocess which have the filename +// FlashPlayerPlugin* +# define FLASH_PROCESS_PREFIX u"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, nsLiteralString(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), + nsLiteralCString(FLASH_PLUGIN_PREFIX))) + return; + + mFinishInitTask = mChromeTaskFactory.NewTask<FinishInjectorInitTask>(); + mFinishInitTask->Init(this); + if (!::QueueUserWorkItem(&PluginModuleChromeParent::GetToolhelpSnapshot, + mFinishInitTask, WT_EXECUTEDEFAULT)) { + mFinishInitTask = nullptr; + return; + } +} + +void PluginModuleChromeParent::DoInjection(const nsAutoHandle& aSnapshot) { + DWORD pluginProcessPID = GetProcessId(Process()->GetChildProcessHandle()); + mFlashProcess1 = GetFlashChildOfPID(pluginProcessPID, aSnapshot); + if (mFlashProcess1) { + InjectCrashReporterIntoProcess(mFlashProcess1, this); + + mFlashProcess2 = GetFlashChildOfPID(mFlashProcess1, aSnapshot); + if (mFlashProcess2) { + InjectCrashReporterIntoProcess(mFlashProcess2, this); + } + } + mFinishInitTask = nullptr; +} + +DWORD WINAPI PluginModuleChromeParent::GetToolhelpSnapshot(LPVOID aContext) { + FinishInjectorInitTask* task = static_cast<FinishInjectorInitTask*>(aContext); + MOZ_ASSERT(task); + task->PostToMainThread(); + return 0; +} + +void PluginModuleChromeParent::OnCrash(DWORD processID) { + if (!mShutdown) { + GetIPCChannel()->CloseWithError(); + mozilla::ipc::ScopedProcessHandle geckoPluginChild; + if (base::OpenProcessHandle(OtherPid(), &geckoPluginChild.rwget())) { + if (!base::KillProcess(geckoPluginChild, base::PROCESS_END_KILLED_BY_USER, + false)) { + NS_ERROR("May have failed to kill child process."); + } + } else { + NS_ERROR("Failed to open child process when attempting kill."); + } + } +} + +#endif // MOZ_CRASHREPORTER_INJECTOR diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h new file mode 100644 index 0000000000..9fd74904a2 --- /dev/null +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -0,0 +1,545 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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 "mozilla/Unused.h" +#include "npapi.h" +#include "npfunctions.h" +#include "nsExceptionHandler.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsIObserver.h" +#ifdef XP_WIN +# include "nsWindowsHelpers.h" +#endif + +class nsPluginTag; + +namespace mozilla { + +namespace ipc { +class CrashReporterHost; +} // namespace ipc +namespace layers { +class TextureClientRecycleAllocator; +} // namespace layers + +namespace plugins { +//----------------------------------------------------------------------------- + +class BrowserStreamParent; +class PluginInstanceParent; + +#ifdef XP_WIN +class PluginHangUIParent; +class FunctionBrokerParent; +#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 +{ + friend class PPluginModuleParent; + + protected: + typedef mozilla::PluginLibrary PluginLibrary; + + PPluginInstanceParent* AllocPPluginInstanceParent( + const nsCString& aMimeType, const nsTArray<nsCString>& aNames, + const nsTArray<nsCString>& aValues); + + bool DeallocPPluginInstanceParent(PPluginInstanceParent* aActor); + + public: + explicit PluginModuleParent(bool aIsChrome); + virtual ~PluginModuleParent(); + + 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 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); + } + + mozilla::ipc::IPCResult RecvBackUpXResources( + const FileDescriptor& aXSocketFd); + + mozilla::ipc::IPCResult AnswerProcessSomeEvents(); + + mozilla::ipc::IPCResult RecvProcessNativeEventsInInterruptCall(); + + mozilla::ipc::IPCResult RecvPluginShowWindow( + const uint32_t& aWindowId, const bool& aModal, const int32_t& aX, + const int32_t& aY, const double& aWidth, const double& aHeight); + + mozilla::ipc::IPCResult RecvPluginHideWindow(const uint32_t& aWindowId); + + mozilla::ipc::IPCResult RecvSetCursor(const NSCursorInfo& aCursorInfo); + + mozilla::ipc::IPCResult RecvShowCursor(const bool& aShow); + + mozilla::ipc::IPCResult RecvPushCursor(const NSCursorInfo& aCursorInfo); + + mozilla::ipc::IPCResult RecvPopCursor(); + + mozilla::ipc::IPCResult RecvNPN_SetException(const nsCString& aMessage); + + mozilla::ipc::IPCResult RecvNPN_ReloadPlugins(const bool& aReloadPages); + + static BrowserStreamParent* StreamCast(NPP instance, NPStream* s); + + virtual mozilla::ipc::IPCResult + AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, NPError* result); + + protected: + void SetChildTimeout(const int32_t aChildTimeout); + static void TimeoutChanged(const char* aPref, void* aModule); + + virtual void UpdatePluginTimeout() {} + + virtual mozilla::ipc::IPCResult RecvNotifyContentModuleDestroyed() { + return IPC_OK(); + } + + mozilla::ipc::IPCResult RecvReturnClearSiteData(const NPError& aRv, + const uint64_t& aCallbackId); + + mozilla::ipc::IPCResult RecvReturnSitesWithData(nsTArray<nsCString>&& aSites, + const uint64_t& aCallbackId); + + void SetPluginFuncs(NPPluginFuncs* aFuncs); + + nsresult NPP_NewInternal(NPMIMEType pluginType, NPP instance, + nsTArray<nsCString>& names, + nsTArray<nsCString>& values, NPSavedData* saved, + NPError* error); + + // NPP-like API that Gecko calls are trampolined into. These + // messages then get forwarded along to the plugin instance, + // and then eventually the child process. + + static NPError NPP_Destroy(NPP instance, NPSavedData** save); + + static NPError NPP_SetWindow(NPP instance, NPWindow* window); + static NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype); + static NPError NPP_DestroyStream(NPP instance, NPStream* stream, + NPReason reason); + static int32_t NPP_WriteReady(NPP instance, NPStream* stream); + static int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, + int32_t len, void* buffer); + static void NPP_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) + 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, int16_t argc, + char* argn[], char* argv[], NPSavedData* saved, + NPError* error) override; + virtual nsresult NPP_ClearSiteData( + const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr<nsIClearSiteDataCallback> callback) override; + virtual nsresult NPP_GetSitesWithData( + nsCOMPtr<nsIGetSitesWithDataCallback> callback) override; + + private: + std::map<uint64_t, nsCOMPtr<nsIClearSiteDataCallback>> + mClearSiteDataCallbacks; + std::map<uint64_t, nsCOMPtr<nsIGetSitesWithDataCallback>> + mSitesWithDataCallbacks; + + nsCString mPluginFilename; + int mQuirks; + void InitQuirksModes(const nsCString& aMimeType); + + public: +#if defined(XP_MACOSX) + virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, + bool* aDrawing) override; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + virtual nsresult ContentsScaleFactorChanged( + NPP instance, double aContentsScaleFactor) override; +#endif + + layers::TextureClientRecycleAllocator* + EnsureTextureAllocatorForDirectBitmap(); + layers::TextureClientRecycleAllocator* EnsureTextureAllocatorForDXGISurface(); + + protected: + void NotifyFlashHang(); + void NotifyPluginCrashed(); + void OnInitFailure(); + bool DoShutdown(NPError* error); + + bool GetSetting(NPNVariable aVariable); + void GetSettings(PluginSettings* aSettings); + + bool mIsChrome; + bool mShutdown; + bool mHadLocalInstance; + bool mClearSiteDataSupported; + bool mGetSitesWithDataSupported; + NPNetscapeFuncs* mNPNIface; + NPPluginFuncs* mNPPIface; + nsNPAPIPlugin* mPlugin; + ipc::TaskFactory<PluginModuleParent> mTaskFactory; + nsString mHangID; + 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(); + + uint32_t mRunID; + + RefPtr<layers::TextureClientRecycleAllocator> + mTextureAllocatorForDirectBitmap; + RefPtr<layers::TextureClientRecycleAllocator> mTextureAllocatorForDXGISurface; + + /** + * 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 CrashReporterHost object + * that mCrashReporter refers to. + */ + mozilla::Mutex mCrashReporterMutex; + UniquePtr<ipc::CrashReporterHost> mCrashReporter; + nsString mOrphanedDumpId; +}; + +class PluginModuleContentParent : public PluginModuleParent { + public: + explicit PluginModuleContentParent(); + + static PluginLibrary* LoadModule(uint32_t aPluginId, nsPluginTag* aPluginTag); + + virtual ~PluginModuleContentParent(); + +#if defined(XP_WIN) || defined(XP_MACOSX) + nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override; +#endif + + private: + static void Initialize(Endpoint<PPluginModuleParent>&& aEndpoint); + + 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::BackgroundHangAnnotator { + friend class mozilla::ipc::CrashReporterHost; + + 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); + + 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 + + void CachedSettingChanged(); + + private: + virtual void EnteredCxxStack() override; + + void ExitedCxxStack() override; + + mozilla::ipc::IProtocol* GetInvokingProtocol(); + PluginInstanceParent* GetManagingInstance(mozilla::ipc::IProtocol* aProtocol); + + virtual void AnnotateHang( + mozilla::BackgroundHangAnnotations& aAnnotations) override; + + virtual bool ShouldContinueFromReplyTimeout() override; + + void ProcessFirstMinidump(); + void HandleOrphanedMinidump(); + void AddCrashAnnotations(); + + PluginProcessParent* Process() const { return mSubprocess; } + base::ProcessHandle ChildProcessHandle() { + return mSubprocess->GetChildProcessHandle(); + } + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + 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); + + void CleanupFromTimeout(const bool aByHangUI); + + virtual void UpdatePluginTimeout() override; + + void RegisterSettingsCallbacks(); + void UnregisterSettingsCallbacks(); + + bool InitCrashReporter(); + + mozilla::ipc::IPCResult RecvNotifyContentModuleDestroyed() override; + + static void CachedSettingChanged(const char* aPref, void* aModule); + + mozilla::ipc::IPCResult + AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, NPError* result) override; + + PluginProcessParent* mSubprocess; + uint32_t mPluginId; + + ipc::TaskFactory<PluginModuleChromeParent> mChromeTaskFactory; + + enum HangAnnotationFlags { + kInPluginCall = (1u << 0), + kHangUIShown = (1u << 1), + kHangUIContinued = (1u << 2), + kHangUIDontShow = (1u << 3) + }; + Atomic<uint32_t> mHangAnnotationFlags; +#ifdef XP_WIN + nsTArray<float> mPluginCpuUsageOnHang; + PluginHangUIParent* mHangUIParent; + bool mHangUIEnabled; + bool mIsTimerReset; + + /** + * 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(); + + FunctionBrokerParent* mBrokerParent; +#endif + +#ifdef MOZ_CRASHREPORTER_INJECTOR + friend class mozilla::plugins::FinishInjectorInitTask; + + void InitializeInjector(); + void DoInjection(const nsAutoHandle& aSnapshot); + static DWORD WINAPI GetToolhelpSnapshot(LPVOID aContext); + + void OnCrash(DWORD processID) override; + + DWORD mFlashProcess1; + DWORD mFlashProcess2; + RefPtr<mozilla::plugins::FinishInjectorInitTask> mFinishInitTask; +#endif + + void OnProcessLaunched(const bool aSucceeded); + + class LaunchedTask : public LaunchCompleteTask { + public: + explicit LaunchedTask(PluginModuleChromeParent* aModule) + : mModule(aModule) { + MOZ_ASSERT(aModule); + } + + NS_IMETHOD Run() override { + mModule->OnProcessLaunched(mLaunchSucceeded); + return NS_OK; + } + + private: + PluginModuleChromeParent* mModule; + }; + + friend class LaunchedTask; + + nsCOMPtr<nsIObserver> mPluginOfflineObserver; + bool mIsBlocklisted; + bool mIsCleaningFromTimeout; +}; + +} // 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 0000000000..986243f93f --- /dev/null +++ b/dom/plugins/ipc/PluginProcessChild.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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/PluginProcessChild.h" + +#include "ClearOnShutdown.h" +#include "base/command_line.h" +#include "base/message_loop.h" // for MessageLoop +#include "base/string_util.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/TaskController.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "nsDebugImpl.h" +#include "nsThreadManager.h" +#include "prlink.h" + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +#endif + +#ifdef XP_WIN +# if defined(MOZ_SANDBOX) +# include "mozilla/sandboxTarget.h" +# include "ProcessUtils.h" +# include "nsDirectoryService.h" +# endif +#endif + +using mozilla::ipc::IOThreadChild; + +#ifdef OS_WIN +# include <algorithm> +#endif + +namespace mozilla::plugins { + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +static void SetSandboxTempPath(const std::wstring& aFullTmpPath) { + // Save the TMP environment variable so that is is picked up by GetTempPath(). + // Note that we specifically write to the TMP variable, as that is the first + // variable that is checked by GetTempPath() to determine its output. + Unused << NS_WARN_IF(!SetEnvironmentVariableW(L"TMP", aFullTmpPath.c_str())); + + // We also set TEMP in case there is naughty third-party code that is + // referencing the environment variable directly. + Unused << NS_WARN_IF(!SetEnvironmentVariableW(L"TEMP", aFullTmpPath.c_str())); +} +#endif + +bool PluginProcessChild::Init(int aArgc, char* aArgv[]) { + nsDebugImpl::SetMultiprocessMode("NPAPI"); + +#if defined(XP_MACOSX) + // Remove the trigger for "dyld interposing" that we added in + // GeckoChildProcessHost::PerformAsyncLaunch(), in the host + // process just before we were launched. Dyld interposing will still + // happen in our process (the plugin child process). But we don't want + // it to happen in any processes that the plugin might launch from our + // process. + nsCString interpose(PR_GetEnv("DYLD_INSERT_LIBRARIES")); + if (!interpose.IsEmpty()) { + // If we added the path to libplugin_child_interpose.dylib to an + // existing DYLD_INSERT_LIBRARIES, we appended it to the end, after a + // ":" path seperator. + int32_t lastSeparatorPos = interpose.RFind(":"); + int32_t lastTriggerPos = interpose.RFind("libplugin_child_interpose.dylib"); + bool needsReset = false; + if (lastTriggerPos != -1) { + if (lastSeparatorPos == -1) { + interpose.Truncate(); + needsReset = true; + } else if (lastTriggerPos > lastSeparatorPos) { + interpose.SetLength(lastSeparatorPos); + needsReset = true; + } + } + if (needsReset) { + nsCString setInterpose("DYLD_INSERT_LIBRARIES="); + if (!interpose.IsEmpty()) { + setInterpose.Append(interpose); + } + // Values passed to PR_SetEnv() must be seperately allocated. + char* setInterposePtr = strdup(setInterpose.get()); + PR_SetEnv(setInterposePtr); + } + } +#endif + + // Certain plugins, such as flash, steal the unhandled exception filter + // thus we never get crash reports when they fault. This call fixes it. + message_loop()->set_exception_restoration(true); + + std::string pluginFilename; + +#if defined(OS_POSIX) + // NB: need to be very careful in ensuring that the first arg + // (after the binary name) here is indeed the plugin module path. + // Keep in sync with dom/plugins/PluginModuleParent. + std::vector<std::string> values = CommandLine::ForCurrentProcess()->argv(); + MOZ_ASSERT(values.size() >= 2, "not enough args"); + + pluginFilename = UnmungePluginDsoPath(values[1]); + +# if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + int level; + if (values.size() >= 4 && values[2] == "-flashSandboxLevel" && + (level = std::stoi(values[3], nullptr)) > 0) { + level = ClampFlashSandboxLevel(level); + MOZ_ASSERT(level > 0); + + bool enableLogging = false; + if (values.size() >= 5 && values[4] == "-flashSandboxLogging") { + enableLogging = true; + } + + mPlugin.EnableFlashSandbox(level, enableLogging); + } +# endif + +#elif defined(OS_WIN) + std::vector<std::wstring> values = + CommandLine::ForCurrentProcess()->GetLooseValues(); + MOZ_ASSERT(values.size() >= 1, "not enough loose args"); + + // parameters are: + // values[0] is path to plugin DLL + // values[1] is path to folder that should be used for temp files + // values[2] is path to the Flash Player roaming folder + // (this is always that Flash folder, regardless of what plugin is being + // run) + pluginFilename = WideToUTF8(values[0]); + + // We don't initialize XPCOM but we need the thread manager and the + // logging framework for the FunctionBroker. + NS_SetMainThread(); + mozilla::TimeStamp::Startup(); + NS_LogInit(); + mozilla::LogModule::Init(aArgc, aArgv); + nsThreadManager::get().Init(); + +# if defined(MOZ_SANDBOX) + MOZ_ASSERT(values.size() >= 3, + "not enough loose args for sandboxed plugin process"); + + // The sandbox closes off the default location temp file location so we set + // a new one here (regardless of whether or not we are sandboxing). + SetSandboxTempPath(values[1]); + PluginModuleChild::SetFlashRoamingPath(values[2]); + + // 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 + + return mPlugin.InitForChrome(pluginFilename, ParentPid(), + IOThreadChild::message_loop(), + IOThreadChild::TakeChannel()); +} + +void PluginProcessChild::CleanUp() { +#if defined(OS_WIN) + MOZ_ASSERT(NS_IsMainThread()); + + // Shutdown components we started in Init. Note that KillClearOnShutdown + // is an event that is regularly part of XPCOM shutdown. We do not + // call XPCOM's shutdown but we need this event to be sent to avoid + // leaking objects labeled as ClearOnShutdown. + nsThreadManager::get().Shutdown(); + NS_LogTerm(); +#endif + + mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownFinal); + + AbstractThread::ShutdownMainThread(); + + mozilla::TaskController::Shutdown(); +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/PluginProcessChild.h b/dom/plugins/ipc/PluginProcessChild.h new file mode 100644 index 0000000000..36c8077ce8 --- /dev/null +++ b/dom/plugins/ipc/PluginProcessChild.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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/ProcessRuntime.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() = default; + + virtual bool Init(int aArgc, char* aArgv[]) override; + virtual void CleanUp() override; + + protected: + static PluginProcessChild* current() { + return static_cast<PluginProcessChild*>(ProcessChild::current()); + } + + private: +#if defined(XP_WIN) + /* Drag-and-drop depends on the host initializing COM. + * This object initializes and configures COM. */ + mozilla::mscom::ProcessRuntime 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 0000000000..2f65311263 --- /dev/null +++ b/dom/plugins/ipc/PluginProcessParent.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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 "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsServiceManagerUtils.h" + +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/Telemetry.h" +#include "nsThreadUtils.h" + +using std::string; +using std::vector; + +using mozilla::ipc::BrowserProcessSubThread; +using mozilla::ipc::GeckoChildProcessHost; +using mozilla::plugins::LaunchCompleteTask; +using mozilla::plugins::PluginProcessParent; + +#ifdef XP_WIN +PluginProcessParent::PidSet* PluginProcessParent::sPidSet = nullptr; +#endif + +PluginProcessParent::PluginProcessParent(const std::string& aPluginFilePath) + : GeckoChildProcessHost(GeckoProcessType_Plugin), + mPluginFilePath(aPluginFilePath), + mTaskFactory(this), + mMainMsgLoop(MessageLoop::current()) +#ifdef XP_WIN + , + mChildPid(0) +#endif +{ +} + +PluginProcessParent::~PluginProcessParent() { +#ifdef XP_WIN + if (sPidSet && mChildPid) { + sPidSet->RemoveEntry(mChildPid); + if (sPidSet->IsEmpty()) { + delete sPidSet; + sPidSet = nullptr; + } + } +#endif +} + +bool PluginProcessParent::Launch( + mozilla::UniquePtr<LaunchCompleteTask> aLaunchCompleteTask, + int32_t aSandboxLevel, bool aIsSandboxLoggingEnabled) { +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_SANDBOX) + // At present, the Mac Flash plugin sandbox does not support different + // levels and is enabled via a boolean pref or environment variable. + // On Mac, when |aSandboxLevel| is positive, we enable the sandbox. +# if defined(XP_WIN) + mSandboxLevel = aSandboxLevel; + + // The sandbox process sometimes needs read access to the plugin file. + if (aSandboxLevel >= 3) { + std::wstring pluginFile( + NS_ConvertUTF8toUTF16(mPluginFilePath.c_str()).get()); + mAllowedFilesRead.push_back(pluginFile); + } +# endif // XP_WIN +#else + if (aSandboxLevel != 0) { + MOZ_ASSERT(false, + "Can't enable an NPAPI process sandbox for platform/build."); + } +#endif + + mLaunchCompleteTask = std::move(aLaunchCompleteTask); + + vector<string> args; + args.push_back(MungePluginDsoPath(mPluginFilePath)); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (aSandboxLevel > 0) { + args.push_back("-flashSandboxLevel"); + args.push_back(std::to_string(aSandboxLevel)); + if (aIsSandboxLoggingEnabled) { + args.push_back("-flashSandboxLogging"); + } + } +#elif defined(XP_WIN) && defined(MOZ_SANDBOX) + nsresult rv; + nsCOMPtr<nsIProperties> dirSvc = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to get directory service."); + return false; + } + + nsCOMPtr<nsIFile> dir; + rv = dirSvc->Get(NS_APP_PLUGIN_PROCESS_TEMP_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(dir)); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to get plugin process temp directory."); + return false; + } + + nsAutoString tempDir; + MOZ_ALWAYS_SUCCEEDS(dir->GetPath(tempDir)); + args.push_back(NS_ConvertUTF16toUTF8(tempDir).get()); + + rv = + dirSvc->Get(NS_WIN_APPDATA_DIR, NS_GET_IID(nsIFile), getter_AddRefs(dir)); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to get appdata directory."); + return false; + } + + nsAutoString appdataDir; + MOZ_ALWAYS_SUCCEEDS(dir->GetPath(appdataDir)); + appdataDir.Append(L"\\Adobe\\"); + args.push_back(NS_ConvertUTF16toUTF8(appdataDir).get()); +#endif + + bool result = AsyncLaunch(args); + if (!result) { + mLaunchCompleteTask = nullptr; + } + return result; +} + +/** + * 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 (mLaunchCompleteTask) { + if (result) { + mLaunchCompleteTask->SetLaunchSucceeded(); + } + RunLaunchCompleteTask(); + } + return result; +} + +void PluginProcessParent::OnChannelConnected(int32_t peer_pid) { +#ifdef XP_WIN + mChildPid = static_cast<uint32_t>(peer_pid); + if (!sPidSet) { + sPidSet = new PluginProcessParent::PidSet(); + } + sPidSet->PutEntry(mChildPid); +#endif + + GeckoChildProcessHost::OnChannelConnected(peer_pid); +} + +void PluginProcessParent::OnChannelError() { + GeckoChildProcessHost::OnChannelError(); +} + +bool PluginProcessParent::IsConnected() { + mozilla::MonitorAutoLock lock(mMonitor); + return mProcessState == PROCESS_CONNECTED; +} + +bool PluginProcessParent::IsPluginProcessId(base::ProcessId procId) { +#ifdef XP_WIN + MOZ_ASSERT(XRE_IsParentProcess()); + return sPidSet && sPidSet->Contains(static_cast<uint32_t>(procId)); +#else + NS_ERROR("IsPluginProcessId not available on this platform."); + return false; +#endif +} diff --git a/dom/plugins/ipc/PluginProcessParent.h b/dom/plugins/ipc/PluginProcessParent.h new file mode 100644 index 0000000000..48ce24cc70 --- /dev/null +++ b/dom/plugins/ipc/PluginProcessParent.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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 "nsTHashtable.h" +#include "nsHashKeys.h" + +namespace mozilla { +namespace plugins { + +class LaunchCompleteTask : public Runnable { + public: + LaunchCompleteTask() + : Runnable("plugins::LaunchCompleteTask"), mLaunchSucceeded(false) {} + + void SetLaunchSucceeded() { mLaunchSucceeded = true; } + + protected: + bool mLaunchSucceeded; +}; + +class PluginProcessParent final : public mozilla::ipc::GeckoChildProcessHost { + public: + explicit PluginProcessParent(const std::string& aPluginFilePath); + + /** + * 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. + * @param aIsSandboxLoggingEnabled Indicates if sandbox violation + * logging should be enabled for the plugin process. + */ + bool Launch(UniquePtr<LaunchCompleteTask> aLaunchCompleteTask = + UniquePtr<LaunchCompleteTask>(), + int32_t aSandboxLevel = 0, bool aIsSandboxLoggingEnabled = false); + + virtual bool CanShutdown() override { return true; } + + const std::string& GetPluginFilePath() { return mPluginFilePath; } + + using mozilla::ipc::GeckoChildProcessHost::GetChannel; + + virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0) override; + + virtual void OnChannelConnected(int32_t peer_pid) override; + virtual void OnChannelError() override; + + bool IsConnected(); + + static bool IsPluginProcessId(base::ProcessId procId); + + private: + ~PluginProcessParent(); + + void RunLaunchCompleteTask(); + + std::string mPluginFilePath; + ipc::TaskFactory<PluginProcessParent> mTaskFactory; + UniquePtr<LaunchCompleteTask> mLaunchCompleteTask; + MessageLoop* mMainMsgLoop; +#ifdef XP_WIN + typedef nsTHashtable<nsUint32HashKey> PidSet; + // Set of PIDs for all plugin child processes or NULL if empty. + static PidSet* sPidSet; + uint32_t mChildPid; +#endif + + 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 0000000000..2e83cbc37b --- /dev/null +++ b/dom/plugins/ipc/PluginQuirks.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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::plugins { + +int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType, + const nsCString& aPluginFilename) { + int quirks = 0; + + nsPluginHost::SpecialType specialType = + nsPluginHost::GetSpecialType(aMimeType); + + 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; + quirks |= QUIRK_FLASH_HOOK_PRINTDLGW; + quirks |= QUIRK_FLASH_HOOK_SSL; + quirks |= QUIRK_FLASH_HOOK_CREATEMUTEXW; +# endif +#endif + } + +#ifdef XP_MACOSX + // Whitelist Flash to support offline renderer. + if (specialType == nsPluginHost::eSpecialType_Flash) { + quirks |= QUIRK_ALLOW_OFFLINE_RENDERER; + } +#endif + +#ifdef OS_WIN + if (specialType == nsPluginHost::eSpecialType_Test) { + quirks |= QUIRK_WINLESS_HOOK_IME; + } +#endif + + return quirks; +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/PluginQuirks.h b/dom/plugins/ipc/PluginQuirks.h new file mode 100644 index 0000000000..852ebb6b7a --- /dev/null +++ b/dom/plugins/ipc/PluginQuirks.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 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 + +#include "nsString.h" + +namespace mozilla { +namespace plugins { + +// Quirks mode support for various plugin mime types +enum PluginQuirks { + QUIRKS_NOT_INITIALIZED = 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, + // 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: 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, + // Win: Hook PrintDlgW to show print settings dialog on sandbox process + QUIRK_FLASH_HOOK_PRINTDLGW = 1 << 14, + // Win: Broker Win32 SSL operations + QUIRK_FLASH_HOOK_SSL = 1 << 15, + // Win: Hook CreateMutexW for brokering when using the camera + QUIRK_FLASH_HOOK_CREATEMUTEXW = 1 << 16, +}; + +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 0000000000..86c4b83d18 --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectChild.cpp @@ -0,0 +1,1205 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginScriptableObjectChild.h" +#include "PluginScriptableObjectUtils.h" +#include "mozilla/plugins/PluginTypes.h" + +using namespace mozilla::plugins; + +/** + * NPIdentifiers in the plugin process use a tagged representation. The low bit + * stores the tag. If it's zero, the identifier is a string, and the value is a + * pointer to a StoredIdentifier. If the tag bit is 1, then the rest of the + * NPIdentifier value is the integer itself. Like the JSAPI, we require that all + * integers stored in NPIdentifier be non-negative. + * + * String identifiers are stored in the sIdentifiers hashtable to ensure + * uniqueness. The lifetime of these identifiers is only as long as the incoming + * IPC call from the chrome process. If the plugin wants to retain an + * identifier, it needs to call NPN_GetStringIdentifier, which causes the + * mPermanent flag to be set on the identifier. When this flag is set, the + * identifier is saved until the plugin process exits. + * + * The StackIdentifier RAII class is used to manage ownership of + * identifiers. Any identifier obtained from this class should not be used + * outside its scope, except when the MakePermanent() method has been called on + * it. + * + * The lifetime of an NPIdentifier in the plugin process is totally divorced + * from the lifetime of an NPIdentifier in the chrome process (where an + * NPIdentifier is stored as a jsid). The JS GC in the chrome process is able to + * trace through the entire heap, unlike in the plugin process, so there is no + * reason to retain identifiers there. + */ + +PluginScriptableObjectChild::IdentifierTable + PluginScriptableObjectChild::sIdentifiers; + +/* static */ PluginScriptableObjectChild::StoredIdentifier* +PluginScriptableObjectChild::HashIdentifier(const nsCString& aIdentifier) { + StoredIdentifier* stored = sIdentifiers.Get(aIdentifier).get(); + if (stored) { + return stored; + } + + stored = new StoredIdentifier(aIdentifier); + sIdentifiers.Put(aIdentifier, stored); + return stored; +} + +/* static */ +void PluginScriptableObjectChild::UnhashIdentifier(StoredIdentifier* aStored) { + MOZ_ASSERT(sIdentifiers.Get(aStored->mIdentifier)); + sIdentifiers.Remove(aStored->mIdentifier); +} + +/* static */ +void PluginScriptableObjectChild::ClearIdentifiers() { sIdentifiers.Clear(); } + +PluginScriptableObjectChild::StackIdentifier::StackIdentifier( + const PluginIdentifier& aIdentifier) + : mIdentifier(aIdentifier), mStored(nullptr) { + if (aIdentifier.type() == PluginIdentifier::TnsCString) { + mStored = PluginScriptableObjectChild::HashIdentifier( + mIdentifier.get_nsCString()); + } +} + +PluginScriptableObjectChild::StackIdentifier::StackIdentifier( + NPIdentifier aIdentifier) + : mStored(nullptr) { + uintptr_t bits = reinterpret_cast<uintptr_t>(aIdentifier); + if (bits & 1) { + int32_t num = int32_t(bits >> 1); + mIdentifier = PluginIdentifier(num); + } else { + mStored = static_cast<StoredIdentifier*>(aIdentifier); + mIdentifier = mStored->mIdentifier; + } +} + +PluginScriptableObjectChild::StackIdentifier::~StackIdentifier() { + if (!mStored) { + return; + } + + // Each StackIdentifier owns one reference to its StoredIdentifier. In + // addition, the sIdentifiers table owns a reference. If mPermanent is false + // and sIdentifiers has the last reference, then we want to remove the + // StoredIdentifier from the table (and destroy it). + StoredIdentifier* stored = mStored; + mStored = nullptr; + if (stored->mRefCnt == 1 && !stored->mPermanent) { + PluginScriptableObjectChild::UnhashIdentifier(stored); + } +} + +NPIdentifier PluginScriptableObjectChild::StackIdentifier::ToNPIdentifier() + const { + if (mStored) { + MOZ_ASSERT(mIdentifier.type() == PluginIdentifier::TnsCString); + MOZ_ASSERT((reinterpret_cast<uintptr_t>(mStored.get()) & 1) == 0); + return mStored; + } + + int32_t num = mIdentifier.get_int32_t(); + // The JS engine imposes this condition on int32s in jsids, so we assume it. + MOZ_ASSERT(num >= 0); + return reinterpret_cast<NPIdentifier>((num << 1) | 1); +} + +static PluginIdentifier FromNPIdentifier(NPIdentifier aIdentifier) { + PluginScriptableObjectChild::StackIdentifier stack(aIdentifier); + return stack.GetIdentifier(); +} + +// static +NPObject* PluginScriptableObjectChild::ScriptableAllocate(NPP aInstance, + NPClass* aClass) { + AssertPluginThread(); + + if (aClass != GetClass()) { + MOZ_CRASH("Huh?! Wrong class!"); + } + + return new ChildNPObject(); +} + +// static +void PluginScriptableObjectChild::ScriptableInvalidate(NPObject* aObject) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + // This can happen more than once, and is just fine. + return; + } + + object->invalidated = true; +} + +// static +void PluginScriptableObjectChild::ScriptableDeallocate(NPObject* aObject) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + PluginScriptableObjectChild* actor = object->parent; + if (actor) { + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + actor->DropNPObject(); + } + + delete object; +} + +// static +bool PluginScriptableObjectChild::ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + actor->CallHasMethod(FromNPIdentifier(aName), &result); + + return result; +} + +// static +bool PluginScriptableObjectChild::ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + actor->CallInvoke(FromNPIdentifier(aName), args, &remoteResult, &success); + + if (!success) { + return false; + } + + ConvertToVariant(remoteResult, *aResult); + return true; +} + +// static +bool PluginScriptableObjectChild::ScriptableInvokeDefault( + NPObject* aObject, const NPVariant* aArgs, uint32_t aArgCount, + NPVariant* aResult) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + actor->CallInvokeDefault(args, &remoteResult, &success); + + if (!success) { + return false; + } + + ConvertToVariant(remoteResult, *aResult); + return true; +} + +// static +bool PluginScriptableObjectChild::ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + actor->CallHasProperty(FromNPIdentifier(aName), &result); + + return result; +} + +// static +bool PluginScriptableObjectChild::ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + PluginInstanceChild::AutoStackHelper guard(actor->mInstance); + + Variant result; + bool success; + actor->CallGetParentProperty(FromNPIdentifier(aName), &result, &success); + + if (!success) { + return false; + } + + ConvertToVariant(result, *aResult); + return true; +} + +// static +bool PluginScriptableObjectChild::ScriptableSetProperty( + NPObject* aObject, NPIdentifier aName, const NPVariant* aValue) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariant value(*aValue, actor->GetInstance()); + if (!value.IsOk()) { + NS_WARNING("Failed to convert variant!"); + return false; + } + + bool success; + actor->CallSetProperty(FromNPIdentifier(aName), value, &success); + + return success; +} + +// static +bool PluginScriptableObjectChild::ScriptableRemoveProperty(NPObject* aObject, + NPIdentifier aName) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool success; + actor->CallRemoveProperty(FromNPIdentifier(aName), &success); + + return success; +} + +// static +bool PluginScriptableObjectChild::ScriptableEnumerate( + NPObject* aObject, NPIdentifier** aIdentifiers, uint32_t* aCount) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + AutoTArray<PluginIdentifier, 10> identifiers; + bool success; + actor->CallEnumerate(&identifiers, &success); + + if (!success) { + return false; + } + + *aCount = identifiers.Length(); + if (!*aCount) { + *aIdentifiers = nullptr; + return true; + } + + *aIdentifiers = + reinterpret_cast<NPIdentifier*>(PluginModuleChild::sBrowserFuncs.memalloc( + *aCount * sizeof(NPIdentifier))); + if (!*aIdentifiers) { + NS_ERROR("Out of memory!"); + return false; + } + + for (uint32_t index = 0; index < *aCount; index++) { + StackIdentifier id(identifiers[index]); + // Make the id permanent in case the plugin retains it. + id.MakePermanent(); + (*aIdentifiers)[index] = id.ToNPIdentifier(); + } + return true; +} + +// static +bool PluginScriptableObjectChild::ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast<ChildNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectChild> actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + actor->CallConstruct(args, &remoteResult, &success); + + if (!success) { + return false; + } + + ConvertToVariant(remoteResult, *aResult); + return true; +} + +const NPClass PluginScriptableObjectChild::sNPClass = { + NP_CLASS_STRUCT_VERSION, + PluginScriptableObjectChild::ScriptableAllocate, + PluginScriptableObjectChild::ScriptableDeallocate, + PluginScriptableObjectChild::ScriptableInvalidate, + PluginScriptableObjectChild::ScriptableHasMethod, + PluginScriptableObjectChild::ScriptableInvoke, + PluginScriptableObjectChild::ScriptableInvokeDefault, + PluginScriptableObjectChild::ScriptableHasProperty, + PluginScriptableObjectChild::ScriptableGetProperty, + PluginScriptableObjectChild::ScriptableSetProperty, + PluginScriptableObjectChild::ScriptableRemoveProperty, + PluginScriptableObjectChild::ScriptableEnumerate, + PluginScriptableObjectChild::ScriptableConstruct}; + +PluginScriptableObjectChild::PluginScriptableObjectChild( + ScriptableObjectType aType) + : mInstance(nullptr), + mObject(nullptr), + mInvalidated(false), + mProtectCount(0), + mType(aType) { + AssertPluginThread(); +} + +PluginScriptableObjectChild::~PluginScriptableObjectChild() { + AssertPluginThread(); + + if (mObject) { + UnregisterActor(mObject); + + if (mObject->_class == GetClass()) { + NS_ASSERTION(mType == Proxy, "Wrong type!"); + static_cast<ChildNPObject*>(mObject)->parent = nullptr; + } else { + NS_ASSERTION(mType == LocalObject, "Wrong type!"); + PluginModuleChild::sBrowserFuncs.releaseobject(mObject); + } + } +} + +bool PluginScriptableObjectChild::InitializeProxy() { + AssertPluginThread(); + NS_ASSERTION(mType == Proxy, "Bad type!"); + NS_ASSERTION(!mObject, "Calling Initialize more than once!"); + NS_ASSERTION(!mInvalidated, "Already invalidated?!"); + + mInstance = static_cast<PluginInstanceChild*>(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + NPObject* object = CreateProxyObject(); + if (!object) { + NS_ERROR("Failed to create object!"); + return false; + } + + if (!RegisterActor(object)) { + NS_ERROR("RegisterActor failed"); + return false; + } + + mObject = object; + return true; +} + +void PluginScriptableObjectChild::InitializeLocal(NPObject* aObject) { + AssertPluginThread(); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + NS_ASSERTION(!mObject, "Calling Initialize more than once!"); + NS_ASSERTION(!mInvalidated, "Already invalidated?!"); + + mInstance = static_cast<PluginInstanceChild*>(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + PluginModuleChild::sBrowserFuncs.retainobject(aObject); + + NS_ASSERTION(!mProtectCount, "Should be zero!"); + mProtectCount++; + + if (!RegisterActor(aObject)) { + NS_ERROR("RegisterActor failed"); + } + + mObject = aObject; +} + +NPObject* PluginScriptableObjectChild::CreateProxyObject() { + NS_ASSERTION(mInstance, "Must have an instance!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + NPClass* proxyClass = const_cast<NPClass*>(GetClass()); + NPObject* npobject = PluginModuleChild::sBrowserFuncs.createobject( + mInstance->GetNPP(), proxyClass); + NS_ASSERTION(npobject, "Failed to create object?!"); + NS_ASSERTION(npobject->_class == GetClass(), "Wrong kind of object!"); + NS_ASSERTION(npobject->referenceCount == 1, "Some kind of live object!"); + + ChildNPObject* object = static_cast<ChildNPObject*>(npobject); + NS_ASSERTION(!object->invalidated, "Bad object!"); + NS_ASSERTION(!object->parent, "Bad object!"); + + // We don't want to have the actor own this object but rather let the object + // own this actor. Set the reference count to 0 here so that when the object + // dies we will send the destructor message to the child. + object->referenceCount = 0; + NS_LOG_RELEASE(object, 0, "NPObject"); + + object->parent = const_cast<PluginScriptableObjectChild*>(this); + return object; +} + +bool PluginScriptableObjectChild::ResurrectProxyObject() { + NS_ASSERTION(mInstance, "Must have an instance already!"); + NS_ASSERTION(!mObject, "Should not have an object already!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + if (!InitializeProxy()) { + NS_ERROR("Initialize failed!"); + return false; + } + + SendProtect(); + return true; +} + +NPObject* PluginScriptableObjectChild::GetObject(bool aCanResurrect) { + if (!mObject && aCanResurrect && !ResurrectProxyObject()) { + NS_ERROR("Null object!"); + return nullptr; + } + return mObject; +} + +void PluginScriptableObjectChild::Protect() { + NS_ASSERTION(mObject, "No object!"); + NS_ASSERTION(mProtectCount >= 0, "Negative retain count?!"); + + if (mType == LocalObject) { + ++mProtectCount; + } +} + +void PluginScriptableObjectChild::Unprotect() { + NS_ASSERTION(mObject, "Bad state!"); + NS_ASSERTION(mProtectCount >= 0, "Negative retain count?!"); + + if (mType == LocalObject) { + if (--mProtectCount == 0) { + PluginScriptableObjectChild::Send__delete__(this); + } + } +} + +void PluginScriptableObjectChild::DropNPObject() { + NS_ASSERTION(mObject, "Invalidated object!"); + NS_ASSERTION(mObject->_class == GetClass(), "Wrong type of object!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + // We think we're about to be deleted, but we could be racing with the other + // process. + UnregisterActor(mObject); + mObject = nullptr; + + SendUnprotect(); +} + +void PluginScriptableObjectChild::NPObjectDestroyed() { + NS_ASSERTION(LocalObject == mType, + "ScriptableDeallocate should have handled this for proxies"); + mInvalidated = true; + mObject = nullptr; +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerInvalidate() { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + return IPC_OK(); + } + + 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 IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasMethod)) { + *aHasMethod = false; + return IPC_OK(); + } + + StackIdentifier id(aId); + *aHasMethod = mObject->_class->hasMethod(mObject, id.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerInvoke( + const PluginIdentifier& aId, nsTArray<Variant>&& aArgs, Variant* aResult, + bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerInvoke with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, mozilla::fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + Variant convertedResult; + success = + ConvertToRemoteVariant(result, convertedResult, GetInstance(), false); + + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aSuccess = true; + *aResult = convertedResult; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerInvokeDefault( + nsTArray<Variant>&& aArgs, Variant* aResult, bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerInvokeDefault with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, mozilla::fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + Variant convertedResult; + success = + ConvertToRemoteVariant(result, convertedResult, GetInstance(), false); + + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aResult = convertedResult; + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasProperty)) { + *aHasProperty = false; + return IPC_OK(); + } + + StackIdentifier id(aId); + *aHasProperty = mObject->_class->hasProperty(mObject, id.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); + } + + 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 IPC_OK(); + } + + 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 IPC_OK(); + } + + Variant converted; + if ((*aSuccess = + ConvertToRemoteVariant(result, converted, GetInstance(), false))) { + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + *aResult = converted; + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); + } + + 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 IPC_OK(); + } + + StackIdentifier stackID(aId); + NPIdentifier id = stackID.ToNPIdentifier(); + + if (!mObject->_class->hasProperty(mObject, id)) { + *aSuccess = false; + return IPC_OK(); + } + + NPVariant converted; + ConvertToVariant(aValue, converted); + + if ((*aSuccess = mObject->_class->setProperty(mObject, id, &converted))) { + PluginModuleChild::sBrowserFuncs.releasevariantvalue(&converted); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); + } + + 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 IPC_OK(); + } + + StackIdentifier stackID(aId); + NPIdentifier id = stackID.ToNPIdentifier(); + *aSuccess = mObject->_class->hasProperty(mObject, id) + ? mObject->_class->removeProperty(mObject, id) + : true; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerEnumerate( + nsTArray<PluginIdentifier>* aProperties, bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerEnumerate with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->enumerate)) { + *aSuccess = false; + return IPC_OK(); + } + + NPIdentifier* ids; + uint32_t idCount; + if (!mObject->_class->enumerate(mObject, &ids, &idCount)) { + *aSuccess = false; + return IPC_OK(); + } + + aProperties->SetCapacity(idCount); + + for (uint32_t index = 0; index < idCount; index++) { + aProperties->AppendElement(FromNPIdentifier(ids[index])); + } + + PluginModuleChild::sBrowserFuncs.memfree(ids); + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerConstruct( + nsTArray<Variant>&& aArgs, Variant* aResult, bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerConstruct with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, mozilla::fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + Variant convertedResult; + success = + ConvertToRemoteVariant(result, convertedResult, GetInstance(), false); + + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aResult = convertedResult; + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::RecvProtect() { + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Protect(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::RecvUnprotect() { + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Unprotect(); + return IPC_OK(); +} + +bool PluginScriptableObjectChild::Evaluate(NPString* aScript, + NPVariant* aResult) { + PluginInstanceChild::AutoStackHelper guard(mInstance); + + nsDependentCString script(""); + if (aScript->UTF8Characters && aScript->UTF8Length) { + script.Rebind(aScript->UTF8Characters, aScript->UTF8Length); + } + + bool success; + Variant result; + CallNPN_Evaluate(script, &result, &success); + + if (!success) { + return false; + } + + ConvertToVariant(result, *aResult); + return true; +} + +nsTHashtable<PluginScriptableObjectChild::NPObjectData>* + PluginScriptableObjectChild::sObjectMap; + +bool PluginScriptableObjectChild::RegisterActor(NPObject* aObject) { + AssertPluginThread(); + MOZ_ASSERT(aObject, "Null pointer!"); + + NPObjectData* d = sObjectMap->GetEntry(aObject); + if (!d) { + NS_ERROR("NPObject not in object table"); + return false; + } + + d->actor = this; + return true; +} + +void PluginScriptableObjectChild::UnregisterActor(NPObject* aObject) { + AssertPluginThread(); + MOZ_ASSERT(aObject, "Null pointer!"); + + NPObjectData* d = sObjectMap->GetEntry(aObject); + MOZ_ASSERT(d, "NPObject not in object table"); + if (d) { + d->actor = nullptr; + } +} + +/* static */ +PluginScriptableObjectChild* PluginScriptableObjectChild::GetActorForNPObject( + NPObject* aObject) { + AssertPluginThread(); + MOZ_ASSERT(aObject, "Null pointer!"); + + NPObjectData* d = sObjectMap->GetEntry(aObject); + if (!d) { + NS_ERROR("Plugin using object not created with NPN_CreateObject?"); + return nullptr; + } + + return d->actor; +} + +/* static */ +void PluginScriptableObjectChild::RegisterObject( + NPObject* aObject, PluginInstanceChild* aInstance) { + AssertPluginThread(); + + if (!sObjectMap) { + sObjectMap = new nsTHashtable<PluginScriptableObjectChild::NPObjectData>(); + } + + NPObjectData* d = sObjectMap->PutEntry(aObject); + MOZ_ASSERT(!d->instance, "New NPObject already mapped?"); + d->instance = aInstance; +} + +/* static */ +void PluginScriptableObjectChild::UnregisterObject(NPObject* aObject) { + AssertPluginThread(); + + sObjectMap->RemoveEntry(aObject); + + if (!sObjectMap->Count()) { + delete sObjectMap; + sObjectMap = nullptr; + } +} + +/* static */ +PluginInstanceChild* PluginScriptableObjectChild::GetInstanceForNPObject( + NPObject* aObject) { + AssertPluginThread(); + if (!sObjectMap) { + // All PluginInstanceChilds have been destroyed + return nullptr; + } + + 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 0000000000..46ce160c9a --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectChild.h @@ -0,0 +1,276 @@ +/* -*- 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); + } + + MOZ_COUNTED_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); + + mozilla::ipc::IPCResult AnswerInvalidate(); + + mozilla::ipc::IPCResult AnswerHasMethod(const PluginIdentifier& aId, + bool* aHasMethod); + + mozilla::ipc::IPCResult AnswerInvoke(const PluginIdentifier& aId, + nsTArray<Variant>&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerInvokeDefault(nsTArray<Variant>&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerHasProperty(const PluginIdentifier& aId, + bool* aHasProperty); + + mozilla::ipc::IPCResult AnswerGetChildProperty(const PluginIdentifier& aId, + bool* aHasProperty, + bool* aHasMethod, + Variant* aResult, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerSetProperty(const PluginIdentifier& aId, + const Variant& aValue, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerRemoveProperty(const PluginIdentifier& aId, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerEnumerate( + nsTArray<PluginIdentifier>* aProperties, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerConstruct(nsTArray<Variant>&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult RecvProtect(); + + mozilla::ipc::IPCResult RecvUnprotect(); + + 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); + } + + MOZ_COUNTED_DTOR(StoredIdentifier) + }; + + public: + class MOZ_STACK_CLASS StackIdentifier { + public: + explicit StackIdentifier(const PluginIdentifier& aIdentifier); + explicit StackIdentifier(NPIdentifier aIdentifier); + ~StackIdentifier(); + + void MakePermanent() { + if (mStored) { + mStored->mPermanent = true; + } + } + NPIdentifier ToNPIdentifier() const; + + bool IsString() const { + return mIdentifier.type() == PluginIdentifier::TnsCString; + } + const nsCString& GetString() const { return mIdentifier.get_nsCString(); } + + int32_t GetInt() const { return mIdentifier.get_int32_t(); } + + PluginIdentifier GetIdentifier() const { return mIdentifier; } + + private: + DISALLOW_COPY_AND_ASSIGN(StackIdentifier); + + PluginIdentifier mIdentifier; + RefPtr<StoredIdentifier> mStored; + }; + + static void ClearIdentifiers(); + + bool RegisterActor(NPObject* aObject); + void UnregisterActor(NPObject* aObject); + + static PluginScriptableObjectChild* GetActorForNPObject(NPObject* aObject); + + static void RegisterObject(NPObject* aObject, PluginInstanceChild* aInstance); + static void UnregisterObject(NPObject* aObject); + + static PluginInstanceChild* GetInstanceForNPObject(NPObject* aObject); + + /** + * Fill PluginInstanceChild.mDeletingHash with all the remaining NPObjects + * associated with that instance. + */ + static void NotifyOfInstanceShutdown(PluginInstanceChild* aInstance); + + private: + static NPObject* ScriptableAllocate(NPP aInstance, NPClass* aClass); + + static void ScriptableInvalidate(NPObject* aObject); + + static void ScriptableDeallocate(NPObject* aObject); + + static bool ScriptableHasMethod(NPObject* aObject, NPIdentifier aName); + + static bool ScriptableInvoke(NPObject* aObject, NPIdentifier aName, + const NPVariant* aArgs, uint32_t aArgCount, + NPVariant* aResult); + + static bool ScriptableInvokeDefault(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + + static bool ScriptableHasProperty(NPObject* aObject, NPIdentifier aName); + + static bool ScriptableGetProperty(NPObject* aObject, NPIdentifier aName, + NPVariant* aResult); + + static bool ScriptableSetProperty(NPObject* aObject, NPIdentifier aName, + const NPVariant* aValue); + + static bool ScriptableRemoveProperty(NPObject* aObject, NPIdentifier aName); + + static bool ScriptableEnumerate(NPObject* aObject, + NPIdentifier** aIdentifiers, + uint32_t* aCount); + + static bool ScriptableConstruct(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + + NPObject* CreateProxyObject(); + + // ResurrectProxyObject is only used with Proxy actors. It is called when the + // parent process uses an actor whose NPObject was deleted by the child + // process. + bool ResurrectProxyObject(); + + private: + PluginInstanceChild* mInstance; + NPObject* mObject; + bool mInvalidated; + int mProtectCount; + + ScriptableObjectType mType; + + static const NPClass sNPClass; + + static StoredIdentifier* HashIdentifier(const nsCString& aIdentifier); + static void UnhashIdentifier(StoredIdentifier* aIdentifier); + + typedef nsDataHashtable<nsCStringHashKey, RefPtr<StoredIdentifier>> + IdentifierTable; + static IdentifierTable sIdentifiers; + + struct NPObjectData : public nsPtrHashKey<NPObject> { + explicit NPObjectData(const NPObject* key) + : nsPtrHashKey<NPObject>(key), instance(nullptr), actor(nullptr) {} + + // never nullptr + PluginInstanceChild* instance; + + // sometimes nullptr (no actor associated with an NPObject) + PluginScriptableObjectChild* actor; + }; + + /** + * mObjectMap contains all the currently active NPObjects (from + * NPN_CreateObject until the final release/dealloc, whether or not an actor + * is currently associated with the object. + */ + static nsTHashtable<NPObjectData>* sObjectMap; +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif /* dom_plugins_PluginScriptableObjectChild_h */ diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.cpp b/dom/plugins/ipc/PluginScriptableObjectParent.cpp new file mode 100644 index 0000000000..d12474c999 --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectParent.cpp @@ -0,0 +1,1289 @@ +/* -*- 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 "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) { + const NPNetscapeFuncs* npn = GetNetscapeFuncs(aInstance); + if (npn) { + npn->releasevariantvalue(&aVariant); + } +} + +} // namespace + +// static +NPObject* PluginScriptableObjectParent::ScriptableAllocate(NPP aInstance, + NPClass* aClass) { + if (aClass != GetClass()) { + NS_ERROR("Huh?! Wrong class!"); + return nullptr; + } + + return new ParentNPObject(); +} + +// static +void PluginScriptableObjectParent::ScriptableInvalidate(NPObject* aObject) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + // This can happen more than once, and is just fine. + return; + } + + object->invalidated = true; + + // |object->parent| may be null already if the instance has gone away. + if (object->parent && !object->parent->CallInvalidate()) { + NS_ERROR("Failed to send message!"); + } +} + +// static +void PluginScriptableObjectParent::ScriptableDeallocate(NPObject* aObject) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + + if (object->asyncWrapperCount > 0) { + // In this case we should just drop the refcount to the asyncWrapperCount + // instead of deallocating because there are still some async wrappers + // out there that are referencing this object. + object->referenceCount = object->asyncWrapperCount; + return; + } + + PluginScriptableObjectParent* actor = object->parent; + if (actor) { + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + actor->DropNPObject(); + } + + delete object; +} + +// static +bool PluginScriptableObjectParent::ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + if (!actor->CallHasMethod(identifier, &result)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return result; +} + +// static +bool PluginScriptableObjectParent::ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + if (!actor->CallInvoke(identifier, args, &remoteResult, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + return true; +} + +// static +bool PluginScriptableObjectParent::ScriptableInvokeDefault( + NPObject* aObject, const NPVariant* aArgs, uint32_t aArgCount, + NPVariant* aResult) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + if (!actor->CallInvokeDefault(args, &remoteResult, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + return true; +} + +// static +bool PluginScriptableObjectParent::ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + if (!actor->CallHasProperty(identifier, &result)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return result; +} + +// static +bool PluginScriptableObjectParent::ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult) { + // See GetPropertyHelper below. + MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this directly!"); + return false; +} + +// static +bool PluginScriptableObjectParent::ScriptableSetProperty( + NPObject* aObject, NPIdentifier aName, const NPVariant* aValue) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariant value(*aValue, actor->GetInstance()); + if (!value.IsOk()) { + NS_WARNING("Failed to convert variant!"); + return false; + } + + bool success; + if (!actor->CallSetProperty(identifier, value, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return success; +} + +// static +bool PluginScriptableObjectParent::ScriptableRemoveProperty( + NPObject* aObject, NPIdentifier aName) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool success; + if (!actor->CallRemoveProperty(identifier, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return success; +} + +// static +bool PluginScriptableObjectParent::ScriptableEnumerate( + NPObject* aObject, NPIdentifier** aIdentifiers, uint32_t* aCount) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(aObject); + if (!npn) { + NS_ERROR("No netscape funcs!"); + return false; + } + + AutoTArray<PluginIdentifier, 10> identifiers; + bool success; + if (!actor->CallEnumerate(&identifiers, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + *aCount = identifiers.Length(); + if (!*aCount) { + *aIdentifiers = nullptr; + return true; + } + + *aIdentifiers = (NPIdentifier*)npn->memalloc(*aCount * sizeof(NPIdentifier)); + if (!*aIdentifiers) { + NS_ERROR("Out of memory!"); + return false; + } + + for (uint32_t index = 0; index < *aCount; index++) { + // We pin the ID to avoid a GC hazard here. This could probably be fixed + // if the interface with nsJSNPRuntime were smarter. + StackIdentifier stackID(identifiers[index], true /* aAtomizeAndPin */); + if (stackID.Failed()) { + return false; + } + (*aIdentifiers)[index] = stackID.ToNPIdentifier(); + } + return true; +} + +// static +bool PluginScriptableObjectParent::ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor<PluginScriptableObjectParent> actor(object->parent); + if (!actor) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + if (!actor->CallConstruct(args, &remoteResult, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + return true; +} + +const NPClass PluginScriptableObjectParent::sNPClass = { + NP_CLASS_STRUCT_VERSION, + PluginScriptableObjectParent::ScriptableAllocate, + PluginScriptableObjectParent::ScriptableDeallocate, + PluginScriptableObjectParent::ScriptableInvalidate, + PluginScriptableObjectParent::ScriptableHasMethod, + PluginScriptableObjectParent::ScriptableInvoke, + PluginScriptableObjectParent::ScriptableInvokeDefault, + PluginScriptableObjectParent::ScriptableHasProperty, + PluginScriptableObjectParent::ScriptableGetProperty, + PluginScriptableObjectParent::ScriptableSetProperty, + PluginScriptableObjectParent::ScriptableRemoveProperty, + PluginScriptableObjectParent::ScriptableEnumerate, + PluginScriptableObjectParent::ScriptableConstruct}; + +PluginScriptableObjectParent::PluginScriptableObjectParent( + ScriptableObjectType aType) + : mInstance(nullptr), mObject(nullptr), mProtectCount(0), mType(aType) {} + +PluginScriptableObjectParent::~PluginScriptableObjectParent() { + if (mObject) { + if (mObject->_class == GetClass()) { + NS_ASSERTION(mType == Proxy, "Wrong type!"); + static_cast<ParentNPObject*>(mObject)->parent = nullptr; + } else { + NS_ASSERTION(mType == LocalObject, "Wrong type!"); + GetInstance()->GetNPNIface()->releaseobject(mObject); + } + } +} + +void PluginScriptableObjectParent::InitializeProxy() { + NS_ASSERTION(mType == Proxy, "Bad type!"); + NS_ASSERTION(!mObject, "Calling Initialize more than once!"); + + mInstance = static_cast<PluginInstanceParent*>(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + NPObject* object = CreateProxyObject(); + NS_ASSERTION(object, "Failed to create object!"); + + if (!mInstance->RegisterNPObjectForActor(object, this)) { + NS_ERROR("Out of memory?"); + } + + mObject = object; +} + +void PluginScriptableObjectParent::InitializeLocal(NPObject* aObject) { + NS_ASSERTION(mType == LocalObject, "Bad type!"); + NS_ASSERTION(!(mInstance && mObject), "Calling Initialize more than once!"); + + mInstance = static_cast<PluginInstanceParent*>(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + mInstance->GetNPNIface()->retainobject(aObject); + + NS_ASSERTION(!mProtectCount, "Should be zero!"); + mProtectCount++; + + if (!mInstance->RegisterNPObjectForActor(aObject, this)) { + NS_ERROR("Out of memory?"); + } + + mObject = aObject; +} + +NPObject* PluginScriptableObjectParent::CreateProxyObject() { + NS_ASSERTION(mInstance, "Must have an instance!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(mInstance); + + NPObject* npobject = + npn->createobject(mInstance->GetNPP(), const_cast<NPClass*>(GetClass())); + NS_ASSERTION(npobject, "Failed to create object?!"); + NS_ASSERTION(npobject->_class == GetClass(), "Wrong kind of object!"); + NS_ASSERTION(npobject->referenceCount == 1, "Some kind of live object!"); + + ParentNPObject* object = static_cast<ParentNPObject*>(npobject); + NS_ASSERTION(!object->invalidated, "Bad object!"); + NS_ASSERTION(!object->parent, "Bad object!"); + + // We don't want to have the actor own this object but rather let the object + // own this actor. Set the reference count to 0 here so that when the object + // dies we will send the destructor message to the child. + object->referenceCount = 0; + NS_LOG_RELEASE(object, 0, "BrowserNPObject"); + + object->parent = const_cast<PluginScriptableObjectParent*>(this); + return object; +} + +bool PluginScriptableObjectParent::ResurrectProxyObject() { + NS_ASSERTION(mInstance, "Must have an instance already!"); + NS_ASSERTION(!mObject, "Should not have an object already!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + InitializeProxy(); + NS_ASSERTION(mObject, "Initialize failed!"); + + if (!SendProtect()) { + NS_WARNING("Failed to send message!"); + return false; + } + + return true; +} + +NPObject* PluginScriptableObjectParent::GetObject(bool aCanResurrect) { + if (!mObject && aCanResurrect && !ResurrectProxyObject()) { + NS_ERROR("Null object!"); + return nullptr; + } + return mObject; +} + +void PluginScriptableObjectParent::Protect() { + NS_ASSERTION(mObject, "No object!"); + NS_ASSERTION(mProtectCount >= 0, "Negative protect count?!"); + + if (mType == LocalObject) { + ++mProtectCount; + } +} + +void PluginScriptableObjectParent::Unprotect() { + NS_ASSERTION(mObject, "No object!"); + NS_ASSERTION(mProtectCount >= 0, "Negative protect count?!"); + + if (mType == LocalObject) { + if (--mProtectCount == 0) { + Unused << PluginScriptableObjectParent::Send__delete__(this); + } + } +} + +void PluginScriptableObjectParent::DropNPObject() { + NS_ASSERTION(mObject, "Invalidated object!"); + NS_ASSERTION(mObject->_class == GetClass(), "Wrong type of object!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + // We think we're about to be deleted, but we could be racing with the other + // process. + PluginInstanceParent* instance = GetInstance(); + NS_ASSERTION(instance, "Must have an instance!"); + + instance->UnregisterNPObject(mObject); + mObject = nullptr; + + Unused << SendUnprotect(); +} + +void PluginScriptableObjectParent::ActorDestroy(ActorDestroyReason aWhy) { + // Implement me! Bug 1005163 +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerHasMethod( + const PluginIdentifier& aId, bool* aHasMethod) { + if (!mObject) { + NS_WARNING("Calling AnswerHasMethod with an invalidated object!"); + *aHasMethod = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aHasMethod = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aHasMethod = false; + return IPC_OK(); + } + *aHasMethod = + npn->hasmethod(instance->GetNPP(), mObject, stackID.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerInvoke( + const PluginIdentifier& aId, nsTArray<Variant>&& aArgs, Variant* aResult, + bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerInvoke with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + } + + 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 IPC_OK(); + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, GetInstance()); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aResult = convertedResult; + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerInvokeDefault( + nsTArray<Variant>&& aArgs, Variant* aResult, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerInvoke with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + } + + 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 IPC_OK(); + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, GetInstance()); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aResult = convertedResult; + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerHasProperty( + const PluginIdentifier& aId, bool* aHasProperty) { + if (!mObject) { + NS_WARNING("Calling AnswerHasProperty with an invalidated object!"); + *aHasProperty = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aHasProperty = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aHasProperty = false; + return IPC_OK(); + } + + *aHasProperty = + npn->hasproperty(instance->GetNPP(), mObject, stackID.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); + } + + 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NPVariant result; + if (!npn->getproperty(instance->GetNPP(), mObject, stackID.ToNPIdentifier(), + &result)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + Variant converted; + if ((*aSuccess = ConvertToRemoteVariant(result, converted, instance))) { + DeferNPVariantLastRelease(npn, &result); + *aResult = converted; + } else { + *aResult = void_t(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerSetProperty( + const PluginIdentifier& aId, const Variant& aValue, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerSetProperty with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aSuccess = false; + return IPC_OK(); + } + + NPVariant converted; + if (!ConvertToVariant(aValue, converted, instance)) { + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aSuccess = false; + return IPC_OK(); + } + + if ((*aSuccess = npn->setproperty(instance->GetNPP(), mObject, + stackID.ToNPIdentifier(), &converted))) { + ReleaseVariant(converted, instance); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerRemoveProperty( + const PluginIdentifier& aId, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerRemoveProperty with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aSuccess = false; + return IPC_OK(); + } + + *aSuccess = npn->removeproperty(instance->GetNPP(), mObject, + stackID.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerEnumerate( + nsTArray<PluginIdentifier>* aProperties, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerEnumerate with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_WARNING("No netscape funcs?!"); + *aSuccess = false; + return IPC_OK(); + } + + NPIdentifier* ids; + uint32_t idCount; + if (!npn->enumerate(instance->GetNPP(), mObject, &ids, &idCount)) { + *aSuccess = false; + return IPC_OK(); + } + + aProperties->SetCapacity(idCount); + + for (uint32_t index = 0; index < idCount; index++) { + PluginIdentifier id; + if (!FromNPIdentifier(ids[index], &id)) { + return IPC_FAIL_NO_REASON(this); + } + aProperties->AppendElement(id); + } + + npn->memfree(ids); + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerConstruct( + nsTArray<Variant>&& aArgs, Variant* aResult, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerConstruct with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + AutoTArray<NPVariant, 10> convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + } + + 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 IPC_OK(); + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, instance); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aSuccess = true; + *aResult = convertedResult; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::RecvProtect() { + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Protect(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::RecvUnprotect() { + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Unprotect(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult 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 IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + 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 IPC_OK(); + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, instance); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aSuccess = true; + *aResult = convertedResult; + return IPC_OK(); +} + +bool PluginScriptableObjectParent::GetPropertyHelper(NPIdentifier aName, + bool* aHasProperty, + bool* aHasMethod, + NPVariant* aResult) { + NS_ASSERTION(Type() == Proxy, "Bad type!"); + + ParentNPObject* object = static_cast<ParentNPObject*>(mObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + bool hasProperty, hasMethod, success; + Variant result; + if (!CallGetChildProperty(identifier, &hasProperty, &hasMethod, &result, + &success)) { + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(result, *aResult, GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + + *aHasProperty = hasProperty; + *aHasMethod = hasMethod; + return true; +} diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.h b/dom/plugins/ipc/PluginScriptableObjectParent.h new file mode 100644 index 0000000000..cf8bc5f04f --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectParent.h @@ -0,0 +1,169 @@ +/* -*- 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; + + mozilla::ipc::IPCResult AnswerHasMethod(const PluginIdentifier& aId, + bool* aHasMethod); + + mozilla::ipc::IPCResult AnswerInvoke(const PluginIdentifier& aId, + nsTArray<Variant>&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerInvokeDefault(nsTArray<Variant>&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerHasProperty(const PluginIdentifier& aId, + bool* aHasProperty); + + mozilla::ipc::IPCResult AnswerGetParentProperty(const PluginIdentifier& aId, + Variant* aResult, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerSetProperty(const PluginIdentifier& aId, + const Variant& aValue, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerRemoveProperty(const PluginIdentifier& aId, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerEnumerate( + nsTArray<PluginIdentifier>* aProperties, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerConstruct(nsTArray<Variant>&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerNPN_Evaluate(const nsCString& aScript, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult RecvProtect(); + + mozilla::ipc::IPCResult RecvUnprotect(); + + 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 0000000000..b211e1687f --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h @@ -0,0 +1,154 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginScriptableObjectUtils.h" + +namespace { + +template <class InstanceType> +class VariantTraits; + +template <> +class VariantTraits<mozilla::plugins::PluginInstanceParent> { + public: + typedef mozilla::plugins::PluginScriptableObjectParent ScriptableObjectType; +}; + +template <> +class VariantTraits<mozilla::plugins::PluginInstanceChild> { + public: + typedef mozilla::plugins::PluginScriptableObjectChild ScriptableObjectType; +}; + +} /* anonymous namespace */ + +inline bool mozilla::plugins::ConvertToVariant( + const Variant& aRemoteVariant, NPVariant& aVariant, + PluginInstanceParent* aInstance) { + switch (aRemoteVariant.type()) { + case Variant::Tvoid_t: { + VOID_TO_NPVARIANT(aVariant); + break; + } + + case Variant::Tnull_t: { + NULL_TO_NPVARIANT(aVariant); + break; + } + + case Variant::Tbool: { + BOOLEAN_TO_NPVARIANT(aRemoteVariant.get_bool(), aVariant); + break; + } + + case Variant::Tint: { + INT32_TO_NPVARIANT(aRemoteVariant.get_int(), aVariant); + break; + } + + case Variant::Tdouble: { + DOUBLE_TO_NPVARIANT(aRemoteVariant.get_double(), aVariant); + break; + } + + case Variant::TnsCString: { + const nsCString& string = aRemoteVariant.get_nsCString(); + const size_t length = string.Length(); + NPUTF8* buffer = + static_cast<NPUTF8*>(::malloc(sizeof(NPUTF8) * (length + 1))); + if (!buffer) { + NS_ERROR("Out of memory!"); + return false; + } + + std::copy(string.get(), string.get() + length, buffer); + buffer[length] = '\0'; + STRINGN_TO_NPVARIANT(buffer, length, aVariant); + break; + } + + case Variant::TPPluginScriptableObjectParent: { + NS_ASSERTION(aInstance, "Must have an instance!"); + NPObject* object = NPObjectFromVariant(aRemoteVariant); + if (!object) { + NS_ERROR("Er, this shouldn't fail!"); + return false; + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(aInstance); + if (!npn) { + NS_ERROR("Null netscape funcs!"); + return false; + } + + npn->retainobject(object); + OBJECT_TO_NPVARIANT(object, aVariant); + break; + } + + case Variant::TPPluginScriptableObjectChild: { + NS_ASSERTION(!aInstance, "No instance should be given!"); + NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Plugin, + "Should be running on child only!"); + + NPObject* object = NPObjectFromVariant(aRemoteVariant); + NS_ASSERTION(object, "Null object?!"); + + PluginModuleChild::sBrowserFuncs.retainobject(object); + OBJECT_TO_NPVARIANT(object, aVariant); + break; + } + + default: + MOZ_ASSERT_UNREACHABLE("Shouldn't get here!"); + return false; + } + + return true; +} + +template <class InstanceType> +bool mozilla::plugins::ConvertToRemoteVariant(const NPVariant& aVariant, + Variant& aRemoteVariant, + InstanceType* aInstance, + bool aProtectActors) { + if (NPVARIANT_IS_VOID(aVariant)) { + aRemoteVariant = mozilla::void_t(); + } else if (NPVARIANT_IS_NULL(aVariant)) { + aRemoteVariant = mozilla::null_t(); + } else if (NPVARIANT_IS_BOOLEAN(aVariant)) { + aRemoteVariant = NPVARIANT_TO_BOOLEAN(aVariant); + } else if (NPVARIANT_IS_INT32(aVariant)) { + aRemoteVariant = NPVARIANT_TO_INT32(aVariant); + } else if (NPVARIANT_IS_DOUBLE(aVariant)) { + aRemoteVariant = NPVARIANT_TO_DOUBLE(aVariant); + } else if (NPVARIANT_IS_STRING(aVariant)) { + NPString str = NPVARIANT_TO_STRING(aVariant); + nsCString string(str.UTF8Characters, str.UTF8Length); + aRemoteVariant = string; + } else if (NPVARIANT_IS_OBJECT(aVariant)) { + NPObject* object = NPVARIANT_TO_OBJECT(aVariant); + + typename VariantTraits<InstanceType>::ScriptableObjectType* actor = + aInstance->GetActorForNPObject(object); + + if (!actor) { + NS_ERROR("Null actor!"); + return false; + } + + if (aProtectActors) { + actor->Protect(); + } + + aRemoteVariant = actor; + } else { + MOZ_ASSERT_UNREACHABLE("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 0000000000..e620e017e9 --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectUtils.h @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginScriptableObjectUtils_h +#define dom_plugins_PluginScriptableObjectUtils_h + +#include "PluginModuleParent.h" +#include "PluginModuleChild.h" +#include "PluginInstanceParent.h" +#include "PluginInstanceChild.h" +#include "PluginScriptableObjectParent.h" +#include "PluginScriptableObjectChild.h" + +#include "npapi.h" +#include "npfunctions.h" +#include "npruntime.h" + +#include "nsDebug.h" + +namespace mozilla { +namespace plugins { + +inline PluginInstanceParent* GetInstance(NPObject* aObject) { + NS_ASSERTION(aObject->_class == PluginScriptableObjectParent::GetClass(), + "Bad class!"); + + ParentNPObject* object = reinterpret_cast<ParentNPObject*>(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return nullptr; + } + if (!object->parent) { + return nullptr; + } + return object->parent->GetInstance(); +} + +inline NPObject* NPObjectFromVariant(const Variant& aRemoteVariant) { + switch (aRemoteVariant.type()) { + case Variant::TPPluginScriptableObjectParent: { + PluginScriptableObjectParent* actor = + const_cast<PluginScriptableObjectParent*>( + reinterpret_cast<const PluginScriptableObjectParent*>( + aRemoteVariant.get_PPluginScriptableObjectParent())); + return actor->GetObject(true); + } + + case Variant::TPPluginScriptableObjectChild: { + PluginScriptableObjectChild* actor = + const_cast<PluginScriptableObjectChild*>( + reinterpret_cast<const PluginScriptableObjectChild*>( + aRemoteVariant.get_PPluginScriptableObjectChild())); + return actor->GetObject(true); + } + + default: + MOZ_ASSERT_UNREACHABLE("Shouldn't get here!"); + return nullptr; + } +} + +inline NPObject* NPObjectFromVariant(const NPVariant& aVariant) { + NS_ASSERTION(NPVARIANT_IS_OBJECT(aVariant), "Wrong variant type!"); + return NPVARIANT_TO_OBJECT(aVariant); +} + +inline const NPNetscapeFuncs* GetNetscapeFuncs( + PluginInstanceParent* aInstance) { + PluginModuleParent* module = aInstance->Module(); + if (!module) { + NS_WARNING("Null module?!"); + return nullptr; + } + return module->GetNetscapeFuncs(); +} + +inline const NPNetscapeFuncs* GetNetscapeFuncs(NPObject* aObject) { + NS_ASSERTION(aObject->_class == PluginScriptableObjectParent::GetClass(), + "Bad class!"); + + PluginInstanceParent* instance = GetInstance(aObject); + if (!instance) { + return nullptr; + } + + return GetNetscapeFuncs(instance); +} + +inline void ReleaseRemoteVariant(Variant& aVariant) { + switch (aVariant.type()) { + case Variant::TPPluginScriptableObjectParent: { + PluginScriptableObjectParent* actor = + const_cast<PluginScriptableObjectParent*>( + reinterpret_cast<const PluginScriptableObjectParent*>( + aVariant.get_PPluginScriptableObjectParent())); + actor->Unprotect(); + break; + } + + case Variant::TPPluginScriptableObjectChild: { + NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Plugin, + "Should only be running in the child!"); + PluginScriptableObjectChild* actor = + const_cast<PluginScriptableObjectChild*>( + reinterpret_cast<const PluginScriptableObjectChild*>( + aVariant.get_PPluginScriptableObjectChild())); + actor->Unprotect(); + break; + } + + default: + break; // Intentional fall-through for other variant types. + } + + aVariant = mozilla::void_t(); +} + +bool ConvertToVariant(const Variant& aRemoteVariant, NPVariant& aVariant, + PluginInstanceParent* aInstance = nullptr); + +template <class InstanceType> +bool ConvertToRemoteVariant(const NPVariant& aVariant, Variant& aRemoteVariant, + InstanceType* aInstance, + bool aProtectActors = false); + +class ProtectedVariant { + public: + ProtectedVariant(const NPVariant& aVariant, PluginInstanceParent* aInstance) { + mOk = ConvertToRemoteVariant(aVariant, mVariant, aInstance, true); + } + + ProtectedVariant(const NPVariant& aVariant, PluginInstanceChild* aInstance) { + mOk = ConvertToRemoteVariant(aVariant, mVariant, aInstance, true); + } + + ~ProtectedVariant() { ReleaseRemoteVariant(mVariant); } + + bool IsOk() { return mOk; } + + operator const Variant&() { return mVariant; } + + private: + Variant mVariant; + bool mOk; +}; + +class ProtectedVariantArray { + public: + ProtectedVariantArray(const NPVariant* aArgs, uint32_t aCount, + PluginInstanceParent* aInstance) + : mUsingShadowArray(false) { + for (uint32_t index = 0; index < aCount; index++) { + Variant* remoteVariant = mArray.AppendElement(); + if (!(remoteVariant && + ConvertToRemoteVariant(aArgs[index], *remoteVariant, aInstance, + true))) { + mOk = false; + return; + } + } + mOk = true; + } + + ProtectedVariantArray(const NPVariant* aArgs, uint32_t aCount, + PluginInstanceChild* aInstance) + : mUsingShadowArray(false) { + for (uint32_t index = 0; index < aCount; index++) { + Variant* remoteVariant = mArray.AppendElement(); + if (!(remoteVariant && + ConvertToRemoteVariant(aArgs[index], *remoteVariant, aInstance, + true))) { + mOk = false; + return; + } + } + mOk = true; + } + + ~ProtectedVariantArray() { + nsTArray<Variant>& vars = EnsureAndGetShadowArray(); + uint32_t count = vars.Length(); + for (uint32_t index = 0; index < count; index++) { + ReleaseRemoteVariant(vars[index]); + } + } + + operator const nsTArray<Variant>&() { return EnsureAndGetShadowArray(); } + + bool IsOk() { return mOk; } + + private: + nsTArray<Variant>& EnsureAndGetShadowArray() { + if (!mUsingShadowArray) { + mShadowArray.SwapElements(mArray); + mUsingShadowArray = true; + } + return mShadowArray; + } + + // We convert the variants fallibly, but pass them to Call*() + // methods as an infallible array + nsTArray<Variant> mArray; + nsTArray<Variant> mShadowArray; + bool mOk; + bool mUsingShadowArray; +}; + +template <class ActorType> +struct ProtectedActorTraits { + static bool Nullable(); +}; + +template <class ActorType, class Traits = ProtectedActorTraits<ActorType> > +class ProtectedActor { + public: + explicit ProtectedActor(ActorType* aActor) : mActor(aActor) { + if (!Traits::Nullable()) { + NS_ASSERTION(mActor, "This should never be null!"); + } + } + + ~ProtectedActor() { + if (Traits::Nullable() && !mActor) return; + mActor->Unprotect(); + } + + ActorType* operator->() { return mActor; } + + explicit operator bool() { return !!mActor; } + + private: + ActorType* mActor; +}; + +template <> +struct ProtectedActorTraits<PluginScriptableObjectParent> { + static bool Nullable() { return true; } +}; + +template <> +struct ProtectedActorTraits<PluginScriptableObjectChild> { + static bool Nullable() { return false; } +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#include "PluginScriptableObjectUtils-inl.h" + +#endif /* dom_plugins_PluginScriptableObjectUtils_h */ diff --git a/dom/plugins/ipc/PluginSurfaceParent.cpp b/dom/plugins/ipc/PluginSurfaceParent.cpp new file mode 100644 index 0000000000..251572995e --- /dev/null +++ b/dom/plugins/ipc/PluginSurfaceParent.cpp @@ -0,0 +1,29 @@ +/* -*- 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 +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginSurfaceParent.h b/dom/plugins/ipc/PluginSurfaceParent.h new file mode 100644 index 0000000000..201ecf9da7 --- /dev/null +++ b/dom/plugins/ipc/PluginSurfaceParent.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginSurfaceParent_h +#define dom_plugins_PluginSurfaceParent_h + +#include "mozilla/plugins/PPluginSurfaceParent.h" +#include "mozilla/plugins/PluginMessageUtils.h" + +#ifndef XP_WIN +# error "This header is for Windows only." +#endif + +class gfxASurface; + +namespace mozilla { +namespace plugins { + +class PluginSurfaceParent : public PPluginSurfaceParent { + public: + PluginSurfaceParent(const WindowsSharedMemoryHandle& handle, + const gfx::IntSize& size, const bool transparent); + ~PluginSurfaceParent(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + gfxASurface* Surface() { return mSurface; } + + private: + RefPtr<gfxASurface> mSurface; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugin_PluginSurfaceParent_h diff --git a/dom/plugins/ipc/PluginTypes.ipdlh b/dom/plugins/ipc/PluginTypes.ipdlh new file mode 100644 index 0000000000..0c674c3653 --- /dev/null +++ b/dom/plugins/ipc/PluginTypes.ipdlh @@ -0,0 +1,48 @@ +/* -*- 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 URIParams; + +namespace mozilla { +namespace plugins { + +struct PluginTag +{ + uint32_t id; + nsCString name; + nsCString description; + nsCString[] mimeTypes; + nsCString[] mimeDescriptions; + nsCString[] extensions; + bool isFlashPlugin; + bool supportsAsyncRender; // flash specific + nsCString filename; + nsCString version; + int64_t lastModifiedTime; + int32_t sandboxLevel; + uint16_t blocklistState; +}; + +struct FakePluginTag +{ + uint32_t id; + URIParams handlerURI; + nsCString name; + nsCString description; + nsCString[] mimeTypes; + nsCString[] mimeDescriptions; + nsCString[] extensions; + nsCString niceName; + nsString sandboxScript; +}; + +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 0000000000..bf03c90c78 --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsOSX.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginUtilsOSX_h +#define dom_plugins_PluginUtilsOSX_h 1 + +#include "npapi.h" +#include "mozilla/gfx/QuartzSupport.h" +#include "nsRect.h" + +namespace mozilla { +namespace plugins { +namespace PluginUtilsOSX { + +// Need to call back into the browser's message loop to process event. +typedef void (*RemoteProcessEvents)(void*); + +NPError ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, + RemoteProcessEvents remoteEvent); + +void InvokeNativeEventLoop(); + +// Need to call back and send a cocoa draw event to the plugin. +typedef void (*DrawPluginFunc)(CGContextRef, void*, nsIntRect aUpdateRect); + +void* GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, + double aContentsScaleFactor); +void ReleaseCGLayer(void* cgLayer); +void Repaint(void* cgLayer, nsIntRect aRect); + +bool SetProcessName(const char* aProcessName); + +/* + * Provides a wrapper around nsCARenderer to manage double buffering + * without having to unbind nsCARenderer on every surface swaps. + * + * The double buffer renderer begins with no initialize surfaces. + * The buffers can be initialized and cleared individually. + * Swapping still occurs regardless if the buffers are initialized. + */ +class nsDoubleBufferCARenderer { + public: + nsDoubleBufferCARenderer() : mCALayer(nullptr), mContentsScaleFactor(1.0) {} + // Returns width in "display pixels". A "display pixel" is the smallest + // fully addressable part of a display. But in HiDPI modes each "display + // pixel" corresponds to more than one device pixel. Multiply display pixels + // by mContentsScaleFactor to get device pixels. + size_t GetFrontSurfaceWidth(); + // Returns height in "display pixels". Multiply by + // mContentsScaleFactor to get device pixels. + size_t GetFrontSurfaceHeight(); + double GetFrontSurfaceContentsScaleFactor(); + // Returns width in "display pixels". Multiply by + // mContentsScaleFactor to get device pixels. + size_t GetBackSurfaceWidth(); + // Returns height in "display pixels". Multiply by + // mContentsScaleFactor to get device pixels. + size_t GetBackSurfaceHeight(); + double GetBackSurfaceContentsScaleFactor(); + IOSurfaceID GetFrontSurfaceID(); + + bool HasBackSurface(); + bool HasFrontSurface(); + bool HasCALayer(); + + void SetCALayer(void* aCALayer); + // aWidth and aHeight are in "display pixels". Multiply by + // aContentsScaleFactor to get device pixels. + bool InitFrontSurface(size_t aWidth, size_t aHeight, + double aContentsScaleFactor, + AllowOfflineRendererEnum aAllowOfflineRenderer); + void Render(); + void SwapSurfaces(); + void ClearFrontSurface(); + void ClearBackSurface(); + + double GetContentsScaleFactor() { return mContentsScaleFactor; } + + private: + void* mCALayer; + RefPtr<nsCARenderer> mCARenderer; + RefPtr<MacIOSurface> mFrontSurface; + RefPtr<MacIOSurface> mBackSurface; + double mContentsScaleFactor; +}; + +} // namespace PluginUtilsOSX +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_PluginUtilsOSX_h diff --git a/dom/plugins/ipc/PluginUtilsOSX.mm b/dom/plugins/ipc/PluginUtilsOSX.mm new file mode 100644 index 0000000000..9aac0b3c3d --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsOSX.mm @@ -0,0 +1,429 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <dlfcn.h> +#import <AppKit/AppKit.h> +#import <QuartzCore/QuartzCore.h> +#include "PluginUtilsOSX.h" + +// Remove definitions for try/catch interfering with ObjCException macros. +#include "nsObjCExceptions.h" +#include "nsCocoaUtils.h" + +#include "nsDebug.h" + +#include "mozilla/Sprintf.h" + +@interface CALayer (ContentsScale) +- (double)contentsScale; +- (void)setContentsScale:(double)scale; +@end + +using namespace mozilla::plugins::PluginUtilsOSX; + +@interface CGBridgeLayer : CALayer { + DrawPluginFunc mDrawFunc; + void* mPluginInstance; + nsIntRect mUpdateRect; +} +- (void)setDrawFunc:(DrawPluginFunc)aFunc pluginInstance:(void*)aPluginInstance; +- (void)updateRect:(nsIntRect)aRect; + +@end + +// CGBitmapContextSetData() is an undocumented function present (with +// the same signature) since at least OS X 10.5. As the name suggests, +// it's used to replace the "data" in a bitmap context that was +// originally specified in a call to CGBitmapContextCreate() or +// CGBitmapContextCreateWithData(). +typedef void (*CGBitmapContextSetDataFunc)(CGContextRef c, size_t x, size_t y, size_t width, + size_t height, void* data, size_t bitsPerComponent, + size_t bitsPerPixel, size_t bytesPerRow); +CGBitmapContextSetDataFunc CGBitmapContextSetDataPtr = NULL; + +@implementation CGBridgeLayer +- (void)updateRect:(nsIntRect)aRect { + mUpdateRect.UnionRect(mUpdateRect, aRect); +} + +- (void)setDrawFunc:(DrawPluginFunc)aFunc pluginInstance:(void*)aPluginInstance { + mDrawFunc = aFunc; + mPluginInstance = aPluginInstance; +} + +- (void)drawInContext:(CGContextRef)aCGContext { + ::CGContextSaveGState(aCGContext); + ::CGContextTranslateCTM(aCGContext, 0, self.bounds.size.height); + ::CGContextScaleCTM(aCGContext, (CGFloat)1, (CGFloat)-1); + + mUpdateRect = nsIntRect::Truncate(0, 0, self.bounds.size.width, self.bounds.size.height); + + mDrawFunc(aCGContext, mPluginInstance, mUpdateRect); + + ::CGContextRestoreGState(aCGContext); + + mUpdateRect.SetEmpty(); +} + +@end + +void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, + double aContentsScaleFactor) { + CGBridgeLayer* bridgeLayer = [[CGBridgeLayer alloc] init]; + + // We need to make bridgeLayer behave properly when its superlayer changes + // size (in nsCARenderer::SetBounds()). + bridgeLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; + bridgeLayer.needsDisplayOnBoundsChange = YES; + NSNull* nullValue = [NSNull null]; + NSDictionary* actions = [NSDictionary + dictionaryWithObjectsAndKeys:nullValue, @"bounds", nullValue, @"contents", nullValue, + @"contentsRect", nullValue, @"position", nil]; + [bridgeLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]]; + + // For reasons that aren't clear (perhaps one or more OS bugs), we can only + // use full HiDPI resolution here if the tree is built with the 10.7 SDK or + // up. If we build with the 10.6 SDK, changing the contentsScale property + // of bridgeLayer (even to the same value) causes it to stop working (go + // blank). This doesn't happen with objects that are members of the CALayer + // class (as opposed to one of its subclasses). +#if defined(MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if ([bridgeLayer respondsToSelector:@selector(setContentsScale:)]) { + bridgeLayer.contentsScale = aContentsScaleFactor; + } +#endif + + [bridgeLayer setDrawFunc:aFunc pluginInstance:aPluginInstance]; + return bridgeLayer; +} + +void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void* cgLayer) { + CGBridgeLayer* bridgeLayer = (CGBridgeLayer*)cgLayer; + [bridgeLayer release]; +} + +void mozilla::plugins::PluginUtilsOSX::Repaint(void* caLayer, nsIntRect aRect) { + CGBridgeLayer* bridgeLayer = (CGBridgeLayer*)caLayer; + [CATransaction begin]; + [bridgeLayer updateRect:aRect]; + [bridgeLayer setNeedsDisplay]; + [bridgeLayer displayIfNeeded]; + [CATransaction commit]; +} + +@interface EventProcessor : NSObject { + RemoteProcessEvents aRemoteEvents; + void* aPluginModule; +} +- (void)setRemoteEvents:(RemoteProcessEvents)remoteEvents pluginModule:(void*)pluginModule; +- (void)onTick; +@end + +@implementation EventProcessor +- (void)onTick { + aRemoteEvents(aPluginModule); +} + +- (void)setRemoteEvents:(RemoteProcessEvents)remoteEvents pluginModule:(void*)pluginModule { + aRemoteEvents = remoteEvents; + aPluginModule = pluginModule; +} +@end + +#define EVENT_PROCESS_DELAY 0.05 // 50 ms + +NPError mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(void* aMenu, int aX, int aY, + void* pluginModule, + RemoteProcessEvents remoteEvent) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + // Set the native cursor to the OS default (an arrow) before displaying the + // context menu. Otherwise (if the plugin has changed the cursor) it may + // stay as the plugin has set it -- which means it may be invisible. We + // need to do this because we display the context menu without making the + // plugin process the foreground process. If we did, the cursor would + // change to an arrow cursor automatically -- as it does in Chrome. + [[NSCursor arrowCursor] set]; + + EventProcessor* eventProcessor = nullptr; + NSTimer* eventTimer = nullptr; + if (pluginModule) { + // Create a timer to process browser events while waiting + // on the menu. This prevents the browser from hanging + // during the lifetime of the menu. + eventProcessor = [[EventProcessor alloc] init]; + [eventProcessor setRemoteEvents:remoteEvent pluginModule:pluginModule]; + eventTimer = [NSTimer timerWithTimeInterval:EVENT_PROCESS_DELAY + target:eventProcessor + selector:@selector(onTick) + userInfo:nil + repeats:TRUE]; + // Use NSEventTrackingRunLoopMode otherwise the timer will + // not fire during the right click menu. + [[NSRunLoop currentRunLoop] addTimer:eventTimer forMode:NSEventTrackingRunLoopMode]; + } + + NSMenu* nsmenu = reinterpret_cast<NSMenu*>(aMenu); + NSPoint screen_point = ::NSMakePoint(aX, aY); + + [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil]; + + if (pluginModule) { + [eventTimer invalidate]; + [eventProcessor release]; + } + + return NPERR_NO_ERROR; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NPERR_GENERIC_ERROR); +} + +void mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + ::CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true); + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +#define UNDOCUMENTED_SESSION_CONSTANT ((int)-2) +namespace mozilla { +namespace plugins { +namespace PluginUtilsOSX { +static void* sApplicationASN = NULL; +static void* sApplicationInfoItem = NULL; +} // namespace PluginUtilsOSX +} // namespace plugins +} // namespace mozilla + +bool mozilla::plugins::PluginUtilsOSX::SetProcessName(const char* aProcessName) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + nsAutoreleasePool localPool; + + if (!aProcessName || strcmp(aProcessName, "") == 0) { + return false; + } + + NSString* currentName = + [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:(NSString*)kCFBundleNameKey]; + + char formattedName[1024]; + SprintfLiteral(formattedName, "%s %s", [currentName UTF8String], aProcessName); + + aProcessName = formattedName; + + // This function is based on Chrome/Webkit's and relies on potentially dangerous SPI. + typedef CFTypeRef (*LSGetASNType)(); + typedef OSStatus (*LSSetInformationItemType)(int, CFTypeRef, CFStringRef, CFStringRef, + CFDictionaryRef*); + + CFBundleRef launchServices = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices")); + if (!launchServices) { + NS_WARNING("Failed to set process name: Could not open LaunchServices bundle"); + return false; + } + + if (!sApplicationASN) { + sApplicationASN = + ::CFBundleGetFunctionPointerForName(launchServices, CFSTR("_LSGetCurrentApplicationASN")); + if (!sApplicationASN) { + NS_WARNING("Failed to set process name: Could not get function pointer " + "for LaunchServices"); + return false; + } + } + + LSGetASNType getASNFunc = reinterpret_cast<LSGetASNType>(sApplicationASN); + + if (!sApplicationInfoItem) { + sApplicationInfoItem = ::CFBundleGetFunctionPointerForName( + launchServices, CFSTR("_LSSetApplicationInformationItem")); + } + + LSSetInformationItemType setInformationItemFunc = + reinterpret_cast<LSSetInformationItemType>(sApplicationInfoItem); + + void* displayNameKeyAddr = + ::CFBundleGetDataPointerForName(launchServices, CFSTR("_kLSDisplayNameKey")); + + CFStringRef displayNameKey = nil; + if (displayNameKeyAddr) { + displayNameKey = reinterpret_cast<CFStringRef>(*(CFStringRef*)displayNameKeyAddr); + } + + // Rename will fail without this + ProcessSerialNumber psn; + if (::GetCurrentProcess(&psn) != noErr) { + return false; + } + + CFTypeRef currentAsn = getASNFunc ? getASNFunc() : nullptr; + + if (!getASNFunc || !setInformationItemFunc || !displayNameKey || !currentAsn) { + NS_WARNING("Failed to set process name: Accessing launchServices failed"); + return false; + } + + CFStringRef processName = ::CFStringCreateWithCString(nil, aProcessName, kCFStringEncodingASCII); + if (!processName) { + NS_WARNING("Failed to set process name: Could not create CFStringRef"); + return false; + } + + OSErr err = + setInformationItemFunc(UNDOCUMENTED_SESSION_CONSTANT, currentAsn, displayNameKey, processName, + nil); // Optional out param + ::CFRelease(processName); + if (err != noErr) { + NS_WARNING("Failed to set process name: LSSetInformationItemType err"); + return false; + } + + return true; + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); +} + +namespace mozilla { +namespace plugins { +namespace PluginUtilsOSX { + +size_t nsDoubleBufferCARenderer::GetFrontSurfaceWidth() { + if (!HasFrontSurface()) { + return 0; + } + + return mFrontSurface->GetWidth(); +} + +size_t nsDoubleBufferCARenderer::GetFrontSurfaceHeight() { + if (!HasFrontSurface()) { + return 0; + } + + return mFrontSurface->GetHeight(); +} + +double nsDoubleBufferCARenderer::GetFrontSurfaceContentsScaleFactor() { + if (!HasFrontSurface()) { + return 1.0; + } + + return mFrontSurface->GetContentsScaleFactor(); +} + +size_t nsDoubleBufferCARenderer::GetBackSurfaceWidth() { + if (!HasBackSurface()) { + return 0; + } + + return mBackSurface->GetWidth(); +} + +size_t nsDoubleBufferCARenderer::GetBackSurfaceHeight() { + if (!HasBackSurface()) { + return 0; + } + + return mBackSurface->GetHeight(); +} + +double nsDoubleBufferCARenderer::GetBackSurfaceContentsScaleFactor() { + if (!HasBackSurface()) { + return 1.0; + } + + return mBackSurface->GetContentsScaleFactor(); +} + +IOSurfaceID nsDoubleBufferCARenderer::GetFrontSurfaceID() { + if (!HasFrontSurface()) { + return 0; + } + + return mFrontSurface->GetIOSurfaceID(); +} + +bool nsDoubleBufferCARenderer::HasBackSurface() { return !!mBackSurface; } + +bool nsDoubleBufferCARenderer::HasFrontSurface() { return !!mFrontSurface; } + +bool nsDoubleBufferCARenderer::HasCALayer() { return !!mCALayer; } + +void nsDoubleBufferCARenderer::SetCALayer(void* aCALayer) { mCALayer = aCALayer; } + +bool nsDoubleBufferCARenderer::InitFrontSurface(size_t aWidth, size_t aHeight, + double aContentsScaleFactor, + AllowOfflineRendererEnum aAllowOfflineRenderer) { + if (!mCALayer) { + return false; + } + + mContentsScaleFactor = aContentsScaleFactor; + mFrontSurface = MacIOSurface::CreateIOSurface(aWidth, aHeight, mContentsScaleFactor); + if (!mFrontSurface) { + mCARenderer = nullptr; + return false; + } + + if (!mCARenderer) { + mCARenderer = new nsCARenderer(); + if (!mCARenderer) { + mFrontSurface = nullptr; + return false; + } + + mCARenderer->AttachIOSurface(mFrontSurface); + + nsresult result = + mCARenderer->SetupRenderer(mCALayer, mFrontSurface->GetWidth(), mFrontSurface->GetHeight(), + mContentsScaleFactor, aAllowOfflineRenderer); + + if (result != NS_OK) { + mCARenderer = nullptr; + mFrontSurface = nullptr; + return false; + } + } else { + mCARenderer->AttachIOSurface(mFrontSurface); + } + + return true; +} + +void nsDoubleBufferCARenderer::Render() { + if (!HasFrontSurface() || !mCARenderer) { + return; + } + + mCARenderer->Render(GetFrontSurfaceWidth(), GetFrontSurfaceHeight(), mContentsScaleFactor, + nullptr); +} + +void nsDoubleBufferCARenderer::SwapSurfaces() { + RefPtr<MacIOSurface> prevFrontSurface = mFrontSurface; + mFrontSurface = mBackSurface; + mBackSurface = prevFrontSurface; + + if (mFrontSurface) { + mCARenderer->AttachIOSurface(mFrontSurface); + } +} + +void nsDoubleBufferCARenderer::ClearFrontSurface() { + mFrontSurface = nullptr; + if (!mFrontSurface && !mBackSurface) { + mCARenderer = nullptr; + } +} + +void nsDoubleBufferCARenderer::ClearBackSurface() { + mBackSurface = nullptr; + if (!mFrontSurface && !mBackSurface) { + mCARenderer = nullptr; + } +} + +} // namespace PluginUtilsOSX +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginUtilsWin.cpp b/dom/plugins/ipc/PluginUtilsWin.cpp new file mode 100644 index 0000000000..647d0d385e --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsWin.cpp @@ -0,0 +1,272 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* PluginUtilsWin.cpp - top-level Windows plugin management code */ + +#include <mmdeviceapi.h> +#include "PluginUtilsWin.h" +#include "PluginModuleParent.h" +#include "mozilla/StaticMutex.h" + +namespace mozilla { +namespace plugins { +namespace PluginUtilsWin { + +class AudioNotification; +typedef nsTHashtable<nsPtrHashKey<PluginModuleParent>> PluginModuleSet; +StaticMutex sMutex; + +class AudioDeviceMessageRunnable : public Runnable { + public: + explicit AudioDeviceMessageRunnable( + AudioNotification* aAudioNotification, + NPAudioDeviceChangeDetailsIPC aChangeDetails); + + explicit AudioDeviceMessageRunnable( + AudioNotification* aAudioNotification, + NPAudioDeviceStateChangedIPC aDeviceState); + + NS_IMETHOD Run() override; + + protected: + // The potential payloads for the message. Type determined by mMessageType. + NPAudioDeviceChangeDetailsIPC mChangeDetails; + NPAudioDeviceStateChangedIPC mDeviceState; + enum { DEFAULT_DEVICE_CHANGED, DEVICE_STATE_CHANGED } mMessageType; + + AudioNotification* mAudioNotification; +}; + +class AudioNotification final : public IMMNotificationClient { + public: + AudioNotification() : mIsRegistered(false), mRefCt(1) { + 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; + } + + // 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<AudioDeviceMessageRunnable> runnable = + new AudioDeviceMessageRunnable(this, 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 { + NPAudioDeviceStateChangedIPC deviceStateIPC; + deviceStateIPC.device = device_id ? std::wstring(device_id) : L""; + deviceStateIPC.state = (uint32_t)new_state; + + // Make sure that plugin is notified on the main thread. + RefPtr<AudioDeviceMessageRunnable> runnable = + new AudioDeviceMessageRunnable(this, deviceStateIPC); + NS_DispatchToMainThread(runnable); + 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(); } + + const PluginModuleSet* GetModuleSet() const { return &mAudioNotificationSet; } + + 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; + + ~AudioNotification() { + MOZ_ASSERT(!mIsRegistered, + "Destroying AudioNotification without first calling Unregister"); + if (mDeviceEnum) { + mDeviceEnum->Release(); + } + } +}; // 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; +} + +AudioDeviceMessageRunnable::AudioDeviceMessageRunnable( + AudioNotification* aAudioNotification, + NPAudioDeviceChangeDetailsIPC aChangeDetails) + : Runnable("AudioDeviceMessageRunnableCD"), + mChangeDetails(aChangeDetails), + mMessageType(DEFAULT_DEVICE_CHANGED), + mAudioNotification(aAudioNotification) { + // We increment the AudioNotification ref-count here -- the runnable will + // decrement it when it is done with us. + mAudioNotification->AddRef(); +} + +AudioDeviceMessageRunnable::AudioDeviceMessageRunnable( + AudioNotification* aAudioNotification, + NPAudioDeviceStateChangedIPC aDeviceState) + : Runnable("AudioDeviceMessageRunnableSC"), + mDeviceState(aDeviceState), + mMessageType(DEVICE_STATE_CHANGED), + mAudioNotification(aAudioNotification) { + // We increment the AudioNotification ref-count here -- the runnable will + // decrement it when it is done with us. + mAudioNotification->AddRef(); +} + +NS_IMETHODIMP +AudioDeviceMessageRunnable::Run() { + StaticMutexAutoLock lock(sMutex); + PLUGIN_LOG_DEBUG(("Notifying %d plugins of audio device change.", + mAudioNotification->GetModuleSet()->Count())); + + bool success = true; + for (auto iter = mAudioNotification->GetModuleSet()->ConstIter(); + !iter.Done(); iter.Next()) { + PluginModuleParent* pluginModule = iter.Get()->GetKey(); + switch (mMessageType) { + case DEFAULT_DEVICE_CHANGED: + success &= pluginModule->SendNPP_SetValue_NPNVaudioDeviceChangeDetails( + mChangeDetails); + break; + case DEVICE_STATE_CHANGED: + success &= pluginModule->SendNPP_SetValue_NPNVaudioDeviceStateChanged( + mDeviceState); + break; + default: + MOZ_ASSERT_UNREACHABLE("bad AudioDeviceMessageRunnable state"); + } + } + mAudioNotification->Release(); + return success ? NS_OK : NS_ERROR_FAILURE; +} + +} // 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 0000000000..f3afa79d2c --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsWin.h @@ -0,0 +1,27 @@ +/* -*- 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" +#include "nscore.h" + +namespace mozilla { +namespace plugins { + +class PluginModuleParent; + +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 0000000000..c25c8529fd --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetChild.cpp @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/BrowserChild.h" +#include "mozilla/plugins/PluginWidgetParent.h" +#include "PluginWidgetProxy.h" + +#include "mozilla/Unused.h" +#include "mozilla/DebugOnly.h" +#include "nsDebug.h" + +#include "mozilla/plugins/PluginInstanceParent.h" + +#define PWLOG(...) +//#define PWLOG(...) printf_stderr(__VA_ARGS__) + +namespace mozilla { +namespace plugins { + +PluginWidgetChild::PluginWidgetChild() : mWidget(nullptr) { + PWLOG("PluginWidgetChild::PluginWidgetChild()\n"); + MOZ_COUNT_CTOR(PluginWidgetChild); +} + +PluginWidgetChild::~PluginWidgetChild() { + PWLOG("PluginWidgetChild::~PluginWidgetChild()\n"); + MOZ_COUNT_DTOR(PluginWidgetChild); +} + +// Called by the proxy widget when it is destroyed by layout. Only gets +// called once. +void PluginWidgetChild::ProxyShutdown() { + PWLOG("PluginWidgetChild::ProxyShutdown()\n"); + if (mWidget) { + mWidget = nullptr; + auto tab = static_cast<mozilla::dom::BrowserChild*>(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 0000000000..1fc2d6ee98 --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetChild.h @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +#ifndef XP_WIN +# error "This header should be Windows-only." +#endif + +#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 0000000000..0def364dc9 --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetParent.cpp @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/BrowserParent.h" +#include "mozilla/dom/ContentParent.h" +#include "nsComponentManagerUtils.h" +#include "nsWidgetsCID.h" + +#include "mozilla/Unused.h" +#include "mozilla/DebugOnly.h" +#include "nsDebug.h" + +using namespace mozilla; +using namespace mozilla::widget; + +#define PWLOG(...) +//#define PWLOG(...) printf_stderr(__VA_ARGS__) + +namespace mozilla { +namespace dom { +// For nsWindow +const wchar_t* kPluginWidgetContentParentProperty = + L"kPluginWidgetParentProperty"; +} // namespace dom +} // namespace mozilla + +namespace mozilla { +namespace plugins { + +// This macro returns IPC_OK() 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 IPC_OK(); \ + } \ + } + +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::BrowserParent* PluginWidgetParent::GetBrowserParent() { + return static_cast<mozilla::dom::BrowserParent*>(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. + +mozilla::ipc::IPCResult PluginWidgetParent::RecvCreate( + nsresult* aResult, uint64_t* aScrollCaptureId, + uintptr_t* aPluginInstanceId) { + PWLOG("PluginWidgetParent::RecvCreate()\n"); + + *aScrollCaptureId = 0; + *aPluginInstanceId = 0; + + mWidget = nsIWidget::CreateChildWindow(); + *aResult = mWidget ? NS_OK : NS_ERROR_FAILURE; + + // This returns the top level window widget + nsCOMPtr<nsIWidget> parentWidget = GetBrowserParent()->GetWidget(); + // If this fails, bail. + if (!parentWidget) { + *aResult = NS_ERROR_NOT_AVAILABLE; + KillWidget(); + return IPC_OK(); + } + + nsWidgetInitData initData; + initData.mWindowType = eWindowType_plugin_ipc_chrome; + 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 IPC_FAIL_NO_REASON(this); + } + + mWidget->EnableDragDrop(true); + + // 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 IPC_OK(); +} + +void PluginWidgetParent::KillWidget() { + PWLOG("PluginWidgetParent::KillWidget() widget=%p\n", (void*)mWidget.get()); + if (mWidget) { + mWidget->UnregisterPluginWindowForRemoteUpdates(); + mWidget->Destroy(); + ::RemovePropW((HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW), + mozilla::dom::kPluginWidgetContentParentProperty); + mWidget = nullptr; + } +} + +void PluginWidgetParent::ActorDestroy(ActorDestroyReason aWhy) { + PWLOG("PluginWidgetParent::ActorDestroy(%d)\n", aWhy); + KillWidget(); +} + +// Called by BrowserParent'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"); +} + +mozilla::ipc::IPCResult PluginWidgetParent::RecvSetFocus( + const bool& aRaise, const mozilla::dom::CallerType& aCallerType) { + ENSURE_CHANNEL; + PWLOG("PluginWidgetParent::RecvSetFocus(%d)\n", aRaise); + mWidget->SetFocus(aRaise ? nsIWidget::Raise::Yes : nsIWidget::Raise::No, + aCallerType); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginWidgetParent::RecvGetNativePluginPort( + uintptr_t* value) { + ENSURE_CHANNEL; + *value = (uintptr_t)mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT); + NS_ASSERTION(*value, "no native port??"); + PWLOG("PluginWidgetParent::RecvGetNativeData() %p\n", (void*)*value); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginWidgetParent::RecvSetNativeChildWindow( + const uintptr_t& aChildWindow) { + ENSURE_CHANNEL; + PWLOG("PluginWidgetParent::RecvSetNativeChildWindow(%p)\n", + (void*)aChildWindow); + mWidget->SetNativeData(NS_NATIVE_CHILD_WINDOW, aChildWindow); + return IPC_OK(); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginWidgetParent.h b/dom/plugins/ipc/PluginWidgetParent.h new file mode 100644 index 0000000000..78228345cb --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetParent.h @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +#ifndef XP_WIN +# error "This header should be Windows-only." +#endif + +#include "mozilla/plugins/PPluginWidgetParent.h" +#include "mozilla/UniquePtr.h" +#include "nsIWidget.h" +#include "nsCOMPtr.h" + +namespace mozilla { + +namespace dom { +class BrowserParent; +} // namespace dom + +namespace plugins { + +class PluginWidgetParent : public PPluginWidgetParent { + public: + PluginWidgetParent(); + virtual ~PluginWidgetParent(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + virtual mozilla::ipc::IPCResult RecvCreate( + nsresult* aResult, uint64_t* aScrollCaptureId, + uintptr_t* aPluginInstanceId) override; + virtual mozilla::ipc::IPCResult RecvSetFocus( + const bool& aRaise, const mozilla::dom::CallerType& aCallerType) override; + virtual mozilla::ipc::IPCResult RecvGetNativePluginPort( + uintptr_t* value) override; + mozilla::ipc::IPCResult 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::BrowserParent* GetBrowserParent(); + + private: + void KillWidget(); + + // The chrome side native widget. + nsCOMPtr<nsIWidget> mWidget; +}; + +} // 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 0000000000..a08f37e723 --- /dev/null +++ b/dom/plugins/ipc/StreamNotifyChild.h @@ -0,0 +1,60 @@ +/* -*- 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; + friend class PStreamNotifyChild; + + 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: + mozilla::ipc::IPCResult Recv__delete__(const NPReason& reason); + + mozilla::ipc::IPCResult RecvRedirectNotify(const nsCString& url, + const int32_t& status); + + /** + * 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 0000000000..6020f702ac --- /dev/null +++ b/dom/plugins/ipc/StreamNotifyParent.h @@ -0,0 +1,41 @@ +/* -*- 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; + friend class PStreamNotifyParent; + + 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: + mozilla::ipc::IPCResult RecvRedirectNotifyResponse(const bool& allow); + + 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 0000000000..79cdfc74b4 --- /dev/null +++ b/dom/plugins/ipc/hangui/HangUIDlg.h @@ -0,0 +1,17 @@ +/* -*- 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 0000000000..62e98ca249 --- /dev/null +++ b/dom/plugins/ipc/hangui/HangUIDlg.rc @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "HangUIDlg.h" +#include <windows.h> + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_HANGUIDLG DIALOGEX 0, 0, 400, 75 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Continue",IDC_CONTINUE,283,51,50,18 + PUSHBUTTON "Stop",IDC_STOP,341,51,50,18 + CONTROL "Check1",IDC_NOFUTURE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,37,32,354,10 + LTEXT "Static",IDC_MSG,37,7,353,24 + ICON "",IDC_DLGICON,7,7,20,20 +END + diff --git a/dom/plugins/ipc/hangui/MiniShmBase.h b/dom/plugins/ipc/hangui/MiniShmBase.h new file mode 100644 index 0000000000..9782330b12 --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmBase.h @@ -0,0 +1,280 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_MiniShmBase_h +#define mozilla_plugins_MiniShmBase_h + +#include "base/basictypes.h" + +#include "nsDebug.h" + +#include <windows.h> + +namespace mozilla { +namespace plugins { + +/** + * This class is used to provide RAII semantics for mapped views. + * @see ScopedHandle + */ +class ScopedMappedFileView { + public: + explicit ScopedMappedFileView(LPVOID aView) : mView(aView) {} + + ~ScopedMappedFileView() { Close(); } + + void Close() { + if (mView) { + ::UnmapViewOfFile(mView); + mView = nullptr; + } + } + + void Set(LPVOID aView) { + Close(); + mView = aView; + } + + LPVOID + Get() const { return mView; } + + LPVOID + Take() { + LPVOID result = mView; + mView = nullptr; + return result; + } + + operator LPVOID() { return mView; } + + bool IsValid() const { return (mView); } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedMappedFileView); + + LPVOID mView; +}; + +class MiniShmBase; + +class MiniShmObserver { + public: + /** + * This function is called whenever there is a new shared memory request. + * @param aMiniShmObj MiniShmBase object that may be used to read and + * write from shared memory. + */ + virtual void OnMiniShmEvent(MiniShmBase* aMiniShmObj) = 0; + /** + * This function is called once when a MiniShmParent and a MiniShmChild + * object have successfully negotiated a connection. + * + * @param aMiniShmObj MiniShmBase object that may be used to read and + * write from shared memory. + */ + virtual void OnMiniShmConnect(MiniShmBase* aMiniShmObj) {} +}; + +/** + * Base class for MiniShm connections. This class defines the common + * interfaces and code between parent and child. + */ +class MiniShmBase { + public: + /** + * Obtains a writable pointer into shared memory of type T. + * typename T must be plain-old-data and contain an unsigned integral + * member T::identifier that uniquely identifies T with respect to + * other types used by the protocol being implemented. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T is not valid for MiniShm. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + * NS_ERROR_NOT_AVAILABLE if the memory is not safe to write. + */ + template <typename T> + nsresult GetWritePtr(T*& aPtr) { + if (!mWriteHeader || !mGuard) { + return NS_ERROR_NOT_INITIALIZED; + } + if (sizeof(T) > mPayloadMaxLen || + (int)T::identifier <= (int)RESERVED_CODE_LAST) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (::WaitForSingleObject(mGuard, mTimeout) != WAIT_OBJECT_0) { + return NS_ERROR_NOT_AVAILABLE; + } + mWriteHeader->mId = T::identifier; + mWriteHeader->mPayloadLen = sizeof(T); + aPtr = reinterpret_cast<T*>(mWriteHeader + 1); + return NS_OK; + } + + /** + * Obtains a readable pointer into shared memory of type T. + * typename T must be plain-old-data and contain an unsigned integral + * member T::identifier that uniquely identifies T with respect to + * other types used by the protocol being implemented. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T is not valid for MiniShm or if + * type T does not match the type of the data + * stored in shared memory. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + */ + template <typename T> + nsresult GetReadPtr(const T*& aPtr) { + if (!mReadHeader) { + return NS_ERROR_NOT_INITIALIZED; + } + if (mReadHeader->mId != T::identifier || + sizeof(T) != mReadHeader->mPayloadLen) { + return NS_ERROR_ILLEGAL_VALUE; + } + aPtr = reinterpret_cast<const T*>(mReadHeader + 1); + return NS_OK; + } + + /** + * Fires the peer's event causing its request handler to execute. + * + * @return Should return NS_OK if the send was successful. + */ + virtual nsresult Send() = 0; + + protected: + /** + * MiniShm reserves some identifier codes for its own use. Any + * identifiers used by MiniShm protocol implementations must be + * greater than RESERVED_CODE_LAST. + */ + enum ReservedCodes { + RESERVED_CODE_INIT = 0, + RESERVED_CODE_INIT_COMPLETE = 1, + RESERVED_CODE_LAST = RESERVED_CODE_INIT_COMPLETE + }; + + struct MiniShmHeader { + unsigned int mId; + unsigned int mPayloadLen; + }; + + struct MiniShmInit { + enum identifier_t { identifier = RESERVED_CODE_INIT }; + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + }; + + struct MiniShmInitComplete { + enum identifier_t { identifier = RESERVED_CODE_INIT_COMPLETE }; + bool mSucceeded; + }; + + MiniShmBase() + : mObserver(nullptr), + mWriteHeader(nullptr), + mReadHeader(nullptr), + mPayloadMaxLen(0), + mGuard(nullptr), + mTimeout(INFINITE) {} + virtual ~MiniShmBase() {} + + virtual void OnEvent() { + if (mObserver) { + mObserver->OnMiniShmEvent(this); + } + } + + virtual void OnConnect() { + if (mObserver) { + mObserver->OnMiniShmConnect(this); + } + } + + nsresult SetView(LPVOID aView, const unsigned int aSize, bool aIsChild) { + if (!aView || aSize <= 2 * sizeof(MiniShmHeader)) { + return NS_ERROR_ILLEGAL_VALUE; + } + // Divide the region into halves for parent and child + if (aIsChild) { + mReadHeader = static_cast<MiniShmHeader*>(aView); + mWriteHeader = reinterpret_cast<MiniShmHeader*>( + static_cast<char*>(aView) + aSize / 2U); + } else { + mWriteHeader = static_cast<MiniShmHeader*>(aView); + mReadHeader = reinterpret_cast<MiniShmHeader*>(static_cast<char*>(aView) + + aSize / 2U); + } + mPayloadMaxLen = aSize / 2U - sizeof(MiniShmHeader); + return NS_OK; + } + + nsresult SetGuard(HANDLE aGuard, DWORD aTimeout) { + if (!aGuard || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + mGuard = aGuard; + mTimeout = aTimeout; + return NS_OK; + } + + inline void SetObserver(MiniShmObserver* aObserver) { mObserver = aObserver; } + + /** + * Obtains a writable pointer into shared memory of type T. This version + * differs from GetWritePtr in that it allows typename T to be one of + * the private data structures declared in MiniShmBase. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T not an internal MiniShm struct. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + */ + template <typename T> + nsresult GetWritePtrInternal(T*& aPtr) { + if (!mWriteHeader) { + return NS_ERROR_NOT_INITIALIZED; + } + if (sizeof(T) > mPayloadMaxLen || + (int)T::identifier > (int)RESERVED_CODE_LAST) { + return NS_ERROR_ILLEGAL_VALUE; + } + mWriteHeader->mId = T::identifier; + mWriteHeader->mPayloadLen = sizeof(T); + aPtr = reinterpret_cast<T*>(mWriteHeader + 1); + return NS_OK; + } + + static VOID CALLBACK SOnEvent(PVOID aContext, BOOLEAN aIsTimer) { + MiniShmBase* object = static_cast<MiniShmBase*>(aContext); + object->OnEvent(); + } + + private: + MiniShmObserver* mObserver; + MiniShmHeader* mWriteHeader; + MiniShmHeader* mReadHeader; + unsigned int mPayloadMaxLen; + HANDLE mGuard; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmBase); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmBase_h diff --git a/dom/plugins/ipc/hangui/MiniShmChild.cpp b/dom/plugins/ipc/hangui/MiniShmChild.cpp new file mode 100644 index 0000000000..ec5a79714f --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmChild.cpp @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MiniShmChild.h" + +#include <limits> +#include <sstream> + +namespace mozilla { +namespace plugins { + +MiniShmChild::MiniShmChild() + : mParentEvent(nullptr), + mParentGuard(nullptr), + mChildEvent(nullptr), + mChildGuard(nullptr), + mFileMapping(nullptr), + mRegWait(nullptr), + mView(nullptr), + mTimeout(INFINITE) {} + +MiniShmChild::~MiniShmChild() { + if (mRegWait) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + } + if (mParentGuard) { + // Try to avoid shutting down while the parent's event handler is running. + ::WaitForSingleObject(mParentGuard, mTimeout); + ::CloseHandle(mParentGuard); + } + if (mParentEvent) { + ::CloseHandle(mParentEvent); + } + if (mChildEvent) { + ::CloseHandle(mChildEvent); + } + if (mChildGuard) { + ::CloseHandle(mChildGuard); + } + if (mView) { + ::UnmapViewOfFile(mView); + } + if (mFileMapping) { + ::CloseHandle(mFileMapping); + } +} + +nsresult MiniShmChild::Init(MiniShmObserver* aObserver, + const std::wstring& aCookie, const DWORD aTimeout) { + if (aCookie.empty() || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (mFileMapping) { + return NS_ERROR_ALREADY_INITIALIZED; + } + std::wistringstream iss(aCookie); + HANDLE mapHandle = nullptr; + iss >> mapHandle; + if (!iss) { + return NS_ERROR_ILLEGAL_VALUE; + } + ScopedMappedFileView view( + ::MapViewOfFile(mapHandle, FILE_MAP_WRITE, 0, 0, 0)); + if (!view.IsValid()) { + return NS_ERROR_FAILURE; + } + MEMORY_BASIC_INFORMATION memInfo = {0}; + SIZE_T querySize = ::VirtualQuery(view, &memInfo, sizeof(memInfo)); + unsigned int mappingSize = 0; + if (querySize) { + if (memInfo.RegionSize <= std::numeric_limits<unsigned int>::max()) { + mappingSize = static_cast<unsigned int>(memInfo.RegionSize); + } + } + if (!querySize || !mappingSize) { + return NS_ERROR_FAILURE; + } + nsresult rv = SetView(view, mappingSize, true); + if (NS_FAILED(rv)) { + return rv; + } + + const MiniShmInit* initStruct = nullptr; + rv = GetReadPtr(initStruct); + if (NS_FAILED(rv)) { + return rv; + } + if (!initStruct->mParentEvent || !initStruct->mParentGuard || + !initStruct->mChildEvent || !initStruct->mChildGuard) { + return NS_ERROR_FAILURE; + } + rv = SetGuard(initStruct->mParentGuard, aTimeout); + if (NS_FAILED(rv)) { + return rv; + } + if (!::RegisterWaitForSingleObject(&mRegWait, initStruct->mChildEvent, + &SOnEvent, this, INFINITE, + WT_EXECUTEDEFAULT)) { + return NS_ERROR_FAILURE; + } + + MiniShmInitComplete* initCompleteStruct = nullptr; + rv = GetWritePtrInternal(initCompleteStruct); + if (NS_FAILED(rv)) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = nullptr; + return NS_ERROR_FAILURE; + } + + initCompleteStruct->mSucceeded = true; + + // We must set the member variables before we signal the event + mFileMapping = mapHandle; + mView = view.Take(); + mParentEvent = initStruct->mParentEvent; + mParentGuard = initStruct->mParentGuard; + mChildEvent = initStruct->mChildEvent; + mChildGuard = initStruct->mChildGuard; + SetObserver(aObserver); + mTimeout = aTimeout; + + rv = Send(); + if (NS_FAILED(rv)) { + initCompleteStruct->mSucceeded = false; + mFileMapping = nullptr; + view.Set(mView); + mView = nullptr; + mParentEvent = nullptr; + mParentGuard = nullptr; + mChildEvent = nullptr; + mChildGuard = nullptr; + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = nullptr; + return rv; + } + + OnConnect(); + return NS_OK; +} + +nsresult MiniShmChild::Send() { + if (!mParentEvent) { + return NS_ERROR_NOT_INITIALIZED; + } + if (!::SetEvent(mParentEvent)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void MiniShmChild::OnEvent() { + MiniShmBase::OnEvent(); + ::SetEvent(mChildGuard); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/hangui/MiniShmChild.h b/dom/plugins/ipc/hangui/MiniShmChild.h new file mode 100644 index 0000000000..ddaa3277b2 --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmChild.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_MiniShmChild_h +#define mozilla_plugins_MiniShmChild_h + +#include "MiniShmBase.h" + +#include <string> + +namespace mozilla { +namespace plugins { + +/** + * This class provides a lightweight shared memory interface for a child + * process in Win32. + * This code assumes that there is a parent-child relationship between + * processes, as it inherits handles from the parent process. + * Note that this class is *not* an IPDL actor. + * + * @see MiniShmParent + */ +class MiniShmChild : public MiniShmBase { + public: + MiniShmChild(); + virtual ~MiniShmChild(); + + /** + * Initialize shared memory on the child side. + * + * @param aObserver A MiniShmObserver object to receive event notifications. + * @param aCookie Cookie obtained from MiniShmParent::GetCookie + * @param aTimeout Timeout in milliseconds. + * @return nsresult error code + */ + nsresult Init(MiniShmObserver* aObserver, const std::wstring& aCookie, + const DWORD aTimeout); + + virtual nsresult Send() override; + + protected: + void OnEvent() override; + + private: + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + HANDLE mFileMapping; + HANDLE mRegWait; + LPVOID mView; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmChild_h diff --git a/dom/plugins/ipc/hangui/PluginHangUI.h b/dom/plugins/ipc/hangui/PluginHangUI.h new file mode 100644 index 0000000000..c0880f18cc --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUI.h @@ -0,0 +1,39 @@ +/* -*- 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 0000000000..01bae5cb68 --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUIChild.cpp @@ -0,0 +1,386 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginHangUI.h" + +#include "PluginHangUIChild.h" +#include "HangUIDlg.h" + +#include <assert.h> +#include <commctrl.h> +#include <windowsx.h> +#include <algorithm> +#include <sstream> +#include <vector> + +namespace mozilla { +namespace plugins { + +struct WinInfo { + WinInfo(HWND aHwnd, POINT& aPos, SIZE& aSize) : hwnd(aHwnd) { + pos.x = aPos.x; + pos.y = aPos.y; + size.cx = aSize.cx; + size.cy = aSize.cy; + } + HWND hwnd; + POINT pos; + SIZE size; +}; +typedef std::vector<WinInfo> WinInfoVec; + +PluginHangUIChild* PluginHangUIChild::sSelf = nullptr; +const int PluginHangUIChild::kExpectedMinimumArgc = 10; + +PluginHangUIChild::PluginHangUIChild() + : mResponseBits(0), + mParentWindow(nullptr), + mDlgHandle(nullptr), + mMainThread(nullptr), + mParentProcess(nullptr), + mRegWaitProcess(nullptr), + mIPCTimeoutMs(0) {} + +PluginHangUIChild::~PluginHangUIChild() { + if (mMainThread) { + CloseHandle(mMainThread); + } + if (mRegWaitProcess) { + UnregisterWaitEx(mRegWaitProcess, INVALID_HANDLE_VALUE); + } + if (mParentProcess) { + CloseHandle(mParentProcess); + } + sSelf = nullptr; +} + +bool PluginHangUIChild::Init(int aArgc, wchar_t* aArgv[]) { + if (aArgc < kExpectedMinimumArgc) { + return false; + } + unsigned int i = 1; + mMessageText = aArgv[i]; + mWindowTitle = aArgv[++i]; + mWaitBtnText = aArgv[++i]; + mKillBtnText = aArgv[++i]; + mNoFutureText = aArgv[++i]; + std::wistringstream issHwnd(aArgv[++i]); + issHwnd >> reinterpret_cast<HANDLE&>(mParentWindow); + if (!issHwnd) { + return false; + } + std::wistringstream issProc(aArgv[++i]); + issProc >> mParentProcess; + if (!issProc) { + return false; + } + // Only set the App User Model ID if it's present in the args + if (wcscmp(aArgv[++i], L"-")) { + HMODULE shell32 = LoadLibrary(L"shell32.dll"); + if (shell32) { + SETAPPUSERMODELID fSetAppUserModelID = (SETAPPUSERMODELID)GetProcAddress( + shell32, "SetCurrentProcessExplicitAppUserModelID"); + if (fSetAppUserModelID) { + fSetAppUserModelID(aArgv[i]); + } + FreeLibrary(shell32); + } + } + std::wistringstream issTimeout(aArgv[++i]); + issTimeout >> mIPCTimeoutMs; + if (!issTimeout) { + return false; + } + + nsresult rv = mMiniShm.Init(this, std::wstring(aArgv[++i]), + IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); + if (NS_FAILED(rv)) { + return false; + } + sSelf = this; + return true; +} + +void PluginHangUIChild::OnMiniShmEvent(MiniShmBase* aMiniShmObj) { + const PluginHangUICommand* cmd = nullptr; + nsresult rv = aMiniShmObj->GetReadPtr(cmd); + assert(NS_SUCCEEDED(rv)); + bool returnStatus = false; + if (NS_SUCCEEDED(rv)) { + switch (cmd->mCode) { + case PluginHangUICommand::HANGUI_CMD_SHOW: + returnStatus = RecvShow(); + break; + case PluginHangUICommand::HANGUI_CMD_CANCEL: + returnStatus = RecvCancel(); + break; + default: + break; + } + } +} + +// static +INT_PTR CALLBACK PluginHangUIChild::SHangUIDlgProc(HWND aDlgHandle, + UINT aMsgCode, + WPARAM aWParam, + LPARAM aLParam) { + PluginHangUIChild* self = PluginHangUIChild::sSelf; + if (self) { + return self->HangUIDlgProc(aDlgHandle, aMsgCode, aWParam, aLParam); + } + return FALSE; +} + +void PluginHangUIChild::ResizeButtons() { + // Control IDs are specified right-to-left as they appear in the dialog + UINT ids[] = {IDC_STOP, IDC_CONTINUE}; + UINT numIds = sizeof(ids) / sizeof(ids[0]); + + // Pass 1: Compute the ideal size + bool needResizing = false; + SIZE idealSize = {0}; + WinInfoVec winInfo; + for (UINT i = 0; i < numIds; ++i) { + HWND wnd = GetDlgItem(mDlgHandle, ids[i]); + if (!wnd) { + return; + } + + // Get the button's dimensions in screen coordinates + RECT curRect; + if (!GetWindowRect(wnd, &curRect)) { + return; + } + + // Get (x,y) position of the button in client coordinates + POINT pt; + pt.x = curRect.left; + pt.y = curRect.top; + if (!ScreenToClient(mDlgHandle, &pt)) { + return; + } + + // Request the button's text margins + RECT margins; + if (!Button_GetTextMargin(wnd, &margins)) { + return; + } + + // Compute the button's width and height + SIZE curSize; + curSize.cx = curRect.right - curRect.left; + curSize.cy = curRect.bottom - curRect.top; + + // Request the button's ideal width and height and add in the margins + SIZE size = {0}; + if (!Button_GetIdealSize(wnd, &size)) { + return; + } + size.cx += margins.left + margins.right; + size.cy += margins.top + margins.bottom; + + // Size all buttons to be the same width as the longest button encountered + idealSize.cx = std::max(idealSize.cx, size.cx); + idealSize.cy = std::max(idealSize.cy, size.cy); + + // We won't bother resizing unless we need extra space + if (idealSize.cx > curSize.cx) { + needResizing = true; + } + + // Save the relevant info for the resize, if any. We do this even if + // needResizing is false because another button may trigger a resize later. + winInfo.push_back(WinInfo(wnd, pt, curSize)); + } + + if (!needResizing) { + return; + } + + // Pass 2: Resize the windows + int deltaX = 0; + HDWP hwp = BeginDeferWindowPos((int)winInfo.size()); + if (!hwp) { + return; + } + for (WinInfoVec::const_iterator itr = winInfo.begin(); itr != winInfo.end(); + ++itr) { + // deltaX accumulates the size changes so that each button's x coordinate + // can compensate for the width increases + deltaX += idealSize.cx - itr->size.cx; + hwp = DeferWindowPos(hwp, itr->hwnd, nullptr, itr->pos.x - deltaX, + itr->pos.y, idealSize.cx, itr->size.cy, + SWP_NOZORDER | SWP_NOACTIVATE); + if (!hwp) { + return; + } + } + EndDeferWindowPos(hwp); +} + +INT_PTR +PluginHangUIChild::HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, + LPARAM aLParam) { + mDlgHandle = aDlgHandle; + switch (aMsgCode) { + case WM_INITDIALOG: { + // Register a wait on the Firefox process so that we will be informed + // if it dies while the dialog is showing + RegisterWaitForSingleObject(&mRegWaitProcess, mParentProcess, + &SOnParentProcessExit, this, INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + SetWindowText(aDlgHandle, mWindowTitle); + SetDlgItemText(aDlgHandle, IDC_MSG, mMessageText); + SetDlgItemText(aDlgHandle, IDC_NOFUTURE, mNoFutureText); + SetDlgItemText(aDlgHandle, IDC_CONTINUE, mWaitBtnText); + SetDlgItemText(aDlgHandle, IDC_STOP, mKillBtnText); + ResizeButtons(); + HANDLE icon = LoadImage(nullptr, IDI_QUESTION, IMAGE_ICON, 0, 0, + LR_DEFAULTSIZE | LR_SHARED); + if (icon) { + SendDlgItemMessage(aDlgHandle, IDC_DLGICON, STM_SETICON, (WPARAM)icon, + 0); + } + EnableWindow(mParentWindow, FALSE); + return TRUE; + } + case WM_CLOSE: { + mResponseBits |= HANGUI_USER_RESPONSE_CANCEL; + EndDialog(aDlgHandle, 0); + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + case WM_COMMAND: { + switch (LOWORD(aWParam)) { + case IDC_CONTINUE: + if (HIWORD(aWParam) == BN_CLICKED) { + mResponseBits |= HANGUI_USER_RESPONSE_CONTINUE; + EndDialog(aDlgHandle, 1); + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + break; + case IDC_STOP: + if (HIWORD(aWParam) == BN_CLICKED) { + mResponseBits |= HANGUI_USER_RESPONSE_STOP; + EndDialog(aDlgHandle, 1); + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + break; + case IDC_NOFUTURE: + if (HIWORD(aWParam) == BN_CLICKED) { + if (Button_GetCheck(GetDlgItem(aDlgHandle, IDC_NOFUTURE)) == + BST_CHECKED) { + mResponseBits |= HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN; + } else { + mResponseBits &= + ~static_cast<DWORD>(HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); + } + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + break; + default: + break; + } + break; + } + case WM_DESTROY: { + EnableWindow(mParentWindow, TRUE); + SetForegroundWindow(mParentWindow); + break; + } + default: + break; + } + return FALSE; +} + +// static +VOID CALLBACK PluginHangUIChild::SOnParentProcessExit(PVOID aObject, + BOOLEAN aIsTimer) { + // Simulate a cancel if the parent process died + PluginHangUIChild* object = static_cast<PluginHangUIChild*>(aObject); + object->RecvCancel(); +} + +bool PluginHangUIChild::RecvShow() { + return ( + QueueUserAPC(&ShowAPC, mMainThread, reinterpret_cast<ULONG_PTR>(this))); +} + +bool PluginHangUIChild::Show() { + INT_PTR dlgResult = + DialogBox(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDD_HANGUIDLG), + nullptr, &SHangUIDlgProc); + mDlgHandle = nullptr; + assert(dlgResult != -1); + bool result = false; + if (dlgResult != -1) { + PluginHangUIResponse* response = nullptr; + nsresult rv = mMiniShm.GetWritePtr(response); + if (NS_SUCCEEDED(rv)) { + response->mResponseBits = mResponseBits; + result = NS_SUCCEEDED(mMiniShm.Send()); + } + } + return result; +} + +// static +VOID CALLBACK PluginHangUIChild::ShowAPC(ULONG_PTR aContext) { + PluginHangUIChild* object = reinterpret_cast<PluginHangUIChild*>(aContext); + object->Show(); +} + +bool PluginHangUIChild::RecvCancel() { + if (mDlgHandle) { + PostMessage(mDlgHandle, WM_CLOSE, 0, 0); + } + return true; +} + +bool PluginHangUIChild::WaitForDismissal() { + if (!SetMainThread()) { + return false; + } + DWORD waitResult = WaitForSingleObjectEx(mParentProcess, mIPCTimeoutMs, TRUE); + return waitResult == WAIT_OBJECT_0 || waitResult == WAIT_IO_COMPLETION; +} + +bool PluginHangUIChild::SetMainThread() { + if (mMainThread) { + CloseHandle(mMainThread); + mMainThread = nullptr; + } + mMainThread = OpenThread(THREAD_SET_CONTEXT, FALSE, GetCurrentThreadId()); + return !(!mMainThread); +} + +} // namespace plugins +} // namespace mozilla + +#ifdef __MINGW32__ +extern "C" +#endif + int + wmain(int argc, wchar_t* argv[]) { + INITCOMMONCONTROLSEX icc = {sizeof(INITCOMMONCONTROLSEX), + ICC_STANDARD_CLASSES}; + if (!InitCommonControlsEx(&icc)) { + return 1; + } + mozilla::plugins::PluginHangUIChild hangui; + if (!hangui.Init(argc, argv)) { + return 1; + } + if (!hangui.WaitForDismissal()) { + return 1; + } + return 0; +} diff --git a/dom/plugins/ipc/hangui/PluginHangUIChild.h b/dom/plugins/ipc/hangui/PluginHangUIChild.h new file mode 100644 index 0000000000..d21c717666 --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUIChild.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginHangUIChild_h +#define mozilla_plugins_PluginHangUIChild_h + +#include "MiniShmChild.h" + +#include <string> + +#include <windows.h> + +namespace mozilla { +namespace plugins { + +/** + * This class implements the plugin-hang-ui. + * + * NOTE: PluginHangUIChild is *not* an IPDL actor! In this case, "Child" + * is describing the fact that plugin-hang-ui is a child process to the + * firefox process, which is the PluginHangUIParent. + * PluginHangUIParent and PluginHangUIChild are a matched pair. + * @see PluginHangUIParent + */ +class PluginHangUIChild : public MiniShmObserver { + public: + PluginHangUIChild(); + virtual ~PluginHangUIChild(); + + bool Init(int aArgc, wchar_t* aArgv[]); + + /** + * Displays the Plugin Hang UI and does not return until the UI has + * been dismissed. + * + * @return true if the UI was displayed and the user response was + * successfully sent back to the parent. Otherwise false. + */ + bool Show(); + + /** + * Causes the calling thread to wait either for the Hang UI to be + * dismissed or for the parent process to terminate. This should + * be called by the main thread. + * + * @return true unless there was an error initiating the wait + */ + bool WaitForDismissal(); + + virtual void OnMiniShmEvent(MiniShmBase* aMiniShmObj) override; + + private: + bool RecvShow(); + + bool RecvCancel(); + + bool SetMainThread(); + + void ResizeButtons(); + + INT_PTR + HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, LPARAM aLParam); + + static VOID CALLBACK ShowAPC(ULONG_PTR aContext); + + static INT_PTR CALLBACK SHangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, + WPARAM aWParam, LPARAM aLParam); + + static VOID CALLBACK SOnParentProcessExit(PVOID aObject, BOOLEAN aIsTimer); + + static PluginHangUIChild* sSelf; + + const wchar_t* mMessageText; + const wchar_t* mWindowTitle; + const wchar_t* mWaitBtnText; + const wchar_t* mKillBtnText; + const wchar_t* mNoFutureText; + unsigned int mResponseBits; + HWND mParentWindow; + HWND mDlgHandle; + HANDLE mMainThread; + HANDLE mParentProcess; + HANDLE mRegWaitProcess; + DWORD mIPCTimeoutMs; + MiniShmChild mMiniShm; + + static const int kExpectedMinimumArgc; + + typedef HRESULT(WINAPI* SETAPPUSERMODELID)(PCWSTR); + + DISALLOW_COPY_AND_ASSIGN(PluginHangUIChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUIChild_h diff --git a/dom/plugins/ipc/hangui/module.ver b/dom/plugins/ipc/hangui/module.ver new file mode 100644 index 0000000000..d11506f4a1 --- /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 0000000000..db07f43d9b --- /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 + +DisableStlWrapping() + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + 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 0000000000..f5b7345f99 --- /dev/null +++ b/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<assemblyIdentity + version="1.0.0.0" + processorArchitecture="*" + name="plugin-hang-ui" + type="win32" +/> +<description>Firefox Plugin Hang User Interface</description> +<dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> +</dependency> + <ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3"> + <ms_asmv3:security> + <ms_asmv3:requestedPrivileges> + <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" /> + </ms_asmv3:requestedPrivileges> + </ms_asmv3:security> + </ms_asmv3:trustInfo> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + </application> + </compatibility> +</assembly> diff --git a/dom/plugins/ipc/interpose/moz.build b/dom/plugins/ipc/interpose/moz.build new file mode 100644 index 0000000000..ee24953570 --- /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 0000000000..7f7e0ea372 --- /dev/null +++ b/dom/plugins/ipc/interpose/plugin_child_interpose.mm @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// 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:: +// PerformAsyncLaunch()). For this reason it shouldn't link in other +// browser modules when loaded. Instead it should use dlsym() to load +// pointers to the methods it wants to call in other modules. + +#if !defined(__LP64__) + +# include <dlfcn.h> +# import <Carbon/Carbon.h> + +// The header file QuickdrawAPI.h is missing on OS X 10.7 and up (though the +// QuickDraw APIs defined in it are still present) -- so we need to supply the +// relevant parts of its contents here. It's likely that Apple will eventually +// remove the APIs themselves (probably in OS X 10.8), so we need to make them +// weak imports, and test for their presence before using them. +# if !defined(__QUICKDRAWAPI__) + +struct Cursor; +extern "C" void SetCursor(const Cursor* crsr) __attribute__((weak_import)); + +# endif /* __QUICKDRAWAPI__ */ + +BOOL (*OnSetThemeCursorPtr)(ThemeCursor) = NULL; +BOOL (*OnSetCursorPtr)(const Cursor*) = NULL; +BOOL (*OnHideCursorPtr)() = NULL; +BOOL (*OnShowCursorPtr)() = NULL; + +static BOOL loadXULPtrs() { + if (!OnSetThemeCursorPtr) { + // mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor) is in + // PluginInterposeOSX.mm + OnSetThemeCursorPtr = + (BOOL(*)(ThemeCursor))dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnSetThemeCursor"); + } + if (!OnSetCursorPtr) { + // mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor) is in + // PluginInterposeOSX.mm + OnSetCursorPtr = + (BOOL(*)(const Cursor*))dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnSetCursor"); + } + if (!OnHideCursorPtr) { + // mac_plugin_interposing_child_OnHideCursor() is in PluginInterposeOSX.mm + OnHideCursorPtr = (BOOL(*)())dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnHideCursor"); + } + if (!OnShowCursorPtr) { + // mac_plugin_interposing_child_OnShowCursor() is in PluginInterposeOSX.mm + OnShowCursorPtr = (BOOL(*)())dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnShowCursor"); + } + return (OnSetCursorPtr && OnSetThemeCursorPtr && OnHideCursorPtr && OnShowCursorPtr); +} + +static OSStatus MacPluginChildSetThemeCursor(ThemeCursor cursor) { + if (loadXULPtrs()) { + OnSetThemeCursorPtr(cursor); + } + return ::SetThemeCursor(cursor); +} + +static void MacPluginChildSetCursor(const Cursor* cursor) { + if (::SetCursor) { + if (loadXULPtrs()) { + OnSetCursorPtr(cursor); + } + ::SetCursor(cursor); + } +} + +static CGError MacPluginChildCGDisplayHideCursor(CGDirectDisplayID display) { + if (loadXULPtrs()) { + OnHideCursorPtr(); + } + return ::CGDisplayHideCursor(display); +} + +static CGError MacPluginChildCGDisplayShowCursor(CGDirectDisplayID display) { + if (loadXULPtrs()) { + OnShowCursorPtr(); + } + return ::CGDisplayShowCursor(display); +} + +# pragma mark - + +struct interpose_substitution { + const void* replacement; + const void* original; +}; + +# define INTERPOSE_FUNCTION(function) \ + { \ + reinterpret_cast<const void*>(MacPluginChild##function), \ + reinterpret_cast<const void*>(function) \ + } + +__attribute__((used)) static const interpose_substitution substitutions[] + __attribute__((section("__DATA, __interpose"))) = { + INTERPOSE_FUNCTION(SetThemeCursor), + INTERPOSE_FUNCTION(CGDisplayHideCursor), + INTERPOSE_FUNCTION(CGDisplayShowCursor), + // SetCursor() and other QuickDraw APIs will probably be removed in OS X + // 10.8. But this will make 'SetCursor' NULL, which will just stop the OS + // from interposing it (tested using an INTERPOSE_FUNCTION_BROKEN macro + // that just sets the second address of each tuple to NULL). + INTERPOSE_FUNCTION(SetCursor), +}; + +#endif // !__LP64__ diff --git a/dom/plugins/ipc/moz.build b/dom/plugins/ipc/moz.build new file mode 100644 index 0000000000..62a726e25c --- /dev/null +++ b/dom/plugins/ipc/moz.build @@ -0,0 +1,149 @@ +# -*- 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", + "ChildTimer.h", + "FunctionBrokerIPCUtils.h", + "IpdlTuple.h", + "NPEventAndroid.h", + "NPEventOSX.h", + "NPEventUnix.h", + "NPEventWindows.h", + "PluginBridge.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", + "PluginUtilsOSX.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", + "ChildTimer.cpp", + "FunctionBroker.cpp", + "FunctionBrokerChild.cpp", + "FunctionBrokerIPCUtils.cpp", + "FunctionBrokerParent.cpp", + "FunctionHook.cpp", + "PluginBackgroundDestroyer.cpp", + "PluginInstanceParent.cpp", + "PluginMessageUtils.cpp", + "PluginModuleChild.cpp", + "PluginModuleParent.cpp", + "PluginProcessChild.cpp", + "PluginProcessParent.cpp", + "PluginQuirks.cpp", + "PluginScriptableObjectChild.cpp", + "PluginScriptableObjectParent.cpp", +] + +SOURCES += [ + "PluginInstanceChild.cpp", # 'PluginThreadCallback' : ambiguous symbol +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "PluginInterposeOSX.mm", + "PluginUtilsOSX.mm", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + EXPORTS.mozilla.plugins += [ + "PluginWidgetChild.h", + "PluginWidgetParent.h", + ] + UNIFIED_SOURCES += ["D3D11SurfaceHolder.cpp", "PluginUtilsWin.cpp"] + SOURCES += [ + "PluginWidgetChild.cpp", + "PluginWidgetParent.cpp", + ] + +IPDL_SOURCES += [ + "PBrowserStream.ipdl", + "PFunctionBroker.ipdl", + "PluginTypes.ipdlh", + "PPluginBackgroundDestroyer.ipdl", + "PPluginInstance.ipdl", + "PPluginModule.ipdl", + "PPluginScriptableObject.ipdl", + "PPluginSurface.ipdl", + "PStreamNotify.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") + +FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + "../base", + "/xpcom/base/", + "/xpcom/threads/", +] + +if CONFIG["MOZ_SANDBOX"] and CONFIG["OS_ARCH"] == "WINNT": + LOCAL_INCLUDES += [ + "/security/sandbox/chromium", + "/security/sandbox/chromium-shim", + "/security/sandbox/win/src/sandboxpermissions", + ] + +DEFINES["FORCE_PR_LOG"] = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] != "gtk": + 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["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] |