gecko/dom/plugins/PluginInstanceChild.cpp

1367 lines
41 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: sw=4 ts=4 et :
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Plugin App.
*
* The Initial Developer of the Original Code is
* Chris Jones <jones.chris.g@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jim Mathies <jmathies@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "PluginInstanceChild.h"
#include "PluginModuleChild.h"
#include "BrowserStreamChild.h"
#include "PluginStreamChild.h"
#include "StreamNotifyChild.h"
#include "mozilla/ipc/SyncChannel.h"
using namespace mozilla::plugins;
#ifdef MOZ_WIDGET_GTK2
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdk.h>
#include "gtk2xtbin.h"
#elif defined(OS_WIN)
using mozilla::gfx::SharedDIB;
#include <windows.h>
#define NS_OOPP_DOUBLEPASS_MSGID TEXT("MozDoublePassMsg")
// During nested ui loops, parent is processing windows events via spin loop,
// which results in rpc in-calls to child. If child falls behind in processing
// these, an ugly stall condition occurs. To ensure child stays in sync, we use
// a timer callback to schedule work on in-calls.
#define CHILD_MODALPUMPTIMEOUT 50
#define CHILD_MODALLOOPTIMER 654321
#endif // defined(OS_WIN)
PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface) :
mPluginIface(aPluginIface)
#if defined(OS_WIN)
, mPluginWindowHWND(0)
, mPluginWndProc(0)
, mPluginParentHWND(0)
, mNestedEventHook(0)
, mNestedPumpHook(0)
, mNestedEventLevelDepth(0)
, mNestedEventState(false)
, mCachedWinlessPluginHWND(0)
, mEventPumpTimer(0)
#endif // OS_WIN
{
memset(&mWindow, 0, sizeof(mWindow));
mData.ndata = (void*) this;
#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
mWindow.ws_info = &mWsInfo;
memset(&mWsInfo, 0, sizeof(mWsInfo));
#ifdef MOZ_WIDGET_GTK2
mWsInfo.display = GDK_DISPLAY();
#endif // MOZ_WIDGET_GTK2
#endif // MOZ_X11 && XP_UNIX && !XP_MACOSX
#if defined(OS_WIN)
memset(&mAlphaExtract, 0, sizeof(mAlphaExtract));
mAlphaExtract.doublePassEvent = ::RegisterWindowMessage(NS_OOPP_DOUBLEPASS_MSGID);
#endif // OS_WIN
}
PluginInstanceChild::~PluginInstanceChild()
{
#if defined(OS_WIN)
DestroyPluginWindow();
#endif
}
NPError
PluginInstanceChild::NPN_GetValue(NPNVariable aVar,
void* aValue)
{
PLUGIN_LOG_DEBUG(("%s (aVar=%i)", FULLFUNCTION, (int) aVar));
AssertPluginThread();
switch(aVar) {
case NPNVSupportsWindowless:
#if defined(OS_LINUX) || defined(OS_WIN)
*((NPBool*)aValue) = true;
#else
*((NPBool*)aValue) = false;
#endif
return NPERR_NO_ERROR;
#if defined(OS_LINUX)
case NPNVSupportsXEmbedBool:
*((NPBool*)aValue) = true;
return NPERR_NO_ERROR;
case NPNVToolkit:
*((NPNToolkitType*)aValue) = NPNVGtk2;
return NPERR_NO_ERROR;
#elif defined(OS_WIN)
case NPNVToolkit:
return NPERR_GENERIC_ERROR;
#endif
case NPNVjavascriptEnabledBool: {
bool v = false;
NPError result;
if (!CallNPN_GetValue_NPNVjavascriptEnabledBool(&v, &result)) {
return NPERR_GENERIC_ERROR;
}
*static_cast<NPBool*>(aValue) = v;
return result;
}
case NPNVisOfflineBool: {
bool v = false;
NPError result;
if (!CallNPN_GetValue_NPNVisOfflineBool(&v, &result)) {
return NPERR_GENERIC_ERROR;
}
*static_cast<NPBool*>(aValue) = v;
return result;
}
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 NPNVWindowNPObject: {
PPluginScriptableObjectChild* actor;
NPError result;
if (!CallNPN_GetValue_NPNVWindowNPObject(&actor, &result)) {
NS_WARNING("Failed to send message!");
return NPERR_GENERIC_ERROR;
}
if (result != NPERR_NO_ERROR) {
return result;
}
NS_ASSERTION(actor, "Null actor!");
NPObject* object =
static_cast<PluginScriptableObjectChild*>(actor)->GetObject(true);
NS_ASSERTION(object, "Null object?!");
PluginModuleChild::sBrowserFuncs.retainobject(object);
*((NPObject**)aValue) = object;
return NPERR_NO_ERROR;
}
case NPNVPluginElementNPObject: {
PPluginScriptableObjectChild* actor;
NPError result;
if (!CallNPN_GetValue_NPNVPluginElementNPObject(&actor, &result)) {
NS_WARNING("Failed to send message!");
return NPERR_GENERIC_ERROR;
}
if (result != NPERR_NO_ERROR) {
return result;
}
NS_ASSERTION(actor, "Null actor!");
NPObject* object =
static_cast<PluginScriptableObjectChild*>(actor)->GetObject(true);
NS_ASSERTION(object, "Null object?!");
PluginModuleChild::sBrowserFuncs.retainobject(object);
*((NPObject**)aValue) = object;
return NPERR_NO_ERROR;
}
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
}
default:
PR_LOG(gPluginLog, PR_LOG_WARNING,
("In PluginInstanceChild::NPN_GetValue: Unhandled NPNVariable %i (%s)",
(int) aVar, NPNVariableToString(aVar)));
return NPERR_GENERIC_ERROR;
}
}
NPError
PluginInstanceChild::NPN_SetValue(NPPVariable aVar, void* aValue)
{
PR_LOG(gPluginLog, PR_LOG_DEBUG, ("%s (aVar=%i, aValue=%p)",
FULLFUNCTION, (int) aVar, aValue));
AssertPluginThread();
switch (aVar) {
case NPPVpluginWindowBool: {
NPError rv;
bool windowed = (NPBool) (intptr_t) aValue;
if (!CallNPN_SetValue_NPPVpluginWindow(windowed, &rv))
return NPERR_GENERIC_ERROR;
return rv;
}
case NPPVpluginTransparentBool: {
NPError rv;
bool transparent = (NPBool) (intptr_t) aValue;
if (!CallNPN_SetValue_NPPVpluginTransparent(transparent, &rv))
return NPERR_GENERIC_ERROR;
return rv;
}
default:
PR_LOG(gPluginLog, PR_LOG_WARNING,
("In PluginInstanceChild::NPN_SetValue: Unhandled NPPVariable %i (%s)",
(int) aVar, NPPVariableToString(aVar)));
return NPERR_GENERIC_ERROR;
}
}
bool
PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginWindow(
bool* windowed, NPError* rv)
{
AssertPluginThread();
NPBool isWindowed;
*rv = mPluginIface->getvalue(GetNPP(), NPPVpluginWindowBool,
reinterpret_cast<void*>(&isWindowed));
*windowed = isWindowed;
return true;
}
bool
PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginTransparent(
bool* transparent, NPError* rv)
{
AssertPluginThread();
NPBool isTransparent;
*rv = mPluginIface->getvalue(GetNPP(), NPPVpluginTransparentBool,
reinterpret_cast<void*>(&isTransparent));
*transparent = isTransparent;
return true;
}
bool
PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginNeedsXEmbed(
bool* needs, NPError* rv)
{
AssertPluginThread();
#ifdef OS_LINUX
// on x86/32-bit, flash stores to this using |movl 0x1,&needsXEmbed|.
// thus we can't NPBool for needsXEmbed, or the three bytes above
// it on the stack would get clobbered. so protect with unsigned
// long.
unsigned long needsXEmbed = 0;
*rv = mPluginIface->getvalue(GetNPP(), NPPVpluginNeedsXEmbed,
reinterpret_cast<void*>(&needsXEmbed));
*needs = needsXEmbed;
return true;
#else
NS_RUNTIMEABORT("shouldn't be called on non-linux platforms");
return false; // not reached
#endif
}
bool
PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginScriptableNPObject(
PPluginScriptableObjectChild** aValue,
NPError* aResult)
{
AssertPluginThread();
NPObject* object;
NPError result = mPluginIface->getvalue(GetNPP(),
NPPVpluginScriptableNPObject,
&object);
if (result == NPERR_NO_ERROR && object) {
PluginScriptableObjectChild* actor = GetActorForNPObject(object);
// If we get an actor then it has retained. Otherwise we don't need it
// any longer.
PluginModuleChild::sBrowserFuncs.releaseobject(object);
if (actor) {
*aValue = actor;
*aResult = NPERR_NO_ERROR;
return true;
}
NS_ERROR("Failed to get actor!");
result = NPERR_GENERIC_ERROR;
}
*aValue = nsnull;
*aResult = result;
return true;
}
bool
PluginInstanceChild::AnswerNPP_SetValue_NPNVprivateModeBool(const bool& value,
NPError* result)
{
if (!mPluginIface->setvalue) {
*result = NPERR_GENERIC_ERROR;
return true;
}
// Use `long` instead of NPBool because Flash and other plugins read
// this as a word-size value instead of the 1-byte NPBool that it is.
long v = value;
*result = mPluginIface->setvalue(GetNPP(), NPNVprivateModeBool, &v);
return true;
}
bool
PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event,
int16_t* handled)
{
PLUGIN_LOG_DEBUG_FUNCTION;
AssertPluginThread();
#if defined(OS_LINUX) && defined(DEBUG)
if (GraphicsExpose == event.event.type)
PLUGIN_LOG_DEBUG((" received drawable 0x%lx\n",
event.event.xgraphicsexpose.drawable));
#endif
// Make a copy since we may modify values.
NPEvent evcopy = event.event;
#ifdef OS_WIN
// Painting for win32. SharedSurfacePaint handles everything.
if (mWindow.type == NPWindowTypeDrawable) {
if (evcopy.event == WM_PAINT) {
*handled = SharedSurfacePaint(evcopy);
return true;
}
else if (evcopy.event == mAlphaExtract.doublePassEvent) {
// We'll render to mSharedSurfaceDib first, then render to a cached bitmap
// we store locally. The two passes are for alpha extraction, so the second
// pass must be to a flat white surface in order for things to work.
mAlphaExtract.doublePass = RENDER_BACK_ONE;
*handled = true;
return true;
}
}
*handled = WinlessHandleEvent(evcopy);
return true;
#endif
*handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy));
#ifdef MOZ_X11
if (GraphicsExpose == event.event.type) {
// Make sure the X server completes the drawing before the parent
// draws on top and destroys the Drawable.
//
// XSync() waits for the X server to complete. Really this child
// process does not need to wait; the parent is the process that needs
// to wait. A possibly-slightly-better alternative would be to send
// an X event to the parent that the parent would wait for.
XSync(mWsInfo.display, False);
}
#endif
return true;
}
#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
static bool
XVisualIDToInfo(Display* aDisplay, VisualID aVisualID,
Visual** aVisual, unsigned int* aDepth)
{
if (aVisualID == None) {
*aVisual = NULL;
*aDepth = 0;
return true;
}
const Screen* screen = DefaultScreenOfDisplay(aDisplay);
for (int d = 0; d < screen->ndepths; d++) {
Depth *d_info = &screen->depths[d];
for (int v = 0; v < d_info->nvisuals; v++) {
Visual* visual = &d_info->visuals[v];
if (visual->visualid == aVisualID) {
*aVisual = visual;
*aDepth = d_info->depth;
return true;
}
}
}
NS_ERROR("VisualID not on Screen.");
return false;
}
#endif
bool
PluginInstanceChild::AnswerNPP_SetWindow(const NPRemoteWindow& aWindow,
NPError* rv)
{
PLUGIN_LOG_DEBUG(("%s (aWindow=<window: 0x%lx, x: %d, y: %d, width: %d, height: %d>)",
FULLFUNCTION,
aWindow.window,
aWindow.x, aWindow.y,
aWindow.width, aWindow.height));
AssertPluginThread();
#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
// The minimum info is sent over IPC to allow this
// code to determine the rest.
mWindow.window = reinterpret_cast<void*>(aWindow.window);
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;
if (!XVisualIDToInfo(mWsInfo.display, aWindow.visualID,
&mWsInfo.visual, &mWsInfo.depth))
return false;
if (aWindow.type == NPWindowTypeWindow) {
#ifdef MOZ_WIDGET_GTK2
if (GdkWindow* socket_window = gdk_window_lookup(aWindow.window)) {
// A GdkWindow for the socket already exists. Need to
// workaround https://bugzilla.gnome.org/show_bug.cgi?id=607061
// See wrap_gtk_plug_embedded in PluginModuleChild.cpp.
g_object_set_data(G_OBJECT(socket_window),
"moz-existed-before-set-window",
GUINT_TO_POINTER(1));
}
#endif
}
*rv = mPluginIface->setwindow(&mData, &mWindow);
#elif defined(OS_WIN)
switch (aWindow.type) {
case NPWindowTypeWindow:
{
if (!CreatePluginWindow())
return false;
ReparentPluginWindow((HWND)aWindow.window);
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;
*rv = mPluginIface->setwindow(&mData, &mWindow);
if (*rv == NPERR_NO_ERROR) {
WNDPROC wndProc = reinterpret_cast<WNDPROC>(
GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC));
if (wndProc != PluginWindowProc) {
mPluginWndProc = reinterpret_cast<WNDPROC>(
SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC,
reinterpret_cast<LONG>(PluginWindowProc)));
}
}
}
break;
case NPWindowTypeDrawable:
return SharedSurfaceSetWindow(aWindow, rv);
break;
default:
NS_NOTREACHED("Bad plugin window type.");
return false;
break;
}
#elif defined(OS_MACOSX)
# warning This is only a stub implementation IMPLEMENT ME
#else
# error Implement me for your OS
#endif
return true;
}
bool
PluginInstanceChild::Initialize()
{
return true;
}
#if defined(OS_WIN)
static const TCHAR kWindowClassName[] = TEXT("GeckoPluginWindow");
static const TCHAR kPluginInstanceChildProperty[] = TEXT("PluginInstanceChildProperty");
// 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(NULL);
wcex.hIcon = 0;
wcex.hCursor = 0;
wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
wcex.lpszMenuName = 0;
wcex.lpszClassName = kWindowClassName;
wcex.hIconSm = 0;
return RegisterClassEx(&wcex) ? true : false;
}
bool
PluginInstanceChild::CreatePluginWindow()
{
// already initialized
if (mPluginWindowHWND)
return true;
if (!RegisterWindowClass())
return false;
mPluginWindowHWND =
CreateWindowEx(WS_EX_LEFT | WS_EX_LTRREADING |
WS_EX_NOPARENTNOTIFY | // XXXbent Get rid of this!
WS_EX_RIGHTSCROLLBAR,
kWindowClassName, 0,
WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0,
0, 0, NULL, 0, GetModuleHandle(NULL), 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>(DefWindowProcA));
return true;
}
void
PluginInstanceChild::DestroyPluginWindow()
{
if (mPluginWindowHWND) {
// Unsubclass the window.
WNDPROC wndProc = reinterpret_cast<WNDPROC>(
GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC));
if (wndProc == PluginWindowProc) {
NS_ASSERTION(mPluginWndProc, "Should have old proc here!");
SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC,
reinterpret_cast<LONG>(mPluginWndProc));
mPluginWndProc = 0;
}
RemoveProp(mPluginWindowHWND, kPluginInstanceChildProperty);
DestroyWindow(mPluginWindowHWND);
mPluginWindowHWND = 0;
}
}
void
PluginInstanceChild::ReparentPluginWindow(HWND hWndParent)
{
if (hWndParent != mPluginParentHWND && IsWindow(hWndParent)) {
// Fix the child window's style to be a child window.
LONG style = GetWindowLongPtr(mPluginWindowHWND, GWL_STYLE);
style |= WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
style &= ~WS_POPUP;
SetWindowLongPtr(mPluginWindowHWND, GWL_STYLE, style);
// Do the reparenting.
SetParent(mPluginWindowHWND, hWndParent);
// Make sure we're visible.
ShowWindow(mPluginWindowHWND, SW_SHOWNA);
}
mPluginParentHWND = hWndParent;
}
void
PluginInstanceChild::SizePluginWindow(int width,
int height)
{
if (mPluginWindowHWND) {
SetWindowPos(mPluginWindowHWND, NULL, 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)
{
NS_ASSERTION(!mozilla::ipc::SyncChannel::IsPumpingMessages(),
"Failed to prevent a nonqueued message from running!");
PluginInstanceChild* self = reinterpret_cast<PluginInstanceChild*>(
GetProp(hWnd, kPluginInstanceChildProperty));
if (!self) {
NS_NOTREACHED("Badness!");
return 0;
}
NS_ASSERTION(self->mPluginWindowHWND == hWnd, "Wrong window!");
// The plugin received keyboard focus, let the parent know so the dom is up to date.
if (message == WM_MOUSEACTIVATE)
self->CallPluginGotFocus();
// Prevent lockups due to plugins making rpc calls when the parent
// is making a synchronous SetFocus api call. (bug 541362) Add more
// windowing events as needed for other api.
if (message == WM_KILLFOCUS &&
((InSendMessageEx(NULL) & (ISMEX_REPLIED|ISMEX_SEND)) == ISMEX_SEND)) {
ReplyMessage(0); // Unblock the caller
}
LRESULT res = CallWindowProc(self->mPluginWndProc, hWnd, message, wParam,
lParam);
if (message == WM_CLOSE)
self->DestroyPluginWindow();
if (message == WM_NCDESTROY)
RemoveProp(hWnd, kPluginInstanceChildProperty);
return res;
}
/* winless modal ui loop logic */
static bool
IsUserInputEvent(UINT msg)
{
// Events we assume some sort of modal ui *might* be generated.
switch (msg) {
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_CONTEXTMENU:
return true;
}
return false;
}
VOID CALLBACK
PluginInstanceChild::PumpTimerProc(HWND hwnd,
UINT uMsg,
UINT_PTR idEvent,
DWORD dwTime)
{
MessageLoop::current()->ScheduleWork();
}
LRESULT CALLBACK
PluginInstanceChild::NestedInputPumpHook(int nCode,
WPARAM wParam,
LPARAM lParam)
{
if (nCode >= 0) {
MessageLoop::current()->ScheduleWork();
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
// gTempChildPointer is only in use from the time we enter handle event, to the
// point where ui might be created by that call. If ui isn't created, there's
// no issue. If ui is created, the parent can't start processing messages in
// spin loop until InternalCallSetNestedEventState is set, at which point,
// gTempChildPointer is no longer needed.
static PluginInstanceChild* gTempChildPointer;
LRESULT CALLBACK
PluginInstanceChild::NestedInputEventHook(int nCode,
WPARAM wParam,
LPARAM lParam)
{
if (!gTempChildPointer) {
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
if (nCode >= 0) {
NS_ASSERTION(gTempChildPointer, "Never should be null here!");
gTempChildPointer->ResetNestedEventHook();
gTempChildPointer->SetNestedInputPumpHook();
gTempChildPointer->InternalCallSetNestedEventState(true);
gTempChildPointer = NULL;
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
void
PluginInstanceChild::SetNestedInputPumpHook()
{
NS_ASSERTION(!mNestedPumpHook,
"mNestedPumpHook already setup in call to SetNestedInputPumpHook?");
PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
mNestedPumpHook = SetWindowsHookEx(WH_CALLWNDPROC,
NestedInputPumpHook,
NULL,
GetCurrentThreadId());
mEventPumpTimer =
SetTimer(NULL,
CHILD_MODALLOOPTIMER,
CHILD_MODALPUMPTIMEOUT,
PumpTimerProc);
}
void
PluginInstanceChild::ResetPumpHooks()
{
if (mNestedPumpHook)
UnhookWindowsHookEx(mNestedPumpHook);
mNestedPumpHook = NULL;
if (mEventPumpTimer)
KillTimer(NULL, mEventPumpTimer);
}
void
PluginInstanceChild::SetNestedInputEventHook()
{
NS_ASSERTION(!mNestedEventHook,
"mNestedEventHook already setup in call to SetNestedInputEventHook?");
PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
// WH_GETMESSAGE hooks are triggered by peek message calls in parent due to
// attached message queues, resulting in stomped in-process ipc calls. So
// we use a filter hook specific to dialogs, menus, and scroll bars to kick
// things off.
mNestedEventHook = SetWindowsHookEx(WH_MSGFILTER,
NestedInputEventHook,
NULL,
GetCurrentThreadId());
}
void
PluginInstanceChild::ResetNestedEventHook()
{
PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
if (mNestedEventHook)
UnhookWindowsHookEx(mNestedEventHook);
mNestedEventHook = NULL;
}
void
PluginInstanceChild::InternalCallSetNestedEventState(bool aState)
{
if (aState != mNestedEventState) {
PLUGIN_LOG_DEBUG(
("PluginInstanceChild::InternalCallSetNestedEventState(%i)",
(int)aState));
mNestedEventState = aState;
SendSetNestedEventState(mNestedEventState);
}
}
int16_t
PluginInstanceChild::WinlessHandleEvent(NPEvent& event)
{
if (!IsUserInputEvent(event.event)) {
return mPluginIface->event(&mData, reinterpret_cast<void*>(&event));
}
int16_t handled;
mNestedEventLevelDepth++;
PLUGIN_LOG_DEBUG(("WinlessHandleEvent start depth: %i", mNestedEventLevelDepth));
// On the first, non-reentrant call, setup our modal ui detection hook.
if (mNestedEventLevelDepth == 1) {
NS_ASSERTION(!gTempChildPointer, "valid gTempChildPointer here?");
gTempChildPointer = this;
SetNestedInputEventHook();
}
bool old_state = MessageLoop::current()->NestableTasksAllowed();
MessageLoop::current()->SetNestableTasksAllowed(true);
handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&event));
MessageLoop::current()->SetNestableTasksAllowed(old_state);
gTempChildPointer = NULL;
mNestedEventLevelDepth--;
PLUGIN_LOG_DEBUG(("WinlessHandleEvent end depth: %i", mNestedEventLevelDepth));
NS_ASSERTION(!(mNestedEventLevelDepth < 0), "mNestedEventLevelDepth < 0?");
if (mNestedEventLevelDepth <= 0) {
ResetNestedEventHook();
ResetPumpHooks();
InternalCallSetNestedEventState(false);
}
return handled;
}
/* windowless drawing helpers */
bool
PluginInstanceChild::SharedSurfaceSetWindow(const NPRemoteWindow& aWindow,
NPError* rv)
{
// If the surfaceHandle is empty, parent is telling us we can reuse our cached
// memory surface and hdc. Otherwise, we need to reset, usually due to a
// expanding plugin port size.
if (!aWindow.surfaceHandle) {
if (!mSharedSurfaceDib.IsValid()) {
return false;
}
}
else {
// Attach to the new shared surface parent handed us.
if (NS_FAILED(mSharedSurfaceDib.Attach((SharedDIB::Handle)aWindow.surfaceHandle,
aWindow.width, aWindow.height, 32)))
return false;
// Free any alpha extraction resources if needed. This will be reset
// the next time it's used.
AlphaExtractCacheRelease();
}
// NPRemoteWindow's origin is the origin of our shared dib.
mWindow.x = 0;
mWindow.y = 0;
mWindow.width = aWindow.width;
mWindow.height = aWindow.height;
mWindow.type = aWindow.type;
mWindow.window = reinterpret_cast<void*>(mSharedSurfaceDib.GetHDC());
*rv = mPluginIface->setwindow(&mData, &mWindow);
return true;
}
void
PluginInstanceChild::SharedSurfaceRelease()
{
mSharedSurfaceDib.Close();
AlphaExtractCacheRelease();
}
/* double pass cache buffer - (rarely) used in cases where alpha extraction
* occurs for windowless plugins. */
bool
PluginInstanceChild::AlphaExtractCacheSetup()
{
AlphaExtractCacheRelease();
mAlphaExtract.hdc = ::CreateCompatibleDC(NULL);
if (!mAlphaExtract.hdc)
return false;
BITMAPINFOHEADER bmih;
memset((void*)&bmih, 0, sizeof(BITMAPINFOHEADER));
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biWidth = mWindow.width;
bmih.biHeight = mWindow.height;
bmih.biPlanes = 1;
bmih.biBitCount = 32;
bmih.biCompression = BI_RGB;
void* ppvBits = nsnull;
mAlphaExtract.bmp = ::CreateDIBSection(mAlphaExtract.hdc,
(BITMAPINFO*)&bmih,
DIB_RGB_COLORS,
(void**)&ppvBits,
NULL,
(unsigned long)sizeof(BITMAPINFOHEADER));
if (!mAlphaExtract.bmp)
return false;
DeleteObject(::SelectObject(mAlphaExtract.hdc, mAlphaExtract.bmp));
return true;
}
void
PluginInstanceChild::AlphaExtractCacheRelease()
{
if (mAlphaExtract.bmp)
::DeleteObject(mAlphaExtract.bmp);
if (mAlphaExtract.hdc)
::DeleteObject(mAlphaExtract.hdc);
mAlphaExtract.bmp = NULL;
mAlphaExtract.hdc = NULL;
}
void
PluginInstanceChild::UpdatePaintClipRect(RECT* aRect)
{
if (aRect) {
// Update the clip rect on our internal hdc
HRGN clip = ::CreateRectRgnIndirect(aRect);
::SelectClipRgn(mSharedSurfaceDib.GetHDC(), clip);
::DeleteObject(clip);
}
}
int16_t
PluginInstanceChild::SharedSurfacePaint(NPEvent& evcopy)
{
RECT* pRect = reinterpret_cast<RECT*>(evcopy.lParam);
switch(mAlphaExtract.doublePass) {
case RENDER_NATIVE:
// pass the internal hdc to the plugin
UpdatePaintClipRect(pRect);
evcopy.wParam = WPARAM(mSharedSurfaceDib.GetHDC());
return mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy));
break;
case RENDER_BACK_ONE:
// Handle a double pass render used in alpha extraction for transparent
// plugins. (See nsObjectFrame and gfxWindowsNativeDrawing for details.)
// We render twice, once to the shared dib, and once to a cache which
// we copy back on a second paint. These paints can't be spread across
// multiple rpc messages as delays cause animation frame changes.
if (!mAlphaExtract.bmp && !AlphaExtractCacheSetup()) {
mAlphaExtract.doublePass = RENDER_NATIVE;
return false;
}
// See gfxWindowsNativeDrawing, color order doesn't have to match.
::FillRect(mSharedSurfaceDib.GetHDC(), pRect, (HBRUSH)GetStockObject(WHITE_BRUSH));
UpdatePaintClipRect(pRect);
evcopy.wParam = WPARAM(mSharedSurfaceDib.GetHDC());
if (!mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy))) {
mAlphaExtract.doublePass = RENDER_NATIVE;
return false;
}
// Copy to cache. We render to shared dib so we don't have to call
// setwindow between calls (flash issue).
::BitBlt(mAlphaExtract.hdc,
pRect->left,
pRect->top,
pRect->right - pRect->left,
pRect->bottom - pRect->top,
mSharedSurfaceDib.GetHDC(),
pRect->left,
pRect->top,
SRCCOPY);
::FillRect(mSharedSurfaceDib.GetHDC(), pRect, (HBRUSH)GetStockObject(BLACK_BRUSH));
if (!mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy))) {
mAlphaExtract.doublePass = RENDER_NATIVE;
return false;
}
mAlphaExtract.doublePass = RENDER_BACK_TWO;
return true;
break;
case RENDER_BACK_TWO:
// copy our cached surface back
::BitBlt(mSharedSurfaceDib.GetHDC(),
pRect->left,
pRect->top,
pRect->right - pRect->left,
pRect->bottom - pRect->top,
mAlphaExtract.hdc,
pRect->left,
pRect->top,
SRCCOPY);
mAlphaExtract.doublePass = RENDER_NATIVE;
return true;
break;
}
return false;
}
#endif // OS_WIN
bool
PluginInstanceChild::AnswerSetPluginFocus()
{
PR_LOG(gPluginLog, PR_LOG_DEBUG, ("%s", FULLFUNCTION));
#if defined(OS_WIN)
// Parent is letting us know something set focus to the plugin.
if (::GetFocus() == mPluginWindowHWND)
return true;
::SetFocus(mPluginWindowHWND);
return true;
#else
NS_NOTREACHED("PluginInstanceChild::AnswerSetPluginFocus not implemented!");
return false;
#endif
}
bool
PluginInstanceChild::AnswerUpdateWindow()
{
PR_LOG(gPluginLog, PR_LOG_DEBUG, ("%s", FULLFUNCTION));
#if defined(OS_WIN)
if (mPluginWindowHWND)
UpdateWindow(mPluginWindowHWND);
return true;
#else
NS_NOTREACHED("PluginInstanceChild::AnswerUpdateWindow not implemented!");
return false;
#endif
}
PPluginScriptableObjectChild*
PluginInstanceChild::AllocPPluginScriptableObject()
{
AssertPluginThread();
return new PluginScriptableObjectChild(Proxy);
}
bool
PluginInstanceChild::DeallocPPluginScriptableObject(
PPluginScriptableObjectChild* aObject)
{
AssertPluginThread();
delete aObject;
return true;
}
bool
PluginInstanceChild::AnswerPPluginScriptableObjectConstructor(
PPluginScriptableObjectChild* aActor)
{
AssertPluginThread();
// This is only called in response to the parent process requesting the
// creation of an actor. This actor will represent an NPObject that is
// created by the browser and returned to the plugin.
PluginScriptableObjectChild* actor =
static_cast<PluginScriptableObjectChild*>(aActor);
NS_ASSERTION(!actor->GetObject(false), "Actor already has an object?!");
actor->InitializeProxy();
NS_ASSERTION(actor->GetObject(false), "Actor should have an object!");
return true;
}
bool
PluginInstanceChild::AnswerPBrowserStreamConstructor(
PBrowserStreamChild* aActor,
const nsCString& url,
const uint32_t& length,
const uint32_t& lastmodified,
PStreamNotifyChild* notifyData,
const nsCString& headers,
const nsCString& mimeType,
const bool& seekable,
NPError* rv,
uint16_t* stype)
{
AssertPluginThread();
*rv = static_cast<BrowserStreamChild*>(aActor)
->StreamConstructed(url, length, lastmodified,
notifyData, headers, mimeType, seekable,
stype);
return true;
}
PBrowserStreamChild*
PluginInstanceChild::AllocPBrowserStream(const nsCString& url,
const uint32_t& length,
const uint32_t& lastmodified,
PStreamNotifyChild* notifyData,
const nsCString& headers,
const nsCString& mimeType,
const bool& seekable,
NPError* rv,
uint16_t *stype)
{
AssertPluginThread();
return new BrowserStreamChild(this, url, length, lastmodified, notifyData,
headers, mimeType, seekable, rv, stype);
}
bool
PluginInstanceChild::DeallocPBrowserStream(PBrowserStreamChild* stream)
{
AssertPluginThread();
delete stream;
return true;
}
PPluginStreamChild*
PluginInstanceChild::AllocPPluginStream(const nsCString& mimeType,
const nsCString& target,
NPError* result)
{
NS_RUNTIMEABORT("not callable");
return NULL;
}
bool
PluginInstanceChild::DeallocPPluginStream(PPluginStreamChild* stream)
{
AssertPluginThread();
delete stream;
return true;
}
PStreamNotifyChild*
PluginInstanceChild::AllocPStreamNotify(const nsCString& url,
const nsCString& target,
const bool& post,
const nsCString& buffer,
const bool& file,
NPError* result)
{
AssertPluginThread();
NS_RUNTIMEABORT("not reached");
return NULL;
}
bool
StreamNotifyChild::Answer__delete__(const NPReason& reason)
{
AssertPluginThread();
return static_cast<PluginInstanceChild*>(Manager())
->NotifyStream(this, reason);
}
bool
PluginInstanceChild::NotifyStream(StreamNotifyChild* notifyData,
NPReason reason)
{
if (notifyData->mClosure)
mPluginIface->urlnotify(&mData, notifyData->mURL.get(), reason,
notifyData->mClosure);
return true;
}
bool
PluginInstanceChild::DeallocPStreamNotify(PStreamNotifyChild* notifyData)
{
AssertPluginThread();
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 =
PluginModuleChild::current()->GetActorForNPObject(aObject);
if (actor) {
// Plugin-provided object that we've previously wrapped.
return actor;
}
actor = new PluginScriptableObjectChild(LocalObject);
if (!CallPPluginScriptableObjectConstructor(actor)) {
NS_ERROR("Failed to send constructor message!");
return nsnull;
}
actor->InitializeLocal(aObject);
return actor;
}
NPError
PluginInstanceChild::NPN_NewStream(NPMIMEType aMIMEType, const char* aWindow,
NPStream** aStream)
{
AssertPluginThread();
PluginStreamChild* ps = new PluginStreamChild();
NPError result;
CallPPluginStreamConstructor(ps, nsDependentCString(aMIMEType),
NullableString(aWindow), &result);
if (NPERR_NO_ERROR != result) {
*aStream = NULL;
PPluginStreamChild::Call__delete__(ps, NPERR_GENERIC_ERROR, true);
return result;
}
*aStream = &ps->mStream;
return NPERR_NO_ERROR;
}
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
SendNPN_InvalidateRect(*aInvalidRect);
}
uint32_t
PluginInstanceChild::ScheduleTimer(uint32_t interval, bool repeat,
TimerFunc func)
{
ChildTimer* t = new ChildTimer(this, interval, repeat, func);
if (0 == t->ID()) {
delete t;
return 0;
}
mTimers.AppendElement(t);
return t->ID();
}
void
PluginInstanceChild::UnscheduleTimer(uint32_t id)
{
if (0 == id)
return;
mTimers.RemoveElement(id, ChildTimer::IDComparator());
}
bool
PluginInstanceChild::AnswerNPP_Destroy(NPError* aResult)
{
for (PRUint32 i = 0; i < mPendingAsyncCalls.Length(); ++i)
mPendingAsyncCalls[i]->Cancel();
mPendingAsyncCalls.TruncateLength(0);
mTimers.Clear();
PluginModuleChild* module = PluginModuleChild::current();
bool retval = module->PluginInstanceDestroyed(this, aResult);
#if defined(OS_WIN)
SharedSurfaceRelease();
ResetNestedEventHook();
ResetPumpHooks();
#endif
return retval;
}