diff options
Diffstat (limited to 'dom/plugins')
253 files changed, 59186 insertions, 0 deletions
diff --git a/dom/plugins/base/components.conf b/dom/plugins/base/components.conf new file mode 100644 index 0000000000..4b90c78f44 --- /dev/null +++ b/dom/plugins/base/components.conf @@ -0,0 +1,16 @@ +# -*- 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/. + +Classes = [ + { + 'cid': '{23e8fd98-a625-4b08-be1a-f7cc18a5b106}', + 'contract_ids': ['@mozilla.org/plugin/host;1'], + 'singleton': True, + 'type': 'nsPluginHost', + 'headers': ['nsPluginHost.h'], + 'constructor': 'nsPluginHost::GetInst', + }, +] diff --git a/dom/plugins/base/moz.build b/dom/plugins/base/moz.build new file mode 100644 index 0000000000..e1ee17fc5c --- /dev/null +++ b/dom/plugins/base/moz.build @@ -0,0 +1,95 @@ +# -*- 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/. + +XPIDL_SOURCES += [ + "nsIHTTPHeaderListener.idl", + "nsIPluginDocument.idl", + "nsIPluginHost.idl", + "nsIPluginInputStream.idl", + "nsIPluginInstanceOwner.idl", + "nsIPluginTag.idl", + "nspluginroot.idl", +] + +XPIDL_MODULE = "plugin" + +EXPORTS += [ + "npapi.h", + "npfunctions.h", + "npruntime.h", + "nptypes.h", + "nsJSNPRuntime.h", + "nsNPAPIPluginInstance.h", + "nsPluginHost.h", + "nsPluginInstanceOwner.h", + "nsPluginLogging.h", + "nsPluginNativeWindow.h", + "nsPluginsCID.h", + "nsPluginsDir.h", + "nsPluginTags.h", +] + +UNIFIED_SOURCES += [ + "nsJSNPRuntime.cpp", + "nsNPAPIPluginInstance.cpp", + "nsNPAPIPluginStreamListener.cpp", + "nsPluginInstanceOwner.cpp", + "nsPluginStreamListenerPeer.cpp", + "nsPluginTags.cpp", +] + +SOURCES += [ + "nsNPAPIPlugin.cpp", # Conflict with X11 headers + "nsPluginHost.cpp", # Conflict with NS_NPAPIPLUGIN_CALLBACK +] + +if CONFIG["OS_ARCH"] == "WINNT": + UNIFIED_SOURCES += [ + "nsPluginNativeWindowWin.cpp", + "nsPluginsDirWin.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "nsPluginNativeWindow.cpp", + ] + SOURCES += [ + "nsPluginsDirDarwin.cpp", # conflict with mozilla::EventPriority + ] +else: + UNIFIED_SOURCES += [ + "nsPluginNativeWindow.cpp", + "nsPluginsDirUnix.cpp", + ] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +LOCAL_INCLUDES += [ + "/dom/base", + "/dom/plugins/ipc", + "/layout/generic", + "/layout/xul", + "/netwerk/base", + "/widget", + "/widget/cocoa", + "/xpcom/base", +] + +if CONFIG["OS_ARCH"] == "WINNT": + LOCAL_INCLUDES += [ + "/xpcom/base", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] +CXXFLAGS += CONFIG["TK_CFLAGS"] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] diff --git a/dom/plugins/base/npapi.h b/dom/plugins/base/npapi.h new file mode 100644 index 0000000000..d6b189baef --- /dev/null +++ b/dom/plugins/base/npapi.h @@ -0,0 +1,922 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef npapi_h_ +#define npapi_h_ + +#include "nptypes.h" + +#if defined(_WIN32) && !defined(__SYMBIAN32__) +# include <windef.h> +# ifndef XP_WIN +# define XP_WIN 1 +# endif +#endif + +#if defined(__SYMBIAN32__) +# ifndef XP_SYMBIAN +# define XP_SYMBIAN 1 +# undef XP_WIN +# endif +#endif + +#if defined(__APPLE_CC__) && !defined(XP_UNIX) +# ifndef XP_MACOSX +# define XP_MACOSX 1 +# endif +#endif + +#if defined(XP_MACOSX) && defined(__LP64__) +# define NP_NO_QUICKDRAW +# define NP_NO_CARBON +#endif + +#if defined(XP_MACOSX) +# include <ApplicationServices/ApplicationServices.h> +# include <OpenGL/OpenGL.h> +# ifndef NP_NO_CARBON +# include <Carbon/Carbon.h> +# endif +#endif + +#if defined(XP_UNIX) +# include <stdio.h> +# if defined(MOZ_X11) +# include <X11/Xlib.h> +# include <X11/Xutil.h> +# include "X11UndefineNone.h" +# endif +#endif + +#if defined(XP_SYMBIAN) +# include <QEvent> +# include <QRegion> +#endif + +/*----------------------------------------------------------------------*/ +/* Plugin Version Constants */ +/*----------------------------------------------------------------------*/ + +#define NP_VERSION_MAJOR 0 +#define NP_VERSION_MINOR 29 + +/* clang-format off */ +/* The OS/2 version of Netscape uses RC_DATA to define the + mime types, file extensions, etc that are required. + Use a vertical bar to separate types, end types with \0. + FileVersion and ProductVersion are 32bit ints, all other + entries are strings that MUST be terminated with a \0. + +AN EXAMPLE: + +RCDATA NP_INFO_ProductVersion { 1,0,0,1,} + +RCDATA NP_INFO_MIMEType { "video/x-video|", + "video/x-flick\0" } +RCDATA NP_INFO_FileExtents { "avi|", + "flc\0" } +RCDATA NP_INFO_FileOpenName{ "MMOS2 video player(*.avi)|", + "MMOS2 Flc/Fli player(*.flc)\0" } + +RCDATA NP_INFO_FileVersion { 1,0,0,1 } +RCDATA NP_INFO_CompanyName { "Netscape Communications\0" } +RCDATA NP_INFO_FileDescription { "NPAVI32 Extension DLL\0" +RCDATA NP_INFO_InternalName { "NPAVI32\0" ) +RCDATA NP_INFO_LegalCopyright { "Copyright Netscape Communications \251 1996\0" +RCDATA NP_INFO_OriginalFilename { "NVAPI32.DLL" } +RCDATA NP_INFO_ProductName { "NPAVI32 Dynamic Link Library\0" } +*/ +/* clang-format on */ +/* RC_DATA types for version info - required */ +#define NP_INFO_ProductVersion 1 +#define NP_INFO_MIMEType 2 +#define NP_INFO_FileOpenName 3 +#define NP_INFO_FileExtents 4 +/* RC_DATA types for version info - used if found */ +#define NP_INFO_FileDescription 5 +#define NP_INFO_ProductName 6 +/* RC_DATA types for version info - optional */ +#define NP_INFO_CompanyName 7 +#define NP_INFO_FileVersion 8 +#define NP_INFO_InternalName 9 +#define NP_INFO_LegalCopyright 10 +#define NP_INFO_OriginalFilename 11 + +#ifndef RC_INVOKED + +/*----------------------------------------------------------------------*/ +/* Definition of Basic Types */ +/*----------------------------------------------------------------------*/ + +typedef unsigned char NPBool; +typedef int16_t NPError; +typedef int16_t NPReason; +typedef char* NPMIMEType; + +/*----------------------------------------------------------------------*/ +/* Structures and definitions */ +/*----------------------------------------------------------------------*/ + +# if !defined(__LP64__) +# if defined(XP_MACOSX) +# pragma options align = mac68k +# endif +# endif /* __LP64__ */ + +/* + * NPP is a plug-in's opaque instance handle + */ +typedef struct _NPP { + void* pdata; /* plug-in private data */ + void* ndata; /* netscape private data */ +} NPP_t; + +typedef NPP_t* NPP; + +typedef struct _NPStream { + void* pdata; /* plug-in private data */ + void* ndata; /* netscape private data */ + const char* url; + uint32_t end; + uint32_t lastmodified; + void* notifyData; + const char* headers; /* Response headers from host. + * Exists only for >= NPVERS_HAS_RESPONSE_HEADERS. + * Used for HTTP only; nullptr for non-HTTP. + * Available from NPP_NewStream onwards. + * Plugin should copy this data before storing it. + * Includes HTTP status line and all headers, + * preferably verbatim as received from server, + * headers formatted as in HTTP ("Header: Value"), + * and newlines (\n, NOT \r\n) separating lines. + * Terminated by \n\0 (NOT \n\n\0). */ +} NPStream; + +typedef struct _NPByteRange { + int32_t offset; /* negative offset means from the end */ + uint32_t length; + struct _NPByteRange* next; +} NPByteRange; + +typedef struct _NPSavedData { + int32_t len; + void* buf; +} NPSavedData; + +typedef struct _NPRect { + uint16_t top; + uint16_t left; + uint16_t bottom; + uint16_t right; +} NPRect; + +typedef struct _NPSize { + int32_t width; + int32_t height; +} NPSize; + +typedef enum { NPFocusNext = 0, NPFocusPrevious = 1 } NPFocusDirection; + +/* These formats describe the format in the memory byte-order. This means if + * a 32-bit value of a pixel is viewed on a little-endian system the layout will + * be 0xAARRGGBB. The Alpha channel will be stored in the most significant + * bits. */ +typedef enum { + /* 32-bit per pixel 8-bit per channel - premultiplied alpha */ + NPImageFormatBGRA32 = 0x1, + /* 32-bit per pixel 8-bit per channel - 1 unused channel */ + NPImageFormatBGRX32 = 0x2 +} NPImageFormat; + +typedef struct _NPAsyncSurface { + uint32_t version; + NPSize size; + NPImageFormat format; + union { + struct { + uint32_t stride; + void* data; + } bitmap; +# if defined(XP_WIN) + HANDLE sharedHandle; +# endif + }; +} NPAsyncSurface; + +/* Return values for NPP_HandleEvent */ +# define kNPEventNotHandled 0 +# define kNPEventHandled 1 +/* Exact meaning must be spec'd in event model. */ +# define kNPEventStartIME 2 + +# if defined(XP_UNIX) +/* + * Unix specific structures and definitions + */ + +/* + * Callback Structures. + * + * These are used to pass additional platform specific information. + */ +enum { NP_SETWINDOW = 1, NP_PRINT }; + +typedef struct { + int32_t type; +} NPAnyCallbackStruct; + +typedef struct { + int32_t type; +# if defined(MOZ_X11) + Display* display; + Visual* visual; + Colormap colormap; + unsigned int depth; +# endif +} NPSetWindowCallbackStruct; + +typedef struct { + int32_t type; + FILE* fp; +} NPPrintCallbackStruct; + +# endif /* XP_UNIX */ + +# if defined(XP_WIN) +/* + * Windows specific structures and definitions + */ + +/* + * Information about the default audio device. These values share meaning with + * the parameters to the Windows API IMMNotificationClient object. + * This is the value of the NPNVaudioDeviceChangeDetails variable. + */ +typedef struct _NPAudioDeviceChangeDetails { + int32_t flow; + int32_t role; + const wchar_t* defaultDevice; // this pointer is only valid during the call + // to NPPSetValue. +} NPAudioDeviceChangeDetails; + +# endif /* XP_WIN */ + +/* + * This is the value of the NPNVaudioDeviceStateChanged variable. + */ +typedef struct _NPAudioDeviceStateChanged { + /* Name of device that changed state. This string is only valid during + * the call to NPPSetValue. + */ + const wchar_t* device; + uint32_t newState; +} NPAudioDeviceStateChanged; + +typedef enum { + NPDrawingModelDUMMY +# if defined(XP_MACOSX) +# ifndef NP_NO_QUICKDRAW + , + NPDrawingModelQuickDraw = 0 +# endif + , + NPDrawingModelCoreGraphics = 1, + NPDrawingModelOpenGL = 2, + NPDrawingModelCoreAnimation = 3, + NPDrawingModelInvalidatingCoreAnimation = 4 +# endif +# if defined(XP_WIN) + , + NPDrawingModelSyncWin = 5 +# endif +# if defined(MOZ_X11) + , + NPDrawingModelSyncX = 6 +# endif + , + NPDrawingModelAsyncBitmapSurface = 7 +# if defined(XP_WIN) + , + NPDrawingModelAsyncWindowsDXGISurface = 8 +# endif +} NPDrawingModel; + +# ifdef XP_MACOSX +typedef enum { +# ifndef NP_NO_CARBON + NPEventModelCarbon = 0, +# endif + NPEventModelCocoa = 1 +} NPEventModel; +# endif + +/* + * The following masks are applied on certain platforms to NPNV and + * NPPV selectors that pass around pointers to COM interfaces. Newer + * compilers on some platforms may generate vtables that are not + * compatible with older compilers. To prevent older plugins from + * not understanding a new browser's ABI, these masks change the + * values of those selectors on those platforms. To remain backwards + * compatible with different versions of the browser, plugins can + * use these masks to dynamically determine and use the correct C++ + * ABI that the browser is expecting. This does not apply to Windows + * as Microsoft's COM ABI will likely not change. + */ + +# define NP_ABI_GCC3_MASK 0x10000000 +/* + * gcc 3.x generated vtables on UNIX and OSX are incompatible with + * previous compilers. + */ +# if (defined(XP_UNIX) && defined(__GNUC__) && (__GNUC__ >= 3)) +# define _NP_ABI_MIXIN_FOR_GCC3 NP_ABI_GCC3_MASK +# else +# define _NP_ABI_MIXIN_FOR_GCC3 0 +# endif + +# if defined(XP_MACOSX) +# define NP_ABI_MACHO_MASK 0x01000000 +# define _NP_ABI_MIXIN_FOR_MACHO NP_ABI_MACHO_MASK +# else +# define _NP_ABI_MIXIN_FOR_MACHO 0 +# endif + +# define NP_ABI_MASK (_NP_ABI_MIXIN_FOR_GCC3 | _NP_ABI_MIXIN_FOR_MACHO) + +/* + * List of variable names for which NPP_GetValue shall be implemented + */ +typedef enum { + NPPVpluginNameString = 1, + NPPVpluginDescriptionString, + NPPVpluginWindowBool, + NPPVpluginTransparentBool, + NPPVjavaClass, + NPPVpluginWindowSize, + NPPVpluginTimerInterval, + NPPVpluginScriptableInstance = (10 | NP_ABI_MASK), + NPPVpluginScriptableIID = 11, + NPPVjavascriptPushCallerBool = 12, + NPPVpluginKeepLibraryInMemory = 13, + NPPVpluginNeedsXEmbed = 14, + + /* Get the NPObject for scripting the plugin. Introduced in NPAPI minor + * version 14. + */ + NPPVpluginScriptableNPObject = 15, + + /* Get the plugin value (as \0-terminated UTF-8 string data) for + * form submission if the plugin is part of a form. Use + * NPN_MemAlloc() to allocate memory for the string data. Introduced + * in NPAPI minor version 15. + */ + NPPVformValue = 16, + + NPPVpluginUrlRequestsDisplayedBool = 17, + + /* Checks if the plugin is interested in receiving the http body of + * all http requests (including failed ones, http status != 200). + */ + NPPVpluginWantsAllNetworkStreams = 18, + + /* Browsers can retrieve a native ATK accessibility plug ID via this variable. + */ + NPPVpluginNativeAccessibleAtkPlugId = 19, + + /* Checks to see if the plug-in would like the browser to load the "src" + attribute. */ + NPPVpluginCancelSrcStream = 20, + + NPPVsupportsAdvancedKeyHandling = 21, + + NPPVpluginUsesDOMForCursorBool = 22, + + /* Used for negotiating drawing models */ + NPPVpluginDrawingModel = 1000 +# if defined(XP_MACOSX) + /* Used for negotiating event models */ + , + NPPVpluginEventModel = 1001 + /* In the NPDrawingModelCoreAnimation drawing model, the browser asks the + plug-in for a Core Animation layer. */ + , + NPPVpluginCoreAnimationLayer = 1003 +# endif + /* Notification that the plugin just started or stopped playing audio */ + , + NPPVpluginIsPlayingAudio = 4000 +# if defined(XP_WIN) + /* Notification that the plugin requests notification when the default audio + device has changed */ + , + NPPVpluginRequiresAudioDeviceChanges = 4001 +# endif + +} NPPVariable; + +/* + * List of variable names for which NPN_GetValue should be implemented. + */ +typedef enum { + NPNVxDisplay = 1, + NPNVxtAppContext, + NPNVnetscapeWindow, + NPNVjavascriptEnabledBool, + NPNVasdEnabledBool, + NPNVisOfflineBool, + + NPNVserviceManager = (10 | NP_ABI_MASK), + NPNVDOMElement = (11 | NP_ABI_MASK), + NPNVDOMWindow = (12 | NP_ABI_MASK), + NPNVToolkit = (13 | NP_ABI_MASK), + NPNVSupportsXEmbedBool = 14, + + /* Get the NPObject wrapper for the browser window. */ + NPNVWindowNPObject = 15, + + /* Get the NPObject wrapper for the plugins DOM element. */ + NPNVPluginElementNPObject = 16, + + NPNVSupportsWindowless = 17, + + NPNVprivateModeBool = 18, + + NPNVsupportsAdvancedKeyHandling = 21, + + NPNVdocumentOrigin = 22, + + NPNVCSSZoomFactor = 23, + + NPNVpluginDrawingModel = + 1000 /* Get the current drawing model (NPDrawingModel) */ +# if defined(XP_MACOSX) || defined(XP_WIN) + , + NPNVcontentsScaleFactor = 1001 +# endif +# if defined(XP_MACOSX) +# ifndef NP_NO_QUICKDRAW + , + NPNVsupportsQuickDrawBool = 2000 +# endif + , + NPNVsupportsCoreGraphicsBool = 2001, + NPNVsupportsOpenGLBool = 2002, + NPNVsupportsCoreAnimationBool = 2003, + NPNVsupportsInvalidatingCoreAnimationBool = 2004 +# endif + , + NPNVsupportsAsyncBitmapSurfaceBool = 2007 +# if defined(XP_WIN) + , + NPNVsupportsAsyncWindowsDXGISurfaceBool = 2008, + NPNVpreferredDXGIAdapter = 2009 +# endif +# if defined(XP_MACOSX) +# ifndef NP_NO_CARBON + , + NPNVsupportsCarbonBool = + 3000 /* TRUE if the browser supports the Carbon event model */ +# endif + , + NPNVsupportsCocoaBool = + 3001 /* TRUE if the browser supports the Cocoa event model */ + , + NPNVsupportsUpdatedCocoaTextInputBool = + 3002 /* TRUE if the browser supports the updated + Cocoa text input specification. */ +# endif + , + NPNVmuteAudioBool = + 4000 /* Request that the browser wants to mute or unmute the plugin */ +# if defined(XP_WIN) + , + NPNVaudioDeviceChangeDetails = + 4001 /* Provides information about the new default audio device */ + , + NPNVaudioDeviceStateChanged = + 4002 /* Provides information if any audio device changes state */ +# endif +# if defined(XP_MACOSX) + , + NPNVsupportsCompositingCoreAnimationPluginsBool = + 74656 /* TRUE if the browser supports + CA model compositing */ +# endif + , + NPNVLast +} NPNVariable; + +typedef enum { NPNURLVCookie = 501, NPNURLVProxy } NPNURLVariable; + +/* + * The type of Toolkit the widgets use + */ +typedef enum { NPNVGtk12 = 1, NPNVGtk2 } NPNToolkitType; + +/* + * The type of a NPWindow - it specifies the type of the data structure + * returned in the window field. + */ +typedef enum { NPWindowTypeWindow = 1, NPWindowTypeDrawable } NPWindowType; + +typedef struct _NPWindow { + void* window; /* Platform specific window handle */ + /* OS/2: x - Position of bottom left corner */ + /* OS/2: y - relative to visible netscape window */ + int32_t x; /* Position of top left corner relative */ + int32_t y; /* to a netscape page. */ + uint32_t width; /* Maximum window size */ + uint32_t height; + NPRect clipRect; /* Clipping rectangle in port coordinates */ +# if (defined(XP_UNIX) || defined(XP_SYMBIAN)) && !defined(XP_MACOSX) + void* ws_info; /* Platform-dependent additional data */ +# endif /* XP_UNIX */ + NPWindowType type; /* Is this a window or a drawable? */ +} NPWindow; + +typedef struct _NPImageExpose { + char* data; /* image pointer */ + int32_t stride; /* Stride of data image pointer */ + int32_t depth; /* Depth of image pointer */ + int32_t x; /* Expose x */ + int32_t y; /* Expose y */ + uint32_t width; /* Expose width */ + uint32_t height; /* Expose height */ + NPSize dataSize; /* Data buffer size */ + float translateX; /* translate X matrix value */ + float translateY; /* translate Y matrix value */ + float scaleX; /* scale X matrix value */ + float scaleY; /* scale Y matrix value */ +} NPImageExpose; + +typedef struct _NPFullPrint { + NPBool pluginPrinted; /* Set TRUE if plugin handled fullscreen printing */ + NPBool printOne; /* TRUE if plugin should print one copy to default + printer */ + void* platformPrint; /* Platform-specific printing info */ +} NPFullPrint; + +typedef struct _NPEmbedPrint { + NPWindow window; + void* platformPrint; /* Platform-specific printing info */ +} NPEmbedPrint; + +typedef struct _NPPrint { + uint16_t mode; /* NP_FULL or NP_EMBED */ + union { + NPFullPrint fullPrint; /* if mode is NP_FULL */ + NPEmbedPrint embedPrint; /* if mode is NP_EMBED */ + } print; +} NPPrint; + +# if defined(XP_MACOSX) +# ifndef NP_NO_CARBON +typedef EventRecord NPEvent; +# endif +# elif defined(XP_SYMBIAN) +typedef QEvent NPEvent; +# elif defined(XP_WIN) +typedef struct _NPEvent { + uint16_t event; + uintptr_t wParam; + intptr_t lParam; +} NPEvent; +# elif defined(XP_UNIX) && defined(MOZ_X11) +typedef XEvent NPEvent; +# else +typedef void* NPEvent; +# endif + +# if defined(XP_MACOSX) +typedef void* NPRegion; +# ifndef NP_NO_QUICKDRAW +typedef RgnHandle NPQDRegion; +# endif +typedef CGPathRef NPCGRegion; +# elif defined(XP_WIN) +typedef HRGN NPRegion; +# elif defined(XP_UNIX) && defined(MOZ_X11) +typedef Region NPRegion; +# elif defined(XP_SYMBIAN) +typedef QRegion* NPRegion; +# else +typedef void* NPRegion; +# endif + +typedef struct _NPNSString NPNSString; +typedef struct _NPNSWindow NPNSWindow; +typedef struct _NPNSMenu NPNSMenu; + +# if defined(XP_MACOSX) +typedef NPNSMenu NPMenu; +# else +typedef void* NPMenu; +# endif + +typedef enum { + NPCoordinateSpacePlugin = 1, + NPCoordinateSpaceWindow, + NPCoordinateSpaceFlippedWindow, + NPCoordinateSpaceScreen, + NPCoordinateSpaceFlippedScreen +} NPCoordinateSpace; + +# if defined(XP_MACOSX) + +# ifndef NP_NO_QUICKDRAW +typedef struct NP_Port { + CGrafPtr port; + int32_t portx; /* position inside the topmost window */ + int32_t porty; +} NP_Port; +# endif /* NP_NO_QUICKDRAW */ + +/* + * NP_CGContext is the type of the NPWindow's 'window' when the plugin specifies + * NPDrawingModelCoreGraphics as its drawing model. + */ + +typedef struct NP_CGContext { + CGContextRef context; + void* window; /* A WindowRef under the Carbon event model. */ +} NP_CGContext; + +/* + * NP_GLContext is the type of the NPWindow's 'window' when the plugin specifies + * NPDrawingModelOpenGL as its drawing model. + */ + +typedef struct NP_GLContext { + CGLContextObj context; +# ifdef NP_NO_CARBON + NPNSWindow* window; +# else + void* window; /* Can be either an NSWindow or a WindowRef depending on the + event model */ +# endif +} NP_GLContext; + +typedef enum { + NPCocoaEventDrawRect = 1, + NPCocoaEventMouseDown, + NPCocoaEventMouseUp, + NPCocoaEventMouseMoved, + NPCocoaEventMouseEntered, + NPCocoaEventMouseExited, + NPCocoaEventMouseDragged, + NPCocoaEventKeyDown, + NPCocoaEventKeyUp, + NPCocoaEventFlagsChanged, + NPCocoaEventFocusChanged, + NPCocoaEventWindowFocusChanged, + NPCocoaEventScrollWheel, + NPCocoaEventTextInput +} NPCocoaEventType; + +typedef struct _NPCocoaEvent { + NPCocoaEventType type; + uint32_t version; + union { + struct { + uint32_t modifierFlags; + double pluginX; + double pluginY; + int32_t buttonNumber; + int32_t clickCount; + double deltaX; + double deltaY; + double deltaZ; + } mouse; + struct { + uint32_t modifierFlags; + NPNSString* characters; + NPNSString* charactersIgnoringModifiers; + NPBool isARepeat; + uint16_t keyCode; + } key; + struct { + CGContextRef context; + double x; + double y; + double width; + double height; + } draw; + struct { + NPBool hasFocus; + } focus; + struct { + NPNSString* text; + } text; + } data; +} NPCocoaEvent; + +# ifndef NP_NO_CARBON +/* Non-standard event types that can be passed to HandleEvent */ +enum NPEventType { + NPEventType_GetFocusEvent = (osEvt + 16), + NPEventType_LoseFocusEvent, + NPEventType_AdjustCursorEvent, + NPEventType_MenuCommandEvent, + NPEventType_ClippingChangedEvent, + NPEventType_ScrollingBeginsEvent = 1000, + NPEventType_ScrollingEndsEvent +}; +# endif /* NP_NO_CARBON */ + +# endif /* XP_MACOSX */ + +/* + * Values for mode passed to NPP_New: + */ +# define NP_EMBED 1 +# define NP_FULL 2 + +/* + * Values for stream type passed to NPP_NewStream: + */ +# define NP_NORMAL 1 +# define NP_SEEK 2 +# define NP_ASFILE 3 +# define NP_ASFILEONLY 4 + +# define NP_MAXREADY (((unsigned)(~0) << 1) >> 1) + +/* + * Flags for NPP_ClearSiteData. + */ +# define NP_CLEAR_ALL 0 +# define NP_CLEAR_CACHE (1 << 0) + +# if !defined(__LP64__) +# if defined(XP_MACOSX) +# pragma options align = reset +# endif +# endif /* __LP64__ */ + +/*----------------------------------------------------------------------*/ +/* Error and Reason Code definitions */ +/*----------------------------------------------------------------------*/ + +/* + * Values of type NPError: + */ +# define NPERR_BASE 0 +# define NPERR_NO_ERROR (NPERR_BASE + 0) +# define NPERR_GENERIC_ERROR (NPERR_BASE + 1) +# define NPERR_INVALID_INSTANCE_ERROR (NPERR_BASE + 2) +# define NPERR_INVALID_FUNCTABLE_ERROR (NPERR_BASE + 3) +# define NPERR_MODULE_LOAD_FAILED_ERROR (NPERR_BASE + 4) +# define NPERR_OUT_OF_MEMORY_ERROR (NPERR_BASE + 5) +# define NPERR_INVALID_PLUGIN_ERROR (NPERR_BASE + 6) +# define NPERR_INVALID_PLUGIN_DIR_ERROR (NPERR_BASE + 7) +# define NPERR_INCOMPATIBLE_VERSION_ERROR (NPERR_BASE + 8) +# define NPERR_INVALID_PARAM (NPERR_BASE + 9) +# define NPERR_INVALID_URL (NPERR_BASE + 10) +# define NPERR_FILE_NOT_FOUND (NPERR_BASE + 11) +# define NPERR_NO_DATA (NPERR_BASE + 12) +# define NPERR_STREAM_NOT_SEEKABLE (NPERR_BASE + 13) +# define NPERR_TIME_RANGE_NOT_SUPPORTED (NPERR_BASE + 14) +# define NPERR_MALFORMED_SITE (NPERR_BASE + 15) + +/* + * Values of type NPReason: + */ +# define NPRES_BASE 0 +# define NPRES_DONE (NPRES_BASE + 0) +# define NPRES_NETWORK_ERR (NPRES_BASE + 1) +# define NPRES_USER_BREAK (NPRES_BASE + 2) + +/* + * Don't use these obsolete error codes any more. + */ +# define NP_NOERR NP_NOERR_is_obsolete_use_NPERR_NO_ERROR +# define NP_EINVAL NP_EINVAL_is_obsolete_use_NPERR_GENERIC_ERROR +# define NP_EABORT NP_EABORT_is_obsolete_use_NPRES_USER_BREAK + +/* + * Version feature information + */ +# define NPVERS_HAS_STREAMOUTPUT 8 +# define NPVERS_HAS_NOTIFICATION 9 +# define NPVERS_HAS_LIVECONNECT 9 +# define NPVERS_68K_HAS_LIVECONNECT 11 +# define NPVERS_HAS_WINDOWLESS 11 +# define NPVERS_HAS_XPCONNECT_SCRIPTING 13 +# define NPVERS_HAS_NPRUNTIME_SCRIPTING 14 +# define NPVERS_HAS_FORM_VALUES 15 +# define NPVERS_HAS_POPUPS_ENABLED_STATE 16 +# define NPVERS_HAS_RESPONSE_HEADERS 17 +# define NPVERS_HAS_NPOBJECT_ENUM 18 +# define NPVERS_HAS_PLUGIN_THREAD_ASYNC_CALL 19 +# define NPVERS_HAS_ALL_NETWORK_STREAMS 20 +# define NPVERS_HAS_URL_AND_AUTH_INFO 21 +# define NPVERS_HAS_PRIVATE_MODE 22 +# define NPVERS_MACOSX_HAS_COCOA_EVENTS 23 +# define NPVERS_HAS_ADVANCED_KEY_HANDLING 25 +# define NPVERS_HAS_URL_REDIRECT_HANDLING 26 +# define NPVERS_HAS_CLEAR_SITE_DATA 27 + +/*----------------------------------------------------------------------*/ +/* Function Prototypes */ +/*----------------------------------------------------------------------*/ + +# ifdef __cplusplus +extern "C" { +# endif + +/* NPP_* functions are provided by the plugin and called by the navigator. */ + +# if defined(XP_UNIX) +const char* NPP_GetMIMEDescription(void); +# endif + +NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, + int16_t argc, char* argn[], char* argv[], NPSavedData* saved); +NPError NPP_Destroy(NPP instance, NPSavedData** save); +NPError NPP_SetWindow(NPP instance, NPWindow* window); +NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype); +NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason); +int32_t NPP_WriteReady(NPP instance, NPStream* stream); +int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, + void* buffer); +void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname); +void NPP_Print(NPP instance, NPPrint* platformPrint); +int16_t NPP_HandleEvent(NPP instance, void* event); +void NPP_URLNotify(NPP instance, const char* url, NPReason reason, + void* notifyData); +NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value); +NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value); +NPBool NPP_GotFocus(NPP instance, NPFocusDirection direction); +void NPP_LostFocus(NPP instance); +void NPP_URLRedirectNotify(NPP instance, const char* url, int32_t status, + void* notifyData); +NPError NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge); +char** NPP_GetSitesWithData(void); +void NPP_DidComposite(NPP instance); + +/* NPN_* functions are provided by the navigator and called by the plugin. */ +void NPN_Version(int* plugin_major, int* plugin_minor, int* netscape_major, + int* netscape_minor); +NPError NPN_GetURLNotify(NPP instance, const char* url, const char* target, + void* notifyData); +NPError NPN_GetURL(NPP instance, const char* url, const char* target); +NPError NPN_PostURLNotify(NPP instance, const char* url, const char* target, + uint32_t len, const char* buf, NPBool file, + void* notifyData); +NPError NPN_PostURL(NPP instance, const char* url, const char* target, + uint32_t len, const char* buf, NPBool file); +NPError NPN_RequestRead(NPStream* stream, NPByteRange* rangeList); +NPError NPN_NewStream(NPP instance, NPMIMEType type, const char* target, + NPStream** stream); +int32_t NPN_Write(NPP instance, NPStream* stream, int32_t len, void* buffer); +NPError NPN_DestroyStream(NPP instance, NPStream* stream, NPReason reason); +void NPN_Status(NPP instance, const char* message); +const char* NPN_UserAgent(NPP instance); +void* NPN_MemAlloc(uint32_t size); +void NPN_MemFree(void* ptr); +uint32_t NPN_MemFlush(uint32_t size); +void NPN_ReloadPlugins(NPBool reloadPages); +NPError NPN_GetValue(NPP instance, NPNVariable variable, void* value); +NPError NPN_SetValue(NPP instance, NPPVariable variable, void* value); +void NPN_InvalidateRect(NPP instance, NPRect* invalidRect); +void NPN_InvalidateRegion(NPP instance, NPRegion invalidRegion); +void NPN_ForceRedraw(NPP instance); +void NPN_PushPopupsEnabledState(NPP instance, NPBool enabled); +void NPN_PopPopupsEnabledState(NPP instance); +void NPN_PluginThreadAsyncCall(NPP instance, void (*func)(void*), + void* userData); +NPError NPN_GetValueForURL(NPP instance, NPNURLVariable variable, + const char* url, char** value, uint32_t* len); +NPError NPN_SetValueForURL(NPP instance, NPNURLVariable variable, + const char* url, const char* value, uint32_t len); +NPError NPN_GetAuthenticationInfo(NPP instance, const char* protocol, + const char* host, int32_t port, + const char* scheme, const char* realm, + char** username, uint32_t* ulen, + char** password, uint32_t* plen); +uint32_t NPN_ScheduleTimer(NPP instance, uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)); +void NPN_UnscheduleTimer(NPP instance, uint32_t timerID); +NPError NPN_PopUpContextMenu(NPP instance, NPMenu* menu); +NPBool NPN_ConvertPoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace); +NPBool NPN_HandleEvent(NPP instance, void* event, NPBool handled); +NPBool NPN_UnfocusInstance(NPP instance, NPFocusDirection direction); +void NPN_URLRedirectResponse(NPP instance, void* notifyData, NPBool allow); +NPError NPN_InitAsyncSurface(NPP instance, NPSize* size, NPImageFormat format, + void* initData, NPAsyncSurface* surface); +NPError NPN_FinalizeAsyncSurface(NPP instance, NPAsyncSurface* surface); +void NPN_SetCurrentAsyncSurface(NPP instance, NPAsyncSurface* surface, + NPRect* changed); + +# ifdef __cplusplus +} /* end extern "C" */ +# endif + +#endif /* RC_INVOKED */ + +#endif /* npapi_h_ */ diff --git a/dom/plugins/base/npfunctions.h b/dom/plugins/base/npfunctions.h new file mode 100644 index 0000000000..72d279b25f --- /dev/null +++ b/dom/plugins/base/npfunctions.h @@ -0,0 +1,356 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef npfunctions_h_ +#define npfunctions_h_ + +#include "npapi.h" +#include "npruntime.h" + +typedef NPError (*NPP_NewProcPtr)(NPMIMEType pluginType, NPP instance, + uint16_t mode, int16_t argc, char* argn[], + char* argv[], NPSavedData* saved); +typedef NPError (*NPP_DestroyProcPtr)(NPP instance, NPSavedData** save); +typedef NPError (*NPP_SetWindowProcPtr)(NPP instance, NPWindow* window); +typedef NPError (*NPP_NewStreamProcPtr)(NPP instance, NPMIMEType type, + NPStream* stream, NPBool seekable, + uint16_t* stype); +typedef NPError (*NPP_DestroyStreamProcPtr)(NPP instance, NPStream* stream, + NPReason reason); +typedef int32_t (*NPP_WriteReadyProcPtr)(NPP instance, NPStream* stream); +typedef int32_t (*NPP_WriteProcPtr)(NPP instance, NPStream* stream, + int32_t offset, int32_t len, void* buffer); +typedef void (*NPP_StreamAsFileProcPtr)(NPP instance, NPStream* stream, + const char* fname); +typedef void (*NPP_PrintProcPtr)(NPP instance, NPPrint* platformPrint); +typedef int16_t (*NPP_HandleEventProcPtr)(NPP instance, void* event); +typedef void (*NPP_URLNotifyProcPtr)(NPP instance, const char* url, + NPReason reason, void* notifyData); +/* Any NPObjects returned to the browser via NPP_GetValue should be retained + by the plugin on the way out. The browser is responsible for releasing. */ +typedef NPError (*NPP_GetValueProcPtr)(NPP instance, NPPVariable variable, + void* ret_value); +typedef NPError (*NPP_SetValueProcPtr)(NPP instance, NPNVariable variable, + void* value); +typedef NPBool (*NPP_GotFocusPtr)(NPP instance, NPFocusDirection direction); +typedef void (*NPP_LostFocusPtr)(NPP instance); +typedef void (*NPP_URLRedirectNotifyPtr)(NPP instance, const char* url, + int32_t status, void* notifyData); +typedef NPError (*NPP_ClearSiteDataPtr)(const char* site, uint64_t flags, + uint64_t maxAge); +typedef char** (*NPP_GetSitesWithDataPtr)(void); +typedef void (*NPP_DidCompositePtr)(NPP instance); + +typedef NPError (*NPN_GetValueProcPtr)(NPP instance, NPNVariable variable, + void* ret_value); +typedef NPError (*NPN_SetValueProcPtr)(NPP instance, NPPVariable variable, + void* value); +typedef NPError (*NPN_GetURLNotifyProcPtr)(NPP instance, const char* url, + const char* window, + void* notifyData); +typedef NPError (*NPN_PostURLNotifyProcPtr)(NPP instance, const char* url, + const char* window, uint32_t len, + const char* buf, NPBool file, + void* notifyData); +typedef NPError (*NPN_GetURLProcPtr)(NPP instance, const char* url, + const char* window); +typedef NPError (*NPN_PostURLProcPtr)(NPP instance, const char* url, + const char* window, uint32_t len, + const char* buf, NPBool file); +typedef NPError (*NPN_RequestReadProcPtr)(NPStream* stream, + NPByteRange* rangeList); +typedef NPError (*NPN_NewStreamProcPtr)(NPP instance, NPMIMEType type, + const char* window, NPStream** stream); +typedef int32_t (*NPN_WriteProcPtr)(NPP instance, NPStream* stream, int32_t len, + void* buffer); +typedef NPError (*NPN_DestroyStreamProcPtr)(NPP instance, NPStream* stream, + NPReason reason); +typedef void (*NPN_StatusProcPtr)(NPP instance, const char* message); +/* Browser manages the lifetime of the buffer returned by NPN_UserAgent, don't + depend on it sticking around and don't free it. */ +typedef const char* (*NPN_UserAgentProcPtr)(NPP instance); +typedef void* (*NPN_MemAllocProcPtr)(uint32_t size); +typedef void (*NPN_MemFreeProcPtr)(void* ptr); +typedef uint32_t (*NPN_MemFlushProcPtr)(uint32_t size); +typedef void (*NPN_ReloadPluginsProcPtr)(NPBool reloadPages); +typedef void* (*NPN_GetJavaEnvProcPtr)(void); +typedef void* (*NPN_GetJavaPeerProcPtr)(NPP instance); +typedef void (*NPN_InvalidateRectProcPtr)(NPP instance, NPRect* rect); +typedef void (*NPN_InvalidateRegionProcPtr)(NPP instance, NPRegion region); +typedef void (*NPN_ForceRedrawProcPtr)(NPP instance); +typedef NPIdentifier (*NPN_GetStringIdentifierProcPtr)(const NPUTF8* name); +typedef void (*NPN_GetStringIdentifiersProcPtr)(const NPUTF8** names, + int32_t nameCount, + NPIdentifier* identifiers); +typedef NPIdentifier (*NPN_GetIntIdentifierProcPtr)(int32_t intid); +typedef bool (*NPN_IdentifierIsStringProcPtr)(NPIdentifier identifier); +typedef NPUTF8* (*NPN_UTF8FromIdentifierProcPtr)(NPIdentifier identifier); +typedef int32_t (*NPN_IntFromIdentifierProcPtr)(NPIdentifier identifier); +typedef NPObject* (*NPN_CreateObjectProcPtr)(NPP npp, NPClass* aClass); +typedef NPObject* (*NPN_RetainObjectProcPtr)(NPObject* obj); +typedef void (*NPN_ReleaseObjectProcPtr)(NPObject* obj); +typedef bool (*NPN_InvokeProcPtr)(NPP npp, NPObject* obj, + NPIdentifier methodName, + const NPVariant* args, uint32_t argCount, + NPVariant* result); +typedef bool (*NPN_InvokeDefaultProcPtr)(NPP npp, NPObject* obj, + const NPVariant* args, + uint32_t argCount, NPVariant* result); +typedef bool (*NPN_EvaluateProcPtr)(NPP npp, NPObject* obj, NPString* script, + NPVariant* result); +typedef bool (*NPN_GetPropertyProcPtr)(NPP npp, NPObject* obj, + NPIdentifier propertyName, + NPVariant* result); +typedef bool (*NPN_SetPropertyProcPtr)(NPP npp, NPObject* obj, + NPIdentifier propertyName, + const NPVariant* value); +typedef bool (*NPN_RemovePropertyProcPtr)(NPP npp, NPObject* obj, + NPIdentifier propertyName); +typedef bool (*NPN_HasPropertyProcPtr)(NPP npp, NPObject* obj, + NPIdentifier propertyName); +typedef bool (*NPN_HasMethodProcPtr)(NPP npp, NPObject* obj, + NPIdentifier propertyName); +typedef void (*NPN_ReleaseVariantValueProcPtr)(NPVariant* variant); +typedef void (*NPN_SetExceptionProcPtr)(NPObject* obj, const NPUTF8* message); +typedef void (*NPN_PushPopupsEnabledStateProcPtr)(NPP npp, NPBool enabled); +typedef void (*NPN_PopPopupsEnabledStateProcPtr)(NPP npp); +typedef bool (*NPN_EnumerateProcPtr)(NPP npp, NPObject* obj, + NPIdentifier** identifier, + uint32_t* count); +typedef void (*NPN_PluginThreadAsyncCallProcPtr)(NPP instance, + void (*func)(void*), + void* userData); +typedef bool (*NPN_ConstructProcPtr)(NPP npp, NPObject* obj, + const NPVariant* args, uint32_t argCount, + NPVariant* result); +typedef NPError (*NPN_GetValueForURLPtr)(NPP npp, NPNURLVariable variable, + const char* url, char** value, + uint32_t* len); +typedef NPError (*NPN_SetValueForURLPtr)(NPP npp, NPNURLVariable variable, + const char* url, const char* value, + uint32_t len); +typedef NPError (*NPN_GetAuthenticationInfoPtr)( + NPP npp, const char* protocol, const char* host, int32_t port, + const char* scheme, const char* realm, char** username, uint32_t* ulen, + char** password, uint32_t* plen); +typedef uint32_t (*NPN_ScheduleTimerPtr)(NPP instance, uint32_t interval, + NPBool repeat, + void (*timerFunc)(NPP npp, + uint32_t timerID)); +typedef void (*NPN_UnscheduleTimerPtr)(NPP instance, uint32_t timerID); +typedef NPError (*NPN_PopUpContextMenuPtr)(NPP instance, NPMenu* menu); +typedef NPBool (*NPN_ConvertPointPtr)(NPP instance, double sourceX, + double sourceY, + NPCoordinateSpace sourceSpace, + double* destX, double* destY, + NPCoordinateSpace destSpace); +typedef NPBool (*NPN_HandleEventPtr)(NPP instance, void* event, NPBool handled); +typedef NPBool (*NPN_UnfocusInstancePtr)(NPP instance, + NPFocusDirection direction); +typedef void (*NPN_URLRedirectResponsePtr)(NPP instance, void* notifyData, + NPBool allow); +typedef NPError (*NPN_InitAsyncSurfacePtr)(NPP instance, NPSize* size, + NPImageFormat format, void* initData, + NPAsyncSurface* surface); +typedef NPError (*NPN_FinalizeAsyncSurfacePtr)(NPP instance, + NPAsyncSurface* surface); +typedef void (*NPN_SetCurrentAsyncSurfacePtr)(NPP instance, + NPAsyncSurface* surface, + NPRect* changed); + +typedef void (*NPN_DummyPtr)(void); + +typedef struct _NPPluginFuncs { + uint16_t size; + uint16_t version; + NPP_NewProcPtr newp; + NPP_DestroyProcPtr destroy; + NPP_SetWindowProcPtr setwindow; + NPP_NewStreamProcPtr newstream; + NPP_DestroyStreamProcPtr destroystream; + NPP_StreamAsFileProcPtr asfile; + NPP_WriteReadyProcPtr writeready; + NPP_WriteProcPtr write; + NPP_PrintProcPtr print; + NPP_HandleEventProcPtr event; + NPP_URLNotifyProcPtr urlnotify; + void* javaClass; + NPP_GetValueProcPtr getvalue; + NPP_SetValueProcPtr setvalue; + NPP_GotFocusPtr gotfocus; + NPP_LostFocusPtr lostfocus; + NPP_URLRedirectNotifyPtr urlredirectnotify; + NPP_ClearSiteDataPtr clearsitedata; + NPP_GetSitesWithDataPtr getsiteswithdata; + NPP_DidCompositePtr didComposite; +} NPPluginFuncs; + +typedef struct _NPNetscapeFuncs { + uint16_t size; + uint16_t version; + NPN_GetURLProcPtr geturl; + NPN_PostURLProcPtr posturl; + NPN_RequestReadProcPtr requestread; + NPN_NewStreamProcPtr newstream; + NPN_WriteProcPtr write; + NPN_DestroyStreamProcPtr destroystream; + NPN_StatusProcPtr status; + NPN_UserAgentProcPtr uagent; + NPN_MemAllocProcPtr memalloc; + NPN_MemFreeProcPtr memfree; + NPN_MemFlushProcPtr memflush; + NPN_ReloadPluginsProcPtr reloadplugins; + NPN_GetJavaEnvProcPtr getJavaEnv; + NPN_GetJavaPeerProcPtr getJavaPeer; + NPN_GetURLNotifyProcPtr geturlnotify; + NPN_PostURLNotifyProcPtr posturlnotify; + NPN_GetValueProcPtr getvalue; + NPN_SetValueProcPtr setvalue; + NPN_InvalidateRectProcPtr invalidaterect; + NPN_InvalidateRegionProcPtr invalidateregion; + NPN_ForceRedrawProcPtr forceredraw; + NPN_GetStringIdentifierProcPtr getstringidentifier; + NPN_GetStringIdentifiersProcPtr getstringidentifiers; + NPN_GetIntIdentifierProcPtr getintidentifier; + NPN_IdentifierIsStringProcPtr identifierisstring; + NPN_UTF8FromIdentifierProcPtr utf8fromidentifier; + NPN_IntFromIdentifierProcPtr intfromidentifier; + NPN_CreateObjectProcPtr createobject; + NPN_RetainObjectProcPtr retainobject; + NPN_ReleaseObjectProcPtr releaseobject; + NPN_InvokeProcPtr invoke; + NPN_InvokeDefaultProcPtr invokeDefault; + NPN_EvaluateProcPtr evaluate; + NPN_GetPropertyProcPtr getproperty; + NPN_SetPropertyProcPtr setproperty; + NPN_RemovePropertyProcPtr removeproperty; + NPN_HasPropertyProcPtr hasproperty; + NPN_HasMethodProcPtr hasmethod; + NPN_ReleaseVariantValueProcPtr releasevariantvalue; + NPN_SetExceptionProcPtr setexception; + NPN_PushPopupsEnabledStateProcPtr pushpopupsenabledstate; + NPN_PopPopupsEnabledStateProcPtr poppopupsenabledstate; + NPN_EnumerateProcPtr enumerate; + NPN_PluginThreadAsyncCallProcPtr pluginthreadasynccall; + NPN_ConstructProcPtr construct; + NPN_GetValueForURLPtr getvalueforurl; + NPN_SetValueForURLPtr setvalueforurl; + NPN_GetAuthenticationInfoPtr getauthenticationinfo; + NPN_ScheduleTimerPtr scheduletimer; + NPN_UnscheduleTimerPtr unscheduletimer; + NPN_PopUpContextMenuPtr popupcontextmenu; + NPN_ConvertPointPtr convertpoint; + NPN_HandleEventPtr handleevent; + NPN_UnfocusInstancePtr unfocusinstance; + NPN_URLRedirectResponsePtr urlredirectresponse; + NPN_InitAsyncSurfacePtr initasyncsurface; + NPN_FinalizeAsyncSurfacePtr finalizeasyncsurface; + NPN_SetCurrentAsyncSurfacePtr setcurrentasyncsurface; +} NPNetscapeFuncs; + +#ifdef XP_MACOSX +/* + * Mac OS X version(s) of NP_GetMIMEDescription(const char *) + * These can be called to retreive MIME information from the plugin dynamically + * + * Note: For compatibility with Quicktime, BPSupportedMIMEtypes is another way + * to get mime info from the plugin only on OSX and may not be supported + * in furture version -- use NP_GetMIMEDescription instead + */ +enum { kBPSupportedMIMETypesStructVers_1 = 1 }; +typedef struct _BPSupportedMIMETypes { + SInt32 structVersion; /* struct version */ + Handle typeStrings; /* STR# formated handle, allocated by plug-in */ + Handle infoStrings; /* STR# formated handle, allocated by plug-in */ +} BPSupportedMIMETypes; +OSErr BP_GetSupportedMIMETypes(BPSupportedMIMETypes* mimeInfo, UInt32 flags); +# define NP_GETMIMEDESCRIPTION_NAME "NP_GetMIMEDescription" +typedef const char* (*NP_GetMIMEDescriptionProcPtr)(void); +typedef OSErr (*BP_GetSupportedMIMETypesProcPtr)(BPSupportedMIMETypes*, UInt32); +#endif + +#if defined(_WIN32) +# define OSCALL WINAPI +#else +# define OSCALL +#endif + +#if defined(XP_UNIX) +/* GCC 3.3 and later support the visibility attribute. */ +# if defined(__GNUC__) && \ + ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3)) +# define NP_VISIBILITY_DEFAULT __attribute__((visibility("default"))) +# elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +# define NP_VISIBILITY_DEFAULT __global +# else +# define NP_VISIBILITY_DEFAULT +# endif +# define NP_EXPORT(__type) NP_VISIBILITY_DEFAULT __type +#endif + +#if defined(_WIN32) +# ifdef __cplusplus +extern "C" { +# endif +/* plugin meta member functions */ +typedef NPError(OSCALL* NP_GetEntryPointsFunc)(NPPluginFuncs*); +NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs); +typedef NPError(OSCALL* NP_InitializeFunc)(NPNetscapeFuncs*); +NPError OSCALL NP_Initialize(NPNetscapeFuncs* bFuncs); +typedef NPError(OSCALL* NP_ShutdownFunc)(void); +NPError OSCALL NP_Shutdown(void); +typedef const char* (*NP_GetMIMEDescriptionFunc)(void); +const char* NP_GetMIMEDescription(void); +# ifdef __cplusplus +} +# endif +#endif + +#ifdef XP_UNIX +# ifdef __cplusplus +extern "C" { +# endif +typedef char* (*NP_GetPluginVersionFunc)(void); +NP_EXPORT(char*) NP_GetPluginVersion(void); +typedef const char* (*NP_GetMIMEDescriptionFunc)(void); +NP_EXPORT(const char*) NP_GetMIMEDescription(void); +# ifdef XP_MACOSX +typedef NPError (*NP_InitializeFunc)(NPNetscapeFuncs*); +NP_EXPORT(NPError) NP_Initialize(NPNetscapeFuncs* bFuncs); +typedef NPError (*NP_GetEntryPointsFunc)(NPPluginFuncs*); +NP_EXPORT(NPError) NP_GetEntryPoints(NPPluginFuncs* pFuncs); +# else +typedef NPError (*NP_InitializeFunc)(NPNetscapeFuncs*, NPPluginFuncs*); +NP_EXPORT(NPError) +NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs); +# endif +typedef NPError (*NP_ShutdownFunc)(void); +NP_EXPORT(NPError) NP_Shutdown(void); +typedef NPError (*NP_GetValueFunc)(void*, NPPVariable, void*); +NP_EXPORT(NPError) +NP_GetValue(void* future, NPPVariable aVariable, void* aValue); +# ifdef __cplusplus +} +# endif +#endif + +// clang-format off +// See bug 1431030 +#if defined(XP_WIN) +#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (__stdcall * _name) +#else +#define NS_NPAPIPLUGIN_CALLBACK(_type, _name) _type (* _name) +#endif +// clang-format on + +typedef NS_NPAPIPLUGIN_CALLBACK(NPError, + NP_GETENTRYPOINTS)(NPPluginFuncs* pCallbacks); +typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGININIT)( + const NPNetscapeFuncs* pCallbacks); +typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINUNIXINIT)( + const NPNetscapeFuncs* pCallbacks, NPPluginFuncs* fCallbacks); +typedef NS_NPAPIPLUGIN_CALLBACK(NPError, NP_PLUGINSHUTDOWN)(void); + +#endif /* npfunctions_h_ */ diff --git a/dom/plugins/base/npruntime.h b/dom/plugins/base/npruntime.h new file mode 100644 index 0000000000..a2c4fa1597 --- /dev/null +++ b/dom/plugins/base/npruntime.h @@ -0,0 +1,382 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright (c) 2004, Apple Computer, Inc. and The Mozilla Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of Apple Computer, Inc. ("Apple") or The Mozilla + * Foundation ("Mozilla") nor the names of their contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE, MOZILLA AND THEIR CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE, MOZILLA OR + * THEIR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#ifndef _NP_RUNTIME_H_ +#define _NP_RUNTIME_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "nptypes.h" + +/* + This API is used to facilitate binding code written in C to script + objects. The API in this header does not assume the presence of a + user agent. That is, it can be used to bind C code to scripting + environments outside of the context of a user agent. + + However, the normal use of the this API is in the context of a + scripting environment running in a browser or other user agent. + In particular it is used to support the extended Netscape + script-ability API for plugins (NP-SAP). NP-SAP is an extension + of the Netscape plugin API. As such we have adopted the use of + the "NP" prefix for this API. + + The following NP{N|P}Variables were added to the Netscape plugin + API (in npapi.h): + + NPNVWindowNPObject + NPNVPluginElementNPObject + NPPVpluginScriptableNPObject + + These variables are exposed through NPN_GetValue() and + NPP_GetValue() (respectively) and are used to establish the + initial binding between the user agent and native code. The DOM + objects in the user agent can be examined and manipulated using + the NPN_ functions that operate on NPObjects described in this + header. + + To the extent possible the assumptions about the scripting + language used by the scripting environment have been minimized. +*/ + +#define NP_BEGIN_MACRO do { +#define NP_END_MACRO \ + } \ + while (0) + +/* + Objects (non-primitive data) passed between 'C' and script is + always wrapped in an NPObject. The 'interface' of an NPObject is + described by an NPClass. +*/ +typedef struct NPObject NPObject; +typedef struct NPClass NPClass; + +typedef char NPUTF8; +typedef struct _NPString { + const NPUTF8* UTF8Characters; + uint32_t UTF8Length; +} NPString; + +typedef enum { + NPVariantType_Void, + NPVariantType_Null, + NPVariantType_Bool, + NPVariantType_Int32, + NPVariantType_Double, + NPVariantType_String, + NPVariantType_Object +} NPVariantType; + +typedef struct _NPVariant { + NPVariantType type; + union { + bool boolValue; + int32_t intValue; + double doubleValue; + NPString stringValue; + NPObject* objectValue; + } value; +} NPVariant; + +/* + NPN_ReleaseVariantValue is called on all 'out' parameters + references. Specifically it is to be called on variants that own + their value, as is the case with all non-const NPVariant* + arguments after a successful call to any methods (except this one) + in this API. + + After calling NPN_ReleaseVariantValue, the type of the variant + will be NPVariantType_Void. +*/ +void NPN_ReleaseVariantValue(NPVariant* variant); + +#define NPVARIANT_IS_VOID(_v) ((_v).type == NPVariantType_Void) +#define NPVARIANT_IS_NULL(_v) ((_v).type == NPVariantType_Null) +#define NPVARIANT_IS_BOOLEAN(_v) ((_v).type == NPVariantType_Bool) +#define NPVARIANT_IS_INT32(_v) ((_v).type == NPVariantType_Int32) +#define NPVARIANT_IS_DOUBLE(_v) ((_v).type == NPVariantType_Double) +#define NPVARIANT_IS_STRING(_v) ((_v).type == NPVariantType_String) +#define NPVARIANT_IS_OBJECT(_v) ((_v).type == NPVariantType_Object) + +#define NPVARIANT_TO_BOOLEAN(_v) ((_v).value.boolValue) +#define NPVARIANT_TO_INT32(_v) ((_v).value.intValue) +#define NPVARIANT_TO_DOUBLE(_v) ((_v).value.doubleValue) +#define NPVARIANT_TO_STRING(_v) ((_v).value.stringValue) +#define NPVARIANT_TO_OBJECT(_v) ((_v).value.objectValue) + +#define VOID_TO_NPVARIANT(_v) \ + NP_BEGIN_MACRO(_v).type = NPVariantType_Void; \ + (_v).value.objectValue = NULL; \ + NP_END_MACRO + +#define NULL_TO_NPVARIANT(_v) \ + NP_BEGIN_MACRO(_v).type = NPVariantType_Null; \ + (_v).value.objectValue = NULL; \ + NP_END_MACRO + +#define BOOLEAN_TO_NPVARIANT(_val, _v) \ + NP_BEGIN_MACRO(_v).type = NPVariantType_Bool; \ + (_v).value.boolValue = !!(_val); \ + NP_END_MACRO + +#define INT32_TO_NPVARIANT(_val, _v) \ + NP_BEGIN_MACRO(_v).type = NPVariantType_Int32; \ + (_v).value.intValue = _val; \ + NP_END_MACRO + +#define DOUBLE_TO_NPVARIANT(_val, _v) \ + NP_BEGIN_MACRO(_v).type = NPVariantType_Double; \ + (_v).value.doubleValue = _val; \ + NP_END_MACRO + +#define STRINGZ_TO_NPVARIANT(_val, _v) \ + NP_BEGIN_MACRO(_v).type = NPVariantType_String; \ + NPString str = {_val, (uint32_t)(strlen(_val))}; \ + (_v).value.stringValue = str; \ + NP_END_MACRO + +#define STRINGN_TO_NPVARIANT(_val, _len, _v) \ + NP_BEGIN_MACRO(_v).type = NPVariantType_String; \ + NPString str = {_val, (uint32_t)(_len)}; \ + (_v).value.stringValue = str; \ + NP_END_MACRO + +#define OBJECT_TO_NPVARIANT(_val, _v) \ + NP_BEGIN_MACRO(_v).type = NPVariantType_Object; \ + (_v).value.objectValue = _val; \ + NP_END_MACRO + +/* + Type mappings (JavaScript types have been used for illustration + purposes): + + JavaScript to C (NPVariant with type:) + undefined NPVariantType_Void + null NPVariantType_Null + Boolean NPVariantType_Bool + Number NPVariantType_Double or NPVariantType_Int32 + String NPVariantType_String + Object NPVariantType_Object + + C (NPVariant with type:) to JavaScript + NPVariantType_Void undefined + NPVariantType_Null null + NPVariantType_Bool Boolean + NPVariantType_Int32 Number + NPVariantType_Double Number + NPVariantType_String String + NPVariantType_Object Object +*/ + +typedef void* NPIdentifier; + +/* + NPObjects have methods and properties. Methods and properties are + identified with NPIdentifiers. These identifiers may be reflected + in script. NPIdentifiers can be either strings or integers, IOW, + methods and properties can be identified by either strings or + integers (i.e. foo["bar"] vs foo[1]). NPIdentifiers can be + compared using ==. In case of any errors, the requested + NPIdentifier(s) will be NULL. NPIdentifier lifetime is controlled + by the browser. Plugins do not need to worry about memory management + with regards to NPIdentifiers. +*/ +NPIdentifier NPN_GetStringIdentifier(const NPUTF8* name); +void NPN_GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers); +NPIdentifier NPN_GetIntIdentifier(int32_t intid); +bool NPN_IdentifierIsString(NPIdentifier identifier); + +/* + The NPUTF8 returned from NPN_UTF8FromIdentifier SHOULD be freed. +*/ +NPUTF8* NPN_UTF8FromIdentifier(NPIdentifier identifier); + +/* + Get the integer represented by identifier. If identifier is not an + integer identifier, the behaviour is undefined. +*/ +int32_t NPN_IntFromIdentifier(NPIdentifier identifier); + +/* + NPObject behavior is implemented using the following set of + callback functions. + + The NPVariant *result argument of these functions (where + applicable) should be released using NPN_ReleaseVariantValue(). +*/ +typedef NPObject* (*NPAllocateFunctionPtr)(NPP npp, NPClass* aClass); +typedef void (*NPDeallocateFunctionPtr)(NPObject* npobj); +typedef void (*NPInvalidateFunctionPtr)(NPObject* npobj); +typedef bool (*NPHasMethodFunctionPtr)(NPObject* npobj, NPIdentifier name); +typedef bool (*NPInvokeFunctionPtr)(NPObject* npobj, NPIdentifier name, + const NPVariant* args, uint32_t argCount, + NPVariant* result); +typedef bool (*NPInvokeDefaultFunctionPtr)(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); +typedef bool (*NPHasPropertyFunctionPtr)(NPObject* npobj, NPIdentifier name); +typedef bool (*NPGetPropertyFunctionPtr)(NPObject* npobj, NPIdentifier name, + NPVariant* result); +typedef bool (*NPSetPropertyFunctionPtr)(NPObject* npobj, NPIdentifier name, + const NPVariant* value); +typedef bool (*NPRemovePropertyFunctionPtr)(NPObject* npobj, NPIdentifier name); +typedef bool (*NPEnumerationFunctionPtr)(NPObject* npobj, NPIdentifier** value, + uint32_t* count); +typedef bool (*NPConstructFunctionPtr)(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +/* + NPObjects returned by create, retain, invoke, and getProperty pass + a reference count to the caller. That is, the callee adds a + reference count which passes to the caller. It is the caller's + responsibility to release the returned object. + + NPInvokeFunctionPtr function may return 0 to indicate a void + result. + + NPInvalidateFunctionPtr is called by the scripting environment + when the native code is shutdown. Any attempt to message a + NPObject instance after the invalidate callback has been + called will result in undefined behavior, even if the native code + is still retaining those NPObject instances. (The runtime + will typically return immediately, with 0 or NULL, from an + attempt to dispatch to a NPObject, but this behavior should not + be depended upon.) + + The NPEnumerationFunctionPtr function may pass an array of + NPIdentifiers back to the caller. The callee allocs the memory of + the array using NPN_MemAlloc(), and it's the caller's responsibility + to release it using NPN_MemFree(). +*/ +struct NPClass { + uint32_t structVersion; + NPAllocateFunctionPtr allocate; + NPDeallocateFunctionPtr deallocate; + NPInvalidateFunctionPtr invalidate; + NPHasMethodFunctionPtr hasMethod; + NPInvokeFunctionPtr invoke; + NPInvokeDefaultFunctionPtr invokeDefault; + NPHasPropertyFunctionPtr hasProperty; + NPGetPropertyFunctionPtr getProperty; + NPSetPropertyFunctionPtr setProperty; + NPRemovePropertyFunctionPtr removeProperty; + NPEnumerationFunctionPtr enumerate; + NPConstructFunctionPtr construct; +}; + +#define NP_CLASS_STRUCT_VERSION 3 + +#define NP_CLASS_STRUCT_VERSION_ENUM 2 +#define NP_CLASS_STRUCT_VERSION_CTOR 3 + +#define NP_CLASS_STRUCT_VERSION_HAS_ENUM(npclass) \ + ((npclass)->structVersion >= NP_CLASS_STRUCT_VERSION_ENUM) + +#define NP_CLASS_STRUCT_VERSION_HAS_CTOR(npclass) \ + ((npclass)->structVersion >= NP_CLASS_STRUCT_VERSION_CTOR) + +struct NPObject { + NPClass* _class; + uint32_t referenceCount; + /* + * Additional space may be allocated here by types of NPObjects + */ +}; + +/* + If the class has an allocate function, NPN_CreateObject invokes + that function, otherwise a NPObject is allocated and + returned. This method will initialize the referenceCount member of + the NPObject to 1. +*/ +NPObject* NPN_CreateObject(NPP npp, NPClass* aClass); + +/* + Increment the NPObject's reference count. +*/ +NPObject* NPN_RetainObject(NPObject* npobj); + +/* + Decremented the NPObject's reference count. If the reference + count goes to zero, the class's destroy function is invoke if + specified, otherwise the object is freed directly. +*/ +void NPN_ReleaseObject(NPObject* npobj); + +/* + Functions to access script objects represented by NPObject. + + Calls to script objects are synchronous. If a function returns a + value, it will be supplied via the result NPVariant + argument. Successful calls will return true, false will be + returned in case of an error. + + Calls made from plugin code to script must be made from the thread + on which the plugin was initialized. +*/ + +bool NPN_Invoke(NPP npp, NPObject* npobj, NPIdentifier methodName, + const NPVariant* args, uint32_t argCount, NPVariant* result); +bool NPN_InvokeDefault(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +bool NPN_Evaluate(NPP npp, NPObject* npobj, NPString* script, + NPVariant* result); +bool NPN_GetProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName, + NPVariant* result); +bool NPN_SetProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName, + const NPVariant* value); +bool NPN_RemoveProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName); +bool NPN_HasProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName); +bool NPN_HasMethod(NPP npp, NPObject* npobj, NPIdentifier methodName); +bool NPN_Enumerate(NPP npp, NPObject* npobj, NPIdentifier** identifier, + uint32_t* count); +bool NPN_Construct(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +/* + NPN_SetException may be called to trigger a script exception upon + return from entry points into NPObjects. Typical usage: + + NPN_SetException (npobj, message); +*/ +void NPN_SetException(NPObject* npobj, const NPUTF8* message); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dom/plugins/base/nptypes.h b/dom/plugins/base/nptypes.h new file mode 100644 index 0000000000..02e32ed99c --- /dev/null +++ b/dom/plugins/base/nptypes.h @@ -0,0 +1,89 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nptypes_h_ +#define nptypes_h_ + +/* + * Header file for ensuring that C99 types ([u]int32_t, [u]int64_t and bool) and + * true/false macros are available. + */ + +#if defined(WIN32) +/* + * Win32 and OS/2 don't know C99, so define [u]int_16/32/64 here. The bool + * is predefined tho, both in C and C++. + */ +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; +#elif defined(_AIX) || defined(__sun) || defined(__osf__) || defined(IRIX) || \ + defined(HPUX) +/* + * AIX and SunOS ship a inttypes.h header that defines [u]int32_t, + * but not bool for C. + */ +# include <inttypes.h> + +# ifndef __cplusplus +typedef int bool; +# define true 1 +# define false 0 +# endif +#elif defined(bsdi) || defined(FREEBSD) || defined(OPENBSD) +/* + * BSD/OS, FreeBSD, and OpenBSD ship sys/types.h that define int32_t and + * u_int32_t. + */ +# include <sys/types.h> + +/* + * BSD/OS ships no header that defines uint32_t, nor bool (for C) + */ +# if defined(bsdi) +typedef u_int32_t uint32_t; +typedef u_int64_t uint64_t; + +# if !defined(__cplusplus) +typedef int bool; +# define true 1 +# define false 0 +# endif +# else +/* + * FreeBSD and OpenBSD define uint32_t and bool. + */ +# include <inttypes.h> +# include <stdbool.h> +# endif +#elif defined(BEOS) +# include <inttypes.h> +#else +/* + * For those that ship a standard C99 stdint.h header file, include + * it. Can't do the same for stdbool.h tho, since some systems ship + * with a stdbool.h file that doesn't compile! + */ +# include <stdint.h> + +# ifndef __cplusplus +# if !defined(__GNUC__) || (__GNUC__ > 2 || __GNUC_MINOR__ > 95) +# include <stdbool.h> +# else +/* + * GCC 2.91 can't deal with a typedef for bool, but a #define + * works. + */ +# define bool int +# define true 1 +# define false 0 +# endif +# endif +#endif + +#endif /* nptypes_h_ */ diff --git a/dom/plugins/base/nsIHTTPHeaderListener.idl b/dom/plugins/base/nsIHTTPHeaderListener.idl new file mode 100644 index 0000000000..4a2f34a798 --- /dev/null +++ b/dom/plugins/base/nsIHTTPHeaderListener.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * The nsIHTTPHeaderListener interface allows plugin authors to + * access HTTP Response headers after issuing an + * nsIPluginHost::{GetURL,PostURL}() call. <P> + */ + +[scriptable, uuid(ea51e0b8-871c-4b85-92da-6f400394c5ec)] +interface nsIHTTPHeaderListener : nsISupports +{ + /** + * Called for each HTTP Response header. + * NOTE: You must copy the values of the params. + */ + void newResponseHeader(in string headerName, in string headerValue); + + /** + * Called once for the HTTP Response status line. + * Value does NOT include a terminating newline. + * NOTE: You must copy this value. + */ + void statusLine(in string line); +}; diff --git a/dom/plugins/base/nsIPluginDocument.idl b/dom/plugins/base/nsIPluginDocument.idl new file mode 100644 index 0000000000..f91aac621d --- /dev/null +++ b/dom/plugins/base/nsIPluginDocument.idl @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIStreamListener.idl" + +[uuid(a93a0f0f-24f0-4206-a21b-56a43dcbdd88)] +interface nsIPluginDocument : nsISupports +{ + /** + * Causes the plugin to print in full-page mode + */ + void print(); +}; diff --git a/dom/plugins/base/nsIPluginHost.idl b/dom/plugins/base/nsIPluginHost.idl new file mode 100644 index 0000000000..a459a006c2 --- /dev/null +++ b/dom/plugins/base/nsIPluginHost.idl @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nspluginroot.idl" +#include "nsISupports.idl" +#include "nsIPluginTag.idl" + +%{C++ +#define MOZ_PLUGIN_HOST_CONTRACTID \ + "@mozilla.org/plugin/host;1" +%} + +[scriptable, function, uuid(9c311778-7c2c-4ad8-b439-b8a2786a20dd)] +interface nsIClearSiteDataCallback : nsISupports +{ + /** + * callback with the result from a call to clearSiteData + */ + void callback(in nsresult rv); +}; + +[scriptable, uuid(f938f5ba-7093-42cd-a559-af8039d99204)] +interface nsIPluginHost : nsISupports +{ + /** + * Causes the plugins directory to be searched again for new plugin + * libraries. + */ + void reloadPlugins(); + + Array<nsIPluginTag> getPluginTags(); + + /* + * Flags for use with clearSiteData. + * + * FLAG_CLEAR_ALL: clear all data associated with a site. + * FLAG_CLEAR_CACHE: clear cached data that can be retrieved again without + * loss of functionality. To be used out of concern for + * space and not necessarily privacy. + */ + const uint32_t FLAG_CLEAR_ALL = 0; + const uint32_t FLAG_CLEAR_CACHE = 1; + + /* + * For use with Get*ForType functions + */ + const uint32_t EXCLUDE_NONE = 0; + const uint32_t EXCLUDE_DISABLED = 1 << 0; + const uint32_t EXCLUDE_FAKE = 1 << 1; + + /* + * Clear site data for a given plugin. + * + * @param plugin: the plugin to clear data for, such as one returned by + * nsIPluginHost.getPluginTags. + * @param domain: the domain to clear data for. If this argument is null, + * clear data for all domains. Otherwise, it must be a domain + * only (not a complete URI or IRI). The base domain for the + * given site will be determined; any data for the base domain + * or its subdomains will be cleared. + * @param flags: a flag value defined above. + * @param maxAge: the maximum age in seconds of data to clear, inclusive. If + * maxAge is 0, no data is cleared; if it is -1, all data is + * cleared. + * + * @throws NS_ERROR_INVALID_ARG if the domain argument is malformed. + * @throws NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED if maxAge is a value other + * than -1 and the plugin does not support clearing by timerange in + * general or for that particular site and/or flag combination. + */ + void clearSiteData(in nsIPluginTag plugin, in AUTF8String domain, + in uint64_t flags, in int64_t maxAge, + in nsIClearSiteDataCallback callback); + + /* + * Determine if a plugin has stored data for a given site. + * + * @param plugin: the plugin to query, such as one returned by + * nsIPluginHost.getPluginTags. + * @param domain: the domain to test. If this argument is null, test if data + * is stored for any site. The base domain for the given domain + * will be determined; if any data for the base domain or its + * subdomains is found, return true. + */ + boolean siteHasData(in nsIPluginTag plugin, in AUTF8String domain); + + /** + * Get the "permission string" for the plugin. This is a string that can be + * passed to the permission manager to see whether the plugin is allowed to + * run, for example. This will typically be based on the plugin's "nice name" + * and its blocklist state. + * + * @mimeType The MIME type we're interested in. + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + */ + ACString getPermissionStringForType(in AUTF8String mimeType, + [optional] in uint32_t excludeFlags); + + /** + * Get the "permission string" for the plugin. This is a string that can be + * passed to the permission manager to see whether the plugin is allowed to + * run, for example. This will typically be based on the plugin's "nice name" + * and its blocklist state. + * + * @tag The tage we're interested in + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + */ + ACString getPermissionStringForTag(in nsIPluginTag tag, + [optional] in uint32_t excludeFlags); + + /** + * Get the nsIPluginTag for this MIME type. This method works with both + * enabled and disabled/blocklisted plugins, but an enabled plugin will + * always be returned if available. + * + * A fake plugin tag, if one exists and is available, will be returned in + * preference to NPAPI plugin tags unless excluded by the excludeFlags. + * + * @mimeType The MIME type we're interested in. + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + * + * @throws NS_ERROR_NOT_AVAILABLE if no plugin is available for this MIME + * type. + */ + nsIPluginTag getPluginTagForType(in AUTF8String mimeType, + [optional] in uint32_t excludeFlags); + + /** + * Get the nsIPluginTag enabled state for this MIME type. See + * nsIPluginTag.enabledState. + * + * @mimeType The MIME type we're interested in. + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + */ + unsigned long getStateForType(in AUTF8String mimeType, + [optional] in uint32_t excludeFlags); + + /** + * Get the blocklist state for a MIME type. See nsIPluginTag.blocklistState. + * + * @mimeType The MIME type we're interested in. + * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE. + */ + uint32_t getBlocklistStateForType(in AUTF8String aMimeType, + [optional] in uint32_t excludeFlags); + + /** + * Create a fake plugin tag, register it, and return it. The argument is a + * FakePluginTagInit dictionary. See documentation in + * FakePluginTagInit.webidl for what it should look like. Will throw + * NS_ERROR_UNEXPECTED if there is already a fake plugin registered with the + * given handler URI. + */ + [implicit_jscontext] + nsIFakePluginTag registerFakePlugin(in jsval initDictionary); + + /** + * Create a fake plugin tag without registering it. + * + * Only for use in tests. + */ + [implicit_jscontext] + nsIFakePluginTag createFakePlugin(in jsval initDictionary); + + /** + * Get a reference to an existing fake plugin tag for the given MIME type, if + * any. Can return null. + */ + nsIFakePluginTag getFakePlugin(in AUTF8String mimeType); + + /** + * Unregister a fake plugin. The argument can be the .handlerURI.spec of an + * existing nsIFakePluginTag, or just a known handler URI string that was + * passed in the FakePluginTagInit when registering. + */ + void unregisterFakePlugin(in AUTF8String handlerURI); +}; diff --git a/dom/plugins/base/nsIPluginInputStream.idl b/dom/plugins/base/nsIPluginInputStream.idl new file mode 100644 index 0000000000..8868384417 --- /dev/null +++ b/dom/plugins/base/nsIPluginInputStream.idl @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" +#include "nspluginroot.idl" + +/** + * The nsIPluginInputStream interface ... + */ +[uuid(af160530-542a-11d2-8164-006008119d7a)] +interface nsIPluginInputStream : nsIInputStream { + /** + * Corresponds to NPStream's lastmodified field.) + */ + void getLastModified(out unsigned long aResult); + + void requestRead(out NPByteRange aRangeList); +}; diff --git a/dom/plugins/base/nsIPluginInstanceOwner.idl b/dom/plugins/base/nsIPluginInstanceOwner.idl new file mode 100644 index 0000000000..0aa23d2173 --- /dev/null +++ b/dom/plugins/base/nsIPluginInstanceOwner.idl @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nspluginroot.idl" +#include "nsIInputStream.idl" + +webidl Document; + +%{C++ +#include "npapi.h" +#include "mozilla/EventForwards.h" +class nsNPAPIPluginInstance; + +enum nsPluginTagType { + nsPluginTagType_Unknown, + nsPluginTagType_Embed, + nsPluginTagType_Object +}; +%} + +[ptr] native nsNPAPIPluginInstancePtr(nsNPAPIPluginInstance); + +// Do not make this interface scriptable, because the virtual functions in C++ +// blocks will make script call the wrong functions. +[uuid(7d65452e-c167-4cba-a0e3-ddc61bdde8c3)] +interface nsIPluginInstanceOwner : nsISupports +{ + /** + * Let the owner know what its instance is + */ + void setInstance(in nsNPAPIPluginInstancePtr aInstance); + + /** + * Get the instance associated with this owner. + */ + [notxpcom, nostdcall] nsNPAPIPluginInstancePtr getInstance(); + + /** + * Get a handle to the window structure of the owner. + * This pointer cannot be made persistent by the caller. + */ + void getWindow(in NPWindowStarRef aWindow); + + /** + * Get the display mode for the plugin instance. + */ + readonly attribute int32_t mode; + + /** + * Create a place for the plugin to live in the owner's + * environment. this may or may not create a window + * depending on the windowless state of the plugin instance. + */ + void createWidget(); + +%{C++ + /** + * Called when there is a valid target so that the proper + * frame can be updated with new content. will not be called + * with nullptr aTarget. + */ + NS_IMETHOD + GetURL(const char *aURL, const char *aTarget, + nsIInputStream *aPostStream, + void *aHeadersData, uint32_t aHeadersDataLen, + bool aDoCheckLoadURIChecks) = 0; +%} + + /** + * Get the associated document. + */ + readonly attribute Document document; + + /** + * Invalidate the rectangle + */ + void invalidateRect(in NPRectPtr aRect); + + /** + * Invalidate the region + */ + void invalidateRegion(in NPRegion aRegion); + + /** + * Have the plugin recomposited. + */ + void redrawPlugin(); + + /** + * Get NetscapeWindow, corresponds to NPNVnetscapeWindow + */ + void getNetscapeWindow(in voidPtr aValue); + + /** + * Convert between plugin, window, and screen coordinate spaces. + */ +%{C++ + virtual NPBool ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace, + double *destX, double *destY, NPCoordinateSpace destSpace) = 0; + virtual NPError InitAsyncSurface(NPSize *size, NPImageFormat format, + void *initData, NPAsyncSurface *surface) = 0; + virtual NPError FinalizeAsyncSurface(NPAsyncSurface *surface) = 0; + virtual void SetCurrentAsyncSurface(NPAsyncSurface *surface, NPRect *changed) = 0; +%} + + void setEventModel(in int32_t eventModel); + + /** + * Call NPP_SetWindow on the plugin. + */ + void callSetWindow(); + + /** + * Get the contents scale factor for the screen the plugin is + * drawn on. + */ + double getContentsScaleFactor(); +}; diff --git a/dom/plugins/base/nsIPluginTag.idl b/dom/plugins/base/nsIPluginTag.idl new file mode 100644 index 0000000000..68f8da1dc8 --- /dev/null +++ b/dom/plugins/base/nsIPluginTag.idl @@ -0,0 +1,97 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +[builtinclass, scriptable, uuid(5daa99d5-265a-4397-b429-c943803e2619)] +interface nsIPluginTag : nsISupports +{ + // enabledState is stored as one of the following as an integer in prefs, + // so if new states are added, they must not renumber the existing states. + const unsigned long STATE_DISABLED = 0; + const unsigned long STATE_CLICKTOPLAY = 1; + const unsigned long STATE_ENABLED = 2; + + readonly attribute AUTF8String description; + readonly attribute AUTF8String filename; + readonly attribute AUTF8String fullpath; + readonly attribute AUTF8String version; + readonly attribute AUTF8String name; + + // The 'nice' name of this plugin, e.g. 'flash' 'java' + readonly attribute AUTF8String niceName; + + /** + * true only if this plugin is "hardblocked" and cannot be enabled. + */ + // FIXME-jsplugins QI to fakePluginTag possible + // FIXME-jsplugins implement missing + tests (whatever that means) + [infallible] + readonly attribute boolean blocklisted; + + /** + * true if the state is non-default and locked, false otherwise. + */ + [infallible] + readonly attribute boolean isEnabledStateLocked; + + // If this plugin is capable of being used (not disabled, blocklisted, etc) + [infallible] + readonly attribute boolean active; + + // Get a specific nsIBlocklistService::STATE_* + [infallible] + readonly attribute unsigned long blocklistState; + + [infallible] + readonly attribute boolean disabled; + [infallible] + readonly attribute boolean clicktoplay; + [infallible] + readonly attribute boolean loaded; + // See the STATE_* values above. + attribute unsigned long enabledState; + + readonly attribute PRTime lastModifiedTime; + + readonly attribute boolean isFlashPlugin; + + Array<AUTF8String> getMimeTypes(); + Array<AUTF8String> getMimeDescriptions(); + Array<AUTF8String> getExtensions(); + + /** + * An id for this plugin. 0 is a valid id. + */ + readonly attribute unsigned long id; +}; + +/** + * An interface representing a "fake" plugin: one implemented in JavaScript, not + * as a NPAPI plug-in. See nsIPluginHost.registerFakePlugin and the + * documentation for the FakePluginTagInit dictionary. + */ +[builtinclass, scriptable, uuid(6d22c968-226d-4156-b230-da6ad6bbf6e8)] +interface nsIFakePluginTag : nsIPluginTag +{ + /** + * The URI that should be loaded into the tag (as a frame) to handle the + * plugin. Note that the original data/src value for the plugin is not loaded + * and will need to be requested by the handler via XHR or similar if desired. + */ + readonly attribute nsIURI handlerURI; + + /** + * Optional script to run in a sandbox when instantiating a plugin. If this + * value is an empty string then no such script will be run. + * The script runs in a sandbox with system principal in the process that + * contains the element that instantiates the plugin (ie the EMBED or OBJECT + * element). The sandbox global has a 'pluginElement' property that the script + * can use to access the element that instantiates the plugin. + */ + readonly attribute AString sandboxScript; +}; diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp new file mode 100644 index 0000000000..f4d38002fd --- /dev/null +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -0,0 +1,2183 @@ +/* -*- Mode: C++; tab-width: 2; 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 "base/basictypes.h" + +#include "jsfriendapi.h" + +#include "GeckoProfiler.h" +#include "mozilla/UniquePtr.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsJSNPRuntime.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsIGlobalObject.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsDOMJSUtils.h" +#include "nsJSUtils.h" +#include "mozilla/dom/Document.h" +#include "xpcpublic.h" +#include "nsIContent.h" +#include "nsPluginInstanceOwner.h" +#include "nsWrapperCacheInlines.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/GCHashTable.h" +#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetPrivate, JS::SetPrivate +#include "js/Symbol.h" +#include "js/TracingAPI.h" +#include "js/Wrapper.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/ScriptSettings.h" + +#define NPRUNTIME_JSCLASS_NAME "NPObject JS wrapper class" + +using namespace mozilla::plugins::parent; +using namespace mozilla; + +#include "mozilla/plugins/PluginScriptableObjectParent.h" +using mozilla::plugins::ParentNPObject; +using mozilla::plugins::PluginScriptableObjectParent; + +struct JSObjWrapperHasher { + typedef nsJSObjWrapperKey Key; + typedef Key Lookup; + + static uint32_t hash(const Lookup& l) { + return js::MovableCellHasher<JS::Heap<JSObject*>>::hash(l.mJSObj) ^ + HashGeneric(l.mNpp); + } + + static bool match(const Key& k, const Lookup& l) { + return js::MovableCellHasher<JS::Heap<JSObject*>>::match(k.mJSObj, + l.mJSObj) && + k.mNpp == l.mNpp; + } +}; + +namespace JS { +template <> +struct GCPolicy<nsJSObjWrapper*> { + static void trace(JSTracer* trc, nsJSObjWrapper** wrapper, const char* name) { + MOZ_ASSERT(wrapper); + MOZ_ASSERT(*wrapper); + (*wrapper)->trace(trc); + } + + static bool isValid(const nsJSObjWrapper*& wrapper) { return true; } +}; +} // namespace JS + +class NPObjWrapperHashEntry : public PLDHashEntryHdr { + public: + NPObject* mNPObj; // Must be the first member for the PLDHash stubs to work + JS::TenuredHeap<JSObject*> mJSObj; + NPP mNpp; +}; + +// Hash of JSObject wrappers that wraps JSObjects as NPObjects. There +// will be one wrapper per JSObject per plugin instance, i.e. if two +// plugins access the JSObject x, two wrappers for x will be +// created. This is needed to be able to properly drop the wrappers +// when a plugin is torn down in case there's a leak in the plugin (we +// don't want to leak the world just because a plugin leaks an +// NPObject). +typedef JS::GCHashMap<nsJSObjWrapperKey, nsJSObjWrapper*, JSObjWrapperHasher, + js::SystemAllocPolicy> + JSObjWrapperTable; +static UniquePtr<JSObjWrapperTable> sJSObjWrappers; + +// Whether it's safe to iterate sJSObjWrappers. Set to true when sJSObjWrappers +// has been initialized and is not currently being enumerated. +static bool sJSObjWrappersAccessible = false; + +// Hash of NPObject wrappers that wrap NPObjects as JSObjects. +static PLDHashTable* sNPObjWrappers; + +// Global wrapper count. This includes JSObject wrappers *and* +// NPObject wrappers. When this count goes to zero, there are no more +// wrappers and we can kill off hash tables etc. +static int32_t sWrapperCount; + +static bool sCallbackIsRegistered = false; + +static nsTArray<NPObject*>* sDelayedReleases; + +namespace { + +inline bool NPObjectIsOutOfProcessProxy(NPObject* obj) { + return obj->_class == PluginScriptableObjectParent::GetClass(); +} + +} // namespace + +// Helper class that suppresses any JS exceptions that were thrown while +// the plugin executed JS, if the nsJSObjWrapper has a destroy pending. +// Note that this class is the product (vestige?) of a long evolution in how +// error reporting worked, and hence the mIsDestroyPending check, and hence this +// class in general, may or may not actually be necessary. + +class MOZ_STACK_CLASS AutoJSExceptionSuppressor { + public: + AutoJSExceptionSuppressor(dom::AutoEntryScript& aes, nsJSObjWrapper* aWrapper) + : mAes(aes), mIsDestroyPending(aWrapper->mDestroyPending) {} + + ~AutoJSExceptionSuppressor() { + if (mIsDestroyPending) { + mAes.ClearException(); + } + } + + protected: + dom::AutoEntryScript& mAes; + bool mIsDestroyPending; +}; + +NPClass nsJSObjWrapper::sJSObjWrapperNPClass = { + NP_CLASS_STRUCT_VERSION, nsJSObjWrapper::NP_Allocate, + nsJSObjWrapper::NP_Deallocate, nsJSObjWrapper::NP_Invalidate, + nsJSObjWrapper::NP_HasMethod, nsJSObjWrapper::NP_Invoke, + nsJSObjWrapper::NP_InvokeDefault, nsJSObjWrapper::NP_HasProperty, + nsJSObjWrapper::NP_GetProperty, nsJSObjWrapper::NP_SetProperty, + nsJSObjWrapper::NP_RemoveProperty, nsJSObjWrapper::NP_Enumerate, + nsJSObjWrapper::NP_Construct}; + +class NPObjWrapperProxyHandler : public js::BaseProxyHandler { + static const char family; + + public: + static const NPObjWrapperProxyHandler singleton; + + constexpr NPObjWrapperProxyHandler() : BaseProxyHandler(&family) {} + + bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult& result) const override { + ::JS_ReportErrorASCII(cx, + "Trying to add unsupported property on NPObject!"); + return false; + } + + bool getPrototypeIfOrdinary( + JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary, + JS::MutableHandle<JSObject*> proto) const override { + *isOrdinary = true; + proto.set(js::GetStaticPrototype(proxy)); + return true; + } + + bool isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy, + bool* extensible) const override { + // Needs to be extensible so nsObjectLoadingContent can mutate our + // __proto__. + *extensible = true; + return true; + } + + bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::ObjectOpResult& result) const override { + result.succeed(); + return true; + } + + bool getOwnPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc) const override; + + bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::MutableHandleVector<jsid> properties) const override; + + bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + JS::ObjectOpResult& result) const override; + + bool get(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<JS::Value> receiver, JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp) const override; + + bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + JS::Handle<JS::Value> vp, JS::Handle<JS::Value> receiver, + JS::ObjectOpResult& result) const override; + + bool isCallable(JSObject* obj) const override { return true; } + bool call(JSContext* cx, JS::Handle<JSObject*> proxy, + const JS::CallArgs& args) const override; + + bool isConstructor(JSObject* obj) const override { return true; } + bool construct(JSContext* cx, JS::Handle<JSObject*> proxy, + const JS::CallArgs& args) const override; + + bool finalizeInBackground(const JS::Value& priv) const override { + return false; + } + void finalize(JSFreeOp* fop, JSObject* proxy) const override; + + size_t objectMoved(JSObject* obj, JSObject* old) const override; +}; + +const char NPObjWrapperProxyHandler::family = 0; +const NPObjWrapperProxyHandler NPObjWrapperProxyHandler::singleton; + +static bool NPObjWrapper_Resolve(JSContext* cx, JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, bool* resolved, + JS::MutableHandle<JSObject*> method); + +static bool NPObjWrapper_toPrimitive(JSContext* cx, unsigned argc, + JS::Value* vp); + +static bool CreateNPObjectMember(NPP npp, JSContext* cx, + JS::Handle<JSObject*> obj, NPObject* npobj, + JS::Handle<jsid> id, + NPVariant* getPropertyResult, + JS::MutableHandle<JS::Value> vp); + +const JSClass sNPObjWrapperProxyClass = + PROXY_CLASS_DEF(NPRUNTIME_JSCLASS_NAME, JSCLASS_HAS_RESERVED_SLOTS(1)); + +typedef struct NPObjectMemberPrivate { + JS::Heap<JSObject*> npobjWrapper; + JS::Heap<JS::Value> fieldValue; + JS::Heap<jsid> methodName; + NPP npp = nullptr; +} NPObjectMemberPrivate; + +static void NPObjectMember_Finalize(JSFreeOp* fop, JSObject* obj); + +static bool NPObjectMember_Call(JSContext* cx, unsigned argc, JS::Value* vp); + +static void NPObjectMember_Trace(JSTracer* trc, JSObject* obj); + +static bool NPObjectMember_toPrimitive(JSContext* cx, unsigned argc, + JS::Value* vp); + +static const JSClassOps sNPObjectMemberClassOps = {nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + NPObjectMember_Finalize, + NPObjectMember_Call, + nullptr, + nullptr, + NPObjectMember_Trace}; + +static const JSClass sNPObjectMemberClass = { + "NPObject Ambiguous Member class", + JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE, + &sNPObjectMemberClassOps}; + +static void OnWrapperDestroyed(); + +static void TraceJSObjWrappers(JSTracer* trc, void* data) { + if (sJSObjWrappers) { + sJSObjWrappers->trace(trc); + } +} + +static void DelayedReleaseGCCallback(JSGCStatus status) { + if (JSGC_END == status) { + // Take ownership of sDelayedReleases and null it out now. The + // _releaseobject call below can reenter GC and double-free these objects. + UniquePtr<nsTArray<NPObject*>> delayedReleases(sDelayedReleases); + sDelayedReleases = nullptr; + + if (delayedReleases) { + for (uint32_t i = 0; i < delayedReleases->Length(); ++i) { + NPObject* obj = (*delayedReleases)[i]; + if (obj) _releaseobject(obj); + OnWrapperDestroyed(); + } + } + } +} + +static bool RegisterGCCallbacks() { + if (sCallbackIsRegistered) { + return true; + } + + // Register a callback to trace wrapped JSObjects. + JSContext* cx = dom::danger::GetJSContext(); + if (!JS_AddExtraGCRootsTracer(cx, TraceJSObjWrappers, nullptr)) { + return false; + } + + // Register our GC callback to perform delayed destruction of finalized + // NPObjects. + xpc::AddGCCallback(DelayedReleaseGCCallback); + + sCallbackIsRegistered = true; + + return true; +} + +static void UnregisterGCCallbacks() { + MOZ_ASSERT(sCallbackIsRegistered); + + // Remove tracing callback. + JSContext* cx = dom::danger::GetJSContext(); + JS_RemoveExtraGCRootsTracer(cx, TraceJSObjWrappers, nullptr); + + // Remove delayed destruction callback. + if (sCallbackIsRegistered) { + xpc::RemoveGCCallback(DelayedReleaseGCCallback); + sCallbackIsRegistered = false; + } +} + +static bool CreateJSObjWrapperTable() { + MOZ_ASSERT(!sJSObjWrappersAccessible); + MOZ_ASSERT(!sJSObjWrappers); + + if (!RegisterGCCallbacks()) { + return false; + } + + sJSObjWrappers = MakeUnique<JSObjWrapperTable>(); + sJSObjWrappersAccessible = true; + return true; +} + +static void DestroyJSObjWrapperTable() { + MOZ_ASSERT(sJSObjWrappersAccessible); + MOZ_ASSERT(sJSObjWrappers); + MOZ_ASSERT(sJSObjWrappers->count() == 0); + + // No more wrappers. Delete the table. + sJSObjWrappers = nullptr; + sJSObjWrappersAccessible = false; +} + +static bool CreateNPObjWrapperTable() { + MOZ_ASSERT(!sNPObjWrappers); + + if (!RegisterGCCallbacks()) { + return false; + } + + sNPObjWrappers = + new PLDHashTable(PLDHashTable::StubOps(), sizeof(NPObjWrapperHashEntry)); + return true; +} + +static void DestroyNPObjWrapperTable() { + MOZ_ASSERT(sNPObjWrappers->EntryCount() == 0); + + delete sNPObjWrappers; + sNPObjWrappers = nullptr; +} + +static void OnWrapperCreated() { ++sWrapperCount; } + +static void OnWrapperDestroyed() { + NS_ASSERTION(sWrapperCount, "Whaaa, unbalanced created/destroyed calls!"); + + if (--sWrapperCount == 0) { + if (sJSObjWrappersAccessible) { + DestroyJSObjWrapperTable(); + } + + if (sNPObjWrappers) { + // No more wrappers, and our hash was initialized. Finish the + // hash to prevent leaking it. + DestroyNPObjWrapperTable(); + } + + UnregisterGCCallbacks(); + } +} + +namespace mozilla::plugins::parent { + +static nsIGlobalObject* GetGlobalObject(NPP npp) { + NS_ENSURE_TRUE(npp, nullptr); + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + NS_ENSURE_TRUE(inst, nullptr); + + RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner(); + NS_ENSURE_TRUE(owner, nullptr); + + nsCOMPtr<dom::Document> doc; + owner->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_TRUE(doc, nullptr); + + return doc->GetScopeObject(); +} + +} // namespace mozilla::plugins::parent + +static NPP LookupNPP(NPObject* npobj); + +static JS::Value NPVariantToJSVal(NPP npp, JSContext* cx, + const NPVariant* variant) { + switch (variant->type) { + case NPVariantType_Void: + return JS::UndefinedValue(); + case NPVariantType_Null: + return JS::NullValue(); + case NPVariantType_Bool: + return JS::BooleanValue(NPVARIANT_TO_BOOLEAN(*variant)); + case NPVariantType_Int32: { + // Don't use INT_TO_JSVAL directly to prevent bugs when dealing + // with ints larger than what fits in a integer JS::Value. + return ::JS_NumberValue(NPVARIANT_TO_INT32(*variant)); + } + case NPVariantType_Double: { + return ::JS_NumberValue(NPVARIANT_TO_DOUBLE(*variant)); + } + case NPVariantType_String: { + const NPString* s = &NPVARIANT_TO_STRING(*variant); + NS_ConvertUTF8toUTF16 utf16String(s->UTF8Characters, s->UTF8Length); + + JSString* str = + ::JS_NewUCStringCopyN(cx, utf16String.get(), utf16String.Length()); + + if (str) { + return JS::StringValue(str); + } + + break; + } + case NPVariantType_Object: { + if (npp) { + JSObject* obj = nsNPObjWrapper::GetNewOrUsed( + npp, cx, NPVARIANT_TO_OBJECT(*variant)); + + if (obj) { + return JS::ObjectValue(*obj); + } + } + + NS_ERROR("Error wrapping NPObject!"); + + break; + } + default: + NS_ERROR("Unknown NPVariant type!"); + } + + NS_ERROR("Unable to convert NPVariant to jsval!"); + + return JS::UndefinedValue(); +} + +bool JSValToNPVariant(NPP npp, JSContext* cx, const JS::Value& val, + NPVariant* variant) { + NS_ASSERTION(npp, "Must have an NPP to wrap a jsval!"); + + if (val.isPrimitive()) { + if (val.isUndefined()) { + VOID_TO_NPVARIANT(*variant); + } else if (val.isNull()) { + NULL_TO_NPVARIANT(*variant); + } else if (val.isBoolean()) { + BOOLEAN_TO_NPVARIANT(val.toBoolean(), *variant); + } else if (val.isInt32()) { + INT32_TO_NPVARIANT(val.toInt32(), *variant); + } else if (val.isDouble()) { + double d = val.toDouble(); + int i; + if (JS_DoubleIsInt32(d, &i)) { + INT32_TO_NPVARIANT(i, *variant); + } else { + DOUBLE_TO_NPVARIANT(d, *variant); + } + } else if (val.isString()) { + JSString* jsstr = val.toString(); + + nsAutoJSString str; + if (!str.init(cx, jsstr)) { + return false; + } + + uint32_t len; + char* p = ToNewUTF8String(str, &len); + + if (!p) { + return false; + } + + STRINGN_TO_NPVARIANT(p, len, *variant); + } else { + NS_ERROR("Unknown primitive type!"); + + return false; + } + + return true; + } + + // The reflected plugin object may be in another compartment if the plugin + // element has since been adopted into a new document. We don't bother + // transplanting the plugin objects, and just do a unwrap with security + // checks if we encounter one of them as an argument. If the unwrap fails, + // we run with the original wrapped object, since sometimes there are + // legitimate cases where a security wrapper ends up here (for example, + // Location objects, which are _always_ behind security wrappers). + JS::Rooted<JSObject*> obj(cx, &val.toObject()); + JS::Rooted<JSObject*> global(cx); + // CheckedUnwrapStatic is fine here; if we get a Location or WindowProxy, + // we'll just use the current global instead. + obj = js::CheckedUnwrapStatic(obj); + if (obj) { + global = JS::GetNonCCWObjectGlobal(obj); + } else { + obj = &val.toObject(); + global = JS::CurrentGlobalOrNull(cx); + } + + NPObject* npobj = nsJSObjWrapper::GetNewOrUsed(npp, obj, global); + if (!npobj) { + return false; + } + + // Pass over ownership of npobj to *variant + OBJECT_TO_NPVARIANT(npobj, *variant); + + return true; +} + +static void ThrowJSExceptionASCII(JSContext* cx, const char* message) { + const char* ex = PeekException(); + + if (ex) { + nsAutoString ucex; + + if (message) { + AppendASCIItoUTF16(mozilla::MakeStringSpan(message), ucex); + + ucex.AppendLiteral(" [plugin exception: "); + } + + AppendUTF8toUTF16(mozilla::MakeStringSpan(ex), ucex); + + if (message) { + ucex.AppendLiteral("]."); + } + + JSString* str = ::JS_NewUCStringCopyN(cx, ucex.get(), ucex.Length()); + + if (str) { + JS::Rooted<JS::Value> exn(cx, JS::StringValue(str)); + ::JS_SetPendingException(cx, exn); + } + + PopException(); + } else { + ::JS_ReportErrorASCII(cx, "%s", message); + } +} + +static bool ReportExceptionIfPending(JSContext* cx) { + const char* ex = PeekException(); + + if (!ex) { + return true; + } + + ThrowJSExceptionASCII(cx, nullptr); + + return false; +} + +nsJSObjWrapper::nsJSObjWrapper(NPP npp) + : mJSObj(nullptr), + mJSObjGlobal(nullptr), + mNpp(npp), + mDestroyPending(false) { + MOZ_COUNT_CTOR(nsJSObjWrapper); + OnWrapperCreated(); +} + +nsJSObjWrapper::~nsJSObjWrapper() { + MOZ_COUNT_DTOR(nsJSObjWrapper); + + // Invalidate first, since it relies on sJSObjWrappers. + NP_Invalidate(this); + + OnWrapperDestroyed(); +} + +// static +NPObject* nsJSObjWrapper::NP_Allocate(NPP npp, NPClass* aClass) { + NS_ASSERTION(aClass == &sJSObjWrapperNPClass, + "Huh, wrong class passed to NP_Allocate()!!!"); + + return new nsJSObjWrapper(npp); +} + +// static +void nsJSObjWrapper::NP_Deallocate(NPObject* npobj) { + // nsJSObjWrapper::~nsJSObjWrapper() will call NP_Invalidate(). + delete (nsJSObjWrapper*)npobj; +} + +// static +void nsJSObjWrapper::NP_Invalidate(NPObject* npobj) { + nsJSObjWrapper* jsnpobj = (nsJSObjWrapper*)npobj; + + if (jsnpobj && jsnpobj->mJSObj) { + if (sJSObjWrappersAccessible) { + // Remove the wrapper from the hash + nsJSObjWrapperKey key(jsnpobj->mJSObj, jsnpobj->mNpp); + JSObjWrapperTable::Ptr ptr = sJSObjWrappers->lookup(key); + MOZ_ASSERT(ptr.found()); + sJSObjWrappers->remove(ptr); + } + + // Forget our reference to the JSObject. + jsnpobj->mJSObj = nullptr; + jsnpobj->mJSObjGlobal = nullptr; + } +} + +static bool GetProperty(JSContext* cx, JSObject* objArg, NPIdentifier npid, + JS::MutableHandle<JS::Value> rval) { + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<JSObject*> obj(cx, objArg); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + return ::JS_GetPropertyById(cx, obj, id, rval); +} + +static void MarkCrossZoneNPIdentifier(JSContext* cx, NPIdentifier npid) { + JS_MarkCrossZoneId(cx, NPIdentifierToJSId(npid)); +} + +// static +bool nsJSObjWrapper::NP_HasMethod(NPObject* npobj, NPIdentifier id) { + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI HasMethod"); + JSContext* cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, "Null npobj in nsJSObjWrapper::NP_HasMethod!"); + + return false; + } + + nsJSObjWrapper* npjsobj = (nsJSObjWrapper*)npobj; + + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + MarkCrossZoneNPIdentifier(cx, id); + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + + JS::Rooted<JS::Value> v(cx); + bool ok = GetProperty(cx, npjsobj->mJSObj, id, &v); + + return ok && !v.isPrimitive() && ::JS_ObjectIsFunction(v.toObjectOrNull()); +} + +static bool doInvoke(NPObject* npobj, NPIdentifier method, + const NPVariant* args, uint32_t argCount, bool ctorCall, + NPVariant* result) { + NPP npp = NPPStack::Peek(); + + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + AutoAllowLegacyScriptExecution exemption; + + // We're about to run script via JS_CallFunctionValue, so we need an + // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec. + dom::AutoEntryScript aes(globalObject, "NPAPI doInvoke"); + JSContext* cx = aes.cx(); + + if (!npobj || !result) { + ThrowJSExceptionASCII(cx, "Null npobj, or result in doInvoke!"); + + return false; + } + + // Initialize *result + VOID_TO_NPVARIANT(*result); + + nsJSObjWrapper* npjsobj = (nsJSObjWrapper*)npobj; + + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + MarkCrossZoneNPIdentifier(cx, method); + JS::Rooted<JS::Value> fv(cx); + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + + if (method != NPIdentifier_VOID) { + if (!GetProperty(cx, jsobj, method, &fv) || + ::JS_TypeOfValue(cx, fv) != JSTYPE_FUNCTION) { + return false; + } + } else { + fv.setObject(*jsobj); + } + + // Convert args + JS::RootedVector<JS::Value> jsargs(cx); + if (!jsargs.reserve(argCount)) { + ::JS_ReportOutOfMemory(cx); + return false; + } + for (uint32_t i = 0; i < argCount; ++i) { + jsargs.infallibleAppend(NPVariantToJSVal(npp, cx, args + i)); + } + + JS::Rooted<JS::Value> v(cx); + bool ok = false; + + if (ctorCall) { + JSObject* newObj = ::JS_New(cx, jsobj, jsargs); + + if (newObj) { + v.setObject(*newObj); + ok = true; + } + } else { + ok = ::JS_CallFunctionValue(cx, jsobj, fv, jsargs, &v); + } + + if (ok) ok = JSValToNPVariant(npp, cx, v, result); + + return ok; +} + +// static +bool nsJSObjWrapper::NP_Invoke(NPObject* npobj, NPIdentifier method, + const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (method == NPIdentifier_VOID) { + return false; + } + + return doInvoke(npobj, method, args, argCount, false, result); +} + +// static +bool nsJSObjWrapper::NP_InvokeDefault(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return doInvoke(npobj, NPIdentifier_VOID, args, argCount, false, result); +} + +// static +bool nsJSObjWrapper::NP_HasProperty(NPObject* npobj, NPIdentifier npid) { + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI HasProperty"); + JSContext* cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, "Null npobj in nsJSObjWrapper::NP_HasProperty!"); + + return false; + } + + nsJSObjWrapper* npjsobj = (nsJSObjWrapper*)npobj; + bool found, ok = false; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + MarkCrossZoneNPIdentifier(cx, npid); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_HasPropertyById(cx, jsobj, id, &found); + return ok && found; +} + +// static +bool nsJSObjWrapper::NP_GetProperty(NPObject* npobj, NPIdentifier id, + NPVariant* result) { + NPP npp = NPPStack::Peek(); + + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + // We're about to run script via JS_CallFunctionValue, so we need an + // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec. + dom::AutoEntryScript aes(globalObject, "NPAPI get"); + JSContext* cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, "Null npobj in nsJSObjWrapper::NP_GetProperty!"); + + return false; + } + + nsJSObjWrapper* npjsobj = (nsJSObjWrapper*)npobj; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + MarkCrossZoneNPIdentifier(cx, id); + + JS::Rooted<JS::Value> v(cx); + return (GetProperty(cx, npjsobj->mJSObj, id, &v) && + JSValToNPVariant(npp, cx, v, result)); +} + +// static +bool nsJSObjWrapper::NP_SetProperty(NPObject* npobj, NPIdentifier npid, + const NPVariant* value) { + NPP npp = NPPStack::Peek(); + + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + // We're about to run script via JS_CallFunctionValue, so we need an + // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec. + dom::AutoEntryScript aes(globalObject, "NPAPI set"); + JSContext* cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, "Null npobj in nsJSObjWrapper::NP_SetProperty!"); + + return false; + } + + nsJSObjWrapper* npjsobj = (nsJSObjWrapper*)npobj; + bool ok = false; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsObj(cx, npjsobj->mJSObj); + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + MarkCrossZoneNPIdentifier(cx, npid); + + JS::Rooted<JS::Value> v(cx, NPVariantToJSVal(npp, cx, value)); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_SetPropertyById(cx, jsObj, id, v); + + return ok; +} + +// static +bool nsJSObjWrapper::NP_RemoveProperty(NPObject* npobj, NPIdentifier npid) { + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI RemoveProperty"); + JSContext* cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_RemoveProperty!"); + + return false; + } + + nsJSObjWrapper* npjsobj = (nsJSObjWrapper*)npobj; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::ObjectOpResult result; + JS::Rooted<JSObject*> obj(cx, npjsobj->mJSObj); + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + MarkCrossZoneNPIdentifier(cx, npid); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + if (!::JS_DeletePropertyById(cx, obj, id, result)) return false; + + if (result) { + // FIXME: See bug 425823, we shouldn't need to do this, and once + // that bug is fixed we can remove this code. + bool hasProp; + if (!::JS_HasPropertyById(cx, obj, id, &hasProp)) return false; + if (!hasProp) return true; + + // The property might have been deleted, but it got + // re-resolved, so no, it's not really deleted. + result.failCantDelete(); + } + + return result.reportError(cx, obj, id); +} + +// static +bool nsJSObjWrapper::NP_Enumerate(NPObject* npobj, NPIdentifier** idarray, + uint32_t* count) { + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI Enumerate"); + JSContext* cx = aes.cx(); + + *idarray = 0; + *count = 0; + + if (!npobj) { + ThrowJSExceptionASCII(cx, "Null npobj in nsJSObjWrapper::NP_Enumerate!"); + + return false; + } + + nsJSObjWrapper* npjsobj = (nsJSObjWrapper*)npobj; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + + JS::Rooted<JS::IdVector> ida(cx, JS::IdVector(cx)); + if (!JS_Enumerate(cx, jsobj, &ida)) { + return false; + } + + *count = ida.length(); + *idarray = (NPIdentifier*)malloc(*count * sizeof(NPIdentifier)); + if (!*idarray) { + ThrowJSExceptionASCII(cx, "Memory allocation failed for NPIdentifier!"); + return false; + } + + for (uint32_t i = 0; i < *count; i++) { + JS::Rooted<JS::Value> v(cx); + if (!JS_IdToValue(cx, ida[i], &v)) { + free(*idarray); + return false; + } + + NPIdentifier id; + if (v.isString()) { + JS::Rooted<JSString*> str(cx, v.toString()); + str = JS_AtomizeAndPinJSString(cx, str); + if (!str) { + free(*idarray); + return false; + } + id = StringToNPIdentifier(cx, str); + } else { + NS_ASSERTION(v.isInt32(), + "The element in ida must be either string or int!\n"); + id = IntToNPIdentifier(v.toInt32()); + } + + (*idarray)[i] = id; + } + + return true; +} + +// static +bool nsJSObjWrapper::NP_Construct(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return doInvoke(npobj, NPIdentifier_VOID, args, argCount, true, result); +} + +// Look up or create an NPObject that wraps the JSObject obj. + +// static +NPObject* nsJSObjWrapper::GetNewOrUsed(NPP npp, JS::Handle<JSObject*> obj, + JS::Handle<JSObject*> objGlobal) { + if (!npp) { + NS_ERROR("Null NPP passed to nsJSObjWrapper::GetNewOrUsed()!"); + + return nullptr; + } + + MOZ_ASSERT(JS_IsGlobalObject(objGlobal)); + MOZ_RELEASE_ASSERT(JS::GetCompartment(obj) == JS::GetCompartment(objGlobal)); + + // No need to enter the right compartment here as we only get the + // class and private from the JSObject, neither of which cares about + // compartments. + + if (nsNPObjWrapper::IsWrapper(obj)) { + // obj is one of our own, its private data is the NPObject we're + // looking for. + + NPObject* npobj = (NPObject*)js::GetProxyPrivate(obj).toPrivate(); + + // If the private is null, that means that the object has already been torn + // down, possible because the owning plugin was destroyed (there can be + // multiple plugins, so the fact that it was destroyed does not prevent one + // of its dead JS objects from being passed to another plugin). There's not + // much use in wrapping such a dead object, so we just return null, causing + // us to throw. + if (!npobj) return nullptr; + + if (LookupNPP(npobj) == npp) return _retainobject(npobj); + } + + if (!sJSObjWrappers) { + // No hash yet (or any more), initialize it. + if (!CreateJSObjWrapperTable()) return nullptr; + } + MOZ_ASSERT(sJSObjWrappersAccessible); + + JSObjWrapperTable::Ptr p = + sJSObjWrappers->lookup(nsJSObjWrapperKey(obj, npp)); + if (p) { + MOZ_ASSERT(p->value()); + // Found a live nsJSObjWrapper, return it. + + return _retainobject(p->value()); + } + + // No existing nsJSObjWrapper, create one. + + nsJSObjWrapper* wrapper = + (nsJSObjWrapper*)_createobject(npp, &sJSObjWrapperNPClass); + + if (!wrapper) { + // Out of memory, entry not yet added to table. + return nullptr; + } + + wrapper->mJSObj = obj; + wrapper->mJSObjGlobal = objGlobal; + + // Insert the new wrapper into the hashtable, rooting the JSObject. Its + // lifetime is now tied to that of the NPObject. + if (!sJSObjWrappers->putNew(nsJSObjWrapperKey(obj, npp), wrapper)) { + // Out of memory, free the wrapper we created. + _releaseobject(wrapper); + return nullptr; + } + + return wrapper; +} + +// Climb the prototype chain, unwrapping as necessary until we find an NP object +// wrapper. +// +// Because this function unwraps, its return value must be wrapped for the cx +// compartment for callers that plan to hold onto the result or do anything +// substantial with it. +static JSObject* GetNPObjectWrapper(JSContext* cx, JS::Handle<JSObject*> aObj, + bool wrapResult = true) { + JS::Rooted<JSObject*> obj(cx, aObj); + + // We can't have WindowProxy or Location objects with NP object wrapper + // objects on their proto chain, since they have immutable prototypes. So + // CheckedUnwrapStatic is ok here. + while (obj && (obj = js::CheckedUnwrapStatic(obj))) { + if (nsNPObjWrapper::IsWrapper(obj)) { + if (wrapResult && !JS_WrapObject(cx, &obj)) { + return nullptr; + } + return obj; + } + + JSAutoRealm ar(cx, obj); + if (!::JS_GetPrototype(cx, obj, &obj)) { + return nullptr; + } + } + return nullptr; +} + +static NPObject* GetNPObject(JSContext* cx, JS::Handle<JSObject*> aObj) { + JS::Rooted<JSObject*> obj(cx, aObj); + obj = GetNPObjectWrapper(cx, obj, /* wrapResult = */ false); + if (!obj) { + return nullptr; + } + + return (NPObject*)js::GetProxyPrivate(obj).toPrivate(); +} + +static JSObject* NPObjWrapper_GetResolvedProps(JSContext* cx, + JS::Handle<JSObject*> obj) { + JS::Value slot = js::GetProxyReservedSlot(obj, 0); + if (slot.isObject()) return &slot.toObject(); + + MOZ_ASSERT(slot.isUndefined()); + + JSObject* res = JS_NewObject(cx, nullptr); + if (!res) return nullptr; + + SetProxyReservedSlot(obj, 0, JS::ObjectValue(*res)); + return res; +} + +bool NPObjWrapperProxyHandler::delete_(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::ObjectOpResult& result) const { + NPObject* npobj = GetNPObject(cx, proxy); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->removeProperty) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + JS::Rooted<JSObject*> resolvedProps(cx, + NPObjWrapper_GetResolvedProps(cx, proxy)); + if (!resolvedProps) return false; + if (!JS_DeletePropertyById(cx, resolvedProps, id, result)) return false; + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + if (!NPObjectIsOutOfProcessProxy(npobj)) { + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) return false; + + if (!hasProperty) return result.succeed(); + } + + // This removeProperty hook may throw an exception and return false; or just + // return false without an exception pending, which behaves like `delete + // obj.prop` returning false: in strict mode it becomes a TypeError. Legacy + // code---nothing else that uses the JSAPI works this way anymore. + bool succeeded = npobj->_class->removeProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) return false; + return succeeded ? result.succeed() : result.failCantDelete(); +} + +bool NPObjWrapperProxyHandler::set(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::Handle<JS::Value> vp, + JS::Handle<JS::Value> receiver, + JS::ObjectOpResult& result) const { + NPObject* npobj = GetNPObject(cx, proxy); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->setProperty) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + // Find out what plugin (NPP) is the owner of the object we're + // manipulating, and make it own any JSObject wrappers created here. + NPP npp = LookupNPP(npobj); + + if (!npp) { + ThrowJSExceptionASCII(cx, "No NPP found for NPObject!"); + + return false; + } + + { + bool resolved = false; + JS::Rooted<JSObject*> method(cx); + if (!NPObjWrapper_Resolve(cx, proxy, id, &resolved, &method)) return false; + if (!resolved) { + // We don't have a property/method with this id. Forward to the prototype + // chain. + return js::BaseProxyHandler::set(cx, proxy, id, vp, receiver, result); + } + } + + PluginDestructionGuard pdg(npp); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + if (!NPObjectIsOutOfProcessProxy(npobj)) { + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) return false; + + if (!hasProperty) { + ThrowJSExceptionASCII(cx, + "Trying to set unsupported property on NPObject!"); + + return false; + } + } + + NPVariant npv; + if (!JSValToNPVariant(npp, cx, vp, &npv)) { + ThrowJSExceptionASCII(cx, "Error converting jsval to NPVariant!"); + + return false; + } + + bool ok = npobj->_class->setProperty(npobj, identifier, &npv); + _releasevariantvalue(&npv); // Release the variant + if (!ReportExceptionIfPending(cx)) return false; + + if (!ok) { + ThrowJSExceptionASCII(cx, "Error setting property on NPObject!"); + + return false; + } + + return result.succeed(); +} + +static bool CallNPMethod(JSContext* cx, unsigned argc, JS::Value* vp); + +bool NPObjWrapperProxyHandler::get(JSContext* cx, JS::Handle<JSObject*> proxy, + JS::Handle<JS::Value> receiver, + JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp) const { + NPObject* npobj = GetNPObject(cx, proxy); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->hasMethod || !npobj->_class->getProperty) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + if (id.isSymbol()) { + if (id.isWellKnownSymbol(JS::SymbolCode::toPrimitive)) { + JS::RootedObject obj( + cx, JS_GetFunctionObject(JS_NewFunction(cx, NPObjWrapper_toPrimitive, + 1, 0, "Symbol.toPrimitive"))); + if (!obj) return false; + vp.setObject(*obj); + return true; + } + + if (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) { + JS::RootedString tag(cx, JS_NewStringCopyZ(cx, NPRUNTIME_JSCLASS_NAME)); + if (!tag) { + return false; + } + + vp.setString(tag); + return true; + } + + return js::BaseProxyHandler::get(cx, proxy, receiver, id, vp); + } + + // Find out what plugin (NPP) is the owner of the object we're + // manipulating, and make it own any JSObject wrappers created here. + NPP npp = LookupNPP(npobj); + if (!npp) { + ThrowJSExceptionASCII(cx, "No NPP found for NPObject!"); + + return false; + } + + { + bool resolved = false; + JS::Rooted<JSObject*> method(cx); + if (!NPObjWrapper_Resolve(cx, proxy, id, &resolved, &method)) return false; + if (method) { + vp.setObject(*method); + return true; + } + if (!resolved) { + // We don't have a property/method with this id. Forward to the prototype + // chain. + return js::BaseProxyHandler::get(cx, proxy, receiver, id, vp); + } + } + + PluginDestructionGuard pdg(npp); + + bool hasProperty, hasMethod; + + NPVariant npv; + VOID_TO_NPVARIANT(npv); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + if (NPObjectIsOutOfProcessProxy(npobj)) { + PluginScriptableObjectParent* actor = + static_cast<ParentNPObject*>(npobj)->parent; + + // actor may be null if the plugin crashed. + if (!actor) return false; + + bool success = + actor->GetPropertyHelper(identifier, &hasProperty, &hasMethod, &npv); + + if (!ReportExceptionIfPending(cx)) { + if (success) _releasevariantvalue(&npv); + return false; + } + + if (success) { + // We return NPObject Member class here to support ambiguous members. + if (hasProperty && hasMethod) + return CreateNPObjectMember(npp, cx, proxy, npobj, id, &npv, vp); + + if (hasProperty) { + vp.set(NPVariantToJSVal(npp, cx, &npv)); + _releasevariantvalue(&npv); + + if (!ReportExceptionIfPending(cx)) return false; + return true; + } + } + return js::BaseProxyHandler::get(cx, proxy, receiver, id, vp); + } + + hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) return false; + + hasMethod = npobj->_class->hasMethod(npobj, identifier); + if (!ReportExceptionIfPending(cx)) return false; + + // We return NPObject Member class here to support ambiguous members. + if (hasProperty && hasMethod) + return CreateNPObjectMember(npp, cx, proxy, npobj, id, nullptr, vp); + + if (hasProperty) { + if (npobj->_class->getProperty(npobj, identifier, &npv)) + vp.set(NPVariantToJSVal(npp, cx, &npv)); + + _releasevariantvalue(&npv); + + if (!ReportExceptionIfPending(cx)) return false; + return true; + } + + return js::BaseProxyHandler::get(cx, proxy, receiver, id, vp); +} + +static bool CallNPMethodInternal(JSContext* cx, JS::Handle<JSObject*> obj, + unsigned argc, JS::Value* argv, + JS::Value* rval, bool ctorCall) { + NPObject* npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + // Find out what plugin (NPP) is the owner of the object we're + // manipulating, and make it own any JSObject wrappers created here. + NPP npp = LookupNPP(npobj); + + if (!npp) { + ThrowJSExceptionASCII(cx, "Error finding NPP for NPObject!"); + + return false; + } + + PluginDestructionGuard pdg(npp); + + NPVariant npargs_buf[8]; + NPVariant* npargs = npargs_buf; + + if (argc > (sizeof(npargs_buf) / sizeof(NPVariant))) { + // Our stack buffer isn't large enough to hold all arguments, + // malloc a buffer. + npargs = (NPVariant*)malloc(argc * sizeof(NPVariant)); + + if (!npargs) { + ThrowJSExceptionASCII(cx, "Out of memory!"); + + return false; + } + } + + // Convert arguments + uint32_t i; + for (i = 0; i < argc; ++i) { + if (!JSValToNPVariant(npp, cx, argv[i], npargs + i)) { + ThrowJSExceptionASCII(cx, "Error converting jsvals to NPVariants!"); + + if (npargs != npargs_buf) { + free(npargs); + } + + return false; + } + } + + NPVariant v; + VOID_TO_NPVARIANT(v); + + JSObject* funobj = argv[-2].toObjectOrNull(); + bool ok; + const char* msg = "Error calling method on NPObject!"; + + if (ctorCall) { + // construct a new NPObject based on the NPClass in npobj. Fail if + // no construct method is available. + + if (NP_CLASS_STRUCT_VERSION_HAS_CTOR(npobj->_class) && + npobj->_class->construct) { + ok = npobj->_class->construct(npobj, npargs, argc, &v); + } else { + ok = false; + + msg = "Attempt to construct object from class with no constructor."; + } + } else if (funobj != obj) { + // A obj.function() style call is made, get the method name from + // the function object. + + if (npobj->_class->invoke) { + JSFunction* fun = ::JS_GetObjectFunction(funobj); + JS::Rooted<JSString*> funId(cx, ::JS_GetFunctionId(fun)); + JSString* name = ::JS_AtomizeAndPinJSString(cx, funId); + NPIdentifier id = StringToNPIdentifier(cx, name); + + ok = npobj->_class->invoke(npobj, id, npargs, argc, &v); + } else { + ok = false; + + msg = "Attempt to call a method on object with no invoke method."; + } + } else { + if (npobj->_class->invokeDefault) { + // obj is a callable object that is being called, no method name + // available then. Invoke the default method. + + ok = npobj->_class->invokeDefault(npobj, npargs, argc, &v); + } else { + ok = false; + + msg = + "Attempt to call a default method on object with no " + "invokeDefault method."; + } + } + + // Release arguments. + for (i = 0; i < argc; ++i) { + _releasevariantvalue(npargs + i); + } + + if (npargs != npargs_buf) { + free(npargs); + } + + if (!ok) { + // ReportExceptionIfPending returns a return value, which is true + // if no exception was thrown. In that case, throw our own. + if (ReportExceptionIfPending(cx)) ThrowJSExceptionASCII(cx, msg); + + return false; + } + + *rval = NPVariantToJSVal(npp, cx, &v); + + // *rval now owns the value, release our reference. + _releasevariantvalue(&v); + + return ReportExceptionIfPending(cx); +} + +static bool CallNPMethod(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (!args.thisv().isObject()) { + ThrowJSExceptionASCII(cx, + "plug-in method called on incompatible non-object"); + return false; + } + JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject()); + return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false); +} + +bool NPObjWrapperProxyHandler::getOwnPropertyDescriptor( + JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc) const { + bool resolved = false; + JS::Rooted<JSObject*> method(cx); + if (!NPObjWrapper_Resolve(cx, proxy, id, &resolved, &method)) return false; + if (!resolved) { + // No such property. + desc.object().set(nullptr); + return true; + } + + // This returns a descriptor with |null| JS value if this is a plugin + // property (as opposed to a method). That should be fine, hopefully, as the + // previous code had very inconsistent behavior in this case as well. The main + // reason for returning a descriptor here is to make property enumeration work + // correctly (it will call getOwnPropertyDescriptor to check enumerability). + JS::Rooted<JS::Value> val(cx, JS::ObjectOrNullValue(method)); + desc.initFields(proxy, val, JSPROP_ENUMERATE, nullptr, nullptr); + return true; +} + +bool NPObjWrapperProxyHandler::ownPropertyKeys( + JSContext* cx, JS::Handle<JSObject*> proxy, + JS::MutableHandleVector<jsid> properties) const { + NPObject* npobj = GetNPObject(cx, proxy); + if (!npobj || !npobj->_class) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + return false; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(npobj->_class) || + !npobj->_class->enumerate) { + return true; + } + + NPIdentifier* identifiers; + uint32_t length; + if (!npobj->_class->enumerate(npobj, &identifiers, &length)) { + if (ReportExceptionIfPending(cx)) { + // ReportExceptionIfPending returns a return value, which is true + // if no exception was thrown. In that case, throw our own. + ThrowJSExceptionASCII(cx, + "Error enumerating properties on scriptable " + "plugin object"); + } + return false; + } + + if (!properties.reserve(length)) return false; + + JS::Rooted<jsid> id(cx); + for (uint32_t i = 0; i < length; i++) { + id = NPIdentifierToJSId(identifiers[i]); + properties.infallibleAppend(id); + } + + free(identifiers); + return true; +} + +// This function is very similar to a resolve hook for native objects. Instead +// of defining properties on the object, it defines them on a resolvedProps +// object (a plain JS object that's never exposed to script) that's stored in +// the NPObjWrapper proxy's reserved slot. The behavior is as follows: +// +// - *resolvedp is set to true iff the plugin object has a property or method +// (or both) with this id. +// +// - If the plugin object has a *property* with this id, the caller is +// responsible for getting/setting its value. In this case we assign |null| +// to resolvedProps[id] so we don't have to call hasProperty each time. +// +// - If the plugin object has a *method* with this id, we create a JSFunction to +// call it and assign it to resolvedProps[id]. This function is also assigned +// to the |method| outparam so callers can return it directly if we're doing a +// |get|. +static bool NPObjWrapper_Resolve(JSContext* cx, JS::Handle<JSObject*> obj, + JS::Handle<jsid> id, bool* resolvedp, + JS::MutableHandle<JSObject*> method) { + if (JSID_IS_SYMBOL(id)) return true; + + AUTO_PROFILER_LABEL("NPObjWrapper_Resolve", JS); + + NPObject* npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->hasMethod) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + JS::Rooted<JSObject*> resolvedProps(cx, + NPObjWrapper_GetResolvedProps(cx, obj)); + if (!resolvedProps) return false; + JS::Rooted<JS::Value> res(cx); + if (!JS_GetPropertyById(cx, resolvedProps, id, &res)) return false; + if (res.isObjectOrNull()) { + method.set(res.toObjectOrNull()); + *resolvedp = true; + return true; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) return false; + + if (hasProperty) { + if (!JS_SetPropertyById(cx, resolvedProps, id, JS::NullHandleValue)) + return false; + *resolvedp = true; + + return true; + } + + bool hasMethod = npobj->_class->hasMethod(npobj, identifier); + if (!ReportExceptionIfPending(cx)) return false; + + if (hasMethod) { + NS_ASSERTION(JSID_IS_STRING(id) || JSID_IS_INT(id), + "id must be either string or int!\n"); + + JSFunction* fnc = ::JS_DefineFunctionById( + cx, resolvedProps, id, CallNPMethod, 0, JSPROP_ENUMERATE); + if (!fnc) return false; + + method.set(JS_GetFunctionObject(fnc)); + *resolvedp = true; + return true; + } + + // no property or method + return true; +} + +void NPObjWrapperProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const { + JS::AutoAssertGCCallback inCallback; + + NPObject* npobj = (NPObject*)js::GetProxyPrivate(proxy).toPrivate(); + if (npobj) { + if (sNPObjWrappers) { + // If the sNPObjWrappers map contains an entry that refers to this + // wrapper, remove it. + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + if (entry && entry->mJSObj == proxy) { + sNPObjWrappers->Remove(npobj); + } + } + } + + if (!sDelayedReleases) sDelayedReleases = new nsTArray<NPObject*>; + sDelayedReleases->AppendElement(npobj); +} + +size_t NPObjWrapperProxyHandler::objectMoved(JSObject* obj, + JSObject* old) const { + // The wrapper JSObject has been moved, so we need to update the entry in the + // sNPObjWrappers hash table, if present. + + if (!sNPObjWrappers) { + return 0; + } + + NPObject* npobj = (NPObject*)js::GetProxyPrivate(obj).toPrivate(); + if (!npobj) { + return 0; + } + + // Calling PLDHashTable::Search() will not result in GC. + JS::AutoSuppressGCAnalysis nogc; + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + MOZ_ASSERT(entry && entry->mJSObj); + MOZ_ASSERT(entry->mJSObj == old); + entry->mJSObj = obj; + return 0; +} + +bool NPObjWrapperProxyHandler::call(JSContext* cx, JS::Handle<JSObject*> proxy, + const JS::CallArgs& args) const { + return CallNPMethodInternal(cx, proxy, args.length(), args.array(), + args.rval().address(), false); +} + +bool NPObjWrapperProxyHandler::construct(JSContext* cx, + JS::Handle<JSObject*> proxy, + const JS::CallArgs& args) const { + return CallNPMethodInternal(cx, proxy, args.length(), args.array(), + args.rval().address(), true); +} + +static bool NPObjWrapper_toPrimitive(JSContext* cx, unsigned argc, + JS::Value* vp) { + // Plugins do not simply use the default OrdinaryToPrimitive behavior, + // because that behavior involves calling toString or valueOf on objects + // which weren't designed to accommodate this. Usually this wouldn't be a + // problem, because the absence of either property, or the presence of either + // property with a value that isn't callable, will cause that property to + // simply be ignored. But there is a problem in one specific case: Java, + // specifically java.lang.Integer. The Integer class has static valueOf + // methods, none of which are nullary, so the JS-reflected method will behave + // poorly when called with no arguments. We work around this problem by + // giving plugins a [Symbol.toPrimitive]() method which uses only toString + // and not valueOf. + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedValue thisv(cx, args.thisv()); + if (thisv.isPrimitive()) return true; + + JS::RootedObject obj(cx, &thisv.toObject()); + JS::RootedValue v(cx); + if (!JS_GetProperty(cx, obj, "toString", &v)) return false; + if (v.isObject() && JS::IsCallable(&v.toObject())) { + if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), + args.rval())) + return false; + if (args.rval().isPrimitive()) return true; + } + + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, + JSMSG_CANT_CONVERT_TO, JS::GetClass(obj)->name, + "primitive type"); + return false; +} + +bool nsNPObjWrapper::IsWrapper(JSObject* obj) { + return JS::GetClass(obj) == &sNPObjWrapperProxyClass; +} + +// An NPObject is going away, make sure we null out the JS object's +// private data in case this is an NPObject that came from a plugin +// and it's destroyed prematurely. + +// static +void nsNPObjWrapper::OnDestroy(NPObject* npobj) { + if (!npobj) { + return; + } + + if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { + // npobj is one of our own, no private data to clean up here. + + return; + } + + if (!sNPObjWrappers) { + // No hash yet (or any more), no used wrappers available. + + return; + } + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + + if (entry && entry->mJSObj) { + // Found an NPObject wrapper, null out its JSObjects' private data. + js::SetProxyPrivate(entry->mJSObj.unbarrieredGetPtr(), + JS::PrivateValue(nullptr)); + + // Remove the npobj from the hash now that it went away. + sNPObjWrappers->RawRemove(entry); + + // The finalize hook will call OnWrapperDestroyed(). + } +} + +// Look up or create a JSObject that wraps the NPObject npobj. The return value +// is always in the compartment of the passed-in JSContext (it might be a CCW). + +// static +JSObject* nsNPObjWrapper::GetNewOrUsed(NPP npp, JSContext* cx, + NPObject* npobj) { + if (!npobj) { + NS_ERROR("Null NPObject passed to nsNPObjWrapper::GetNewOrUsed()!"); + + return nullptr; + } + + if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { + // npobj is one of our own, return its existing JSObject. + + JS::Rooted<JSObject*> obj(cx, ((nsJSObjWrapper*)npobj)->mJSObj); + if (!JS_WrapObject(cx, &obj)) { + return nullptr; + } + return obj; + } + + if (!npp) { + NS_ERROR("No npp passed to nsNPObjWrapper::GetNewOrUsed()!"); + + return nullptr; + } + + if (!sNPObjWrappers) { + // No hash yet (or any more), initialize it. + if (!CreateNPObjWrapperTable()) { + return nullptr; + } + } + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Add(npobj, fallible)); + + if (!entry) { + // Out of memory + JS_ReportOutOfMemory(cx); + + return nullptr; + } + + if (entry->mJSObj) { + // Found a NPObject wrapper. First check it is still alive. + JSObject* obj = entry->mJSObj.unbarrieredGetPtr(); + if (js::gc::EdgeNeedsSweepUnbarriered(&obj)) { + // The object is dead (finalization will happen at a later time). By the + // time we leave this function, this entry will either be updated with a + // new wrapper or removed if that fails. Clear it anyway to make sure + // nothing touches the dead object. + entry->mJSObj = nullptr; + } else { + // It may not be in the same compartment as cx, so we need to wrap it + // before returning it. + JS::Rooted<JSObject*> obj(cx, entry->mJSObj); + if (!JS_WrapObject(cx, &obj)) { + return nullptr; + } + return obj; + } + } + + entry->mNPObj = npobj; + entry->mNpp = npp; + + uint32_t generation = sNPObjWrappers->Generation(); + + // No existing JSObject, create one. + + JS::RootedValue priv(cx, JS::PrivateValue(nullptr)); + js::ProxyOptions options; + options.setClass(&sNPObjWrapperProxyClass); + JS::Rooted<JSObject*> obj( + cx, js::NewProxyObject(cx, &NPObjWrapperProxyHandler::singleton, priv, + nullptr, options)); + + if (generation != sNPObjWrappers->Generation()) { + // Reload entry if the JS_NewObject call caused a GC and reallocated + // the table (see bug 445229). This is guaranteed to succeed. + + entry = static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + NS_ASSERTION(entry, "Hashtable didn't find what we just added?"); + } + + if (!obj) { + // OOM? Remove the stale entry from the hash. + + sNPObjWrappers->RawRemove(entry); + + return nullptr; + } + + OnWrapperCreated(); + + entry->mJSObj = obj; + + js::SetProxyPrivate(obj, JS::PrivateValue(npobj)); + + // The new JSObject now holds on to npobj + _retainobject(npobj); + + return obj; +} + +// static +void nsJSNPRuntime::OnPluginDestroy(NPP npp) { + if (sJSObjWrappersAccessible) { + // Prevent modification of sJSObjWrappers table if we go reentrant. + sJSObjWrappersAccessible = false; + + for (auto iter = sJSObjWrappers->modIter(); !iter.done(); iter.next()) { + nsJSObjWrapper* npobj = iter.get().value(); + MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass); + if (npobj->mNpp == npp) { + if (npobj->_class && npobj->_class->invalidate) { + npobj->_class->invalidate(npobj); + } + + _releaseobject(npobj); + + iter.remove(); + } + } + + sJSObjWrappersAccessible = true; + } + + if (sNPObjWrappers) { + for (auto i = sNPObjWrappers->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast<NPObjWrapperHashEntry*>(i.Get()); + + if (entry->mNpp == npp) { + // HACK: temporarily hide the table we're enumerating so that + // invalidate() and deallocate() don't touch it. + PLDHashTable* tmp = sNPObjWrappers; + sNPObjWrappers = nullptr; + + NPObject* npobj = entry->mNPObj; + + if (npobj->_class && npobj->_class->invalidate) { + npobj->_class->invalidate(npobj); + } + +#ifdef NS_BUILD_REFCNT_LOGGING + { + int32_t refCnt = npobj->referenceCount; + while (refCnt) { + --refCnt; + NS_LOG_RELEASE(npobj, refCnt, "BrowserNPObject"); + } + } +#endif + + // Force deallocation of plugin objects since the plugin they came + // from is being torn down. + if (npobj->_class && npobj->_class->deallocate) { + npobj->_class->deallocate(npobj); + } else { + free(npobj); + } + + js::SetProxyPrivate(entry->mJSObj.unbarrieredGetPtr(), + JS::PrivateValue(nullptr)); + + sNPObjWrappers = tmp; + + if (sDelayedReleases && sDelayedReleases->RemoveElement(npobj)) { + OnWrapperDestroyed(); + } + + i.Remove(); + } + } + } +} + +// static +void nsJSNPRuntime::OnPluginDestroyPending(NPP npp) { + if (sJSObjWrappersAccessible) { + // Prevent modification of sJSObjWrappers table if we go reentrant. + sJSObjWrappersAccessible = false; + for (auto iter = sJSObjWrappers->iter(); !iter.done(); iter.next()) { + nsJSObjWrapper* npobj = iter.get().value(); + MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass); + if (npobj->mNpp == npp) { + npobj->mDestroyPending = true; + } + } + sJSObjWrappersAccessible = true; + } +} + +// Find the NPP for a NPObject. +static NPP LookupNPP(NPObject* npobj) { + if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { + nsJSObjWrapper* o = static_cast<nsJSObjWrapper*>(npobj); + return o->mNpp; + } + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Add(npobj, fallible)); + + if (!entry) { + return nullptr; + } + + NS_ASSERTION(entry->mNpp, "Live NPObject entry w/o an NPP!"); + + return entry->mNpp; +} + +static bool CreateNPObjectMember(NPP npp, JSContext* cx, + JS::Handle<JSObject*> aObj, NPObject* npobj, + JS::Handle<jsid> id, + NPVariant* getPropertyResult, + JS::MutableHandle<JS::Value> vp) { + if (!npobj || !npobj->_class || !npobj->_class->getProperty || + !npobj->_class->invoke) { + ThrowJSExceptionASCII(cx, "Bad NPObject"); + + return false; + } + + NPObjectMemberPrivate* memberPrivate = new NPObjectMemberPrivate; + + JS::Rooted<JSObject*> obj(cx, aObj); + + JS::Rooted<JSObject*> memobj(cx, ::JS_NewObject(cx, &sNPObjectMemberClass)); + if (!memobj) { + delete memberPrivate; + return false; + } + + vp.setObject(*memobj); + + JS::SetPrivate(memobj, (void*)memberPrivate); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + JS::Rooted<JS::Value> fieldValue(cx); + NPVariant npv; + + if (getPropertyResult) { + // Plugin has already handed us the value we want here. + npv = *getPropertyResult; + } else { + VOID_TO_NPVARIANT(npv); + + NPBool hasProperty = npobj->_class->getProperty(npobj, identifier, &npv); + if (!ReportExceptionIfPending(cx) || !hasProperty) { + return false; + } + } + + fieldValue = NPVariantToJSVal(npp, cx, &npv); + + // npobjWrapper is the JSObject through which we make sure we don't + // outlive the underlying NPObject, so make sure it points to the + // real JSObject wrapper for the NPObject. + obj = GetNPObjectWrapper(cx, obj); + + memberPrivate->npobjWrapper = obj; + + memberPrivate->fieldValue = fieldValue; + memberPrivate->methodName = id; + memberPrivate->npp = npp; + + // Finally, define the Symbol.toPrimitive property on |memobj|. + + JS::Rooted<jsid> toPrimitiveId(cx); + toPrimitiveId = + SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::toPrimitive)); + + JSFunction* fun = JS_NewFunction(cx, NPObjectMember_toPrimitive, 1, 0, + "Symbol.toPrimitive"); + if (!fun) return false; + + JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun)); + if (!JS_DefinePropertyById(cx, memobj, toPrimitiveId, funObj, 0)) + return false; + + return true; +} + +static void NPObjectMember_Finalize(JSFreeOp* fop, JSObject* obj) { + NPObjectMemberPrivate* memberPrivate; + + memberPrivate = (NPObjectMemberPrivate*)JS::GetPrivate(obj); + if (!memberPrivate) return; + + delete memberPrivate; +} + +static bool NPObjectMember_Call(JSContext* cx, unsigned argc, JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> memobj(cx, &args.callee()); + NS_ENSURE_TRUE(memobj, false); + + NPObjectMemberPrivate* memberPrivate = + (NPObjectMemberPrivate*)::JS_GetInstancePrivate( + cx, memobj, &sNPObjectMemberClass, &args); + if (!memberPrivate || !memberPrivate->npobjWrapper) return false; + + JS::Rooted<JSObject*> objWrapper(cx, memberPrivate->npobjWrapper); + NPObject* npobj = GetNPObject(cx, objWrapper); + if (!npobj) { + ThrowJSExceptionASCII(cx, "Call on invalid member object"); + + return false; + } + + NPVariant npargs_buf[8]; + NPVariant* npargs = npargs_buf; + + if (args.length() > (sizeof(npargs_buf) / sizeof(NPVariant))) { + // Our stack buffer isn't large enough to hold all arguments, + // malloc a buffer. + npargs = (NPVariant*)malloc(args.length() * sizeof(NPVariant)); + + if (!npargs) { + ThrowJSExceptionASCII(cx, "Out of memory!"); + + return false; + } + } + + // Convert arguments + for (uint32_t i = 0; i < args.length(); ++i) { + if (!JSValToNPVariant(memberPrivate->npp, cx, args[i], npargs + i)) { + ThrowJSExceptionASCII(cx, "Error converting jsvals to NPVariants!"); + + if (npargs != npargs_buf) { + free(npargs); + } + + return false; + } + } + + NPVariant npv; + bool ok = npobj->_class->invoke(npobj, + JSIdToNPIdentifier(memberPrivate->methodName), + npargs, args.length(), &npv); + + // Release arguments. + for (uint32_t i = 0; i < args.length(); ++i) { + _releasevariantvalue(npargs + i); + } + + if (npargs != npargs_buf) { + free(npargs); + } + + if (!ok) { + // ReportExceptionIfPending returns a return value, which is true + // if no exception was thrown. In that case, throw our own. + if (ReportExceptionIfPending(cx)) + ThrowJSExceptionASCII(cx, "Error calling method on NPObject!"); + + return false; + } + + args.rval().set(NPVariantToJSVal(memberPrivate->npp, cx, &npv)); + + // *vp now owns the value, release our reference. + _releasevariantvalue(&npv); + + return ReportExceptionIfPending(cx); +} + +static void NPObjectMember_Trace(JSTracer* trc, JSObject* obj) { + auto* memberPrivate = (NPObjectMemberPrivate*)JS::GetPrivate(obj); + if (!memberPrivate) return; + + // Our NPIdentifier is not always interned, so we must trace it. + JS::TraceEdge(trc, &memberPrivate->methodName, + "NPObjectMemberPrivate.methodName"); + + JS::TraceEdge(trc, &memberPrivate->fieldValue, + "NPObject Member => fieldValue"); + + // There's no strong reference from our private data to the + // NPObject, so make sure to mark the NPObject wrapper to keep the + // NPObject alive as long as this NPObjectMember is alive. + JS::TraceEdge(trc, &memberPrivate->npobjWrapper, + "NPObject Member => npobjWrapper"); +} + +static bool NPObjectMember_toPrimitive(JSContext* cx, unsigned argc, + JS::Value* vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedValue thisv(cx, args.thisv()); + if (thisv.isPrimitive()) { + args.rval().set(thisv); + return true; + } + + JS::RootedObject obj(cx, &thisv.toObject()); + NPObjectMemberPrivate* memberPrivate = + (NPObjectMemberPrivate*)::JS_GetInstancePrivate( + cx, obj, &sNPObjectMemberClass, &args); + if (!memberPrivate) return false; + + JSType hint; + if (!JS::GetFirstArgumentAsTypeHint(cx, args, &hint)) return false; + + args.rval().set(memberPrivate->fieldValue); + if (args.rval().isObject()) { + JS::Rooted<JSObject*> objVal(cx, &args.rval().toObject()); + return JS::ToPrimitive(cx, objVal, hint, args.rval()); + } + return true; +} diff --git a/dom/plugins/base/nsJSNPRuntime.h b/dom/plugins/base/nsJSNPRuntime.h new file mode 100644 index 0000000000..c1e877f859 --- /dev/null +++ b/dom/plugins/base/nsJSNPRuntime.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsJSNPRuntime_h_ +#define nsJSNPRuntime_h_ + +#include "nscore.h" +#include "npapi.h" +#include "npruntime.h" +#include "PLDHashTable.h" +#include "js/RootingAPI.h" + +class nsJSNPRuntime { + public: + static void OnPluginDestroy(NPP npp); + static void OnPluginDestroyPending(NPP npp); +}; + +class nsJSObjWrapperKey { + public: + nsJSObjWrapperKey(JSObject* obj, NPP npp) : mJSObj(obj), mNpp(npp) {} + + bool operator==(const nsJSObjWrapperKey& other) const { + return mJSObj == other.mJSObj && mNpp == other.mNpp; + } + bool operator!=(const nsJSObjWrapperKey& other) const { + return !(*this == other); + } + + void trace(JSTracer* trc) { + JS::TraceEdge(trc, &mJSObj, "nsJSObjWrapperKey"); + } + + nsJSObjWrapperKey(nsJSObjWrapperKey&& other) = default; + nsJSObjWrapperKey& operator=(nsJSObjWrapperKey&& other) = default; + + JS::Heap<JSObject*> mJSObj; + NPP mNpp; +}; + +class nsJSObjWrapper : public NPObject { + public: + JS::Heap<JSObject*> mJSObj; + // Because mJSObj might be a cross-compartment wrapper, we can't use it to + // enter the target realm. We use this global instead (it's always + // same-compartment with mJSObj). + JS::Heap<JSObject*> mJSObjGlobal; + const NPP mNpp; + bool mDestroyPending; + + static NPObject* GetNewOrUsed(NPP npp, JS::Handle<JSObject*> obj, + JS::Handle<JSObject*> objGlobal); + + void trace(JSTracer* trc) { + JS::TraceEdge(trc, &mJSObj, "nsJSObjWrapper::mJSObj"); + JS::TraceEdge(trc, &mJSObjGlobal, "nsJSObjWrapper::mJSObjGlobal"); + } + + protected: + explicit nsJSObjWrapper(NPP npp); + ~nsJSObjWrapper(); + + static NPObject* NP_Allocate(NPP npp, NPClass* aClass); + static void NP_Deallocate(NPObject* obj); + static void NP_Invalidate(NPObject* obj); + static bool NP_HasMethod(NPObject*, NPIdentifier identifier); + static bool NP_Invoke(NPObject* obj, NPIdentifier method, + const NPVariant* args, uint32_t argCount, + NPVariant* result); + static bool NP_InvokeDefault(NPObject* obj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + static bool NP_HasProperty(NPObject* obj, NPIdentifier property); + static bool NP_GetProperty(NPObject* obj, NPIdentifier property, + NPVariant* result); + static bool NP_SetProperty(NPObject* obj, NPIdentifier property, + const NPVariant* value); + static bool NP_RemoveProperty(NPObject* obj, NPIdentifier property); + static bool NP_Enumerate(NPObject* npobj, NPIdentifier** identifier, + uint32_t* count); + static bool NP_Construct(NPObject* obj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + + public: + static NPClass sJSObjWrapperNPClass; +}; + +class nsNPObjWrapper { + public: + static bool IsWrapper(JSObject* obj); + static void OnDestroy(NPObject* npobj); + static JSObject* GetNewOrUsed(NPP npp, JSContext* cx, NPObject* npobj); +}; + +bool JSValToNPVariant(NPP npp, JSContext* cx, const JS::Value& val, + NPVariant* variant); + +#endif // nsJSNPRuntime_h_ diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp new file mode 100644 index 0000000000..6d302573b4 --- /dev/null +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -0,0 +1,1884 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" + +/* This must occur *after* layers/PLayerTransaction.h to avoid typedefs + * conflicts. */ +#include "mozilla/ArrayUtils.h" + +#include "pratom.h" +#include "prenv.h" + +#include "jsfriendapi.h" +#include "js/friend/WindowProxy.h" // js::ToWindowIfWindowProxy +#include "js/Object.h" // JS::GetCompartment + +#include "nsPluginHost.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsPluginStreamListenerPeer.h" +#include "nsThreadUtils.h" +#include "mozilla/CycleCollectedJSContext.h" // for nsAutoMicroTask +#include "mozilla/Preferences.h" +#include "nsPluginInstanceOwner.h" + +#include "nsPluginsDir.h" +#include "nsPluginLogging.h" + +#include "nsPIDOMWindow.h" +#include "nsGlobalWindow.h" +#include "mozilla/dom/Document.h" +#include "nsIContent.h" +#include "nsIIDNService.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsDOMJSUtils.h" +#include "nsIPrincipal.h" +#include "nsWildCard.h" +#include "nsContentUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/JSExecutionContext.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsIXPConnect.h" +#include "nsMemory.h" + +#include <prinrval.h> + +#ifdef MOZ_WIDGET_COCOA +# include <Carbon/Carbon.h> +# include <ApplicationServices/ApplicationServices.h> +# include <OpenGL/OpenGL.h> +# include "nsCocoaFeatures.h" +# include "PluginUtilsOSX.h" +#endif + +// needed for nppdf plugin +#if (MOZ_WIDGET_GTK) +# include <gdk/gdk.h> +# include <gdk/gdkx.h> +#endif + +#include "nsJSUtils.h" +#include "nsJSNPRuntime.h" + +#include "nsNetUtil.h" +#include "nsNetCID.h" + +#include "mozilla/Mutex.h" +#include "mozilla/PluginLibrary.h" +using mozilla::PluginLibrary; + +#include "mozilla/plugins/PluginModuleParent.h" +using mozilla::plugins::PluginModuleChromeParent; +using mozilla::plugins::PluginModuleContentParent; + +#ifdef MOZ_X11 +# include "mozilla/X11Util.h" +#endif + +#ifdef XP_WIN +# include <windows.h> +# include "mozilla/WindowsVersion.h" +# ifdef ACCESSIBILITY +# include "mozilla/a11y/Compatibility.h" +# endif +#endif + +#include "AudioChannelService.h" + +using namespace mozilla; +using namespace mozilla::plugins::parent; +using mozilla::dom::Document; +using mozilla::dom::JSExecutionContext; + +// We should make this const... +static NPNetscapeFuncs sBrowserFuncs = { + sizeof(sBrowserFuncs), + (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR, + _geturl, + _posturl, + _requestread, + nullptr, // _newstream, unimplemented + nullptr, // _write, unimplemented + nullptr, // _destroystream, unimplemented + _status, + _useragent, + _memalloc, + _memfree, + _memflush, + _reloadplugins, + _getJavaEnv, + _getJavaPeer, + _geturlnotify, + _posturlnotify, + _getvalue, + _setvalue, + _invalidaterect, + _invalidateregion, + _forceredraw, + _getstringidentifier, + _getstringidentifiers, + _getintidentifier, + _identifierisstring, + _utf8fromidentifier, + _intfromidentifier, + _createobject, + _retainobject, + _releaseobject, + _invoke, + _invokeDefault, + _evaluate, + _getproperty, + _setproperty, + _removeproperty, + _hasproperty, + _hasmethod, + _releasevariantvalue, + _setexception, + _pushpopupsenabledstate, + _poppopupsenabledstate, + _enumerate, + nullptr, // pluginthreadasynccall, not used + _construct, + _getvalueforurl, + _setvalueforurl, + nullptr, // NPN GetAuthenticationInfo, not supported + _scheduletimer, + _unscheduletimer, + _popupcontextmenu, + _convertpoint, + nullptr, // handleevent, unimplemented + nullptr, // unfocusinstance, unimplemented + _urlredirectresponse, + _initasyncsurface, + _finalizeasyncsurface, + _setcurrentasyncsurface}; + +// POST/GET stream type +enum eNPPStreamTypeInternal { + eNPPStreamTypeInternal_Get, + eNPPStreamTypeInternal_Post +}; + +void NS_NotifyBeginPluginCall(NSPluginCallReentry aReentryState) { + nsNPAPIPluginInstance::BeginPluginCall(aReentryState); +} + +void NS_NotifyPluginCall(NSPluginCallReentry aReentryState) { + nsNPAPIPluginInstance::EndPluginCall(aReentryState); +} + +nsNPAPIPlugin::nsNPAPIPlugin() { + memset((void*)&mPluginFuncs, 0, sizeof(mPluginFuncs)); + mPluginFuncs.size = sizeof(mPluginFuncs); + mPluginFuncs.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + + mLibrary = nullptr; +} + +nsNPAPIPlugin::~nsNPAPIPlugin() { + delete mLibrary; + mLibrary = nullptr; +} + +void nsNPAPIPlugin::PluginCrashed(const nsAString& aPluginDumpID, + const nsACString& aAdditionalMinidumps) { + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + host->PluginCrashed(this, aPluginDumpID, aAdditionalMinidumps); +} + +inline PluginLibrary* GetNewPluginLibrary(nsPluginTag* aPluginTag) { + AUTO_PROFILER_LABEL("GetNewPluginLibrary", OTHER); + + if (!aPluginTag) { + return nullptr; + } + + if (XRE_IsContentProcess()) { + return PluginModuleContentParent::LoadModule(aPluginTag->mId, aPluginTag); + } + + return PluginModuleChromeParent::LoadModule(aPluginTag->mFullPath.get(), + aPluginTag->mId, aPluginTag); +} + +// Creates an nsNPAPIPlugin object. One nsNPAPIPlugin object exists per plugin +// (not instance). +nsresult nsNPAPIPlugin::CreatePlugin(nsPluginTag* aPluginTag, + nsNPAPIPlugin** aResult) { + AUTO_PROFILER_LABEL("nsNPAPIPlugin::CreatePlugin", OTHER); + *aResult = nullptr; + + if (!aPluginTag) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsNPAPIPlugin> plugin = new nsNPAPIPlugin(); + + PluginLibrary* pluginLib = GetNewPluginLibrary(aPluginTag); + if (!pluginLib) { + return NS_ERROR_FAILURE; + } + +#if defined(XP_MACOSX) + if (!pluginLib->HasRequiredFunctions()) { + NS_WARNING( + "Not all necessary functions exposed by plugin, it will not load."); + delete pluginLib; + return NS_ERROR_FAILURE; + } +#endif + + plugin->mLibrary = pluginLib; + pluginLib->SetPlugin(plugin); + +// Exchange NPAPI entry points. +#if defined(XP_WIN) + // NP_GetEntryPoints must be called before NP_Initialize on Windows. + NPError pluginCallError; + nsresult rv = + pluginLib->NP_GetEntryPoints(&plugin->mPluginFuncs, &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } + + // NP_Initialize must be called after NP_GetEntryPoints on Windows. + rv = pluginLib->NP_Initialize(&sBrowserFuncs, &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_MACOSX) + // NP_Initialize must be called before NP_GetEntryPoints on Mac OS X. + // We need to match WebKit's behavior. + NPError pluginCallError; + nsresult rv = pluginLib->NP_Initialize(&sBrowserFuncs, &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } + + rv = pluginLib->NP_GetEntryPoints(&plugin->mPluginFuncs, &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } +#else + NPError pluginCallError; + nsresult rv = pluginLib->NP_Initialize(&sBrowserFuncs, &plugin->mPluginFuncs, + &pluginCallError); + if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) { + return NS_ERROR_FAILURE; + } +#endif + + plugin.forget(aResult); + return NS_OK; +} + +PluginLibrary* nsNPAPIPlugin::GetLibrary() { return mLibrary; } + +NPPluginFuncs* nsNPAPIPlugin::PluginFuncs() { return &mPluginFuncs; } + +nsresult nsNPAPIPlugin::Shutdown() { + NPP_PLUGIN_LOG(PLUGIN_LOG_BASIC, + ("NPP Shutdown to be called: this=%p\n", this)); + + NPError shutdownError; + mLibrary->NP_Shutdown(&shutdownError); + + return NS_OK; +} + +nsresult nsNPAPIPlugin::RetainStream(NPStream* pstream, + nsISupports** aRetainedPeer) { + if (!aRetainedPeer) return NS_ERROR_NULL_POINTER; + + *aRetainedPeer = nullptr; + + if (!pstream || !pstream->ndata) return NS_ERROR_NULL_POINTER; + + nsNPAPIStreamWrapper* streamWrapper = + static_cast<nsNPAPIStreamWrapper*>(pstream->ndata); + nsNPAPIPluginStreamListener* listener = streamWrapper->GetStreamListener(); + if (!listener) { + return NS_ERROR_NULL_POINTER; + } + + nsIStreamListener* streamListener = listener->GetStreamListenerPeer(); + if (!streamListener) { + return NS_ERROR_NULL_POINTER; + } + + *aRetainedPeer = streamListener; + NS_ADDREF(*aRetainedPeer); + return NS_OK; +} + +// Create a new NPP GET or POST (given in the type argument) url +// stream that may have a notify callback +NPError MakeNewNPAPIStreamInternal(NPP npp, const char* relativeURL, + const char* target, + eNPPStreamTypeInternal type, + bool bDoNotify = false, + void* notifyData = nullptr, uint32_t len = 0, + const char* buf = nullptr) { + if (!npp) return NPERR_INVALID_INSTANCE_ERROR; + + PluginDestructionGuard guard(npp); + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + if (!inst || !inst->IsRunning()) return NPERR_INVALID_INSTANCE_ERROR; + + nsCOMPtr<nsIPluginHost> pluginHostCOM = + do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); + nsPluginHost* pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get()); + if (!pluginHost) { + return NPERR_GENERIC_ERROR; + } + + RefPtr<nsNPAPIPluginStreamListener> listener; + // Set aCallNotify here to false. If pluginHost->GetURL or PostURL fail, + // the listener's destructor will do the notification while we are about to + // return a failure code. + // Call SetCallNotify(true) below after we are sure we cannot return a failure + // code. + if (!target) { + inst->NewStreamListener(relativeURL, notifyData, getter_AddRefs(listener)); + if (listener) { + listener->SetCallNotify(false); + } + } + + switch (type) { + case eNPPStreamTypeInternal_Get: { + if (NS_FAILED(pluginHost->GetURL(inst, relativeURL, target, listener, + nullptr, nullptr, false))) + return NPERR_GENERIC_ERROR; + break; + } + case eNPPStreamTypeInternal_Post: { + if (NS_FAILED(pluginHost->PostURL(inst, relativeURL, len, buf, target, + listener, nullptr, nullptr, false, 0, + nullptr))) + return NPERR_GENERIC_ERROR; + break; + } + default: + NS_ERROR("how'd I get here"); + } + + if (listener) { + // SetCallNotify(bDoNotify) here, see comment above. + listener->SetCallNotify(bDoNotify); + } + + return NPERR_NO_ERROR; +} + +#if defined(MOZ_MEMORY) && defined(XP_WIN) +extern "C" size_t malloc_usable_size(const void* ptr); +#endif + +namespace { + +static char* gNPPException; + +static Document* GetDocumentFromNPP(NPP npp) { + NS_ENSURE_TRUE(npp, nullptr); + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + NS_ENSURE_TRUE(inst, nullptr); + + PluginDestructionGuard guard(inst); + + RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner(); + NS_ENSURE_TRUE(owner, nullptr); + + nsCOMPtr<Document> doc; + owner->GetDocument(getter_AddRefs(doc)); + + return doc; +} + +static NPIdentifier doGetIdentifier(JSContext* cx, const NPUTF8* name) { + NS_ConvertUTF8toUTF16 utf16name(name); + + JSString* str = + ::JS_AtomizeAndPinUCStringN(cx, utf16name.get(), utf16name.Length()); + + if (!str) return nullptr; + + return StringToNPIdentifier(cx, str); +} + +#if defined(MOZ_MEMORY) && defined(XP_WIN) +BOOL InHeap(HANDLE hHeap, LPVOID lpMem) { + BOOL success = FALSE; + PROCESS_HEAP_ENTRY he; + he.lpData = nullptr; + while (HeapWalk(hHeap, &he) != 0) { + if (he.lpData == lpMem) { + success = TRUE; + break; + } + } + HeapUnlock(hHeap); + return success; +} +#endif + +} /* anonymous namespace */ + +NPPExceptionAutoHolder::NPPExceptionAutoHolder() + : mOldException(gNPPException) { + gNPPException = nullptr; +} + +NPPExceptionAutoHolder::~NPPExceptionAutoHolder() { + NS_ASSERTION(!gNPPException, "NPP exception not properly cleared!"); + + gNPPException = mOldException; +} + +NPP NPPStack::sCurrentNPP = nullptr; + +const char* PeekException() { return gNPPException; } + +void PopException() { + NS_ASSERTION(gNPPException, "Uh, no NPP exception to pop!"); + + if (gNPPException) { + free(gNPPException); + + gNPPException = nullptr; + } +} + +// +// Static callbacks that get routed back through the new C++ API +// + +namespace mozilla::plugins::parent { + +NPError _geturl(NPP npp, const char* relativeURL, const char* target) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_geturl called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_GetURL: npp=%p, target=%s, url=%s\n", + (void*)npp, target, relativeURL)); + + PluginDestructionGuard guard(npp); + + return MakeNewNPAPIStreamInternal(npp, relativeURL, target, + eNPPStreamTypeInternal_Get); +} + +NPError _geturlnotify(NPP npp, const char* relativeURL, const char* target, + void* notifyData) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_geturlnotify called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_GetURLNotify: npp=%p, target=%s, notify=%p, url=%s\n", + (void*)npp, target, notifyData, relativeURL)); + + PluginDestructionGuard guard(npp); + + return MakeNewNPAPIStreamInternal( + npp, relativeURL, target, eNPPStreamTypeInternal_Get, true, notifyData); +} + +NPError _posturlnotify(NPP npp, const char* relativeURL, const char* target, + uint32_t len, const char* buf, NPBool file, + void* notifyData) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_posturlnotify called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + if (!buf) return NPERR_INVALID_PARAM; + + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_PostURLNotify: npp=%p, target=%s, len=%d, file=%d, " + "notify=%p, url=%s, buf=%s\n", + (void*)npp, target, len, file, notifyData, relativeURL, buf)); + + PluginDestructionGuard guard(npp); + + return MakeNewNPAPIStreamInternal(npp, relativeURL, target, + eNPPStreamTypeInternal_Post, true, + notifyData, len, buf); +} + +NPError _posturl(NPP npp, const char* relativeURL, const char* target, + uint32_t len, const char* buf, NPBool file) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_posturl called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_PostURL: npp=%p, target=%s, file=%d, len=%d, url=%s, " + "buf=%s\n", + (void*)npp, target, file, len, relativeURL, buf)); + + PluginDestructionGuard guard(npp); + + return MakeNewNPAPIStreamInternal(npp, relativeURL, target, + eNPPStreamTypeInternal_Post, false, nullptr, + len, buf); +} + +void _status(NPP npp, const char* message) { + // NPN_Status is no longer supported. +} + +void _memfree(void* ptr) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_memfree called from the wrong thread\n")); + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, ("NPN_MemFree: ptr=%p\n", ptr)); + + if (ptr) free(ptr); +} + +uint32_t _memflush(uint32_t size) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_memflush called from the wrong thread\n")); + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, ("NPN_MemFlush: size=%d\n", size)); + + nsMemory::HeapMinimize(true); + return 0; +} + +void _reloadplugins(NPBool reloadPages) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_reloadplugins called from the wrong thread\n")); + return; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_ReloadPlugins: reloadPages=%d\n", reloadPages)); + + nsCOMPtr<nsIPluginHost> pluginHost(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); + if (!pluginHost) return; + + pluginHost->ReloadPlugins(); +} + +void _invalidaterect(NPP npp, NPRect* invalidRect) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_invalidaterect called from the wrong thread\n")); + return; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_InvalidateRect: npp=%p, top=%d, left=%d, bottom=%d, " + "right=%d\n", + (void*)npp, invalidRect->top, invalidRect->left, + invalidRect->bottom, invalidRect->right)); + + if (!npp || !npp->ndata) { + NS_WARNING("_invalidaterect: npp or npp->ndata == 0"); + return; + } + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + + PluginDestructionGuard guard(inst); + + inst->InvalidateRect((NPRect*)invalidRect); +} + +void _invalidateregion(NPP npp, NPRegion invalidRegion) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_invalidateregion called from the wrong thread\n")); + return; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_InvalidateRegion: npp=%p, region=%p\n", (void*)npp, + (void*)invalidRegion)); + + if (!npp || !npp->ndata) { + NS_WARNING("_invalidateregion: npp or npp->ndata == 0"); + return; + } + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + + PluginDestructionGuard guard(inst); + + inst->InvalidateRegion((NPRegion)invalidRegion); +} + +void _forceredraw(NPP npp) {} + +NPObject* _getwindowobject(NPP npp) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getwindowobject called from the wrong thread\n")); + return nullptr; + } + + // The window want to return here is the outer window, *not* the inner (since + // we don't know what the plugin will do with it). + Document* doc = GetDocumentFromNPP(npp); + NS_ENSURE_TRUE(doc, nullptr); + nsCOMPtr<nsPIDOMWindowOuter> outer = doc->GetWindow(); + NS_ENSURE_TRUE(outer, nullptr); + + JS::Rooted<JSObject*> windowProxy( + dom::RootingCx(), nsGlobalWindowOuter::Cast(outer)->GetGlobalJSObject()); + JS::Rooted<JSObject*> global(dom::RootingCx(), + JS::GetNonCCWObjectGlobal(windowProxy)); + return nsJSObjWrapper::GetNewOrUsed(npp, windowProxy, global); +} + +NPObject* _getpluginelement(NPP npp) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getpluginelement called from the wrong thread\n")); + return nullptr; + } + + nsNPAPIPluginInstance* inst = static_cast<nsNPAPIPluginInstance*>(npp->ndata); + if (!inst) return nullptr; + + RefPtr<dom::Element> element; + inst->GetDOMElement(getter_AddRefs(element)); + + if (!element) return nullptr; + + Document* doc = GetDocumentFromNPP(npp); + if (NS_WARN_IF(!doc)) { + return nullptr; + } + + dom::AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(doc->GetInnerWindow()))) { + return nullptr; + } + JSContext* cx = jsapi.cx(); + + nsCOMPtr<nsIXPConnect> xpc(nsIXPConnect::XPConnect()); + NS_ENSURE_TRUE(xpc, nullptr); + + JS::RootedValue val(cx); + if (!ToJSValue(cx, element, &val)) { + return nullptr; + } + + if (NS_WARN_IF(!val.isObject())) { + return nullptr; + } + + JS::RootedObject obj(cx, &val.toObject()); + JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + return nsJSObjWrapper::GetNewOrUsed(npp, obj, global); +} + +NPIdentifier _getstringidentifier(const NPUTF8* name) { + if (!name) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getstringidentifier: passed null name")); + return nullptr; + } + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getstringidentifier called from the wrong thread\n")); + } + + AutoSafeJSContext cx; + return doGetIdentifier(cx, name); +} + +void _getstringidentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getstringidentifiers called from the wrong thread\n")); + } + + AutoSafeJSContext cx; + + for (int32_t i = 0; i < nameCount; ++i) { + if (names[i]) { + identifiers[i] = doGetIdentifier(cx, names[i]); + } else { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getstringidentifiers: passed null name")); + identifiers[i] = nullptr; + } + } +} + +NPIdentifier _getintidentifier(int32_t intid) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getstringidentifier called from the wrong thread\n")); + } + return IntToNPIdentifier(intid); +} + +NPUTF8* _utf8fromidentifier(NPIdentifier id) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_utf8fromidentifier called from the wrong thread\n")); + } + if (!id) return nullptr; + + if (!NPIdentifierIsString(id)) { + return nullptr; + } + + JSString* str = NPIdentifierToString(id); + nsAutoString autoStr; + AssignJSLinearString(autoStr, JS_ASSERT_STRING_IS_LINEAR(str)); + + return ToNewUTF8String(autoStr); +} + +int32_t _intfromidentifier(NPIdentifier id) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_intfromidentifier called from the wrong thread\n")); + } + + if (!NPIdentifierIsInt(id)) { + return INT32_MIN; + } + + return NPIdentifierToInt(id); +} + +bool _identifierisstring(NPIdentifier id) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_identifierisstring called from the wrong thread\n")); + } + + return NPIdentifierIsString(id); +} + +NPObject* _createobject(NPP npp, NPClass* aClass) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_createobject called from the wrong thread\n")); + return nullptr; + } + if (!npp) { + NS_ERROR("Null npp passed to _createobject()!"); + + return nullptr; + } + + PluginDestructionGuard guard(npp); + + if (!aClass) { + NS_ERROR("Null class passed to _createobject()!"); + + return nullptr; + } + + NPPAutoPusher nppPusher(npp); + + NPObject* npobj; + + if (aClass->allocate) { + npobj = aClass->allocate(npp, aClass); + } else { + npobj = (NPObject*)malloc(sizeof(NPObject)); + } + + if (npobj) { + npobj->_class = aClass; + npobj->referenceCount = 1; + NS_LOG_ADDREF(npobj, 1, "BrowserNPObject", sizeof(NPObject)); + } + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("Created NPObject %p, NPClass %p\n", npobj, aClass)); + + return npobj; +} + +NPObject* _retainobject(NPObject* npobj) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_retainobject called from the wrong thread\n")); + } + if (npobj) { +#ifdef NS_BUILD_REFCNT_LOGGING + int32_t refCnt = +#endif + PR_ATOMIC_INCREMENT((int32_t*)&npobj->referenceCount); + NS_LOG_ADDREF(npobj, refCnt, "BrowserNPObject", sizeof(NPObject)); + } + + return npobj; +} + +void _releaseobject(NPObject* npobj) { + // If nothing is passed, just return, even if we're on the wrong thread. + if (!npobj) { + return; + } + + int32_t refCnt = PR_ATOMIC_DECREMENT((int32_t*)&npobj->referenceCount); + NS_LOG_RELEASE(npobj, refCnt, "BrowserNPObject"); + + if (refCnt == 0) { + nsNPObjWrapper::OnDestroy(npobj); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("Deleting NPObject %p, refcount hit 0\n", npobj)); + + if (npobj->_class && npobj->_class->deallocate) { + npobj->_class->deallocate(npobj); + } else { + free(npobj); + } + } +} + +bool _invoke(NPP npp, NPObject* npobj, NPIdentifier method, + const NPVariant* args, uint32_t argCount, NPVariant* result) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_invoke called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->invoke) return false; + + PluginDestructionGuard guard(npp); + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_Invoke(npp %p, npobj %p, method %p, args %d\n", npp, + npobj, method, argCount)); + + return npobj->_class->invoke(npobj, method, args, argCount, result); +} + +bool _invokeDefault(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_invokedefault called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->invokeDefault) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG( + PLUGIN_LOG_NOISY, + ("NPN_InvokeDefault(npp %p, npobj %p, args %d\n", npp, npobj, argCount)); + + return npobj->_class->invokeDefault(npobj, args, argCount, result); +} + +bool _evaluate(NPP npp, NPObject* npobj, NPString* script, NPVariant* result) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_evaluate called from the wrong thread\n")); + return false; + } + if (!npp) return false; + + NPPAutoPusher nppPusher(npp); + + Document* doc = GetDocumentFromNPP(npp); + NS_ENSURE_TRUE(doc, false); + + nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(doc->GetInnerWindow()); + if (NS_WARN_IF(!win || !win->HasJSGlobal())) { + return false; + } + + nsAutoMicroTask mt; + dom::AutoEntryScript aes(win, "NPAPI NPN_evaluate"); + JSContext* cx = aes.cx(); + + JS::Rooted<JSObject*> obj(cx, nsNPObjWrapper::GetNewOrUsed(npp, cx, npobj)); + + if (!obj) { + return false; + } + + obj = js::ToWindowIfWindowProxy(obj); + MOZ_ASSERT(obj, "ToWindowIfWindowProxy should never return null"); + + if (result) { + // Initialize the out param to void + VOID_TO_NPVARIANT(*result); + } + + if (!script || !script->UTF8Length || !script->UTF8Characters) { + // Nothing to evaluate. + + return true; + } + + NS_ConvertUTF8toUTF16 utf16script(script->UTF8Characters, script->UTF8Length); + + nsIPrincipal* principal = doc->NodePrincipal(); + + nsCString specStr; + const char* spec; + + principal->GetAsciiSpec(specStr); + spec = specStr.get(); + + if (specStr.IsEmpty()) { + // No URI in a principal means it's the system principal. If the + // document URI is a chrome:// URI, pass that in as the URI of the + // script, else pass in null for the filename as there's no way to + // know where this document really came from. Passing in null here + // also means that the script gets treated by XPConnect as if it + // needs additional protection, which is what we want for unknown + // chrome code anyways. + nsCOMPtr<nsIURI> uri = doc->GetDocumentURI(); + if (uri && uri->SchemeIs("chrome")) { + uri->GetSpec(specStr); + spec = specStr.get(); + } else { + spec = nullptr; + } + } + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_Evaluate(npp %p, npobj %p, script <<<%s>>>) called\n", + npp, npobj, script->UTF8Characters)); + + JS::CompileOptions options(cx); + options.setFileAndLine(spec, 0); + JS::Rooted<JS::Value> rval(cx); + JS::RootedVector<JSObject*> scopeChain(cx); + if (!JS_IsGlobalObject(obj) && !scopeChain.append(obj)) { + return false; + } + // nsNPObjWrapper::GetNewOrUsed returns an object in the current compartment + // of the JSContext (it might be a CCW). + MOZ_RELEASE_ASSERT(JS::GetCompartment(obj) == js::GetContextCompartment(cx), + "nsNPObjWrapper::GetNewOrUsed must wrap its return value"); + obj = JS::CurrentGlobalOrNull(cx); + MOZ_ASSERT(obj); + nsresult rv = NS_OK; + { + JSExecutionContext exec(cx, obj); + exec.SetScopeChain(scopeChain); + exec.Compile(options, utf16script); + rv = exec.ExecScript(&rval); + } + + if (!JS_WrapValue(cx, &rval)) { + return false; + } + + return NS_SUCCEEDED(rv) && + (!result || JSValToNPVariant(npp, cx, rval, result)); +} + +bool _getproperty(NPP npp, NPObject* npobj, NPIdentifier property, + NPVariant* result) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getproperty called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->getProperty) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_GetProperty(npp %p, npobj %p, property %p) called\n", + npp, npobj, property)); + + if (!npobj->_class->getProperty(npobj, property, result)) return false; + + return true; +} + +bool _setproperty(NPP npp, NPObject* npobj, NPIdentifier property, + const NPVariant* value) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_setproperty called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->setProperty) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_SetProperty(npp %p, npobj %p, property %p) called\n", + npp, npobj, property)); + + return npobj->_class->setProperty(npobj, property, value); +} + +bool _removeproperty(NPP npp, NPObject* npobj, NPIdentifier property) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_removeproperty called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->removeProperty) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_RemoveProperty(npp %p, npobj %p, property %p) called\n", + npp, npobj, property)); + + return npobj->_class->removeProperty(npobj, property); +} + +bool _hasproperty(NPP npp, NPObject* npobj, NPIdentifier propertyName) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_hasproperty called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->hasProperty) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_HasProperty(npp %p, npobj %p, property %p) called\n", + npp, npobj, propertyName)); + + return npobj->_class->hasProperty(npobj, propertyName); +} + +bool _hasmethod(NPP npp, NPObject* npobj, NPIdentifier methodName) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_hasmethod called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || !npobj->_class->hasMethod) + return false; + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_HasMethod(npp %p, npobj %p, property %p) called\n", npp, + npobj, methodName)); + + return npobj->_class->hasMethod(npobj, methodName); +} + +bool _enumerate(NPP npp, NPObject* npobj, NPIdentifier** identifier, + uint32_t* count) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_enumerate called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class) return false; + + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("NPN_Enumerate(npp %p, npobj %p) called\n", npp, npobj)); + + if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(npobj->_class) || + !npobj->_class->enumerate) { + *identifier = 0; + *count = 0; + return true; + } + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + return npobj->_class->enumerate(npobj, identifier, count); +} + +bool _construct(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_construct called from the wrong thread\n")); + return false; + } + if (!npp || !npobj || !npobj->_class || + !NP_CLASS_STRUCT_VERSION_HAS_CTOR(npobj->_class) || + !npobj->_class->construct) { + return false; + } + + NPPExceptionAutoHolder nppExceptionHolder; + NPPAutoPusher nppPusher(npp); + + return npobj->_class->construct(npobj, args, argCount, result); +} + +void _releasevariantvalue(NPVariant* variant) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_releasevariantvalue called from the wrong thread\n")); + } + switch (variant->type) { + case NPVariantType_Void: + case NPVariantType_Null: + case NPVariantType_Bool: + case NPVariantType_Int32: + case NPVariantType_Double: + break; + case NPVariantType_String: { + const NPString* s = &NPVARIANT_TO_STRING(*variant); + + if (s->UTF8Characters) { +#if defined(MOZ_MEMORY) && defined(XP_WIN) + if (malloc_usable_size((void*)s->UTF8Characters) != 0) { + free((void*)s->UTF8Characters); + } else { + void* p = (void*)s->UTF8Characters; + DWORD nheaps = 0; + AutoTArray<HANDLE, 50> heaps; + nheaps = GetProcessHeaps(0, heaps.Elements()); + heaps.AppendElements(nheaps); + GetProcessHeaps(nheaps, heaps.Elements()); + for (DWORD i = 0; i < nheaps; i++) { + if (InHeap(heaps[i], p)) { + HeapFree(heaps[i], 0, p); + break; + } + } + } +#else + free((void*)s->UTF8Characters); +#endif + } + break; + } + case NPVariantType_Object: { + NPObject* npobj = NPVARIANT_TO_OBJECT(*variant); + + if (npobj) _releaseobject(npobj); + + break; + } + default: + NS_ERROR("Unknown NPVariant type!"); + } + + VOID_TO_NPVARIANT(*variant); +} + +void _setexception(NPObject* npobj, const NPUTF8* message) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_setexception called from the wrong thread\n")); + return; + } + + if (!message) return; + + if (gNPPException) { + // If a plugin throws multiple exceptions, we'll only report the + // last one for now. + free(gNPPException); + } + + gNPPException = strdup(message); +} + +NPError _getvalue(NPP npp, NPNVariable variable, void* result) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getvalue called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_GetValue: npp=%p, var=%d\n", (void*)npp, (int)variable)); + + nsresult res; + + PluginDestructionGuard guard(npp); + + // Cast NPNVariable enum to int to avoid warnings about including switch + // cases for android_npapi.h's non-standard ANPInterface values. + switch (static_cast<int>(variable)) { +#if defined(XP_UNIX) && !defined(XP_MACOSX) + case NPNVxDisplay: { +# if defined(MOZ_X11) + if (npp) { + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + bool windowless = false; + inst->IsWindowless(&windowless); + // The documentation on the types for many variables in NP(N|P)_GetValue + // is vague. Often boolean values are NPBool (1 byte), but + // https://developer.mozilla.org/en/XEmbed_Extension_for_Mozilla_Plugins + // treats NPPVpluginNeedsXEmbed as PRBool (int), and + // on x86/32-bit, flash stores to this using |movl 0x1,&needsXEmbed|. + // thus we can't use NPBool for needsXEmbed, or the three bytes above + // it on the stack would get clobbered. so protect with the larger bool. + int needsXEmbed = 0; + if (!windowless) { + res = inst->GetValueFromPlugin(NPPVpluginNeedsXEmbed, &needsXEmbed); + // If the call returned an error code make sure we still use our + // default value. + if (NS_FAILED(res)) { + needsXEmbed = 0; + } + } + if (windowless || needsXEmbed) { + (*(Display**)result) = mozilla::DefaultXDisplay(); + return NPERR_NO_ERROR; + } + } +# endif + return NPERR_GENERIC_ERROR; + } + + case NPNVxtAppContext: + return NPERR_GENERIC_ERROR; +#endif + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + case NPNVnetscapeWindow: { + if (!npp || !npp->ndata) return NPERR_INVALID_INSTANCE_ERROR; + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + + RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner(); + NS_ENSURE_TRUE(owner, NPERR_NO_ERROR); + + if (NS_SUCCEEDED(owner->GetNetscapeWindow(result))) { + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; + } +#endif + + case NPNVjavascriptEnabledBool: { + *(NPBool*)result = false; + bool js = false; + res = Preferences::GetBool("javascript.enabled", &js); + if (NS_SUCCEEDED(res)) { + *(NPBool*)result = js; + } + return NPERR_NO_ERROR; + } + + case NPNVasdEnabledBool: + *(NPBool*)result = false; + return NPERR_NO_ERROR; + + case NPNVisOfflineBool: { + bool offline = false; + nsCOMPtr<nsIIOService> ioservice = + do_GetService(NS_IOSERVICE_CONTRACTID, &res); + if (NS_SUCCEEDED(res)) res = ioservice->GetOffline(&offline); + if (NS_FAILED(res)) return NPERR_GENERIC_ERROR; + + *(NPBool*)result = offline; + return NPERR_NO_ERROR; + } + + case NPNVToolkit: { +#ifdef MOZ_WIDGET_GTK + *((NPNToolkitType*)result) = NPNVGtk2; +#endif + + if (*(NPNToolkitType*)result) return NPERR_NO_ERROR; + + return NPERR_GENERIC_ERROR; + } + + case NPNVSupportsXEmbedBool: { +#ifdef MOZ_WIDGET_GTK + *(NPBool*)result = true; +#else + *(NPBool*)result = false; +#endif + return NPERR_NO_ERROR; + } + + case NPNVWindowNPObject: { + *(NPObject**)result = _getwindowobject(npp); + + return *(NPObject**)result ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR; + } + + case NPNVPluginElementNPObject: { + *(NPObject**)result = _getpluginelement(npp); + + return *(NPObject**)result ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR; + } + + case NPNVSupportsWindowless: { +#if defined(XP_WIN) || defined(XP_MACOSX) || \ + (defined(MOZ_X11) && defined(MOZ_WIDGET_GTK)) + *(NPBool*)result = true; +#else + *(NPBool*)result = false; +#endif + return NPERR_NO_ERROR; + } + + case NPNVprivateModeBool: { + bool privacy; + nsNPAPIPluginInstance* inst = + static_cast<nsNPAPIPluginInstance*>(npp->ndata); + if (!inst) return NPERR_GENERIC_ERROR; + + nsresult rv = inst->IsPrivateBrowsing(&privacy); + if (NS_FAILED(rv)) return NPERR_GENERIC_ERROR; + *(NPBool*)result = (NPBool)privacy; + return NPERR_NO_ERROR; + } + + case NPNVdocumentOrigin: { + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + if (!inst) { + return NPERR_GENERIC_ERROR; + } + + RefPtr<dom::Element> element; + inst->GetDOMElement(getter_AddRefs(element)); + if (!element) { + return NPERR_GENERIC_ERROR; + } + + nsIPrincipal* principal = element->NodePrincipal(); + + nsAutoString utf16Origin; + res = nsContentUtils::GetUTFOrigin(principal, utf16Origin); + if (NS_FAILED(res)) { + return NPERR_GENERIC_ERROR; + } + + nsCOMPtr<nsIIDNService> idnService = + do_GetService(NS_IDNSERVICE_CONTRACTID); + if (!idnService) { + return NPERR_GENERIC_ERROR; + } + + // This is a bit messy: we convert to UTF-8 here, but then + // nsIDNService::Normalize will convert back to UTF-16 for processing, + // and back to UTF-8 again to return the result. + // Alternative: perhaps we should add a NormalizeUTF16 version of the API, + // and just convert to UTF-8 for the final return (resulting in one + // encoding form conversion instead of three). + NS_ConvertUTF16toUTF8 utf8Origin(utf16Origin); + nsAutoCString normalizedUTF8Origin; + res = idnService->Normalize(utf8Origin, normalizedUTF8Origin); + if (NS_FAILED(res)) { + return NPERR_GENERIC_ERROR; + } + + *(char**)result = ToNewCString(normalizedUTF8Origin); + return *(char**)result ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR; + } + +#ifdef XP_MACOSX + case NPNVpluginDrawingModel: { + if (npp) { + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + if (inst) { + NPDrawingModel drawingModel; + inst->GetDrawingModel((int32_t*)&drawingModel); + *(NPDrawingModel*)result = drawingModel; + return NPERR_NO_ERROR; + } + } + return NPERR_GENERIC_ERROR; + } + +# ifndef NP_NO_QUICKDRAW + case NPNVsupportsQuickDrawBool: { + *(NPBool*)result = false; + + return NPERR_NO_ERROR; + } +# endif + + case NPNVsupportsCoreGraphicsBool: { + *(NPBool*)result = true; + + return NPERR_NO_ERROR; + } + + case NPNVsupportsCoreAnimationBool: { + *(NPBool*)result = true; + + return NPERR_NO_ERROR; + } + + case NPNVsupportsInvalidatingCoreAnimationBool: { + *(NPBool*)result = true; + + return NPERR_NO_ERROR; + } + + case NPNVsupportsCompositingCoreAnimationPluginsBool: { + *(NPBool*)result = PR_TRUE; + + return NPERR_NO_ERROR; + } + +# ifndef NP_NO_CARBON + case NPNVsupportsCarbonBool: { + *(NPBool*)result = false; + + return NPERR_NO_ERROR; + } +# endif + case NPNVsupportsCocoaBool: { + *(NPBool*)result = true; + + return NPERR_NO_ERROR; + } + + case NPNVsupportsUpdatedCocoaTextInputBool: { + *(NPBool*)result = true; + return NPERR_NO_ERROR; + } +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + case NPNVcontentsScaleFactor: { + nsNPAPIPluginInstance* inst = + (nsNPAPIPluginInstance*)(npp ? npp->ndata : nullptr); + double scaleFactor = inst ? inst->GetContentsScaleFactor() : 1.0; + *(double*)result = scaleFactor; + return NPERR_NO_ERROR; + } +#endif + + case NPNVCSSZoomFactor: { + nsNPAPIPluginInstance* inst = + (nsNPAPIPluginInstance*)(npp ? npp->ndata : nullptr); + double scaleFactor = inst ? inst->GetCSSZoomFactor() : 1.0; + *(double*)result = scaleFactor; + return NPERR_NO_ERROR; + } + + // we no longer hand out any XPCOM objects + case NPNVDOMElement: + case NPNVDOMWindow: + case NPNVserviceManager: + // old XPCOM objects, no longer supported, but null out the out + // param to avoid crashing plugins that still try to use this. + *(nsISupports**)result = nullptr; + [[fallthrough]]; + + default: + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_getvalue unhandled get value: %d\n", variable)); + return NPERR_GENERIC_ERROR; + } +} + +NPError _setvalue(NPP npp, NPPVariable variable, void* result) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_setvalue called from the wrong thread\n")); + return NPERR_INVALID_PARAM; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_SetValue: npp=%p, var=%d\n", (void*)npp, (int)variable)); + + if (!npp) return NPERR_INVALID_INSTANCE_ERROR; + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + + NS_ASSERTION(inst, "null instance"); + + if (!inst) return NPERR_INVALID_INSTANCE_ERROR; + + PluginDestructionGuard guard(inst); + + // Cast NPNVariable enum to int to avoid warnings about including switch + // cases for android_npapi.h's non-standard ANPInterface values. + switch (static_cast<int>(variable)) { + // we should keep backward compatibility with NPAPI where the + // actual pointer value is checked rather than its content + // when passing booleans + case NPPVpluginWindowBool: { +#ifdef XP_MACOSX + // This setting doesn't apply to OS X (only to Windows and Unix/Linux). + // See https://developer.mozilla.org/En/NPN_SetValue#section_5. Return + // NPERR_NO_ERROR here to conform to other browsers' behavior on OS X + // (e.g. Safari and Opera). + return NPERR_NO_ERROR; +#else + NPBool bWindowless = (result == nullptr); + return inst->SetWindowless(bWindowless); +#endif + } + case NPPVpluginTransparentBool: { + NPBool bTransparent = (result != nullptr); + return inst->SetTransparent(bTransparent); + } + + case NPPVjavascriptPushCallerBool: { + return NPERR_NO_ERROR; + } + + case NPPVpluginKeepLibraryInMemory: { + NPBool bCached = (result != nullptr); + inst->SetCached(bCached); + return NPERR_NO_ERROR; + } + + case NPPVpluginUsesDOMForCursorBool: { + bool useDOMForCursor = (result != nullptr); + return inst->SetUsesDOMForCursor(useDOMForCursor); + } + + case NPPVpluginIsPlayingAudio: { + const bool isPlaying = result; + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + MOZ_ASSERT(inst); + + if (!isPlaying && !inst->HasAudioChannelAgent()) { + return NPERR_NO_ERROR; + } + + if (isPlaying) { + inst->NotifyStartedPlaying(); + } else { + inst->NotifyStoppedPlaying(); + } + + return NPERR_NO_ERROR; + } + + case NPPVpluginDrawingModel: { + if (inst) { + inst->SetDrawingModel((NPDrawingModel)NS_PTR_TO_INT32(result)); + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; + } + +#ifdef XP_MACOSX + case NPPVpluginEventModel: { + if (inst) { + inst->SetEventModel((NPEventModel)NS_PTR_TO_INT32(result)); + return NPERR_NO_ERROR; + } else { + return NPERR_GENERIC_ERROR; + } + } +#endif + default: + return NPERR_GENERIC_ERROR; + } +} + +NPError _requestread(NPStream* pstream, NPByteRange* rangeList) { + return NPERR_STREAM_NOT_SEEKABLE; +} + +// Deprecated, only stubbed out +void* /* OJI type: JRIEnv* */ +_getJavaEnv() { + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_GetJavaEnv\n")); + return nullptr; +} + +const char* _useragent(NPP npp) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_useragent called from the wrong thread\n")); + return nullptr; + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_UserAgent: npp=%p\n", (void*)npp)); + + nsCOMPtr<nsIPluginHost> pluginHostCOM( + do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); + nsPluginHost* pluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get()); + if (!pluginHost) { + return nullptr; + } + + const char* retstr; + nsresult rv = pluginHost->UserAgent(&retstr); + if (NS_FAILED(rv)) return nullptr; + + return retstr; +} + +void* _memalloc(uint32_t size) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPN_memalloc called from the wrong thread\n")); + } + NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, ("NPN_MemAlloc: size=%d\n", size)); + return moz_xmalloc(size); +} + +// Deprecated, only stubbed out +void* /* OJI type: jref */ +_getJavaPeer(NPP npp) { + NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_GetJavaPeer: npp=%p\n", (void*)npp)); + return nullptr; +} + +void _pushpopupsenabledstate(NPP npp, NPBool enabled) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG( + PLUGIN_LOG_ALWAYS, + ("NPN_pushpopupsenabledstate called from the wrong thread\n")); + return; + } + nsNPAPIPluginInstance* inst = + npp ? (nsNPAPIPluginInstance*)npp->ndata : nullptr; + if (!inst) return; + + inst->PushPopupsEnabledState(enabled); +} + +void _poppopupsenabledstate(NPP npp) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG( + PLUGIN_LOG_ALWAYS, + ("NPN_poppopupsenabledstate called from the wrong thread\n")); + return; + } + nsNPAPIPluginInstance* inst = + npp ? (nsNPAPIPluginInstance*)npp->ndata : nullptr; + if (!inst) return; + + inst->PopPopupsEnabledState(); +} + +NPError _getvalueforurl(NPP instance, NPNURLVariable variable, const char* url, + char** value, uint32_t* len) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_getvalueforurl called from the wrong thread\n")); + return NPERR_GENERIC_ERROR; + } + + if (!instance) { + return NPERR_INVALID_PARAM; + } + + if (!url || !*url || !len) { + return NPERR_INVALID_URL; + } + + *len = 0; + + switch (variable) { + case NPNURLVProxy: + // NPNURLVProxy is no longer supported. + *value = nullptr; + return NPERR_GENERIC_ERROR; + + case NPNURLVCookie: + // NPNURLVCookie is no longer supported. + *value = nullptr; + return NPERR_GENERIC_ERROR; + + default: + // Fall through and return an error... + ; + } + + return NPERR_GENERIC_ERROR; +} + +NPError _setvalueforurl(NPP instance, NPNURLVariable variable, const char* url, + const char* value, uint32_t len) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_setvalueforurl called from the wrong thread\n")); + return NPERR_GENERIC_ERROR; + } + + if (!instance) { + return NPERR_INVALID_PARAM; + } + + if (!url || !*url) { + return NPERR_INVALID_URL; + } + + switch (variable) { + case NPNURLVCookie: + // NPNURLVCookie is no longer supported. + return NPERR_GENERIC_ERROR; + + case NPNURLVProxy: + // We don't support setting proxy values, fall through... + default: + // Fall through and return an error... + ; + } + + return NPERR_GENERIC_ERROR; +} + +uint32_t _scheduletimer(NPP instance, uint32_t interval, NPBool repeat, + PluginTimerFunc timerFunc) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_scheduletimer called from the wrong thread\n")); + return 0; + } + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + if (!inst) return 0; + + return inst->ScheduleTimer(interval, repeat, timerFunc); +} + +void _unscheduletimer(NPP instance, uint32_t timerID) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_unscheduletimer called from the wrong thread\n")); + return; + } + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + if (!inst) return; + + inst->UnscheduleTimer(timerID); +} + +NPError _popupcontextmenu(NPP instance, NPMenu* menu) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_popupcontextmenu called from the wrong thread\n")); + return 0; + } + +#ifdef MOZ_WIDGET_COCOA + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + + double pluginX, pluginY; + double screenX, screenY; + + const NPCocoaEvent* currentEvent = + static_cast<NPCocoaEvent*>(inst->GetCurrentEvent()); + if (!currentEvent) { + return NPERR_GENERIC_ERROR; + } + + // Ensure that the events has an x/y value. + if (currentEvent->type != NPCocoaEventMouseDown && + currentEvent->type != NPCocoaEventMouseUp && + currentEvent->type != NPCocoaEventMouseMoved && + currentEvent->type != NPCocoaEventMouseEntered && + currentEvent->type != NPCocoaEventMouseExited && + currentEvent->type != NPCocoaEventMouseDragged) { + return NPERR_GENERIC_ERROR; + } + + pluginX = currentEvent->data.mouse.pluginX; + pluginY = currentEvent->data.mouse.pluginY; + + if ((pluginX < 0.0) || (pluginY < 0.0)) return NPERR_GENERIC_ERROR; + + NPBool success = + _convertpoint(instance, pluginX, pluginY, NPCoordinateSpacePlugin, + &screenX, &screenY, NPCoordinateSpaceScreen); + + if (success) { + return mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu( + menu, screenX, screenY, nullptr, nullptr); + } else { + NS_WARNING("Convertpoint failed, could not created contextmenu."); + return NPERR_GENERIC_ERROR; + } +#else + NS_WARNING("Not supported on this platform!"); + return NPERR_GENERIC_ERROR; +#endif +} + +NPBool _convertpoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_convertpoint called from the wrong thread\n")); + return 0; + } + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + if (!inst) return false; + + return inst->ConvertPoint(sourceX, sourceY, sourceSpace, destX, destY, + destSpace); +} + +void _urlredirectresponse(NPP instance, void* notifyData, NPBool allow) { + if (!NS_IsMainThread()) { + NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS, + ("NPN_convertpoint called from the wrong thread\n")); + return; + } + + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + if (!inst) { + return; + } + + inst->URLRedirectResponse(notifyData, allow); +} + +NPError _initasyncsurface(NPP instance, NPSize* size, NPImageFormat format, + void* initData, NPAsyncSurface* surface) { + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + if (!inst) { + return NPERR_GENERIC_ERROR; + } + + return inst->InitAsyncSurface(size, format, initData, surface); +} + +NPError _finalizeasyncsurface(NPP instance, NPAsyncSurface* surface) { + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + if (!inst) { + return NPERR_GENERIC_ERROR; + } + + return inst->FinalizeAsyncSurface(surface); +} + +void _setcurrentasyncsurface(NPP instance, NPAsyncSurface* surface, + NPRect* changed) { + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata; + if (!inst) { + return; + } + + inst->SetCurrentAsyncSurface(surface, changed); +} + +} // namespace mozilla::plugins::parent diff --git a/dom/plugins/base/nsNPAPIPlugin.h b/dom/plugins/base/nsNPAPIPlugin.h new file mode 100644 index 0000000000..ebc0404932 --- /dev/null +++ b/dom/plugins/base/nsNPAPIPlugin.h @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNPAPIPlugin_h_ +#define nsNPAPIPlugin_h_ + +#include "prlink.h" +#include "npfunctions.h" +#include "nsPluginHost.h" + +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/PluginLibrary.h" +#include "mozilla/RefCounted.h" + +// nsNPAPIPlugin is held alive both by active nsPluginTag instances and +// by active nsNPAPIPluginInstance. +class nsNPAPIPlugin final { + private: + typedef mozilla::PluginLibrary PluginLibrary; + + public: + nsNPAPIPlugin(); + + NS_INLINE_DECL_REFCOUNTING(nsNPAPIPlugin) + + // Constructs and initializes an nsNPAPIPlugin object. A nullptr file path + // will prevent this from calling NP_Initialize. + static nsresult CreatePlugin(nsPluginTag* aPluginTag, + nsNPAPIPlugin** aResult); + + PluginLibrary* GetLibrary(); + // PluginFuncs() can't fail but results are only valid if GetLibrary() + // succeeds + NPPluginFuncs* PluginFuncs(); + +#if defined(XP_MACOSX) && !defined(__LP64__) + void SetPluginRefNum(short aRefNum); +#endif + + // The IPC mechanism notifies the nsNPAPIPlugin if the plugin + // crashes and is no longer usable. pluginDumpID is the ID of the minidump + // that was written, or empty if no minidump was written. + void PluginCrashed(const nsAString& aPluginDumpID, + const nsACString& aAdditionalMinidumps); + + nsresult Shutdown(); + + static nsresult RetainStream(NPStream* pstream, nsISupports** aRetainedPeer); + + private: + ~nsNPAPIPlugin(); + + NPPluginFuncs mPluginFuncs; + PluginLibrary* mLibrary; +}; + +namespace mozilla { +namespace plugins { +namespace parent { + +static_assert(sizeof(NPIdentifier) == sizeof(jsid), + "NPIdentifier must be binary compatible with jsid."); + +inline jsid NPIdentifierToJSId(NPIdentifier id) { + jsid tmp; + JSID_BITS(tmp) = (size_t)id; + return tmp; +} + +inline NPIdentifier JSIdToNPIdentifier(jsid id) { + return (NPIdentifier)JSID_BITS(id); +} + +inline bool NPIdentifierIsString(NPIdentifier id) { + return JSID_IS_STRING(NPIdentifierToJSId(id)); +} + +inline JSString* NPIdentifierToString(NPIdentifier id) { + return JSID_TO_STRING(NPIdentifierToJSId(id)); +} + +inline NPIdentifier StringToNPIdentifier(JSContext* cx, JSString* str) { + return JSIdToNPIdentifier(JS::PropertyKey::fromPinnedString(str)); +} + +inline bool NPIdentifierIsInt(NPIdentifier id) { + return JSID_IS_INT(NPIdentifierToJSId(id)); +} + +inline int NPIdentifierToInt(NPIdentifier id) { + return JSID_TO_INT(NPIdentifierToJSId(id)); +} + +inline NPIdentifier IntToNPIdentifier(int i) { + return JSIdToNPIdentifier(INT_TO_JSID(i)); +} + +JSContext* GetJSContext(NPP npp); + +inline bool NPStringIdentifierIsPermanent(NPIdentifier id) { + AutoSafeJSContext cx; + return JS_StringHasBeenPinned(cx, NPIdentifierToString(id)); +} + +#define NPIdentifier_VOID (JSIdToNPIdentifier(JSID_VOID)) + +NPObject* _getwindowobject(NPP npp); + +NPObject* _getpluginelement(NPP npp); + +NPIdentifier _getstringidentifier(const NPUTF8* name); + +void _getstringidentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers); + +bool _identifierisstring(NPIdentifier identifiers); + +NPIdentifier _getintidentifier(int32_t intid); + +NPUTF8* _utf8fromidentifier(NPIdentifier identifier); + +int32_t _intfromidentifier(NPIdentifier identifier); + +NPObject* _createobject(NPP npp, NPClass* aClass); + +NPObject* _retainobject(NPObject* npobj); + +void _releaseobject(NPObject* npobj); + +bool _invoke(NPP npp, NPObject* npobj, NPIdentifier method, + const NPVariant* args, uint32_t argCount, NPVariant* result); + +bool _invokeDefault(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +bool _evaluate(NPP npp, NPObject* npobj, NPString* script, NPVariant* result); + +bool _getproperty(NPP npp, NPObject* npobj, NPIdentifier property, + NPVariant* result); + +bool _setproperty(NPP npp, NPObject* npobj, NPIdentifier property, + const NPVariant* value); + +bool _removeproperty(NPP npp, NPObject* npobj, NPIdentifier property); + +bool _hasproperty(NPP npp, NPObject* npobj, NPIdentifier propertyName); + +bool _hasmethod(NPP npp, NPObject* npobj, NPIdentifier methodName); + +bool _enumerate(NPP npp, NPObject* npobj, NPIdentifier** identifier, + uint32_t* count); + +bool _construct(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +void _releasevariantvalue(NPVariant* variant); + +void _setexception(NPObject* npobj, const NPUTF8* message); + +void _pushpopupsenabledstate(NPP npp, NPBool enabled); + +void _poppopupsenabledstate(NPP npp); + +NPError _getvalueforurl(NPP instance, NPNURLVariable variable, const char* url, + char** value, uint32_t* len); + +NPError _setvalueforurl(NPP instance, NPNURLVariable variable, const char* url, + const char* value, uint32_t len); + +typedef void (*PluginTimerFunc)(NPP npp, uint32_t timerID); + +uint32_t _scheduletimer(NPP instance, uint32_t interval, NPBool repeat, + PluginTimerFunc timerFunc); + +void _unscheduletimer(NPP instance, uint32_t timerID); + +NPError _popupcontextmenu(NPP instance, NPMenu* menu); + +NPBool _convertpoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace); + +NPError _requestread(NPStream* pstream, NPByteRange* rangeList); + +NPError _geturlnotify(NPP npp, const char* relativeURL, const char* target, + void* notifyData); + +NPError _getvalue(NPP npp, NPNVariable variable, void* r_value); + +NPError _setvalue(NPP npp, NPPVariable variable, void* r_value); + +NPError _geturl(NPP npp, const char* relativeURL, const char* target); + +NPError _posturlnotify(NPP npp, const char* relativeURL, const char* target, + uint32_t len, const char* buf, NPBool file, + void* notifyData); + +NPError _posturl(NPP npp, const char* relativeURL, const char* target, + uint32_t len, const char* buf, NPBool file); + +void _status(NPP npp, const char* message); + +void _memfree(void* ptr); + +uint32_t _memflush(uint32_t size); + +void _reloadplugins(NPBool reloadPages); + +void _invalidaterect(NPP npp, NPRect* invalidRect); + +void _invalidateregion(NPP npp, NPRegion invalidRegion); + +void _forceredraw(NPP npp); + +const char* _useragent(NPP npp); + +void* _memalloc(uint32_t size); + +// Deprecated entry points for the old Java plugin. +void* /* OJI type: JRIEnv* */ +_getJavaEnv(); + +void* /* OJI type: jref */ +_getJavaPeer(NPP npp); + +void _urlredirectresponse(NPP instance, void* notifyData, NPBool allow); + +NPError _initasyncsurface(NPP instance, NPSize* size, NPImageFormat format, + void* initData, NPAsyncSurface* surface); + +NPError _finalizeasyncsurface(NPP instance, NPAsyncSurface* surface); + +void _setcurrentasyncsurface(NPP instance, NPAsyncSurface* surface, + NPRect* changed); + +} /* namespace parent */ +} /* namespace plugins */ +} /* namespace mozilla */ + +const char* PeekException(); + +void PopException(); + +class NPPStack { + public: + static NPP Peek() { return sCurrentNPP; } + + protected: + static NPP sCurrentNPP; +}; + +// XXXjst: The NPPAutoPusher stack is a bit redundant now that +// PluginDestructionGuard exists, and could thus be replaced by code +// that uses the PluginDestructionGuard list of plugins on the +// stack. But they're not identical, and to minimize code changes +// we're keeping both for the moment, and making NPPAutoPusher inherit +// the PluginDestructionGuard class to avoid having to keep two +// separate objects on the stack since we always want a +// PluginDestructionGuard where we use an NPPAutoPusher. + +class MOZ_STACK_CLASS NPPAutoPusher : public NPPStack, + protected PluginDestructionGuard { + public: + explicit NPPAutoPusher(NPP aNpp) + : PluginDestructionGuard(aNpp), mOldNPP(sCurrentNPP) { + NS_ASSERTION(aNpp, "Uh, null aNpp passed to NPPAutoPusher!"); + + sCurrentNPP = aNpp; + } + + ~NPPAutoPusher() { sCurrentNPP = mOldNPP; } + + private: + NPP mOldNPP; +}; + +class NPPExceptionAutoHolder { + public: + NPPExceptionAutoHolder(); + ~NPPExceptionAutoHolder(); + + protected: + char* mOldException; +}; + +#endif // nsNPAPIPlugin_h_ diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp new file mode 100644 index 0000000000..a1462f66f5 --- /dev/null +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -0,0 +1,1203 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#include "mozilla/Logging.h" +#include "nscore.h" +#include "prenv.h" + +#include "nsNPAPIPluginInstance.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsPluginHost.h" +#include "nsPluginLogging.h" +#include "nsContentUtils.h" +#include "nsPluginInstanceOwner.h" + +#include "nsThreadUtils.h" +#include "mozilla/dom/Document.h" +#include "nsIDocShell.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsDirectoryServiceDefs.h" +#include "nsJSNPRuntime.h" +#include "nsPluginStreamListenerPeer.h" +#include "nsSize.h" +#include "nsNetCID.h" +#include "nsIContent.h" +#include "nsVersionComparator.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "nsILoadContext.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/HTMLObjectElementBinding.h" +#include "AudioChannelService.h" +#include "GeckoProfiler.h" + +using namespace mozilla; +using namespace mozilla::dom; + +using namespace mozilla; +using namespace mozilla::plugins::parent; +using namespace mozilla::layers; + +NS_IMPL_ISUPPORTS(nsNPAPIPluginInstance, nsIAudioChannelAgentCallback) + +nsNPAPIPluginInstance::nsNPAPIPluginInstance() + : mDrawingModel(kDefaultDrawingModel), + mRunning(NOT_STARTED), + mWindowless(false), + mTransparent(false), + mCached(false), + mUsesDOMForCursor(false), + mInPluginInitCall(false), + mPlugin(nullptr), + mMIMEType(nullptr), + mOwner(nullptr) +#ifdef XP_MACOSX + , + mCurrentPluginEvent(nullptr) +#endif + , + mCachedParamLength(0), + mCachedParamNames(nullptr), + mCachedParamValues(nullptr) { + mNPP.pdata = nullptr; + mNPP.ndata = this; + + PLUGIN_LOG(PLUGIN_LOG_BASIC, ("nsNPAPIPluginInstance ctor: this=%p\n", this)); +} + +nsNPAPIPluginInstance::~nsNPAPIPluginInstance() { + PLUGIN_LOG(PLUGIN_LOG_BASIC, ("nsNPAPIPluginInstance dtor: this=%p\n", this)); + + if (mMIMEType) { + free(mMIMEType); + mMIMEType = nullptr; + } + + if (!mCachedParamValues || !mCachedParamNames) { + return; + } + MOZ_ASSERT(mCachedParamValues && mCachedParamNames); + + for (uint32_t i = 0; i < mCachedParamLength; i++) { + if (mCachedParamNames[i]) { + free(mCachedParamNames[i]); + mCachedParamNames[i] = nullptr; + } + if (mCachedParamValues[i]) { + free(mCachedParamValues[i]); + mCachedParamValues[i] = nullptr; + } + } + + free(mCachedParamNames); + mCachedParamNames = nullptr; + + free(mCachedParamValues); + mCachedParamValues = nullptr; +} + +uint32_t nsNPAPIPluginInstance::gInUnsafePluginCalls = 0; + +void nsNPAPIPluginInstance::Destroy() { + Stop(); + mPlugin = nullptr; + mAudioChannelAgent = nullptr; +} + +TimeStamp nsNPAPIPluginInstance::StopTime() { return mStopTime; } + +nsresult nsNPAPIPluginInstance::Initialize(nsNPAPIPlugin* aPlugin, + nsPluginInstanceOwner* aOwner, + const nsACString& aMIMEType) { + AUTO_PROFILER_LABEL("nsNPAPIPlugin::Initialize", OTHER); + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsNPAPIPluginInstance::Initialize this=%p\n", this)); + + NS_ENSURE_ARG_POINTER(aPlugin); + NS_ENSURE_ARG_POINTER(aOwner); + + mPlugin = aPlugin; + mOwner = aOwner; + + if (!aMIMEType.IsEmpty()) { + mMIMEType = ToNewCString(aMIMEType); + } + + return Start(); +} + +nsresult nsNPAPIPluginInstance::Stop() { + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsNPAPIPluginInstance::Stop this=%p\n", this)); + + // Make sure the plugin didn't leave popups enabled. + if (mPopupStates.Length() > 0) { + PopupBlocker::PopPopupControlState(PopupBlocker::openAbused); + } + + if (RUNNING != mRunning) { + return NS_OK; + } + + // clean up all outstanding timers + for (uint32_t i = mTimers.Length(); i > 0; i--) + UnscheduleTimer(mTimers[i - 1]->id); + + // If there's code from this plugin instance on the stack, delay the + // destroy. + if (PluginDestructionGuard::DelayDestroy(this)) { + return NS_OK; + } + + mRunning = DESTROYING; + mStopTime = TimeStamp::Now(); + + // clean up open streams + while (mStreamListeners.Length() > 0) { + RefPtr<nsNPAPIPluginStreamListener> currentListener(mStreamListeners[0]); + currentListener->CleanUpStream(NPRES_USER_BREAK); + mStreamListeners.RemoveElement(currentListener); + } + + if (!mPlugin || !mPlugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + NPError error = NPERR_GENERIC_ERROR; + if (pluginFunctions->destroy) { + NPSavedData* sdata = 0; + + NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->destroy)(&mNPP, &sdata), + this, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP Destroy called: this=%p, npp=%p, return=%d\n", this, + &mNPP, error)); + } + mRunning = DESTROYED; + + nsJSNPRuntime::OnPluginDestroy(&mNPP); + + if (error != NPERR_NO_ERROR) + return NS_ERROR_FAILURE; + else + return NS_OK; +} + +already_AddRefed<nsPIDOMWindowOuter> nsNPAPIPluginInstance::GetDOMWindow() { + if (!mOwner) return nullptr; + + RefPtr<nsPluginInstanceOwner> kungFuDeathGrip(mOwner); + + nsCOMPtr<Document> doc; + kungFuDeathGrip->GetDocument(getter_AddRefs(doc)); + if (!doc) return nullptr; + + RefPtr<nsPIDOMWindowOuter> window = doc->GetWindow(); + + return window.forget(); +} + +nsresult nsNPAPIPluginInstance::GetTagType(nsPluginTagType* result) { + if (!mOwner) { + return NS_ERROR_FAILURE; + } + + return mOwner->GetTagType(result); +} + +nsTArray<nsNPAPIPluginStreamListener*>* +nsNPAPIPluginInstance::StreamListeners() { + return &mStreamListeners; +} + +nsTArray<nsPluginStreamListenerPeer*>* +nsNPAPIPluginInstance::FileCachedStreamListeners() { + return &mFileCachedStreamListeners; +} + +nsresult nsNPAPIPluginInstance::Start() { + if (mRunning == RUNNING) { + return NS_OK; + } + + if (!mOwner) { + MOZ_ASSERT(false, "Should not be calling Start() on unowned plugin."); + return NS_ERROR_FAILURE; + } + + PluginDestructionGuard guard(this); + + nsTArray<MozPluginParameter> attributes; + nsTArray<MozPluginParameter> params; + + nsPluginTagType tagtype; + nsresult rv = GetTagType(&tagtype); + if (NS_SUCCEEDED(rv)) { + mOwner->GetAttributes(attributes); + mOwner->GetParameters(params); + } else { + MOZ_ASSERT(false, "Failed to get tag type."); + } + + mCachedParamLength = attributes.Length() + 1 + params.Length(); + + // We add an extra entry "PARAM" as a separator between the attribute + // and param values, but we don't count it if there are no <param> entries. + // Legacy behavior quirk. + uint32_t quirkParamLength = + params.Length() ? mCachedParamLength : attributes.Length(); + + mCachedParamNames = (char**)moz_xmalloc(sizeof(char*) * mCachedParamLength); + mCachedParamValues = (char**)moz_xmalloc(sizeof(char*) * mCachedParamLength); + + for (uint32_t i = 0; i < attributes.Length(); i++) { + mCachedParamNames[i] = ToNewUTF8String(attributes[i].mName); + mCachedParamValues[i] = ToNewUTF8String(attributes[i].mValue); + } + + mCachedParamNames[attributes.Length()] = ToNewUTF8String(u"PARAM"_ns); + mCachedParamValues[attributes.Length()] = nullptr; + + for (uint32_t i = 0, pos = attributes.Length() + 1; i < params.Length(); + i++) { + mCachedParamNames[pos] = ToNewUTF8String(params[i].mName); + mCachedParamValues[pos] = ToNewUTF8String(params[i].mValue); + pos++; + } + + const char* mimetype; + NPError error = NPERR_GENERIC_ERROR; + + GetMIMEType(&mimetype); + + bool oldVal = mInPluginInitCall; + mInPluginInitCall = true; + + // Need this on the stack before calling NPP_New otherwise some callbacks that + // the plugin may make could fail (NPN_HasProperty, for example). + NPPAutoPusher autopush(&mNPP); + + if (!mPlugin) return NS_ERROR_FAILURE; + + PluginLibrary* library = mPlugin->GetLibrary(); + if (!library) return NS_ERROR_FAILURE; + + // Mark this instance as running before calling NPP_New because the plugin may + // call other NPAPI functions, like NPN_GetURLNotify, that assume this is set + // before returning. If the plugin returns failure, we'll clear it out below. + mRunning = RUNNING; + + nsresult newResult = + library->NPP_New((char*)mimetype, &mNPP, quirkParamLength, + mCachedParamNames, mCachedParamValues, nullptr, &error); + mInPluginInitCall = oldVal; + + NPP_PLUGIN_LOG( + PLUGIN_LOG_NORMAL, + ("NPP New called: this=%p, npp=%p, mime=%s, argc=%d, return=%d\n", this, + &mNPP, mimetype, quirkParamLength, error)); + + if (NS_FAILED(newResult) || error != NPERR_NO_ERROR) { + mRunning = DESTROYED; + nsJSNPRuntime::OnPluginDestroy(&mNPP); + return NS_ERROR_FAILURE; + } + + return newResult; +} + +nsresult nsNPAPIPluginInstance::SetWindow(NPWindow* window) { + // NPAPI plugins don't want a SetWindow(nullptr). + if (!window || RUNNING != mRunning) return NS_OK; + +#if MOZ_WIDGET_GTK + // bug 108347, flash plugin on linux doesn't like window->width <= 0 + return NS_OK; +#endif + + if (!mPlugin || !mPlugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + if (pluginFunctions->setwindow) { + PluginDestructionGuard guard(this); + + // XXX Turns out that NPPluginWindow and NPWindow are structurally + // identical (on purpose!), so there's no need to make a copy. + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsNPAPIPluginInstance::SetWindow (about to call it) this=%p\n", + this)); + + bool oldVal = mInPluginInitCall; + mInPluginInitCall = true; + + NPPAutoPusher nppPusher(&mNPP); + + NPError error; + NS_TRY_SAFE_CALL_RETURN( + error, (*pluginFunctions->setwindow)(&mNPP, (NPWindow*)window), this, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + // 'error' is only used if this is a logging-enabled build. + // That is somewhat complex to check, so we just use "unused" + // to suppress any compiler warnings in build configurations + // where the logging is a no-op. + mozilla::Unused << error; + + mInPluginInitCall = oldVal; + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP SetWindow called: this=%p, [x=%d,y=%d,w=%d,h=%d], " + "clip[t=%d,b=%d,l=%d,r=%d], return=%d\n", + this, window->x, window->y, window->width, window->height, + window->clipRect.top, window->clipRect.bottom, + window->clipRect.left, window->clipRect.right, error)); + } + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::NewStreamListener( + const char* aURL, void* notifyData, + nsNPAPIPluginStreamListener** listener) { + RefPtr<nsNPAPIPluginStreamListener> sl = + new nsNPAPIPluginStreamListener(this, notifyData, aURL); + + mStreamListeners.AppendElement(sl); + + sl.forget(listener); + + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::Print(NPPrint* platformPrint) { + NS_ENSURE_TRUE(platformPrint, NS_ERROR_NULL_POINTER); + + PluginDestructionGuard guard(this); + + if (!mPlugin || !mPlugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + NPPrint* thePrint = (NPPrint*)platformPrint; + + // to be compatible with the older SDK versions and to match what + // NPAPI and other browsers do, overwrite |window.type| field with one + // more copy of |platformPrint|. See bug 113264 + uint16_t sdkmajorversion = (pluginFunctions->version & 0xff00) >> 8; + uint16_t sdkminorversion = pluginFunctions->version & 0x00ff; + if ((sdkmajorversion == 0) && (sdkminorversion < 11)) { + // Let's copy platformPrint bytes over to where it was supposed to be + // in older versions -- four bytes towards the beginning of the struct + // but we should be careful about possible misalignments + if (sizeof(NPWindowType) >= sizeof(void*)) { + void* source = thePrint->print.embedPrint.platformPrint; + void** destination = (void**)&(thePrint->print.embedPrint.window.type); + *destination = source; + } else { + NS_ERROR("Incompatible OS for assignment"); + } + } + + if (pluginFunctions->print) + NS_TRY_SAFE_CALL_VOID((*pluginFunctions->print)(&mNPP, thePrint), this, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP PrintProc called: this=%p, pDC=%p, " + "[x=%d,y=%d,w=%d,h=%d], clip[t=%d,b=%d,l=%d,r=%d]\n", + this, platformPrint->print.embedPrint.platformPrint, + platformPrint->print.embedPrint.window.x, + platformPrint->print.embedPrint.window.y, + platformPrint->print.embedPrint.window.width, + platformPrint->print.embedPrint.window.height, + platformPrint->print.embedPrint.window.clipRect.top, + platformPrint->print.embedPrint.window.clipRect.bottom, + platformPrint->print.embedPrint.window.clipRect.left, + platformPrint->print.embedPrint.window.clipRect.right)); + + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::HandleEvent( + void* event, int16_t* result, NSPluginCallReentry aSafeToReenterGecko) { + if (RUNNING != mRunning) return NS_OK; + + AUTO_PROFILER_LABEL("nsNPAPIPluginInstance::HandleEvent", OTHER); + + if (!event) return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(this); + + if (!mPlugin || !mPlugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + int16_t tmpResult = kNPEventNotHandled; + + if (pluginFunctions->event) { +#ifdef XP_MACOSX + mCurrentPluginEvent = event; +#endif +#if defined(XP_WIN) + NS_TRY_SAFE_CALL_RETURN(tmpResult, (*pluginFunctions->event)(&mNPP, event), + this, aSafeToReenterGecko); +#else + tmpResult = (*pluginFunctions->event)(&mNPP, event); +#endif + NPP_PLUGIN_LOG( + PLUGIN_LOG_NOISY, + ("NPP HandleEvent called: this=%p, npp=%p, event=%p, return=%d\n", this, + &mNPP, event, tmpResult)); + + if (result) *result = tmpResult; +#ifdef XP_MACOSX + mCurrentPluginEvent = nullptr; +#endif + } + + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::GetValueFromPlugin(NPPVariable variable, + void* value) { + if (!mPlugin || !mPlugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + nsresult rv = NS_ERROR_FAILURE; + + if (pluginFunctions->getvalue && RUNNING == mRunning) { + PluginDestructionGuard guard(this); + + NPError pluginError = NPERR_GENERIC_ERROR; + NS_TRY_SAFE_CALL_RETURN( + pluginError, (*pluginFunctions->getvalue)(&mNPP, variable, value), this, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + NPP_PLUGIN_LOG( + PLUGIN_LOG_NORMAL, + ("NPP GetValue called: this=%p, npp=%p, var=%d, value=%p, return=%d\n", + this, &mNPP, variable, value, pluginError)); + + if (pluginError == NPERR_NO_ERROR) { + rv = NS_OK; + } + } + + return rv; +} + +nsNPAPIPlugin* nsNPAPIPluginInstance::GetPlugin() { return mPlugin; } + +nsresult nsNPAPIPluginInstance::GetNPP(NPP* aNPP) { + if (aNPP) + *aNPP = &mNPP; + else + return NS_ERROR_NULL_POINTER; + + return NS_OK; +} + +NPError nsNPAPIPluginInstance::SetWindowless(bool aWindowless) { + mWindowless = aWindowless; + return NPERR_NO_ERROR; +} + +NPError nsNPAPIPluginInstance::SetTransparent(bool aTransparent) { + mTransparent = aTransparent; + return NPERR_NO_ERROR; +} + +NPError nsNPAPIPluginInstance::SetUsesDOMForCursor(bool aUsesDOMForCursor) { + mUsesDOMForCursor = aUsesDOMForCursor; + return NPERR_NO_ERROR; +} + +bool nsNPAPIPluginInstance::UsesDOMForCursor() { return mUsesDOMForCursor; } + +void nsNPAPIPluginInstance::SetDrawingModel(NPDrawingModel aModel) { + mDrawingModel = aModel; +} + +void nsNPAPIPluginInstance::RedrawPlugin() { mOwner->RedrawPlugin(); } + +#if defined(XP_MACOSX) +void nsNPAPIPluginInstance::SetEventModel(NPEventModel aModel) { + // the event model needs to be set for the object frame immediately + if (!mOwner) { + NS_WARNING("Trying to set event model without a plugin instance owner!"); + return; + } + + mOwner->SetEventModel(aModel); +} +#endif + +nsresult nsNPAPIPluginInstance::GetDrawingModel(int32_t* aModel) { + *aModel = (int32_t)mDrawingModel; + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::IsRemoteDrawingCoreAnimation(bool* aDrawing) { +#ifdef XP_MACOSX + if (!mPlugin) return NS_ERROR_FAILURE; + + PluginLibrary* library = mPlugin->GetLibrary(); + if (!library) return NS_ERROR_FAILURE; + + return library->IsRemoteDrawingCoreAnimation(&mNPP, aDrawing); +#else + return NS_ERROR_FAILURE; +#endif +} + +nsresult nsNPAPIPluginInstance::ContentsScaleFactorChanged( + double aContentsScaleFactor) { +#if defined(XP_MACOSX) || defined(XP_WIN) + if (!mPlugin) return NS_ERROR_FAILURE; + + PluginLibrary* library = mPlugin->GetLibrary(); + if (!library) return NS_ERROR_FAILURE; + + // We only need to call this if the plugin is running OOP. + if (!library->IsOOP()) return NS_OK; + + return library->ContentsScaleFactorChanged(&mNPP, aContentsScaleFactor); +#else + return NS_ERROR_FAILURE; +#endif +} + +nsresult nsNPAPIPluginInstance::CSSZoomFactorChanged(float aCSSZoomFactor) { + if (RUNNING != mRunning) return NS_OK; + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance informing plugin of " + "CSS Zoom Factor change this=%p\n", + this)); + + if (!mPlugin || !mPlugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + if (!pluginFunctions->setvalue) return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(this); + + NPError error; + double value = static_cast<double>(aCSSZoomFactor); + NS_TRY_SAFE_CALL_RETURN( + error, (*pluginFunctions->setvalue)(&mNPP, NPNVCSSZoomFactor, &value), + this, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + return (error == NPERR_NO_ERROR) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsNPAPIPluginInstance::GetJSObject(JSContext* cx, + JSObject** outObject) { + NPObject* npobj = nullptr; + nsresult rv = GetValueFromPlugin(NPPVpluginScriptableNPObject, &npobj); + if (NS_FAILED(rv) || !npobj) return NS_ERROR_FAILURE; + + *outObject = nsNPObjWrapper::GetNewOrUsed(&mNPP, cx, npobj); + + _releaseobject(npobj); + + return NS_OK; +} + +void nsNPAPIPluginInstance::SetCached(bool aCache) { mCached = aCache; } + +bool nsNPAPIPluginInstance::ShouldCache() { return mCached; } + +nsresult nsNPAPIPluginInstance::IsWindowless(bool* isWindowless) { +#if defined(XP_MACOSX) + // All OS X plugins are windowless. + *isWindowless = true; +#else + *isWindowless = mWindowless; +#endif + return NS_OK; +} + +class MOZ_STACK_CLASS AutoPluginLibraryCall { + public: + explicit AutoPluginLibraryCall(nsNPAPIPluginInstance* aThis) + : mThis(aThis), mGuard(aThis), mLibrary(nullptr) { + nsNPAPIPlugin* plugin = mThis->GetPlugin(); + if (plugin) mLibrary = plugin->GetLibrary(); + } + explicit operator bool() { return !!mLibrary; } + PluginLibrary* operator->() { return mLibrary; } + + private: + nsNPAPIPluginInstance* mThis; + PluginDestructionGuard mGuard; + PluginLibrary* mLibrary; +}; + +nsresult nsNPAPIPluginInstance::AsyncSetWindow(NPWindow* window) { + if (RUNNING != mRunning) return NS_OK; + + AutoPluginLibraryCall library(this); + if (!library) return NS_ERROR_FAILURE; + + return library->AsyncSetWindow(&mNPP, window); +} + +nsresult nsNPAPIPluginInstance::GetImageContainer(ImageContainer** aContainer) { + *aContainer = nullptr; + + if (RUNNING != mRunning) return NS_OK; + + AutoPluginLibraryCall library(this); + return !library ? NS_ERROR_FAILURE + : library->GetImageContainer(&mNPP, aContainer); +} + +nsresult nsNPAPIPluginInstance::GetImageSize(nsIntSize* aSize) { + *aSize = nsIntSize(0, 0); + + if (RUNNING != mRunning) return NS_OK; + + AutoPluginLibraryCall library(this); + return !library ? NS_ERROR_FAILURE : library->GetImageSize(&mNPP, aSize); +} + +#if defined(XP_WIN) +nsresult nsNPAPIPluginInstance::GetScrollCaptureContainer( + ImageContainer** aContainer) { + *aContainer = nullptr; + + if (RUNNING != mRunning) return NS_OK; + + AutoPluginLibraryCall library(this); + return !library ? NS_ERROR_FAILURE + : library->GetScrollCaptureContainer(&mNPP, aContainer); +} +#endif + +nsresult nsNPAPIPluginInstance::HandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, bool aIsConsumed) { + if (NS_WARN_IF(!mPlugin)) { + return NS_ERROR_FAILURE; + } + + PluginLibrary* library = mPlugin->GetLibrary(); + if (NS_WARN_IF(!library)) { + return NS_ERROR_FAILURE; + } + return library->HandledWindowedPluginKeyEvent(&mNPP, aKeyEventData, + aIsConsumed); +} + +void nsNPAPIPluginInstance::DidComposite() { + if (RUNNING != mRunning) return; + + AutoPluginLibraryCall library(this); + library->DidComposite(&mNPP); +} + +nsresult nsNPAPIPluginInstance::NotifyPainted(void) { + MOZ_ASSERT_UNREACHABLE("Dead code, shouldn't be called."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsNPAPIPluginInstance::GetIsOOP(bool* aIsAsync) { + AutoPluginLibraryCall library(this); + if (!library) return NS_ERROR_FAILURE; + + *aIsAsync = library->IsOOP(); + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::SetBackgroundUnknown() { + if (RUNNING != mRunning) return NS_OK; + + AutoPluginLibraryCall library(this); + if (!library) return NS_ERROR_FAILURE; + + return library->SetBackgroundUnknown(&mNPP); +} + +nsresult nsNPAPIPluginInstance::BeginUpdateBackground( + nsIntRect* aRect, DrawTarget** aDrawTarget) { + if (RUNNING != mRunning) return NS_OK; + + AutoPluginLibraryCall library(this); + if (!library) return NS_ERROR_FAILURE; + + return library->BeginUpdateBackground(&mNPP, *aRect, aDrawTarget); +} + +nsresult nsNPAPIPluginInstance::EndUpdateBackground(nsIntRect* aRect) { + if (RUNNING != mRunning) return NS_OK; + + AutoPluginLibraryCall library(this); + if (!library) return NS_ERROR_FAILURE; + + return library->EndUpdateBackground(&mNPP, *aRect); +} + +nsresult nsNPAPIPluginInstance::IsTransparent(bool* isTransparent) { + *isTransparent = mTransparent; + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::GetFormValue(nsAString& aValue) { + aValue.Truncate(); + + char* value = nullptr; + nsresult rv = GetValueFromPlugin(NPPVformValue, &value); + if (NS_FAILED(rv) || !value) return NS_ERROR_FAILURE; + + CopyUTF8toUTF16(MakeStringSpan(value), aValue); + + // NPPVformValue allocates with NPN_MemAlloc(), which uses + // nsMemory. + free(value); + + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::PushPopupsEnabledState(bool aEnabled) { + nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow(); + if (!window) return NS_ERROR_FAILURE; + + PopupBlocker::PopupControlState oldState = + PopupBlocker::PushPopupControlState( + aEnabled ? PopupBlocker::openAllowed : PopupBlocker::openAbused, + true); + + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + mPopupStates.AppendElement(oldState); + + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::PopPopupsEnabledState() { + if (mPopupStates.IsEmpty()) { + // Nothing to pop. + return NS_OK; + } + + nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow(); + if (!window) return NS_ERROR_FAILURE; + + PopupBlocker::PopPopupControlState(mPopupStates.PopLastElement()); + + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::GetPluginAPIVersion(uint16_t* version) { + NS_ENSURE_ARG_POINTER(version); + + if (!mPlugin) return NS_ERROR_FAILURE; + + if (!mPlugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + *version = pluginFunctions->version; + + return NS_OK; +} + +nsresult nsNPAPIPluginInstance::PrivateModeStateChanged(bool enabled) { + if (RUNNING != mRunning) return NS_OK; + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance informing plugin of " + "private mode state change this=%p\n", + this)); + + if (!mPlugin || !mPlugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + if (!pluginFunctions->setvalue) return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(this); + + NPError error; + NPBool value = static_cast<NPBool>(enabled); + NS_TRY_SAFE_CALL_RETURN( + error, (*pluginFunctions->setvalue)(&mNPP, NPNVprivateModeBool, &value), + this, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + return (error == NPERR_NO_ERROR) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsNPAPIPluginInstance::IsPrivateBrowsing(bool* aEnabled) { + if (!mOwner) return NS_ERROR_FAILURE; + + nsCOMPtr<Document> doc; + mOwner->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIDOMWindowOuter> domwindow = doc->GetWindow(); + NS_ENSURE_TRUE(domwindow, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocShell> docShell = domwindow->GetDocShell(); + nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell); + *aEnabled = (loadContext && loadContext->UsePrivateBrowsing()); + return NS_OK; +} + +static void PluginTimerCallback(nsITimer* aTimer, void* aClosure) { + nsNPAPITimer* t = (nsNPAPITimer*)aClosure; + NPP npp = t->npp; + uint32_t id = t->id; + + PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("nsNPAPIPluginInstance running plugin timer callback this=%p\n", + npp->ndata)); + + // Some plugins (Flash on Android) calls unscheduletimer + // from this callback. + t->inCallback = true; + (*(t->callback))(npp, id); + t->inCallback = false; + + // Make sure we still have an instance and the timer is still alive + // after the callback. + nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)npp->ndata; + if (!inst || !inst->TimerWithID(id, nullptr)) return; + + // use UnscheduleTimer to clean up if this is a one-shot timer + uint32_t timerType; + t->timer->GetType(&timerType); + if (t->needUnschedule || timerType == nsITimer::TYPE_ONE_SHOT) + inst->UnscheduleTimer(id); +} + +nsNPAPITimer* nsNPAPIPluginInstance::TimerWithID(uint32_t id, uint32_t* index) { + uint32_t len = mTimers.Length(); + for (uint32_t i = 0; i < len; i++) { + if (mTimers[i]->id == id) { + if (index) *index = i; + return mTimers[i]; + } + } + return nullptr; +} + +uint32_t nsNPAPIPluginInstance::ScheduleTimer( + uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)) { + if (RUNNING != mRunning) return 0; + + nsNPAPITimer* newTimer = new nsNPAPITimer(); + + newTimer->inCallback = newTimer->needUnschedule = false; + newTimer->npp = &mNPP; + + // generate ID that is unique to this instance + uint32_t uniqueID = mTimers.Length(); + while ((uniqueID == 0) || TimerWithID(uniqueID, nullptr)) uniqueID++; + newTimer->id = uniqueID; + + // create new xpcom timer, scheduled correctly + nsresult rv; + const short timerType = (repeat ? (short)nsITimer::TYPE_REPEATING_SLACK + : (short)nsITimer::TYPE_ONE_SHOT); + rv = NS_NewTimerWithFuncCallback( + getter_AddRefs(newTimer->timer), PluginTimerCallback, newTimer, interval, + timerType, "nsNPAPIPluginInstance::ScheduleTimer"); + if (NS_FAILED(rv)) { + delete newTimer; + return 0; + } + + // save callback function + newTimer->callback = timerFunc; + + // add timer to timers array + mTimers.AppendElement(newTimer); + + return newTimer->id; +} + +void nsNPAPIPluginInstance::UnscheduleTimer(uint32_t timerID) { + // find the timer struct by ID + uint32_t index; + nsNPAPITimer* t = TimerWithID(timerID, &index); + if (!t) return; + + if (t->inCallback) { + t->needUnschedule = true; + return; + } + + // cancel the timer + t->timer->Cancel(); + + // remove timer struct from array + mTimers.RemoveElementAt(index); + + // delete timer + delete t; +} + +NPBool nsNPAPIPluginInstance::ConvertPoint(double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, + double* destX, double* destY, + NPCoordinateSpace destSpace) { + if (mOwner) { + return mOwner->ConvertPoint(sourceX, sourceY, sourceSpace, destX, destY, + destSpace); + } + + return false; +} + +nsresult nsNPAPIPluginInstance::GetDOMElement(Element** result) { + if (!mOwner) { + *result = nullptr; + return NS_ERROR_FAILURE; + } + + return mOwner->GetDOMElement(result); +} + +nsresult nsNPAPIPluginInstance::InvalidateRect(NPRect* invalidRect) { + if (RUNNING != mRunning) return NS_OK; + + if (!mOwner) return NS_ERROR_FAILURE; + + return mOwner->InvalidateRect(invalidRect); +} + +nsresult nsNPAPIPluginInstance::InvalidateRegion(NPRegion invalidRegion) { + if (RUNNING != mRunning) return NS_OK; + + if (!mOwner) return NS_ERROR_FAILURE; + + return mOwner->InvalidateRegion(invalidRegion); +} + +nsresult nsNPAPIPluginInstance::GetMIMEType(const char** result) { + if (!mMIMEType) + *result = ""; + else + *result = mMIMEType; + + return NS_OK; +} + +nsPluginInstanceOwner* nsNPAPIPluginInstance::GetOwner() { return mOwner; } + +void nsNPAPIPluginInstance::SetOwner(nsPluginInstanceOwner* aOwner) { + mOwner = aOwner; +} + +nsresult nsNPAPIPluginInstance::AsyncSetWindow(NPWindow& window) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void nsNPAPIPluginInstance::URLRedirectResponse(void* notifyData, + NPBool allow) { + if (!notifyData) { + return; + } + + uint32_t listenerCount = mStreamListeners.Length(); + for (uint32_t i = 0; i < listenerCount; i++) { + nsNPAPIPluginStreamListener* currentListener = mStreamListeners[i]; + if (currentListener->GetNotifyData() == notifyData) { + currentListener->URLRedirectResponse(allow); + } + } +} + +NPError nsNPAPIPluginInstance::InitAsyncSurface(NPSize* size, + NPImageFormat format, + void* initData, + NPAsyncSurface* surface) { + if (mOwner) { + return mOwner->InitAsyncSurface(size, format, initData, surface); + } + + return NPERR_GENERIC_ERROR; +} + +NPError nsNPAPIPluginInstance::FinalizeAsyncSurface(NPAsyncSurface* surface) { + if (mOwner) { + return mOwner->FinalizeAsyncSurface(surface); + } + + return NPERR_GENERIC_ERROR; +} + +void nsNPAPIPluginInstance::SetCurrentAsyncSurface(NPAsyncSurface* surface, + NPRect* changed) { + if (mOwner) { + mOwner->SetCurrentAsyncSurface(surface, changed); + } +} + +double nsNPAPIPluginInstance::GetContentsScaleFactor() { + double scaleFactor = 1.0; + if (mOwner) { + mOwner->GetContentsScaleFactor(&scaleFactor); + } + return scaleFactor; +} + +float nsNPAPIPluginInstance::GetCSSZoomFactor() { + float zoomFactor = 1.0; + if (mOwner) { + mOwner->GetCSSZoomFactor(&zoomFactor); + } + return zoomFactor; +} + +nsresult nsNPAPIPluginInstance::GetRunID(uint32_t* aRunID) { + if (NS_WARN_IF(!aRunID)) { + return NS_ERROR_INVALID_POINTER; + } + + if (NS_WARN_IF(!mPlugin)) { + return NS_ERROR_FAILURE; + } + + PluginLibrary* library = mPlugin->GetLibrary(); + if (!library) { + return NS_ERROR_FAILURE; + } + + return library->GetRunID(aRunID); +} + +nsresult nsNPAPIPluginInstance::CreateAudioChannelAgentIfNeeded() { + if (mAudioChannelAgent) { + return NS_OK; + } + + mAudioChannelAgent = new AudioChannelAgent(); + + nsCOMPtr<nsPIDOMWindowOuter> window = GetDOMWindow(); + if (NS_WARN_IF(!window)) { + return NS_ERROR_FAILURE; + } + + nsresult rv = mAudioChannelAgent->Init(window->GetCurrentInnerWindow(), this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +void nsNPAPIPluginInstance::NotifyStartedPlaying() { + nsresult rv = CreateAudioChannelAgentIfNeeded(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + MOZ_ASSERT(mAudioChannelAgent); + rv = mAudioChannelAgent->NotifyStartedPlaying( + mIsMuted ? AudioChannelService::AudibleState::eNotAudible + : AudioChannelService::AudibleState::eAudible); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + mAudioChannelAgent->PullInitialUpdate(); +} + +void nsNPAPIPluginInstance::NotifyStoppedPlaying() { + MOZ_ASSERT(mAudioChannelAgent); + nsresult rv = mAudioChannelAgent->NotifyStoppedPlaying(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } +} + +NS_IMETHODIMP +nsNPAPIPluginInstance::WindowVolumeChanged(float aVolume, bool aMuted) { + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("nsNPAPIPluginInstance, WindowVolumeChanged, " + "this = %p, aVolume = %f, aMuted = %s\n", + this, aVolume, aMuted ? "true" : "false")); + // We just support mute/unmute + if (mWindowMuted != aMuted) { + mWindowMuted = aMuted; + return UpdateMutedIfNeeded(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsNPAPIPluginInstance::WindowSuspendChanged(nsSuspendedTypes aSuspend) { + MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, + ("nsNPAPIPluginInstance, WindowSuspendChanged, " + "this = %p, aSuspend = %s\n", + this, SuspendTypeToStr(aSuspend))); + const bool isSuspended = aSuspend != nsISuspendedTypes::NONE_SUSPENDED; + if (mWindowSuspended != isSuspended) { + mWindowSuspended = isSuspended; + // It doesn't support suspending, so we just do something like mute/unmute. + return UpdateMutedIfNeeded(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsNPAPIPluginInstance::WindowAudioCaptureChanged(bool aCapture) { + return NS_OK; +} + +void nsNPAPIPluginInstance::NotifyAudibleStateChanged() const { + // This happens when global window destroyed, we would notify agent's callback + // to mute its volume, but the nsNSNPAPI had released the agent before that. + if (!mAudioChannelAgent) { + return; + } + AudioChannelService::AudibleState audibleState = + mIsMuted ? AudioChannelService::AudibleState::eNotAudible + : AudioChannelService::AudibleState::eAudible; + // Because we don't really support suspending nsNPAPI, so all audible changes + // come from changing its volume. + mAudioChannelAgent->NotifyStartedAudible( + audibleState, AudioChannelService::AudibleChangedReasons::eVolumeChanged); +} + +nsresult nsNPAPIPluginInstance::UpdateMutedIfNeeded() { + const bool shouldMute = mWindowSuspended || mWindowMuted; + if (mIsMuted == shouldMute) { + return NS_OK; + } + + mIsMuted = shouldMute; + NotifyAudibleStateChanged(); + nsresult rv = SetMuted(mIsMuted); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetMuted failed"); + return rv; +} + +nsresult nsNPAPIPluginInstance::SetMuted(bool aIsMuted) { + if (RUNNING != mRunning) return NS_OK; + + PLUGIN_LOG( + PLUGIN_LOG_NORMAL, + ("nsNPAPIPluginInstance informing plugin of mute state change this=%p\n", + this)); + + if (!mPlugin || !mPlugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs(); + + if (!pluginFunctions->setvalue) return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(this); + + NPError error; + NPBool value = static_cast<NPBool>(aIsMuted); + NS_TRY_SAFE_CALL_RETURN( + error, (*pluginFunctions->setvalue)(&mNPP, NPNVmuteAudioBool, &value), + this, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + return (error == NPERR_NO_ERROR) ? NS_OK : NS_ERROR_FAILURE; +} diff --git a/dom/plugins/base/nsNPAPIPluginInstance.h b/dom/plugins/base/nsNPAPIPluginInstance.h new file mode 100644 index 0000000000..1ca2569ae8 --- /dev/null +++ b/dom/plugins/base/nsNPAPIPluginInstance.h @@ -0,0 +1,327 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNPAPIPluginInstance_h_ +#define nsNPAPIPluginInstance_h_ + +#include "nsSize.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsPIDOMWindow.h" +#include "nsITimer.h" +#include "nsIPluginInstanceOwner.h" +#include "nsHashKeys.h" +#include <prinrval.h> +#include "js/TypeDecls.h" +#include "AudioChannelAgent.h" + +#include "mozilla/EventForwards.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/PluginLibrary.h" +#include "mozilla/RefPtr.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/dom/PopupBlocker.h" + +class nsPluginStreamListenerPeer; // browser-initiated stream class +class nsNPAPIPluginStreamListener; // plugin-initiated stream class +class nsIPluginInstanceOwner; +class nsIOutputStream; +class nsPluginInstanceOwner; + +namespace mozilla { +namespace dom { +class Element; +} // namespace dom +} // namespace mozilla + +#if defined(OS_WIN) +const NPDrawingModel kDefaultDrawingModel = NPDrawingModelSyncWin; +#elif defined(MOZ_X11) +const NPDrawingModel kDefaultDrawingModel = NPDrawingModelSyncX; +#elif defined(XP_MACOSX) +# ifndef NP_NO_QUICKDRAW +const NPDrawingModel kDefaultDrawingModel = + NPDrawingModelQuickDraw; // Not supported +# else +const NPDrawingModel kDefaultDrawingModel = NPDrawingModelCoreGraphics; +# endif +#else +const NPDrawingModel kDefaultDrawingModel = static_cast<NPDrawingModel>(0); +#endif + +#if defined(OS_WIN) +static const DWORD NPAPI_INVALID_WPARAM = 0xffffffff; +#endif + +/** + * Used to indicate whether it's OK to reenter Gecko and repaint, flush frames, + * run scripts, etc, during this plugin call. + * When NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO is set, we try to avoid dangerous + * Gecko activities when the plugin spins a nested event loop, on a best-effort + * basis. + */ +enum NSPluginCallReentry { + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO, + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO +}; + +class nsNPAPITimer { + public: + NPP npp; + uint32_t id; + nsCOMPtr<nsITimer> timer; + void (*callback)(NPP npp, uint32_t timerID); + bool inCallback; + bool needUnschedule; +}; + +class nsNPAPIPluginInstance final : public nsIAudioChannelAgentCallback, + public mozilla::SupportsWeakPtr { + private: + typedef mozilla::PluginLibrary PluginLibrary; + + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK + + nsresult Initialize(nsNPAPIPlugin* aPlugin, nsPluginInstanceOwner* aOwner, + const nsACString& aMIMEType); + nsresult Start(); + nsresult Stop(); + nsresult SetWindow(NPWindow* window); + nsresult NewStreamFromPlugin(const char* type, const char* target, + nsIOutputStream** result); + nsresult Print(NPPrint* platformPrint); + nsresult HandleEvent(void* event, int16_t* result, + NSPluginCallReentry aSafeToReenterGecko = + NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + nsresult GetValueFromPlugin(NPPVariable variable, void* value); + nsresult GetDrawingModel(int32_t* aModel); + nsresult IsRemoteDrawingCoreAnimation(bool* aDrawing); + nsresult ContentsScaleFactorChanged(double aContentsScaleFactor); + nsresult CSSZoomFactorChanged(float aCSSZoomFactor); + nsresult GetJSObject(JSContext* cx, JSObject** outObject); + bool ShouldCache(); + nsresult IsWindowless(bool* isWindowless); + nsresult AsyncSetWindow(NPWindow* window); + nsresult GetImageContainer(mozilla::layers::ImageContainer** aContainer); + nsresult GetImageSize(nsIntSize* aSize); + nsresult NotifyPainted(void); + nsresult GetIsOOP(bool* aIsOOP); + nsresult SetBackgroundUnknown(); + nsresult BeginUpdateBackground(nsIntRect* aRect, DrawTarget** aContext); + nsresult EndUpdateBackground(nsIntRect* aRect); + nsresult IsTransparent(bool* isTransparent); + nsresult GetFormValue(nsAString& aValue); + nsresult PushPopupsEnabledState(bool aEnabled); + nsresult PopPopupsEnabledState(); + nsresult GetPluginAPIVersion(uint16_t* version); + nsresult InvalidateRect(NPRect* invalidRect); + nsresult InvalidateRegion(NPRegion invalidRegion); + nsresult GetMIMEType(const char** result); +#if defined(XP_WIN) + nsresult GetScrollCaptureContainer( + mozilla::layers::ImageContainer** aContainer); +#endif + nsresult HandledWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, bool aIsConsumed); + nsPluginInstanceOwner* GetOwner(); + void SetOwner(nsPluginInstanceOwner* aOwner); + void DidComposite(); + + bool HasAudioChannelAgent() const { return !!mAudioChannelAgent; } + + void NotifyStartedPlaying(); + void NotifyStoppedPlaying(); + + nsresult SetMuted(bool aIsMuted); + + nsNPAPIPlugin* GetPlugin(); + + nsresult GetNPP(NPP* aNPP); + + NPError SetWindowless(bool aWindowless); + + NPError SetTransparent(bool aTransparent); + + NPError SetWantsAllNetworkStreams(bool aWantsAllNetworkStreams); + + NPError SetUsesDOMForCursor(bool aUsesDOMForCursor); + bool UsesDOMForCursor(); + + void SetDrawingModel(NPDrawingModel aModel); + void RedrawPlugin(); +#ifdef XP_MACOSX + void SetEventModel(NPEventModel aModel); + + void* GetCurrentEvent() { return mCurrentPluginEvent; } +#endif + + nsresult NewStreamListener(const char* aURL, void* notifyData, + nsNPAPIPluginStreamListener** listener); + + nsNPAPIPluginInstance(); + + // To be called when an instance becomes orphaned, when + // it's plugin is no longer guaranteed to be around. + void Destroy(); + + // Indicates whether the plugin is running normally. + bool IsRunning() { return RUNNING == mRunning; } + bool HasStartedDestroying() { return mRunning >= DESTROYING; } + + // Indicates whether the plugin is running normally or being shut down + bool CanFireNotifications() { + return mRunning == RUNNING || mRunning == DESTROYING; + } + + // return is only valid when the plugin is not running + mozilla::TimeStamp StopTime(); + + // cache this NPAPI plugin + void SetCached(bool aCache); + + already_AddRefed<nsPIDOMWindowOuter> GetDOMWindow(); + + nsresult PrivateModeStateChanged(bool aEnabled); + + nsresult IsPrivateBrowsing(bool* aEnabled); + + nsresult GetDOMElement(mozilla::dom::Element** result); + + nsNPAPITimer* TimerWithID(uint32_t id, uint32_t* index); + uint32_t ScheduleTimer(uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)); + void UnscheduleTimer(uint32_t timerID); + NPBool ConvertPoint(double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace); + + nsTArray<nsNPAPIPluginStreamListener*>* StreamListeners(); + + nsTArray<nsPluginStreamListenerPeer*>* FileCachedStreamListeners(); + + nsresult AsyncSetWindow(NPWindow& window); + + void URLRedirectResponse(void* notifyData, NPBool allow); + + NPError InitAsyncSurface(NPSize* size, NPImageFormat format, void* initData, + NPAsyncSurface* surface); + NPError FinalizeAsyncSurface(NPAsyncSurface* surface); + void SetCurrentAsyncSurface(NPAsyncSurface* surface, NPRect* changed); + + // Returns the contents scale factor of the screen the plugin is drawn on. + double GetContentsScaleFactor(); + + // Returns the css zoom factor of the document the plugin is drawn on. + float GetCSSZoomFactor(); + + nsresult GetRunID(uint32_t* aRunID); + + static bool InPluginCallUnsafeForReentry() { + return gInUnsafePluginCalls > 0; + } + static void BeginPluginCall(NSPluginCallReentry aReentryState) { + if (aReentryState == NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO) { + ++gInUnsafePluginCalls; + } + } + static void EndPluginCall(NSPluginCallReentry aReentryState) { + if (aReentryState == NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO) { + NS_ASSERTION(gInUnsafePluginCalls > 0, "Must be in plugin call"); + --gInUnsafePluginCalls; + } + } + + protected: + virtual ~nsNPAPIPluginInstance(); + + nsresult GetTagType(nsPluginTagType* result); + + nsresult CreateAudioChannelAgentIfNeeded(); + + void NotifyAudibleStateChanged() const; + + nsresult UpdateMutedIfNeeded(); + + // The structure used to communicate between the plugin instance and + // the browser. + NPP_t mNPP; + + NPDrawingModel mDrawingModel; + + enum { NOT_STARTED, RUNNING, DESTROYING, DESTROYED } mRunning; + + // these are used to store the windowless properties + // which the browser will later query + bool mWindowless; + bool mTransparent; + bool mCached; + bool mUsesDOMForCursor; + + public: + // True while creating the plugin, or calling NPP_SetWindow() on it. + bool mInPluginInitCall; + + private: + RefPtr<nsNPAPIPlugin> mPlugin; + + nsTArray<nsNPAPIPluginStreamListener*> mStreamListeners; + + nsTArray<nsPluginStreamListenerPeer*> mFileCachedStreamListeners; + + nsTArray<mozilla::dom::PopupBlocker::PopupControlState> mPopupStates; + + char* mMIMEType; + + // Weak pointer to the owner. The owner nulls this out (by calling + // InvalidateOwner()) when it's no longer our owner. + nsPluginInstanceOwner* mOwner; + + nsTArray<nsNPAPITimer*> mTimers; + +#ifdef XP_MACOSX + // non-null during a HandleEvent call + void* mCurrentPluginEvent; +#endif + + // Timestamp for the last time this plugin was stopped. + // This is only valid when the plugin is actually stopped! + mozilla::TimeStamp mStopTime; + + static uint32_t gInUnsafePluginCalls; + + // The arrays can only be released when the plugin instance is destroyed, + // because the plugin, in in-process mode, might keep a reference to them. + uint32_t mCachedParamLength; + char** mCachedParamNames; + char** mCachedParamValues; + + RefPtr<mozilla::dom::AudioChannelAgent> mAudioChannelAgent; + bool mIsMuted = false; + bool mWindowMuted = false; + bool mWindowSuspended = false; +}; + +void NS_NotifyBeginPluginCall(NSPluginCallReentry aReentryState); +void NS_NotifyPluginCall(NSPluginCallReentry aReentryState); + +#define NS_TRY_SAFE_CALL_RETURN(ret, fun, pluginInst, pluginCallReentry) \ + PR_BEGIN_MACRO \ + NS_NotifyBeginPluginCall(pluginCallReentry); \ + ret = fun; \ + NS_NotifyPluginCall(pluginCallReentry); \ + PR_END_MACRO + +#define NS_TRY_SAFE_CALL_VOID(fun, pluginInst, pluginCallReentry) \ + PR_BEGIN_MACRO \ + NS_NotifyBeginPluginCall(pluginCallReentry); \ + fun; \ + NS_NotifyPluginCall(pluginCallReentry); \ + PR_END_MACRO + +#endif // nsNPAPIPluginInstance_h_ diff --git a/dom/plugins/base/nsNPAPIPluginStreamListener.cpp b/dom/plugins/base/nsNPAPIPluginStreamListener.cpp new file mode 100644 index 0000000000..96ee7d2060 --- /dev/null +++ b/dom/plugins/base/nsNPAPIPluginStreamListener.cpp @@ -0,0 +1,775 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNPAPIPluginStreamListener.h" +#include "plstr.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIHttpChannel.h" +#include "nsNetUtil.h" +#include "nsPluginHost.h" +#include "nsNPAPIPlugin.h" +#include "nsPluginLogging.h" +#include "nsPluginStreamListenerPeer.h" + +#include <stdint.h> +#include <algorithm> + +nsNPAPIStreamWrapper::nsNPAPIStreamWrapper( + nsIOutputStream* outputStream, + nsNPAPIPluginStreamListener* streamListener) { + mOutputStream = outputStream; + mStreamListener = streamListener; + + memset(&mNPStream, 0, sizeof(mNPStream)); + mNPStream.ndata = static_cast<void*>(this); +} + +nsNPAPIStreamWrapper::~nsNPAPIStreamWrapper() { + if (mOutputStream) { + mOutputStream->Close(); + } +} + +// nsNPAPIPluginStreamListener Methods +NS_IMPL_ISUPPORTS(nsNPAPIPluginStreamListener, nsITimerCallback, + nsIHTTPHeaderListener, nsINamed) + +nsNPAPIPluginStreamListener::nsNPAPIPluginStreamListener( + nsNPAPIPluginInstance* inst, void* notifyData, const char* aURL) + : mStreamBuffer(nullptr), + mNotifyURL(aURL ? PL_strdup(aURL) : nullptr), + mInst(inst), + mStreamBufferSize(0), + mStreamBufferByteCount(0), + mStreamState(eStreamStopped), + mStreamCleanedUp(false), + mCallNotify(notifyData ? true : false), + mIsSuspended(false), + mIsPluginInitJSStream( + mInst->mInPluginInitCall && aURL && + strncmp(aURL, "javascript:", sizeof("javascript:") - 1) == 0), + mRedirectDenied(false), + mResponseHeaderBuf(nullptr), + mStreamStopMode(eNormalStop), + mPendingStopBindingStatus(NS_OK) { + mNPStreamWrapper = new nsNPAPIStreamWrapper(nullptr, this); + mNPStreamWrapper->mNPStream.notifyData = notifyData; +} + +nsNPAPIPluginStreamListener::~nsNPAPIPluginStreamListener() { + // remove this from the plugin instance's stream list + nsTArray<nsNPAPIPluginStreamListener*>* streamListeners = + mInst->StreamListeners(); + streamListeners->RemoveElement(this); + + // For those cases when NewStream is never called, we still may need + // to fire a notification callback. Return network error as fallback + // reason because for other cases, notify should have already been + // called for other reasons elsewhere. + CallURLNotify(NPRES_NETWORK_ERR); + + // lets get rid of the buffer + if (mStreamBuffer) { + free(mStreamBuffer); + mStreamBuffer = nullptr; + } + + if (mNotifyURL) PL_strfree(mNotifyURL); + + if (mResponseHeaderBuf) PL_strfree(mResponseHeaderBuf); + + if (mNPStreamWrapper) { + delete mNPStreamWrapper; + } +} + +nsresult nsNPAPIPluginStreamListener::CleanUpStream(NPReason reason) { + nsresult rv = NS_ERROR_FAILURE; + + // Various bits of code in the rest of this method may result in the + // deletion of this object. Use a KungFuDeathGrip to keep ourselves + // alive during cleanup. + RefPtr<nsNPAPIPluginStreamListener> kungFuDeathGrip(this); + + if (mStreamCleanedUp) return NS_OK; + + mStreamCleanedUp = true; + + StopDataPump(); + + // Release any outstanding redirect callback. + if (mHTTPRedirectCallback) { + mHTTPRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_FAILURE); + mHTTPRedirectCallback = nullptr; + } + + if (mStreamListenerPeer) { + mStreamListenerPeer->CancelRequests(NS_BINDING_ABORTED); + mStreamListenerPeer = nullptr; + } + + if (!mInst || !mInst->CanFireNotifications()) return rv; + + PluginDestructionGuard guard(mInst); + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) return rv; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + NPP npp; + mInst->GetNPP(&npp); + + if (mStreamState >= eNewStreamCalled && pluginFunctions->destroystream) { + NPPAutoPusher nppPusher(npp); + + NPError error; + NS_TRY_SAFE_CALL_RETURN(error, + (*pluginFunctions->destroystream)( + npp, &mNPStreamWrapper->mNPStream, reason), + mInst, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP DestroyStream called: this=%p, npp=%p, reason=%d, " + "return=%d, url=%s\n", + this, npp, reason, error, mNPStreamWrapper->mNPStream.url)); + + if (error == NPERR_NO_ERROR) rv = NS_OK; + } + + mStreamState = eStreamStopped; + + // fire notification back to plugin, just like before + CallURLNotify(reason); + + return rv; +} + +void nsNPAPIPluginStreamListener::CallURLNotify(NPReason reason) { + if (!mCallNotify || !mInst || !mInst->CanFireNotifications()) return; + + PluginDestructionGuard guard(mInst); + + mCallNotify = false; // only do this ONCE and prevent recursion + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) return; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + if (pluginFunctions->urlnotify) { + NPP npp; + mInst->GetNPP(&npp); + + NS_TRY_SAFE_CALL_VOID( + (*pluginFunctions->urlnotify)(npp, mNotifyURL, reason, + mNPStreamWrapper->mNPStream.notifyData), + mInst, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP URLNotify called: this=%p, npp=%p, notify=%p, " + "reason=%d, url=%s\n", + this, npp, mNPStreamWrapper->mNPStream.notifyData, reason, + mNotifyURL)); + } +} + +nsresult nsNPAPIPluginStreamListener::OnStartBinding( + nsPluginStreamListenerPeer* streamPeer) { + AUTO_PROFILER_LABEL("nsNPAPIPluginStreamListener::OnStartBinding", OTHER); + if (!mInst || !mInst->CanFireNotifications() || mStreamCleanedUp) + return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(mInst); + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + if (!pluginFunctions->newstream) return NS_ERROR_FAILURE; + + NPP npp; + mInst->GetNPP(&npp); + + char* contentType; + uint16_t streamType = NP_NORMAL; + NPError error; + + streamPeer->GetURL(&mNPStreamWrapper->mNPStream.url); + streamPeer->GetLength((uint32_t*)&(mNPStreamWrapper->mNPStream.end)); + streamPeer->GetLastModified( + (uint32_t*)&(mNPStreamWrapper->mNPStream.lastmodified)); + streamPeer->GetContentType(&contentType); + + if (!mResponseHeaders.IsEmpty()) { + mResponseHeaderBuf = PL_strdup(mResponseHeaders.get()); + mNPStreamWrapper->mNPStream.headers = mResponseHeaderBuf; + } + + mStreamListenerPeer = streamPeer; + + NPPAutoPusher nppPusher(npp); + + NS_TRY_SAFE_CALL_RETURN(error, + (*pluginFunctions->newstream)( + npp, (char*)contentType, + &mNPStreamWrapper->mNPStream, false, &streamType), + mInst, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP NewStream called: this=%p, npp=%p, mime=%s, seek=%d, " + "type=%d, return=%d, url=%s\n", + this, npp, (char*)contentType, false, streamType, error, + mNPStreamWrapper->mNPStream.url)); + + if (error != NPERR_NO_ERROR) return NS_ERROR_FAILURE; + + mStreamState = eNewStreamCalled; + + if (streamType != NP_NORMAL) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void nsNPAPIPluginStreamListener::SuspendRequest() { + NS_ASSERTION(!mIsSuspended, "Suspending a request that's already suspended!"); + + nsresult rv = StartDataPump(); + if (NS_FAILED(rv)) return; + + mIsSuspended = true; + + if (mStreamListenerPeer) { + mStreamListenerPeer->SuspendRequests(); + } +} + +void nsNPAPIPluginStreamListener::ResumeRequest() { + if (mStreamListenerPeer) { + mStreamListenerPeer->ResumeRequests(); + } + mIsSuspended = false; +} + +nsresult nsNPAPIPluginStreamListener::StartDataPump() { + // Start pumping data to the plugin every 100ms until it obeys and + // eats the data. + return NS_NewTimerWithCallback(getter_AddRefs(mDataPumpTimer), this, 100, + nsITimer::TYPE_REPEATING_SLACK); +} + +void nsNPAPIPluginStreamListener::StopDataPump() { + if (mDataPumpTimer) { + mDataPumpTimer->Cancel(); + mDataPumpTimer = nullptr; + } +} + +// Return true if a javascript: load that was started while the plugin +// was being initialized is still in progress. +bool nsNPAPIPluginStreamListener::PluginInitJSLoadInProgress() { + if (!mInst) return false; + + nsTArray<nsNPAPIPluginStreamListener*>* streamListeners = + mInst->StreamListeners(); + for (unsigned int i = 0; i < streamListeners->Length(); i++) { + if (streamListeners->ElementAt(i)->mIsPluginInitJSStream) { + return true; + } + } + + return false; +} + +// This method is called when there's more data available off the +// network, but it's also called from our data pump when we're feeding +// the plugin data that we already got off the network, but the plugin +// was unable to consume it at the point it arrived. In the case when +// the plugin pump calls this method, the input argument will be null, +// and the length will be the number of bytes available in our +// internal buffer. +nsresult nsNPAPIPluginStreamListener::OnDataAvailable( + nsPluginStreamListenerPeer* streamPeer, nsIInputStream* input, + uint32_t length) { + if (!length || !mInst || !mInst->CanFireNotifications()) + return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(mInst); + + // Just in case the caller switches plugin info on us. + mStreamListenerPeer = streamPeer; + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + // check out if plugin implements NPP_Write call + if (!pluginFunctions->write) + return NS_ERROR_FAILURE; // it'll cancel necko transaction + + if (!mStreamBuffer) { + // To optimize the mem usage & performance we have to allocate + // mStreamBuffer here in first ODA when length of data available + // in input stream is known. mStreamBuffer will be freed in DTOR. + // we also have to remember the size of that buff to make safe + // consecutive Read() calls form input stream into our buff. + + uint32_t contentLength; + streamPeer->GetLength(&contentLength); + + mStreamBufferSize = std::max(length, contentLength); + + // Limit the size of the initial buffer to MAX_PLUGIN_NECKO_BUFFER + // (16k). This buffer will grow if needed, as in the case where + // we're getting data faster than the plugin can process it. + mStreamBufferSize = + std::min(mStreamBufferSize, uint32_t(MAX_PLUGIN_NECKO_BUFFER)); + + mStreamBuffer = (char*)malloc(mStreamBufferSize); + if (!mStreamBuffer) return NS_ERROR_OUT_OF_MEMORY; + } + + // prepare NPP_ calls params + NPP npp; + mInst->GetNPP(&npp); + + int32_t streamPosition; + streamPeer->GetStreamOffset(&streamPosition); + int32_t streamOffset = streamPosition; + + if (input) { + streamOffset += length; + + // Set new stream offset for the next ODA call regardless of how + // following NPP_Write call will behave we pretend to consume all + // data from the input stream. It's possible that current steam + // position will be overwritten from NPP_RangeRequest call made + // from NPP_Write, so we cannot call SetStreamOffset after + // NPP_Write. + // + // Note: there is a special case when data flow should be + // temporarily stopped if NPP_WriteReady returns 0 (bug #89270) + streamPeer->SetStreamOffset(streamOffset); + + // set new end in case the content is compressed + // initial end is less than end of decompressed stream + // and some plugins (e.g. acrobat) can fail. + if ((int32_t)mNPStreamWrapper->mNPStream.end < streamOffset) + mNPStreamWrapper->mNPStream.end = streamOffset; + } + + nsresult rv = NS_OK; + while (NS_SUCCEEDED(rv) && length > 0) { + if (input && length) { + if (mStreamBufferSize < mStreamBufferByteCount + length) { + // We're in the ::OnDataAvailable() call that we might get + // after suspending a request, or we suspended the request + // from within this ::OnDataAvailable() call while there's + // still data in the input, or we have resumed a previously + // suspended request and our buffer is already full, and we + // don't have enough space to store what we got off the network. + // Reallocate our internal buffer. + mStreamBufferSize = mStreamBufferByteCount + length; + char* buf = (char*)realloc(mStreamBuffer, mStreamBufferSize); + if (!buf) return NS_ERROR_OUT_OF_MEMORY; + + mStreamBuffer = buf; + } + + uint32_t bytesToRead = + std::min(length, mStreamBufferSize - mStreamBufferByteCount); + MOZ_ASSERT(bytesToRead > 0); + + uint32_t amountRead = 0; + rv = input->Read(mStreamBuffer + mStreamBufferByteCount, bytesToRead, + &amountRead); + NS_ENSURE_SUCCESS(rv, rv); + + if (amountRead == 0) { + MOZ_ASSERT_UNREACHABLE( + "input->Read() returns no data, it's almost " + "impossible to get here"); + + break; + } + + mStreamBufferByteCount += amountRead; + length -= amountRead; + } else { + // No input, nothing to read. Set length to 0 so that we don't + // keep iterating through this outer loop any more. + + length = 0; + } + + // Temporary pointer to the beginning of the data we're writing as + // we loop and feed the plugin data. + char* ptrStreamBuffer = mStreamBuffer; + + // it is possible plugin's NPP_Write() returns 0 byte consumed. We + // use zeroBytesWriteCount to count situation like this and break + // the loop + int32_t zeroBytesWriteCount = 0; + + // mStreamBufferByteCount tells us how many bytes there are in the + // buffer. WriteReady returns to us how many bytes the plugin is + // ready to handle. + while (mStreamBufferByteCount > 0) { + int32_t numtowrite; + if (pluginFunctions->writeready) { + NPPAutoPusher nppPusher(npp); + + NS_TRY_SAFE_CALL_RETURN( + numtowrite, + (*pluginFunctions->writeready)(npp, &mNPStreamWrapper->mNPStream), + mInst, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + NPP_PLUGIN_LOG( + PLUGIN_LOG_NOISY, + ("NPP WriteReady called: this=%p, npp=%p, " + "return(towrite)=%d, url=%s\n", + this, npp, numtowrite, mNPStreamWrapper->mNPStream.url)); + + if (mStreamState == eStreamStopped) { + // The plugin called NPN_DestroyStream() from within + // NPP_WriteReady(), kill the stream. + + return NS_BINDING_ABORTED; + } + + // if WriteReady returned 0, the plugin is not ready to handle + // the data, suspend the stream (if it isn't already + // suspended). + // + // Also suspend the stream if the stream we're loading is not + // a javascript: URL load that was initiated during plugin + // initialization and there currently is such a stream + // loading. This is done to work around a Windows Media Player + // plugin bug where it can't deal with being fed data for + // other streams while it's waiting for data from the + // javascript: URL loads it requests during + // initialization. See bug 386493 for more details. + + if (numtowrite <= 0 || + (!mIsPluginInitJSStream && PluginInitJSLoadInProgress())) { + if (!mIsSuspended) { + SuspendRequest(); + } + + // Break out of the inner loop, but keep going through the + // outer loop in case there's more data to read from the + // input stream. + + break; + } + + numtowrite = std::min(numtowrite, mStreamBufferByteCount); + } else { + // if WriteReady is not supported by the plugin, just write + // the whole buffer + numtowrite = mStreamBufferByteCount; + } + + NPPAutoPusher nppPusher(npp); + + int32_t writeCount = 0; // bytes consumed by plugin instance + NS_TRY_SAFE_CALL_RETURN(writeCount, + (*pluginFunctions->write)( + npp, &mNPStreamWrapper->mNPStream, + streamPosition, numtowrite, ptrStreamBuffer), + mInst, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG( + PLUGIN_LOG_NOISY, + ("NPP Write called: this=%p, npp=%p, pos=%d, len=%d, " + "buf=%.*s, return(written)=%d, url=%s\n", + this, npp, streamPosition, numtowrite, numtowrite, ptrStreamBuffer, + writeCount, mNPStreamWrapper->mNPStream.url)); + + if (mStreamState == eStreamStopped) { + // The plugin called NPN_DestroyStream() from within + // NPP_Write(), kill the stream. + return NS_BINDING_ABORTED; + } + + if (writeCount > 0) { + NS_ASSERTION(writeCount <= mStreamBufferByteCount, + "Plugin read past the end of the available data!"); + + writeCount = std::min(writeCount, mStreamBufferByteCount); + mStreamBufferByteCount -= writeCount; + + streamPosition += writeCount; + + zeroBytesWriteCount = 0; + + if (mStreamBufferByteCount > 0) { + // This alignment code is most likely bogus, but we'll leave + // it in for now in case it matters for some plugins on some + // architectures. Who knows... + if (writeCount % sizeof(intptr_t)) { + // memmove will take care about alignment + memmove(mStreamBuffer, ptrStreamBuffer + writeCount, + mStreamBufferByteCount); + ptrStreamBuffer = mStreamBuffer; + } else { + // if aligned we can use ptrStreamBuffer += to eliminate + // memmove() + ptrStreamBuffer += writeCount; + } + } + } else if (writeCount == 0) { + // if NPP_Write() returns writeCount == 0 lets say 3 times in + // a row, suspend the request and continue feeding the plugin + // the data we got so far. Once that data is consumed, we'll + // resume the request. + if (mIsSuspended || ++zeroBytesWriteCount == 3) { + if (!mIsSuspended) { + SuspendRequest(); + } + + // Break out of the for loop, but keep going through the + // while loop in case there's more data to read from the + // input stream. + + break; + } + } else { + // Something's really wrong, kill the stream. + rv = NS_ERROR_FAILURE; + + break; + } + } // end of inner while loop + + if (mStreamBufferByteCount && mStreamBuffer != ptrStreamBuffer) { + memmove(mStreamBuffer, ptrStreamBuffer, mStreamBufferByteCount); + } + } + + if (streamPosition != streamOffset) { + // The plugin didn't consume all available data, or consumed some + // of our cached data while we're pumping cached data. Adjust the + // plugin info's stream offset to match reality, except if the + // plugin info's stream offset was set by a re-entering + // NPN_RequestRead() call. + + int32_t postWriteStreamPosition; + streamPeer->GetStreamOffset(&postWriteStreamPosition); + + if (postWriteStreamPosition == streamOffset) { + streamPeer->SetStreamOffset(streamPosition); + } + } + + return rv; +} + +nsresult nsNPAPIPluginStreamListener::OnFileAvailable( + nsPluginStreamListenerPeer* streamPeer, const char* fileName) { + if (!mInst || !mInst->CanFireNotifications()) return NS_ERROR_FAILURE; + + PluginDestructionGuard guard(mInst); + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) return NS_ERROR_FAILURE; + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + + if (!pluginFunctions->asfile) return NS_ERROR_FAILURE; + + NPP npp; + mInst->GetNPP(&npp); + + NS_TRY_SAFE_CALL_VOID( + (*pluginFunctions->asfile)(npp, &mNPStreamWrapper->mNPStream, fileName), + mInst, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); + + NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("NPP StreamAsFile called: this=%p, npp=%p, url=%s, file=%s\n", + this, npp, mNPStreamWrapper->mNPStream.url, fileName)); + + return NS_OK; +} + +nsresult nsNPAPIPluginStreamListener::OnStopBinding( + nsPluginStreamListenerPeer* streamPeer, nsresult status) { + if (NS_FAILED(status)) { + // The stream was destroyed, or died for some reason. Make sure we + // cancel the underlying request. + if (mStreamListenerPeer) { + mStreamListenerPeer->CancelRequests(status); + } + } + + if (!mInst || !mInst->CanFireNotifications()) { + StopDataPump(); + return NS_ERROR_FAILURE; + } + + // We need to detect that the stop is due to async stream init completion. + if (mStreamStopMode == eDoDeferredStop) { + // We shouldn't be delivering this until async init is done + mStreamStopMode = eStopPending; + mPendingStopBindingStatus = status; + if (!mDataPumpTimer) { + StartDataPump(); + } + return NS_OK; + } + + StopDataPump(); + + NPReason reason = NS_FAILED(status) ? NPRES_NETWORK_ERR : NPRES_DONE; + if (mRedirectDenied || status == NS_BINDING_ABORTED) { + reason = NPRES_USER_BREAK; + } + + // The following code can result in the deletion of 'this'. Don't + // assume we are alive after this! + return CleanUpStream(reason); +} + +bool nsNPAPIPluginStreamListener::MaybeRunStopBinding() { + if (mIsSuspended || mStreamStopMode != eStopPending) { + return false; + } + OnStopBinding(mStreamListenerPeer, mPendingStopBindingStatus); + mStreamStopMode = eNormalStop; + return true; +} + +NS_IMETHODIMP +nsNPAPIPluginStreamListener::Notify(nsITimer* aTimer) { + NS_ASSERTION(aTimer == mDataPumpTimer, "Uh, wrong timer?"); + + int32_t oldStreamBufferByteCount = mStreamBufferByteCount; + + nsresult rv = + OnDataAvailable(mStreamListenerPeer, nullptr, mStreamBufferByteCount); + + if (NS_FAILED(rv)) { + // We ran into an error, no need to keep firing this timer then. + StopDataPump(); + MaybeRunStopBinding(); + return NS_OK; + } + + if (mStreamBufferByteCount != oldStreamBufferByteCount && + ((mStreamState == eStreamTypeSet && mStreamBufferByteCount < 1024) || + mStreamBufferByteCount == 0)) { + // The plugin read some data and we've got less than 1024 bytes in + // our buffer (or its empty and the stream is already + // done). Resume the request so that we get more data off the + // network. + ResumeRequest(); + // Necko will pump data now that we've resumed the request. + StopDataPump(); + } + + MaybeRunStopBinding(); + return NS_OK; +} + +NS_IMETHODIMP +nsNPAPIPluginStreamListener::GetName(nsACString& aName) { + aName.AssignLiteral("nsNPAPIPluginStreamListener"); + return NS_OK; +} + +NS_IMETHODIMP +nsNPAPIPluginStreamListener::StatusLine(const char* line) { + mResponseHeaders.Append(line); + mResponseHeaders.Append('\n'); + return NS_OK; +} + +NS_IMETHODIMP +nsNPAPIPluginStreamListener::NewResponseHeader(const char* headerName, + const char* headerValue) { + mResponseHeaders.Append(headerName); + mResponseHeaders.AppendLiteral(": "); + mResponseHeaders.Append(headerValue); + mResponseHeaders.Append('\n'); + return NS_OK; +} + +bool nsNPAPIPluginStreamListener::HandleRedirectNotification( + nsIChannel* oldChannel, nsIChannel* newChannel, + nsIAsyncVerifyRedirectCallback* callback) { + nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(oldChannel); + nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel); + if (!oldHttpChannel || !newHttpChannel) { + return false; + } + + if (!mInst || !mInst->CanFireNotifications()) { + return false; + } + + nsNPAPIPlugin* plugin = mInst->GetPlugin(); + if (!plugin || !plugin->GetLibrary()) { + return false; + } + + NPPluginFuncs* pluginFunctions = plugin->PluginFuncs(); + if (!pluginFunctions->urlredirectnotify) { + return false; + } + + // A non-null closure is required for redirect handling support. + if (mNPStreamWrapper->mNPStream.notifyData) { + uint32_t status; + if (NS_SUCCEEDED(oldHttpChannel->GetResponseStatus(&status))) { + nsCOMPtr<nsIURI> uri; + if (NS_SUCCEEDED(newHttpChannel->GetURI(getter_AddRefs(uri))) && uri) { + nsAutoCString spec; + if (NS_SUCCEEDED(uri->GetAsciiSpec(spec))) { + // At this point the plugin will be responsible for making the + // callback so save the callback object. + mHTTPRedirectCallback = callback; + + NPP npp; + mInst->GetNPP(&npp); +#if defined(XP_WIN) + NS_TRY_SAFE_CALL_VOID( + (*pluginFunctions->urlredirectnotify)( + npp, spec.get(), static_cast<int32_t>(status), + mNPStreamWrapper->mNPStream.notifyData), + mInst, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); +#else + (*pluginFunctions->urlredirectnotify)( + npp, spec.get(), static_cast<int32_t>(status), + mNPStreamWrapper->mNPStream.notifyData); +#endif + return true; + } + } + } + } + + callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE); + return true; +} + +void nsNPAPIPluginStreamListener::URLRedirectResponse(NPBool allow) { + if (mHTTPRedirectCallback) { + mHTTPRedirectCallback->OnRedirectVerifyCallback(allow ? NS_OK + : NS_ERROR_FAILURE); + mRedirectDenied = !allow; + mHTTPRedirectCallback = nullptr; + } +} + +void* nsNPAPIPluginStreamListener::GetNotifyData() { + if (mNPStreamWrapper) { + return mNPStreamWrapper->mNPStream.notifyData; + } + return nullptr; +} diff --git a/dom/plugins/base/nsNPAPIPluginStreamListener.h b/dom/plugins/base/nsNPAPIPluginStreamListener.h new file mode 100644 index 0000000000..93e70b515a --- /dev/null +++ b/dom/plugins/base/nsNPAPIPluginStreamListener.h @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNPAPIPluginStreamListener_h_ +#define nsNPAPIPluginStreamListener_h_ + +#include "nscore.h" +#include "nsIHTTPHeaderListener.h" +#include "nsINamed.h" +#include "nsITimer.h" +#include "nsCOMArray.h" +#include "nsIOutputStream.h" +#include "nsString.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "mozilla/PluginLibrary.h" + +#define MAX_PLUGIN_NECKO_BUFFER 16384 + +class nsPluginStreamListenerPeer; +class nsNPAPIPluginStreamListener; +class nsNPAPIPluginInstance; +class nsIChannel; + +class nsNPAPIStreamWrapper { + public: + nsNPAPIStreamWrapper(nsIOutputStream* outputStream, + nsNPAPIPluginStreamListener* streamListener); + ~nsNPAPIStreamWrapper(); + + nsIOutputStream* GetOutputStream() { return mOutputStream.get(); } + nsNPAPIPluginStreamListener* GetStreamListener() { return mStreamListener; } + + NPStream mNPStream; + + protected: + nsCOMPtr<nsIOutputStream> + mOutputStream; // only valid if not browser initiated + nsNPAPIPluginStreamListener* + mStreamListener; // only valid if browser initiated +}; + +class nsNPAPIPluginStreamListener : public nsITimerCallback, + public nsIHTTPHeaderListener, + public nsINamed { + private: + typedef mozilla::PluginLibrary PluginLibrary; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIHTTPHEADERLISTENER + NS_DECL_NSINAMED + + nsNPAPIPluginStreamListener(nsNPAPIPluginInstance* inst, void* notifyData, + const char* aURL); + + nsresult OnStartBinding(nsPluginStreamListenerPeer* streamPeer); + nsresult OnDataAvailable(nsPluginStreamListenerPeer* streamPeer, + nsIInputStream* input, uint32_t length); + nsresult OnFileAvailable(nsPluginStreamListenerPeer* streamPeer, + const char* fileName); + nsresult OnStopBinding(nsPluginStreamListenerPeer* streamPeer, + nsresult status); + + bool IsStarted(); + nsresult CleanUpStream(NPReason reason); + void CallURLNotify(NPReason reason); + void SetCallNotify(bool aCallNotify) { mCallNotify = aCallNotify; } + void SuspendRequest(); + void ResumeRequest(); + nsresult StartDataPump(); + void StopDataPump(); + bool PluginInitJSLoadInProgress(); + + void* GetNotifyData(); + nsPluginStreamListenerPeer* GetStreamListenerPeer() { + return mStreamListenerPeer; + } + void SetStreamListenerPeer(nsPluginStreamListenerPeer* aPeer) { + mStreamListenerPeer = aPeer; + } + + // Returns true if the redirect will be handled by NPAPI, false otherwise. + bool HandleRedirectNotification(nsIChannel* oldChannel, + nsIChannel* newChannel, + nsIAsyncVerifyRedirectCallback* callback); + void URLRedirectResponse(NPBool allow); + + protected: + enum StreamState { + eStreamStopped = 0, // The stream is stopped + eNewStreamCalled, // NPP_NewStream was called but has not completed yet + eStreamTypeSet // The stream is fully initialized + }; + + enum StreamStopMode { eNormalStop = 0, eDoDeferredStop, eStopPending }; + + virtual ~nsNPAPIPluginStreamListener(); + bool MaybeRunStopBinding(); + + char* mStreamBuffer; + char* mNotifyURL; + RefPtr<nsNPAPIPluginInstance> mInst; + nsNPAPIStreamWrapper* mNPStreamWrapper; + uint32_t mStreamBufferSize; + int32_t mStreamBufferByteCount; + StreamState mStreamState; + bool mStreamCleanedUp; + bool mCallNotify; + bool mIsSuspended; + bool mIsPluginInitJSStream; + bool mRedirectDenied; + nsCString mResponseHeaders; + char* mResponseHeaderBuf; + nsCOMPtr<nsITimer> mDataPumpTimer; + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mHTTPRedirectCallback; + StreamStopMode mStreamStopMode; + nsresult mPendingStopBindingStatus; + + public: + RefPtr<nsPluginStreamListenerPeer> mStreamListenerPeer; +}; + +#endif // nsNPAPIPluginStreamListener_h_ diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp new file mode 100644 index 0000000000..4cc7d89e59 --- /dev/null +++ b/dom/plugins/base/nsPluginHost.cpp @@ -0,0 +1,2792 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* nsPluginHost.cpp - top-level plugin management code */ + +#include "nsPluginHost.h" + +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <cstdlib> +#include <new> +#include <utility> +#include "GeckoProfiler.h" +#include "ReferrerInfo.h" +#include "js/RootingAPI.h" +#include "mozilla/ArrayIterator.h" +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "mozilla/MacroForEach.h" +#include "mozilla/NotNull.h" +#include "mozilla/PluginLibrary.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryHistogramEnums.h" +#include "mozilla/TextUtils.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/FakePluginTagInitBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseNativeHandler.h" +#include "mozilla/dom/ReferrerPolicyBinding.h" +#include "mozilla/fallible.h" +#include "mozilla/ipc/URIParams.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/mozalloc.h" +#include "mozilla/plugins/PluginTypes.h" +#include "npapi.h" +#include "npfunctions.h" +#include "nsComponentManagerUtils.h" +#include "nsIAsyncShutdown.h" +#include "nsIBlocklistService.h" +#include "nsICategoryManager.h" +#include "nsIChannel.h" +#include "nsIContent.h" +#include "nsIContentPolicy.h" +#include "nsID.h" +#include "nsIEffectiveTLDService.h" +#include "nsIFile.h" +#include "nsIHttpChannel.h" +#include "nsIHttpProtocolHandler.h" +#include "nsIIDNService.h" +#include "nsIInputStream.h" +#include "nsILoadInfo.h" +#include "nsIObjectLoadingContent.h" +#include "nsIObserverService.h" +#include "nsIPluginInstanceOwner.h" +#include "nsIPluginTag.h" +#include "nsIPrefBranch.h" +#include "nsIProtocolHandler.h" +#include "nsIReferrerInfo.h" +#include "nsIRequest.h" +#include "nsIScriptChannel.h" +#include "nsISeekableStream.h" +#include "nsIStringStream.h" +#include "nsISupportsUtils.h" +#include "nsIURI.h" +#include "nsIUploadChannel.h" +#include "nsIWeakReference.h" +#include "nsIWeakReferenceUtils.h" +#include "nsIWritablePropertyBag2.h" +#include "nsLiteralString.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsObjectLoadingContent.h" +#include "nsPluginInstanceOwner.h" +#include "nsPluginLogging.h" +#include "nsPluginNativeWindow.h" +#include "nsPluginStreamListenerPeer.h" +#include "nsPluginTags.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsStringFlags.h" +#include "nsTArray.h" +#include "nsTLiteralString.h" +#include "nsTPromiseFlatString.h" +#include "nsTStringRepr.h" +#include "nsThreadUtils.h" +#include "nsVersionComparator.h" +#include "nsXPCOM.h" +#include "nsXPCOMCID.h" +#include "nsXULAppAPI.h" +#include "nscore.h" +#include "plstr.h" + +#if defined(XP_WIN) +# include "nsIWindowMediator.h" +# include "nsIBaseWindow.h" +# include "windows.h" +# include "winbase.h" +#endif +#if (MOZ_WIDGET_GTK) +# include <gdk/gdk.h> +# include <gdk/gdkx.h> +#endif + +using namespace mozilla; +using mozilla::TimeStamp; +using mozilla::dom::Document; +using mozilla::dom::FakePluginMimeEntry; +using mozilla::dom::FakePluginTagInit; +using mozilla::dom::Promise; +using mozilla::plugins::FakePluginTag; +using mozilla::plugins::PluginTag; + +// Null out a strong ref to a linked list iteratively to avoid +// exhausting the stack (bug 486349). +#define NS_ITERATIVE_UNREF_LIST(type_, list_, mNext_) \ + { \ + while (list_) { \ + type_ temp = list_->mNext_; \ + list_->mNext_ = nullptr; \ + list_ = temp; \ + } \ + } + +static const char* kPrefDisableFullPage = + "plugin.disable_full_page_plugin_for_types"; + +LazyLogModule nsPluginLogging::gNPNLog(NPN_LOG_NAME); +LazyLogModule nsPluginLogging::gNPPLog(NPP_LOG_NAME); +LazyLogModule nsPluginLogging::gPluginLog(PLUGIN_LOG_NAME); + +// #defines for plugin cache and prefs +#define NS_PREF_MAX_NUM_CACHED_INSTANCES \ + "browser.plugins.max_num_cached_plugins" +// Raise this from '10' to '50' to work around a bug in Apple's current Java +// plugins on OS X Lion and SnowLeopard. See bug 705931. +#define DEFAULT_NUMBER_OF_STOPPED_INSTANCES 50 + +nsIFile* nsPluginHost::sPluginTempDir; +StaticRefPtr<nsPluginHost> nsPluginHost::sInst; + +// Helper to check for a MIME in a comma-delimited preference +static bool IsTypeInList(const nsCString& aMimeType, nsCString aTypeList) { + nsAutoCString searchStr; + searchStr.Assign(','); + searchStr.Append(aTypeList); + searchStr.Append(','); + + nsACString::const_iterator start, end; + + searchStr.BeginReading(start); + searchStr.EndReading(end); + + nsAutoCString commaSeparated; + commaSeparated.Assign(','); + commaSeparated += aMimeType; + commaSeparated.Append(','); + + // Lower-case the search string and MIME type to properly handle a mixed-case + // type, as MIME types are case insensitive. + ToLowerCase(searchStr); + ToLowerCase(commaSeparated); + + return FindInReadable(commaSeparated, start, end); +} + +namespace mozilla::plugins { +class BlocklistPromiseHandler final + : public mozilla::dom::PromiseNativeHandler { + public: + NS_DECL_ISUPPORTS + + BlocklistPromiseHandler(nsPluginTag* aTag, const bool aShouldSoftblock) + : mTag(aTag), mShouldDisableWhenSoftblocked(aShouldSoftblock) { + MOZ_ASSERT(mTag, "Should always be passed a plugin tag"); + sPendingBlocklistStateRequests++; + } + + void MaybeWriteBlocklistChanges() { + // We're called immediately when the promise resolves/rejects, and (as a + // backup) when the handler is destroyed. To ensure we only run once, we use + // mTag as a sentinel, setting it to nullptr when we run. + if (!mTag) { + return; + } + mTag = nullptr; + sPendingBlocklistStateRequests--; + // If this was the only remaining pending request, check if we need to write + // state and if so update the child processes. + if (!sPendingBlocklistStateRequests) { + if (sPluginBlocklistStatesChangedSinceLastWrite) { + sPluginBlocklistStatesChangedSinceLastWrite = false; + + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + // We update blocklist info in content processes asynchronously + // by just sending a new plugin list to content. + host->IncrementChromeEpoch(); + host->BroadcastPluginsToContent(); + } + + // Now notify observers that we're done updating plugin state. + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) { + obsService->NotifyObservers( + nullptr, "plugin-blocklist-updates-finished", nullptr); + } + } + } + + void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override { + if (!aValue.isInt32()) { + MOZ_ASSERT(false, "Blocklist should always return int32"); + return; + } + int32_t newState = aValue.toInt32(); + MOZ_ASSERT(newState >= 0 && newState < nsIBlocklistService::STATE_MAX, + "Shouldn't get an out of bounds blocklist state"); + + // Check the old and new state and see if there was a change: + uint32_t oldState = mTag->GetBlocklistState(); + bool changed = oldState != (uint32_t)newState; + mTag->SetBlocklistState(newState); + + if (newState == nsIBlocklistService::STATE_SOFTBLOCKED && + mShouldDisableWhenSoftblocked) { + mTag->SetEnabledState(nsIPluginTag::STATE_DISABLED); + changed = true; + } + sPluginBlocklistStatesChangedSinceLastWrite |= changed; + + MaybeWriteBlocklistChanges(); + } + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override { + MOZ_ASSERT(false, "Shouldn't reject plugin blocklist state request"); + MaybeWriteBlocklistChanges(); + } + + private: + ~BlocklistPromiseHandler() { + // If we have multiple plugins and the last pending request is GC'd + // and so never resolves/rejects, ensure we still write the blocklist. + MaybeWriteBlocklistChanges(); + } + + RefPtr<nsPluginTag> mTag; + bool mShouldDisableWhenSoftblocked; + + // Whether we changed any of the plugins' blocklist states since + // we last started fetching them (async). This is reset to false + // every time we finish fetching plugin blocklist information. + // When this happens, if the previous value was true, we store the + // updated list on disk and send it to child processes. + static bool sPluginBlocklistStatesChangedSinceLastWrite; + // How many pending blocklist state requests we've got + static uint32_t sPendingBlocklistStateRequests; +}; + +NS_IMPL_ISUPPORTS0(BlocklistPromiseHandler) + +bool BlocklistPromiseHandler::sPluginBlocklistStatesChangedSinceLastWrite = + false; +uint32_t BlocklistPromiseHandler::sPendingBlocklistStateRequests = 0; +} // namespace mozilla::plugins + +nsPluginHost::nsPluginHost() + : mPluginsLoaded(false), + mOverrideInternalTypes(false), + mPluginsDisabled(false), + mPluginEpoch(0) { + // check to see if pref is set at startup to let plugins take over in + // full page mode for certain image mime types that we handle internally + mOverrideInternalTypes = + Preferences::GetBool("plugin.override_internal_types", false); + + bool waylandBackend = false; +#if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) + GdkDisplay* display = gdk_display_get_default(); + if (display) { + waylandBackend = !GDK_IS_X11_DISPLAY(display); + } +#endif + mPluginsDisabled = + Preferences::GetBool("plugin.disable", false) || waylandBackend; + if (!waylandBackend) { + Preferences::AddStrongObserver(this, "plugin.disable"); + } + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) { + obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + if (XRE_IsParentProcess()) { + obsService->AddObserver(this, "plugin-blocklist-updated", false); + } + } + +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gNPNLog, PLUGIN_LOG_ALWAYS, + ("NPN Logging Active!\n")); + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_ALWAYS, + ("General Plugin Logging Active! (nsPluginHost::ctor)\n")); + MOZ_LOG(nsPluginLogging::gNPPLog, PLUGIN_LOG_ALWAYS, + ("NPP Logging Active!\n")); + + PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("nsPluginHost::ctor\n")); + PR_LogFlush(); +#endif + // We need to ensure that plugin tag sandbox info is available. This needs to + // be done from the main thread: + nsPluginTag::EnsureSandboxInformation(); + + // Load plugins on creation, as there's a good chance we'll need to send them + // to content processes directly after creation. + if (XRE_IsParentProcess()) { + // Always increment the chrome epoch when we bring up the nsPluginHost in + // the parent process. If the only plugins we have are cached in + // pluginreg.dat, we won't see any plugin changes in LoadPlugins and the + // epoch will stay the same between the parent and child process, meaning + // plugins will never update in the child process. + IncrementChromeEpoch(); + LoadPlugins(); + } +} + +nsPluginHost::~nsPluginHost() { + PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("nsPluginHost::dtor\n")); + + UnloadPlugins(); +} + +NS_IMPL_ISUPPORTS(nsPluginHost, nsIPluginHost, nsIObserver, nsITimerCallback, + nsISupportsWeakReference, nsINamed) + +already_AddRefed<nsPluginHost> nsPluginHost::GetInst() { + if (!sInst) { + sInst = new nsPluginHost(); + ClearOnShutdown(&sInst); + } + + return do_AddRef(sInst); +} + +bool nsPluginHost::IsRunningPlugin(nsPluginTag* aPluginTag) { + if (!aPluginTag || !aPluginTag->mPlugin) { + return false; + } + + if (aPluginTag->mContentProcessRunningCount) { + return true; + } + + for (uint32_t i = 0; i < mInstances.Length(); i++) { + nsNPAPIPluginInstance* instance = mInstances[i].get(); + if (instance && instance->GetPlugin() == aPluginTag->mPlugin && + instance->IsRunning()) { + return true; + } + } + + return false; +} + +nsresult nsPluginHost::ReloadPlugins() { + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::ReloadPlugins\n")); + return NS_ERROR_PLUGINS_PLUGINSNOTCHANGED; +} + +void nsPluginHost::ClearNonRunningPlugins() { + // shutdown plugins and kill the list if there are no running plugins + RefPtr<nsPluginTag> prev; + RefPtr<nsPluginTag> next; + + for (RefPtr<nsPluginTag> p = mPlugins; p != nullptr;) { + next = p->mNext; + + // only remove our plugin from the list if it's not running. + if (!IsRunningPlugin(p)) { + if (p == mPlugins) + mPlugins = next; + else + prev->mNext = next; + + p->mNext = nullptr; + + // attempt to unload plugins whenever they are removed from the list + p->TryUnloadPlugin(false); + + p = next; + continue; + } + + prev = p; + p = next; + } +} + +nsresult nsPluginHost::ActuallyReloadPlugins() { + nsresult rv = NS_OK; + ClearNonRunningPlugins(); + + // set flags + mPluginsLoaded = false; + + // load them again + rv = LoadPlugins(); + + if (XRE_IsParentProcess()) { + // If the plugin list changed, update content. If the plugin list changed + // for the content process, it will also reload plugins. + BroadcastPluginsToContent(); + } + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::ReloadPlugins End\n")); + + return rv; +} + +#define NS_RETURN_UASTRING_SIZE 128 + +nsresult nsPluginHost::UserAgent(const char** retstring) { + static char resultString[NS_RETURN_UASTRING_SIZE]; + nsresult res; + + nsCOMPtr<nsIHttpProtocolHandler> http = + do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res); + if (NS_FAILED(res)) return res; + + nsAutoCString uaString; + res = http->GetUserAgent(uaString); + + if (NS_SUCCEEDED(res)) { + if (NS_RETURN_UASTRING_SIZE > uaString.Length()) { + PL_strcpy(resultString, uaString.get()); + } else { + // Copy as much of UA string as we can (terminate at right-most space). + PL_strncpy(resultString, uaString.get(), NS_RETURN_UASTRING_SIZE); + for (int i = NS_RETURN_UASTRING_SIZE - 1; i >= 0; i--) { + if (i == 0) { + resultString[NS_RETURN_UASTRING_SIZE - 1] = '\0'; + } else if (resultString[i] == ' ') { + resultString[i] = '\0'; + break; + } + } + } + *retstring = resultString; + } else { + *retstring = nullptr; + } + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("nsPluginHost::UserAgent return=%s\n", *retstring)); + + return res; +} + +nsresult nsPluginHost::GetURL(nsISupports* pluginInst, const char* url, + const char* target, + nsNPAPIPluginStreamListener* streamListener, + const char* altHost, const char* referrer, + bool forceJSEnabled) { + return GetURLWithHeaders(static_cast<nsNPAPIPluginInstance*>(pluginInst), url, + target, streamListener, altHost, referrer, + forceJSEnabled, 0, nullptr); +} + +nsresult nsPluginHost::GetURLWithHeaders( + nsNPAPIPluginInstance* pluginInst, const char* url, const char* target, + nsNPAPIPluginStreamListener* streamListener, const char* altHost, + const char* referrer, bool forceJSEnabled, uint32_t getHeadersLength, + const char* getHeaders) { + // we can only send a stream back to the plugin (as specified by a + // null target) if we also have a nsNPAPIPluginStreamListener to talk to + if (!target && !streamListener) { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsresult rv = NS_OK; + + if (target) { + RefPtr<nsPluginInstanceOwner> owner = pluginInst->GetOwner(); + if (owner) { + rv = owner->GetURL(url, target, nullptr, nullptr, 0, true); + } + } + + if (streamListener) { + rv = NewPluginURLStream(NS_ConvertUTF8toUTF16(url), pluginInst, + streamListener, nullptr, getHeaders, + getHeadersLength); + } + return rv; +} + +nsresult nsPluginHost::PostURL(nsISupports* pluginInst, const char* url, + uint32_t postDataLen, const char* postData, + const char* target, + nsNPAPIPluginStreamListener* streamListener, + const char* altHost, const char* referrer, + bool forceJSEnabled, uint32_t postHeadersLength, + const char* postHeaders) { + nsresult rv; + + // we can only send a stream back to the plugin (as specified + // by a null target) if we also have a nsNPAPIPluginStreamListener + // to talk to also + if (!target && !streamListener) return NS_ERROR_ILLEGAL_VALUE; + + nsNPAPIPluginInstance* instance = + static_cast<nsNPAPIPluginInstance*>(pluginInst); + + nsCOMPtr<nsIInputStream> postStream; + char* dataToPost; + uint32_t newDataToPostLen; + ParsePostBufferToFixHeaders(postData, postDataLen, &dataToPost, + &newDataToPostLen); + if (!dataToPost) return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIStringInputStream> sis = + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv); + if (!sis) { + free(dataToPost); + return rv; + } + + // data allocated by ParsePostBufferToFixHeaders() is managed and + // freed by the string stream. + postDataLen = newDataToPostLen; + sis->AdoptData(dataToPost, postDataLen); + postStream = sis; + + if (target) { + RefPtr<nsPluginInstanceOwner> owner = instance->GetOwner(); + if (owner) { + rv = owner->GetURL(url, target, postStream, (void*)postHeaders, + postHeadersLength, true); + } + } + + // if we don't have a target, just create a stream. + if (streamListener) { + rv = + NewPluginURLStream(NS_ConvertUTF8toUTF16(url), instance, streamListener, + postStream, postHeaders, postHeadersLength); + } + return rv; +} + +nsresult nsPluginHost::UnloadPlugins() { + PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsPluginHost::UnloadPlugins Called\n")); + + if (!mPluginsLoaded) return NS_OK; + + // we should call nsIPluginInstance::Stop and nsIPluginInstance::SetWindow + // for those plugins who want it + DestroyRunningInstances(nullptr); + + nsPluginTag* pluginTag; + for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { + pluginTag->TryUnloadPlugin(true); + } + + NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mPlugins, mNext); + + // Lets remove any of the temporary files that we created. + if (sPluginTempDir) { + sPluginTempDir->Remove(true); + NS_RELEASE(sPluginTempDir); + } + mSerializablePlugins.Clear(); + mSerializableFakePlugins.Clear(); + + mPluginsLoaded = false; + + return NS_OK; +} + +void nsPluginHost::OnPluginInstanceDestroyed(nsPluginTag* aPluginTag) { + bool hasInstance = false; + for (uint32_t i = 0; i < mInstances.Length(); i++) { + if (TagForPlugin(mInstances[i]->GetPlugin()) == aPluginTag) { + hasInstance = true; + break; + } + } + + if (!hasInstance) { + aPluginTag->TryUnloadPlugin(false); + } +} + +nsresult nsPluginHost::InstantiatePluginInstance( + const nsACString& aMimeType, nsIURI* aURL, nsObjectLoadingContent* aContent, + nsPluginInstanceOwner** aOwner) { + NS_ENSURE_ARG_POINTER(aOwner); + +#ifdef PLUGIN_LOGGING + nsAutoCString urlSpec; + if (aURL) aURL->GetAsciiSpec(urlSpec); + + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginHost::InstantiatePlugin Begin mime=%s, url=%s\n", + PromiseFlatCString(aMimeType).get(), urlSpec.get())); + + PR_LogFlush(); +#endif + + if (aMimeType.IsEmpty()) { + MOZ_ASSERT_UNREACHABLE("Attempting to spawn a plugin with no mime type"); + return NS_ERROR_FAILURE; + } + + RefPtr<nsPluginInstanceOwner> instanceOwner = new nsPluginInstanceOwner(); + if (!instanceOwner) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<nsIContent> ourContent = + do_QueryInterface(static_cast<nsIImageLoadingContent*>(aContent)); + nsresult rv = instanceOwner->Init(ourContent); + if (NS_FAILED(rv)) { + return rv; + } + + nsPluginTagType tagType; + rv = instanceOwner->GetTagType(&tagType); + if (NS_FAILED(rv)) { + instanceOwner->Destroy(); + return rv; + } + + if (tagType != nsPluginTagType_Embed && tagType != nsPluginTagType_Object) { + instanceOwner->Destroy(); + return NS_ERROR_FAILURE; + } + + rv = SetUpPluginInstance(aMimeType, aURL, instanceOwner); + if (NS_FAILED(rv)) { + instanceOwner->Destroy(); + return NS_ERROR_FAILURE; + } + + RefPtr<nsNPAPIPluginInstance> instance = instanceOwner->GetInstance(); + + if (instance) { + CreateWidget(instanceOwner); + } + + // At this point we consider instantiation to be successful. Do not return an + // error. + instanceOwner.forget(aOwner); + +#ifdef PLUGIN_LOGGING + nsAutoCString urlSpec2; + if (aURL != nullptr) aURL->GetAsciiSpec(urlSpec2); + + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginHost::InstantiatePlugin Finished mime=%s, rv=%" PRIu32 + ", url=%s\n", + PromiseFlatCString(aMimeType).get(), static_cast<uint32_t>(rv), + urlSpec2.get())); + + PR_LogFlush(); +#endif + + return NS_OK; +} + +nsPluginTag* nsPluginHost::FindTagForLibrary(PRLibrary* aLibrary) { + nsPluginTag* pluginTag; + for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { + if (pluginTag->mLibrary == aLibrary) { + return pluginTag; + } + } + return nullptr; +} + +nsPluginTag* nsPluginHost::TagForPlugin(nsNPAPIPlugin* aPlugin) { + nsPluginTag* pluginTag; + for (pluginTag = mPlugins; pluginTag; pluginTag = pluginTag->mNext) { + if (pluginTag->mPlugin == aPlugin) { + return pluginTag; + } + } + // a plugin should never exist without a corresponding tag + NS_ERROR("TagForPlugin has failed"); + return nullptr; +} + +nsresult nsPluginHost::SetUpPluginInstance(const nsACString& aMimeType, + nsIURI* aURL, + nsPluginInstanceOwner* aOwner) { + NS_ENSURE_ARG_POINTER(aOwner); + + nsresult rv = TrySetUpPluginInstance(aMimeType, aURL, aOwner); + if (NS_SUCCEEDED(rv)) { + return rv; + } + + // If we failed to load a plugin instance we'll try again after + // reloading our plugin list. Only do that once per document to + // avoid redundant high resource usage on pages with multiple + // unkown instance types. We'll do that by caching the document. + nsCOMPtr<Document> document; + aOwner->GetDocument(getter_AddRefs(document)); + + nsCOMPtr<Document> currentdocument = do_QueryReferent(mCurrentDocument); + if (document == currentdocument) { + return rv; + } + + mCurrentDocument = do_GetWeakReference(document); + + // Don't try to set up an instance again if nothing changed. + if (ReloadPlugins() == NS_ERROR_PLUGINS_PLUGINSNOTCHANGED) { + return rv; + } + + return TrySetUpPluginInstance(aMimeType, aURL, aOwner); +} + +nsresult nsPluginHost::TrySetUpPluginInstance(const nsACString& aMimeType, + nsIURI* aURL, + nsPluginInstanceOwner* aOwner) { +#ifdef PLUGIN_LOGGING + MOZ_LOG( + nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginHost::TrySetupPluginInstance Begin mime=%s, owner=%p, url=%s\n", + PromiseFlatCString(aMimeType).get(), aOwner, + aURL ? aURL->GetSpecOrDefault().get() : "")); + + PR_LogFlush(); +#endif + +#ifdef XP_WIN + bool changed; + if ((mRegKeyHKLM && NS_SUCCEEDED(mRegKeyHKLM->HasChanged(&changed)) && + changed) || + (mRegKeyHKCU && NS_SUCCEEDED(mRegKeyHKCU->HasChanged(&changed)) && + changed)) { + ReloadPlugins(); + } +#endif + + RefPtr<nsNPAPIPlugin> plugin; + GetPlugin(aMimeType, getter_AddRefs(plugin)); + if (!plugin) { + return NS_ERROR_FAILURE; + } + + nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true); + + NS_ASSERTION(pluginTag, "Must have plugin tag here!"); + + plugin->GetLibrary()->SetHasLocalInstance(); + + RefPtr<nsNPAPIPluginInstance> instance = new nsNPAPIPluginInstance(); + + // This will create the owning reference. The connection must be made between + // the instance and the instance owner before initialization. Plugins can call + // into the browser during initialization. + aOwner->SetInstance(instance.get()); + + // Add the instance to the instances list before we call NPP_New so that + // it is "in play" before NPP_New happens. Take it out if NPP_New fails. + mInstances.AppendElement(instance.get()); + + // this should not addref the instance or owner + // except in some cases not Java, see bug 140931 + // our COM pointer will free the peer + nsresult rv = instance->Initialize(plugin.get(), aOwner, aMimeType); + if (NS_FAILED(rv)) { + mInstances.RemoveElement(instance.get()); + aOwner->SetInstance(nullptr); + return rv; + } + + // Cancel the plugin unload timer since we are creating + // an instance for it. + if (pluginTag->mUnloadTimer) { + pluginTag->mUnloadTimer->Cancel(); + } + +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_BASIC, + ("nsPluginHost::TrySetupPluginInstance Finished mime=%s, rv=%" PRIu32 + ", owner=%p, url=%s\n", + PromiseFlatCString(aMimeType).get(), static_cast<uint32_t>(rv), + aOwner, aURL ? aURL->GetSpecOrDefault().get() : "")); + + PR_LogFlush(); +#endif + + return rv; +} + +bool nsPluginHost::HavePluginForType(const nsACString& aMimeType, + PluginFilter aFilter) { + bool checkEnabled = aFilter & eExcludeDisabled; + bool allowFake = !(aFilter & eExcludeFake); + return FindPluginForType(aMimeType, allowFake, checkEnabled); +} + +nsIInternalPluginTag* nsPluginHost::FindPluginForType( + const nsACString& aMimeType, bool aIncludeFake, bool aCheckEnabled) { + if (aIncludeFake) { + nsFakePluginTag* fakeTag = FindFakePluginForType(aMimeType, aCheckEnabled); + if (fakeTag) { + return fakeTag; + } + } + + return FindNativePluginForType(aMimeType, aCheckEnabled); +} + +NS_IMETHODIMP +nsPluginHost::GetPluginTagForType(const nsACString& aMimeType, + uint32_t aExcludeFlags, + nsIPluginTag** aResult) { + bool includeFake = !(aExcludeFlags & eExcludeFake); + bool includeDisabled = !(aExcludeFlags & eExcludeDisabled); + + // First look for an enabled plugin. + RefPtr<nsIInternalPluginTag> tag = + FindPluginForType(aMimeType, includeFake, true); + if (!tag && includeDisabled) { + tag = FindPluginForType(aMimeType, includeFake, false); + } + + if (tag) { + tag.forget(aResult); + return NS_OK; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsPluginHost::GetStateForType(const nsACString& aMimeType, + uint32_t aExcludeFlags, uint32_t* aResult) { + nsCOMPtr<nsIPluginTag> tag; + nsresult rv = + GetPluginTagForType(aMimeType, aExcludeFlags, getter_AddRefs(tag)); + NS_ENSURE_SUCCESS(rv, rv); + + return tag->GetEnabledState(aResult); +} + +NS_IMETHODIMP +nsPluginHost::GetBlocklistStateForType(const nsACString& aMimeType, + uint32_t aExcludeFlags, + uint32_t* aState) { + nsCOMPtr<nsIPluginTag> tag; + nsresult rv = + GetPluginTagForType(aMimeType, aExcludeFlags, getter_AddRefs(tag)); + NS_ENSURE_SUCCESS(rv, rv); + return tag->GetBlocklistState(aState); +} + +NS_IMETHODIMP +nsPluginHost::GetPermissionStringForType(const nsACString& aMimeType, + uint32_t aExcludeFlags, + nsACString& aPermissionString) { + nsCOMPtr<nsIPluginTag> tag; + nsresult rv = + GetPluginTagForType(aMimeType, aExcludeFlags, getter_AddRefs(tag)); + NS_ENSURE_SUCCESS(rv, rv); + return GetPermissionStringForTag(tag, aExcludeFlags, aPermissionString); +} + +NS_IMETHODIMP +nsPluginHost::GetPermissionStringForTag(nsIPluginTag* aTag, + uint32_t aExcludeFlags, + nsACString& aPermissionString) { + NS_ENSURE_TRUE(aTag, NS_ERROR_FAILURE); + + aPermissionString.Truncate(); + uint32_t blocklistState; + nsresult rv = aTag->GetBlocklistState(&blocklistState); + NS_ENSURE_SUCCESS(rv, rv); + + if (blocklistState == + nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE || + blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) { + aPermissionString.AssignLiteral("plugin-vulnerable:"); + } else { + aPermissionString.AssignLiteral("plugin:"); + } + + nsCString niceName; + rv = aTag->GetNiceName(niceName); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(!niceName.IsEmpty(), NS_ERROR_FAILURE); + + aPermissionString.Append(niceName); + + return NS_OK; +} + +bool nsPluginHost::HavePluginForExtension(const nsACString& aExtension, + /* out */ nsACString& aMimeType, + PluginFilter aFilter) { + // As of FF 52, we only support flash and test plugins, so if the extension + // types don't match for that, exit before we start loading plugins. + // + // XXX: Remove tst case when bug 1351885 lands. + if (!aExtension.LowerCaseEqualsLiteral("swf") && + !aExtension.LowerCaseEqualsLiteral("tst")) { + return false; + } + + bool checkEnabled = aFilter & eExcludeDisabled; + bool allowFake = !(aFilter & eExcludeFake); + return FindNativePluginForExtension(aExtension, aMimeType, checkEnabled) || + (allowFake && + FindFakePluginForExtension(aExtension, aMimeType, checkEnabled)); +} + +void nsPluginHost::GetPlugins( + nsTArray<nsCOMPtr<nsIInternalPluginTag>>& aPluginArray, + bool aIncludeDisabled) { + aPluginArray.Clear(); + + LoadPlugins(); + + // Append fake plugins, then normal plugins. + + uint32_t numFake = mFakePlugins.Length(); + for (uint32_t i = 0; i < numFake; i++) { + aPluginArray.AppendElement(mFakePlugins[i]); + } + + // Regular plugins + nsPluginTag* plugin = mPlugins; + while (plugin != nullptr) { + if (plugin->IsEnabled() || aIncludeDisabled) { + aPluginArray.AppendElement(plugin); + } + plugin = plugin->mNext; + } +} + +// FIXME-jsplugins Check users for order of fake v non-fake +NS_IMETHODIMP +nsPluginHost::GetPluginTags(nsTArray<RefPtr<nsIPluginTag>>& aResults) { + LoadPlugins(); + + for (nsPluginTag* plugin = mPlugins; plugin; plugin = plugin->mNext) { + aResults.AppendElement(plugin); + } + + for (nsIInternalPluginTag* plugin : mFakePlugins) { + aResults.AppendElement(plugin); + } + + return NS_OK; +} + +nsPluginTag* nsPluginHost::FindPreferredPlugin( + const nsTArray<nsPluginTag*>& matches) { + // We prefer the plugin with the highest version number. + /// XXX(johns): This seems to assume the only time multiple plugins will have + /// the same MIME type is if they're multiple versions of the same + /// plugin -- but since plugin filenames and pretty names can both + /// update, it's probably less arbitrary than just going at it + /// alphabetically. + + if (matches.IsEmpty()) { + return nullptr; + } + + nsPluginTag* preferredPlugin = matches[0]; + for (unsigned int i = 1; i < matches.Length(); i++) { + if (mozilla::Version(matches[i]->Version().get()) > + preferredPlugin->Version().get()) { + preferredPlugin = matches[i]; + } + } + + return preferredPlugin; +} + +nsFakePluginTag* nsPluginHost::FindFakePluginForExtension( + const nsACString& aExtension, + /* out */ nsACString& aMimeType, bool aCheckEnabled) { + if (aExtension.IsEmpty()) { + return nullptr; + } + + int32_t numFakePlugins = mFakePlugins.Length(); + for (int32_t i = 0; i < numFakePlugins; i++) { + nsFakePluginTag* plugin = mFakePlugins[i]; + bool active; + if ((!aCheckEnabled || + (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) && + plugin->HasExtension(aExtension, aMimeType)) { + return plugin; + } + } + + return nullptr; +} + +nsFakePluginTag* nsPluginHost::FindFakePluginForType( + const nsACString& aMimeType, bool aCheckEnabled) { + int32_t numFakePlugins = mFakePlugins.Length(); + for (int32_t i = 0; i < numFakePlugins; i++) { + nsFakePluginTag* plugin = mFakePlugins[i]; + bool active; + if ((!aCheckEnabled || + (NS_SUCCEEDED(plugin->GetActive(&active)) && active)) && + plugin->HasMimeType(aMimeType)) { + return plugin; + } + } + + return nullptr; +} + +nsPluginTag* nsPluginHost::FindNativePluginForType(const nsACString& aMimeType, + bool aCheckEnabled) { + if (aMimeType.IsEmpty()) { + return nullptr; + } + + // As of FF 52, we only support flash and test plugins, so if the mime types + // don't match for that, exit before we start loading plugins. + if (!nsPluginHost::CanUsePluginForMIMEType(aMimeType)) { + return nullptr; + } + + LoadPlugins(); + + nsTArray<nsPluginTag*> matchingPlugins; + + nsPluginTag* plugin = mPlugins; + while (plugin) { + if ((!aCheckEnabled || plugin->IsActive()) && + plugin->HasMimeType(aMimeType)) { + matchingPlugins.AppendElement(plugin); + } + plugin = plugin->mNext; + } + + return FindPreferredPlugin(matchingPlugins); +} + +nsPluginTag* nsPluginHost::FindNativePluginForExtension( + const nsACString& aExtension, + /* out */ nsACString& aMimeType, bool aCheckEnabled) { + if (aExtension.IsEmpty()) { + return nullptr; + } + + LoadPlugins(); + + nsTArray<nsPluginTag*> matchingPlugins; + nsCString matchingMime; // Don't mutate aMimeType unless returning a match + nsPluginTag* plugin = mPlugins; + + while (plugin) { + if (!aCheckEnabled || plugin->IsActive()) { + if (plugin->HasExtension(aExtension, matchingMime)) { + matchingPlugins.AppendElement(plugin); + } + } + plugin = plugin->mNext; + } + + nsPluginTag* preferredPlugin = FindPreferredPlugin(matchingPlugins); + if (!preferredPlugin) { + return nullptr; + } + + // Re-fetch the matching type because of how FindPreferredPlugin works... + preferredPlugin->HasExtension(aExtension, aMimeType); + return preferredPlugin; +} + +static nsresult CreateNPAPIPlugin(nsPluginTag* aPluginTag, + nsNPAPIPlugin** aOutNPAPIPlugin) { + nsresult rv; + rv = nsNPAPIPlugin::CreatePlugin(aPluginTag, aOutNPAPIPlugin); + + return rv; +} + +nsresult nsPluginHost::EnsurePluginLoaded(nsPluginTag* aPluginTag) { + RefPtr<nsNPAPIPlugin> plugin = aPluginTag->mPlugin; + if (!plugin) { + nsresult rv = CreateNPAPIPlugin(aPluginTag, getter_AddRefs(plugin)); + if (NS_FAILED(rv)) { + return rv; + } + aPluginTag->mPlugin = plugin; + } + return NS_OK; +} + +nsresult nsPluginHost::GetPluginForContentProcess(uint32_t aPluginId, + nsNPAPIPlugin** aPlugin) { + AUTO_PROFILER_LABEL("nsPluginHost::GetPluginForContentProcess", OTHER); + MOZ_ASSERT(XRE_IsParentProcess()); + + // If plugins haven't been scanned yet, do so now + LoadPlugins(); + + nsPluginTag* pluginTag = PluginWithId(aPluginId); + if (pluginTag) { + // When setting up a bridge, double check with chrome to see if this plugin + // is blocked hard. Note this does not protect against vulnerable plugins + // that the user has explicitly allowed. :( + if (pluginTag->IsBlocklisted()) { + return NS_ERROR_PLUGIN_BLOCKLISTED; + } + + nsresult rv = EnsurePluginLoaded(pluginTag); + if (NS_FAILED(rv)) { + return rv; + } + + // We only get here if a content process doesn't have a PluginModuleParent + // for the given plugin already. Therefore, this counter is counting the + // number of outstanding PluginModuleParents for the plugin, excluding the + // one from the chrome process. + pluginTag->mContentProcessRunningCount++; + NS_ADDREF(*aPlugin = pluginTag->mPlugin); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +class nsPluginUnloadRunnable : public Runnable { + public: + explicit nsPluginUnloadRunnable(uint32_t aPluginId) + : Runnable("nsPluginUnloadRunnable"), mPluginId(aPluginId) {} + + NS_IMETHOD Run() override { + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + if (!host) { + return NS_OK; + } + nsPluginTag* pluginTag = host->PluginWithId(mPluginId); + if (!pluginTag) { + return NS_OK; + } + + MOZ_ASSERT(pluginTag->mContentProcessRunningCount > 0); + pluginTag->mContentProcessRunningCount--; + + if (!pluginTag->mContentProcessRunningCount) { + if (!host->IsRunningPlugin(pluginTag)) { + pluginTag->TryUnloadPlugin(false); + } + } + return NS_OK; + } + + protected: + uint32_t mPluginId; +}; + +void nsPluginHost::NotifyContentModuleDestroyed(uint32_t aPluginId) { + MOZ_ASSERT(XRE_IsParentProcess()); + + // This is called in response to a message from the plugin. Don't unload the + // plugin until the message handler is off the stack. + RefPtr<nsPluginUnloadRunnable> runnable = + new nsPluginUnloadRunnable(aPluginId); + NS_DispatchToMainThread(runnable); +} + +nsresult nsPluginHost::GetPlugin(const nsACString& aMimeType, + nsNPAPIPlugin** aPlugin) { + nsresult rv = NS_ERROR_FAILURE; + *aPlugin = nullptr; + + // If plugins haven't been scanned yet, do so now + LoadPlugins(); + + nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true); + if (pluginTag) { + rv = NS_OK; + PLUGIN_LOG( + PLUGIN_LOG_BASIC, + ("nsPluginHost::GetPlugin Begin mime=%s, plugin=%s\n", + PromiseFlatCString(aMimeType).get(), pluginTag->FileName().get())); + +#ifdef DEBUG + if (!pluginTag->FileName().IsEmpty()) + printf("For %s found plugin %s\n", PromiseFlatCString(aMimeType).get(), + pluginTag->FileName().get()); +#endif + + rv = EnsurePluginLoaded(pluginTag); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ADDREF(*aPlugin = pluginTag->mPlugin); + return NS_OK; + } + + PLUGIN_LOG( + PLUGIN_LOG_NORMAL, + ("nsPluginHost::GetPlugin End mime=%s, rv=%" PRIu32 + ", plugin=%p name=%s\n", + PromiseFlatCString(aMimeType).get(), static_cast<uint32_t>(rv), *aPlugin, + (pluginTag ? pluginTag->FileName().get() : "(not found)"))); + + return rv; +} + +// Normalize 'host' to ACE. +nsresult nsPluginHost::NormalizeHostname(nsCString& host) { + if (IsAscii(host)) { + ToLowerCase(host); + return NS_OK; + } + + if (!mIDNService) { + nsresult rv; + mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mIDNService->ConvertUTF8toACE(host, host); +} + +// Enumerate a 'sites' array returned by GetSitesWithData and determine if +// any of them have a base domain in common with 'domain'; if so, append them +// to the 'result' array. If 'firstMatchOnly' is true, return after finding the +// first match. +nsresult nsPluginHost::EnumerateSiteData(const nsACString& domain, + const nsTArray<nsCString>& sites, + nsTArray<nsCString>& result, + bool firstMatchOnly) { + NS_ASSERTION(!domain.IsVoid(), "null domain string"); + + nsresult rv; + if (!mTLDService) { + mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Get the base domain from the domain. + nsCString baseDomain; + rv = mTLDService->GetBaseDomainFromHost(domain, 0, baseDomain); + bool isIP = rv == NS_ERROR_HOST_IS_IP_ADDRESS; + if (isIP || rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + // The base domain is the site itself. However, we must be careful to + // normalize. + baseDomain = domain; + rv = NormalizeHostname(baseDomain); + NS_ENSURE_SUCCESS(rv, rv); + } else if (NS_FAILED(rv)) { + return rv; + } + + // Enumerate the array of sites with data. + for (uint32_t i = 0; i < sites.Length(); ++i) { + const nsCString& site = sites[i]; + + // Check if the site is an IP address. + bool siteIsIP = + site.Length() >= 2 && site.First() == '[' && site.Last() == ']'; + if (siteIsIP != isIP) continue; + + nsCString siteBaseDomain; + if (siteIsIP) { + // Strip the '[]'. + siteBaseDomain = Substring(site, 1, site.Length() - 2); + } else { + // Determine the base domain of the site. + rv = mTLDService->GetBaseDomainFromHost(site, 0, siteBaseDomain); + if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + // The base domain is the site itself. However, we must be careful to + // normalize. + siteBaseDomain = site; + rv = NormalizeHostname(siteBaseDomain); + NS_ENSURE_SUCCESS(rv, rv); + } else if (NS_FAILED(rv)) { + return rv; + } + } + + // At this point, we can do an exact comparison of the two domains. + if (baseDomain != siteBaseDomain) { + continue; + } + + // Append the site to the result array. + result.AppendElement(site); + + // If we're supposed to return early, do so. + if (firstMatchOnly) { + break; + } + } + + return NS_OK; +} + +static bool MimeTypeIsAllowedForFakePlugin(const nsString& aMimeType) { + static const char* const allowedFakePlugins[] = { + // Flash + "application/x-shockwave-flash", + // PDF + "application/pdf", + "application/vnd.adobe.pdf", + "application/vnd.adobe.pdfxml", + "application/vnd.adobe.x-mars", + "application/vnd.adobe.xdp+xml", + "application/vnd.adobe.xfdf", + "application/vnd.adobe.xfd+xml", + "application/vnd.fdf", + }; + + for (const auto allowed : allowedFakePlugins) { + if (aMimeType.EqualsASCII(allowed)) { + return true; + } + } + return false; +} + +NS_IMETHODIMP +nsPluginHost::RegisterFakePlugin(JS::Handle<JS::Value> aInitDictionary, + JSContext* aCx, nsIFakePluginTag** aResult) { + FakePluginTagInit initDictionary; + if (!initDictionary.Init(aCx, aInitDictionary)) { + return NS_ERROR_FAILURE; + } + + for (const FakePluginMimeEntry& mimeEntry : initDictionary.mMimeEntries) { + if (!MimeTypeIsAllowedForFakePlugin(mimeEntry.mType)) { + return NS_ERROR_FAILURE; + } + } + + RefPtr<nsFakePluginTag> newTag; + nsresult rv = nsFakePluginTag::Create(initDictionary, getter_AddRefs(newTag)); + NS_ENSURE_SUCCESS(rv, rv); + + for (const auto& existingTag : mFakePlugins) { + if (newTag->HandlerURIMatches(existingTag->HandlerURI())) { + return NS_ERROR_UNEXPECTED; + } + } + + mFakePlugins.AppendElement(newTag); + + nsAutoCString disableFullPage; + Preferences::GetCString(kPrefDisableFullPage, disableFullPage); + for (uint32_t i = 0; i < newTag->MimeTypes().Length(); i++) { + if (!IsTypeInList(newTag->MimeTypes()[i], disableFullPage)) { + RegisterWithCategoryManager(newTag->MimeTypes()[i], ePluginRegister); + } + } + + newTag.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginHost::CreateFakePlugin(JS::Handle<JS::Value> aInitDictionary, + JSContext* aCx, nsIFakePluginTag** aResult) { + FakePluginTagInit initDictionary; + if (!initDictionary.Init(aCx, aInitDictionary)) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsFakePluginTag> newTag; + nsresult rv = nsFakePluginTag::Create(initDictionary, getter_AddRefs(newTag)); + NS_ENSURE_SUCCESS(rv, rv); + + newTag.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginHost::UnregisterFakePlugin(const nsACString& aHandlerURI) { + nsCOMPtr<nsIURI> handlerURI; + nsresult rv = NS_NewURI(getter_AddRefs(handlerURI), aHandlerURI); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < mFakePlugins.Length(); ++i) { + if (mFakePlugins[i]->HandlerURIMatches(handlerURI)) { + mFakePlugins.RemoveElementAt(i); + return NS_OK; + } + } + + return NS_OK; +} + +// FIXME-jsplugins Is this method actually needed? +NS_IMETHODIMP +nsPluginHost::GetFakePlugin(const nsACString& aMimeType, + nsIFakePluginTag** aResult) { + RefPtr<nsFakePluginTag> result = FindFakePluginForType(aMimeType, false); + if (result) { + result.forget(aResult); + return NS_OK; + } + + *aResult = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +#define ClearDataFromSitesClosure_CID \ + { \ + 0x9fb21761, 0x2403, 0x41ad, { \ + 0x9e, 0xfd, 0x36, 0x7e, 0xc4, 0x4f, 0xa4, 0x5e \ + } \ + } + +// Class to hold all the data we need need for IterateMatchesAndClear and +// ClearDataFromSites +class ClearDataFromSitesClosure : public nsIClearSiteDataCallback, + public nsIGetSitesWithDataCallback { + public: + ClearDataFromSitesClosure(nsIPluginTag* plugin, const nsACString& domain, + uint64_t flags, int64_t maxAge, + nsCOMPtr<nsIClearSiteDataCallback> callback, + nsPluginHost* host) + : domain(domain), + callback(callback), + tag(plugin), + flags(flags), + maxAge(maxAge), + host(host) {} + NS_DECL_ISUPPORTS + + // Callback from NPP_ClearSiteData, continue to iterate the matches and clear + NS_IMETHOD Callback(nsresult rv) override { + if (NS_FAILED(rv)) { + callback->Callback(rv); + return NS_OK; + } + if (!matches.Length()) { + callback->Callback(NS_OK); + return NS_OK; + } + + const nsCString match(matches[0]); + matches.RemoveElement(match); + PluginLibrary* library = + static_cast<nsPluginTag*>(tag)->mPlugin->GetLibrary(); + rv = library->NPP_ClearSiteData(match.get(), flags, maxAge, this); + if (NS_FAILED(rv)) { + callback->Callback(rv); + return NS_OK; + } + return NS_OK; + } + + // Callback from NPP_GetSitesWithData, kick the iteration off to clear the + // data + NS_IMETHOD SitesWithData(nsTArray<nsCString>& sites) override { + // Enumerate the sites and build a list of matches. + nsresult rv = host->EnumerateSiteData(domain, sites, matches, false); + Callback(rv); + return NS_OK; + } + + nsCString domain; + nsCOMPtr<nsIClearSiteDataCallback> callback; + nsTArray<nsCString> matches; + nsIPluginTag* tag; + uint64_t flags; + int64_t maxAge; + nsPluginHost* host; + NS_DECLARE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure_CID) + private: + virtual ~ClearDataFromSitesClosure() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(ClearDataFromSitesClosure, + ClearDataFromSitesClosure_CID) + +NS_IMPL_ADDREF(ClearDataFromSitesClosure) +NS_IMPL_RELEASE(ClearDataFromSitesClosure) + +NS_INTERFACE_MAP_BEGIN(ClearDataFromSitesClosure) + NS_INTERFACE_MAP_ENTRY(nsIClearSiteDataCallback) + NS_INTERFACE_MAP_ENTRY(nsIGetSitesWithDataCallback) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIClearSiteDataCallback) +NS_INTERFACE_MAP_END + +// FIXME-jsplugins what should this do for fake plugins? +NS_IMETHODIMP +nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain, + uint64_t flags, int64_t maxAge, + nsIClearSiteDataCallback* callbackFunc) { + nsCOMPtr<nsIClearSiteDataCallback> callback(callbackFunc); + // maxAge must be either a nonnegative integer or -1. + NS_ENSURE_ARG(maxAge >= 0 || maxAge == -1); + + // Caller may give us a tag object that is no longer live. + if (!IsLiveTag(plugin)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsPluginTag* tag = static_cast<nsPluginTag*>(plugin); + + if (!tag->IsEnabled()) { + return NS_ERROR_NOT_AVAILABLE; + } + + // We only ensure support for clearing Flash site data for now. + // We will also attempt to clear data for any plugin that happens + // to be loaded already. + if (!tag->mIsFlashPlugin && !tag->mPlugin) { + return NS_ERROR_FAILURE; + } + + // Make sure the plugin is loaded. + nsresult rv = EnsurePluginLoaded(tag); + if (NS_FAILED(rv)) { + return rv; + } + + PluginLibrary* library = tag->mPlugin->GetLibrary(); + + // If 'domain' is the null string, clear everything. + if (domain.IsVoid()) { + return library->NPP_ClearSiteData(nullptr, flags, maxAge, callback); + } + nsCOMPtr<nsIGetSitesWithDataCallback> closure(new ClearDataFromSitesClosure( + plugin, domain, flags, maxAge, callback, this)); + rv = library->NPP_GetSitesWithData(closure); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +#define GetSitesClosure_CID \ + { \ + 0x4c9268ac, 0x2fd1, 0x4f2a, { \ + 0x9a, 0x10, 0x7a, 0x09, 0xf1, 0xb7, 0x60, 0x3a \ + } \ + } + +// Closure to contain the data needed to handle the callback from +// NPP_GetSitesWithData +class GetSitesClosure : public nsIGetSitesWithDataCallback { + public: + NS_DECL_ISUPPORTS + GetSitesClosure(const nsACString& domain, nsPluginHost* host) + : domain(domain), + host(host), + result{false}, + keepWaiting(true), + retVal(NS_ERROR_NOT_INITIALIZED) {} + + NS_IMETHOD SitesWithData(nsTArray<nsCString>& sites) override { + retVal = HandleGetSites(sites); + keepWaiting = false; + return NS_OK; + } + + nsresult HandleGetSites(nsTArray<nsCString>& sites) { + // If there's no data, we're done. + if (sites.IsEmpty()) { + result = false; + return NS_OK; + } + + // If 'domain' is the null string, and there's data for at least one site, + // we're done. + if (domain.IsVoid()) { + result = true; + return NS_OK; + } + + // Enumerate the sites and determine if there's a match. + nsTArray<nsCString> matches; + nsresult rv = host->EnumerateSiteData(domain, sites, matches, true); + NS_ENSURE_SUCCESS(rv, rv); + + result = !matches.IsEmpty(); + return NS_OK; + } + + nsCString domain; + RefPtr<nsPluginHost> host; + bool result; + bool keepWaiting; + nsresult retVal; + NS_DECLARE_STATIC_IID_ACCESSOR(GetSitesClosure_CID) + private: + virtual ~GetSitesClosure() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(GetSitesClosure, GetSitesClosure_CID) + +NS_IMPL_ISUPPORTS(GetSitesClosure, GetSitesClosure, nsIGetSitesWithDataCallback) + +// This will spin the event loop while waiting on an async +// call to GetSitesWithData +NS_IMETHODIMP +nsPluginHost::SiteHasData(nsIPluginTag* plugin, const nsACString& domain, + bool* result) { + // Caller may give us a tag object that is no longer live. + if (!IsLiveTag(plugin)) { + return NS_ERROR_NOT_AVAILABLE; + } + + // FIXME-jsplugins audit casts + nsPluginTag* tag = static_cast<nsPluginTag*>(plugin); + + // We only ensure support for clearing Flash site data for now. + // We will also attempt to clear data for any plugin that happens + // to be loaded already. + if (!tag->mIsFlashPlugin && !tag->mPlugin) { + return NS_ERROR_FAILURE; + } + + // Make sure the plugin is loaded. + nsresult rv = EnsurePluginLoaded(tag); + if (NS_FAILED(rv)) { + return rv; + } + + PluginLibrary* library = tag->mPlugin->GetLibrary(); + + // Get the list of sites from the plugin + nsCOMPtr<GetSitesClosure> closure(new GetSitesClosure(domain, this)); + rv = library->NPP_GetSitesWithData( + nsCOMPtr<nsIGetSitesWithDataCallback>(closure)); + NS_ENSURE_SUCCESS(rv, rv); + // Spin the event loop while we wait for the async call to GetSitesWithData + SpinEventLoopUntil([&]() { return !closure->keepWaiting; }); + *result = closure->result; + return closure->retVal; +} + +nsPluginHost::SpecialType nsPluginHost::GetSpecialType( + const nsACString& aMIMEType) { + if (aMIMEType.LowerCaseEqualsASCII("application/x-test")) { + return eSpecialType_Test; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash") || + aMIMEType.LowerCaseEqualsASCII("application/futuresplash") || + aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash-test")) { + return eSpecialType_Flash; + } + + return eSpecialType_None; +} + +// Check whether or not a tag is a live, valid tag, and that it's loaded. +bool nsPluginHost::IsLiveTag(nsIPluginTag* aPluginTag) { + nsCOMPtr<nsIInternalPluginTag> internalTag(do_QueryInterface(aPluginTag)); + uint32_t fakeCount = mFakePlugins.Length(); + for (uint32_t i = 0; i < fakeCount; i++) { + if (mFakePlugins[i] == internalTag) { + return true; + } + } + + nsPluginTag* tag; + for (tag = mPlugins; tag; tag = tag->mNext) { + if (tag == internalTag) { + return true; + } + } + return false; +} + +// FIXME-jsplugins what should happen with jsplugins here, if anything? +nsPluginTag* nsPluginHost::HaveSamePlugin(const nsPluginTag* aPluginTag) { + for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { + if (tag->HasSameNameAndMimes(aPluginTag)) { + return tag; + } + } + return nullptr; +} + +nsPluginTag* nsPluginHost::PluginWithId(uint32_t aId) { + for (nsPluginTag* tag = mPlugins; tag; tag = tag->mNext) { + if (tag->mId == aId) { + return tag; + } + } + return nullptr; +} + +void nsPluginHost::AddPluginTag(nsPluginTag* aPluginTag) { + aPluginTag->mNext = mPlugins; + mPlugins = aPluginTag; + + if (aPluginTag->IsActive()) { + nsAutoCString disableFullPage; + Preferences::GetCString(kPrefDisableFullPage, disableFullPage); + for (uint32_t i = 0; i < aPluginTag->MimeTypes().Length(); i++) { + if (!IsTypeInList(aPluginTag->MimeTypes()[i], disableFullPage)) { + RegisterWithCategoryManager(aPluginTag->MimeTypes()[i], + ePluginRegister); + } + } + } +} + +typedef NS_NPAPIPLUGIN_CALLBACK(char*, NP_GETMIMEDESCRIPTION)(void); + +void nsPluginHost::UpdatePluginBlocklistState(nsPluginTag* aPluginTag, + bool aShouldSoftblock) { + nsCOMPtr<nsIBlocklistService> blocklist = + do_GetService("@mozilla.org/extensions/blocklist;1"); + MOZ_ASSERT(blocklist, "Should be able to access the blocklist"); + if (!blocklist) { + return; + } + // Asynchronously get the blocklist state. + RefPtr<Promise> promise; + blocklist->GetPluginBlocklistState(aPluginTag, u""_ns, u""_ns, + getter_AddRefs(promise)); + MOZ_ASSERT(promise, + "Should always get a promise for plugin blocklist state."); + if (promise) { + promise->AppendNativeHandler(new mozilla::plugins::BlocklistPromiseHandler( + aPluginTag, aShouldSoftblock)); + } +} + +void nsPluginHost::IncrementChromeEpoch() { + MOZ_ASSERT(XRE_IsParentProcess()); + mPluginEpoch++; +} + +uint32_t nsPluginHost::ChromeEpoch() { + MOZ_ASSERT(XRE_IsParentProcess()); + return mPluginEpoch; +} + +uint32_t nsPluginHost::ChromeEpochForContent() { + MOZ_ASSERT(XRE_IsContentProcess()); + return mPluginEpoch; +} + +void nsPluginHost::SetChromeEpochForContent(uint32_t aEpoch) { + MOZ_ASSERT(XRE_IsContentProcess()); + mPluginEpoch = aEpoch; +} + +already_AddRefed<nsIAsyncShutdownClient> GetProfileChangeTeardownPhase() { + nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc = + services::GetAsyncShutdownService(); + MOZ_ASSERT(asyncShutdownSvc); + if (NS_WARN_IF(!asyncShutdownSvc)) { + return nullptr; + } + + nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase; + DebugOnly<nsresult> rv = + asyncShutdownSvc->GetProfileChangeTeardown(getter_AddRefs(shutdownPhase)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return shutdownPhase.forget(); +} + +nsresult nsPluginHost::LoadPlugins() { return NS_OK; } + +void nsPluginHost::FindingFinished() {} + +nsresult nsPluginHost::SetPluginsInContent( + uint32_t aPluginEpoch, nsTArray<mozilla::plugins::PluginTag>& aPlugins, + nsTArray<mozilla::plugins::FakePluginTag>& aFakePlugins) { + MOZ_ASSERT(XRE_IsContentProcess()); + + nsTArray<PluginTag> plugins; + + nsTArray<FakePluginTag> fakePlugins; + + if (aPluginEpoch != ChromeEpochForContent()) { + // Since we know we're going to be repopulating the lists anyways, + // clear out all old entries. + ClearNonRunningPlugins(); + + SetChromeEpochForContent(aPluginEpoch); + + for (auto tag : aPlugins) { + // Don't add the same plugin again. + if (nsPluginTag* existing = PluginWithId(tag.id())) { + UpdateInMemoryPluginInfo(existing); + existing->SetBlocklistState(tag.blocklistState()); + continue; + } + + nsPluginTag* pluginTag = new nsPluginTag( + tag.id(), tag.name().get(), tag.description().get(), + tag.filename().get(), + "", // aFullPath + tag.version().get(), tag.mimeTypes().Clone(), + tag.mimeDescriptions().Clone(), tag.extensions().Clone(), + tag.isFlashPlugin(), tag.supportsAsyncRender(), + tag.lastModifiedTime(), tag.sandboxLevel(), tag.blocklistState()); + AddPluginTag(pluginTag); + } + + for (const auto& tag : aFakePlugins) { + // Don't add the same plugin again. + for (const auto& existingTag : mFakePlugins) { + if (existingTag->Id() == tag.id()) { + continue; + } + } + + RefPtr<nsFakePluginTag> pluginTag = + *mFakePlugins.AppendElement(new nsFakePluginTag( + tag.id(), mozilla::ipc::DeserializeURI(tag.handlerURI()), + tag.name().get(), tag.description().get(), tag.mimeTypes(), + tag.mimeDescriptions(), tag.extensions(), tag.niceName(), + tag.sandboxScript())); + nsAutoCString disableFullPage; + Preferences::GetCString(kPrefDisableFullPage, disableFullPage); + for (uint32_t i = 0; i < pluginTag->MimeTypes().Length(); i++) { + if (!IsTypeInList(pluginTag->MimeTypes()[i], disableFullPage)) { + RegisterWithCategoryManager(pluginTag->MimeTypes()[i], + ePluginRegister); + } + } + } + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) { + obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr); + } + } + + mPluginsLoaded = true; + return NS_OK; +} + +nsresult nsPluginHost::UpdateCachedSerializablePluginList() { + nsTArray<nsCOMPtr<nsIInternalPluginTag>> plugins; + GetPlugins(plugins, true); + mSerializablePlugins.Clear(); + mSerializableFakePlugins.Clear(); + + for (size_t i = 0; i < plugins.Length(); i++) { + nsCOMPtr<nsIInternalPluginTag> basetag = plugins[i]; + + nsCOMPtr<nsIFakePluginTag> faketag = do_QueryInterface(basetag); + if (faketag) { + /// FIXME-jsplugins - We need to add a nsIInternalPluginTag->AsNative() to + /// avoid this hacky static cast + nsFakePluginTag* tag = static_cast<nsFakePluginTag*>(basetag.get()); + mozilla::ipc::URIParams handlerURI; + SerializeURI(tag->HandlerURI(), handlerURI); + mSerializableFakePlugins.AppendElement(FakePluginTag( + tag->Id(), handlerURI, tag->Name(), tag->Description(), + tag->MimeTypes(), tag->MimeDescriptions(), tag->Extensions(), + tag->GetNiceFileName(), tag->SandboxScript())); + continue; + } + + /// FIXME-jsplugins - We need to cleanup the various plugintag classes + /// to be more sane and avoid this dance + nsPluginTag* tag = static_cast<nsPluginTag*>(basetag.get()); + + uint32_t blocklistState; + if (NS_WARN_IF(NS_FAILED(tag->GetBlocklistState(&blocklistState)))) { + return NS_ERROR_FAILURE; + } + + mSerializablePlugins.AppendElement(PluginTag( + tag->mId, tag->Name(), tag->Description(), tag->MimeTypes(), + tag->MimeDescriptions(), tag->Extensions(), tag->mIsFlashPlugin, + tag->mSupportsAsyncRender, tag->FileName(), tag->Version(), + tag->mLastModifiedTime, tag->mSandboxLevel, blocklistState)); + } + return NS_OK; +} + +nsresult nsPluginHost::BroadcastPluginsToContent() { + MOZ_ASSERT(XRE_IsParentProcess()); + + // Load plugins so that the epoch is correct. + nsresult rv = LoadPlugins(); + if (NS_FAILED(rv)) { + return rv; + } + + rv = UpdateCachedSerializablePluginList(); + if (NS_FAILED(rv)) { + return rv; + } + + uint32_t newPluginEpoch = ChromeEpoch(); + + nsTArray<dom::ContentParent*> parents; + dom::ContentParent::GetAll(parents); + for (auto p : parents) { + Unused << p->SendSetPluginList(newPluginEpoch, mSerializablePlugins, + mSerializableFakePlugins); + } + return NS_OK; +} + +nsresult nsPluginHost::SendPluginsToContent(dom::ContentParent* parent) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(parent); + // Load plugins so that the epoch is correct. + nsresult rv = LoadPlugins(); + if (NS_FAILED(rv)) { + return rv; + } + + Unused << parent->SendSetPluginList(ChromeEpoch(), mSerializablePlugins, + mSerializableFakePlugins); + return NS_OK; +} + +void nsPluginHost::UpdateInMemoryPluginInfo(nsPluginTag* aPluginTag) { + if (!aPluginTag) { + return; + } + + // Update types with category manager + nsAutoCString disableFullPage; + Preferences::GetCString(kPrefDisableFullPage, disableFullPage); + for (uint32_t i = 0; i < aPluginTag->MimeTypes().Length(); i++) { + nsRegisterType shouldRegister; + + if (IsTypeInList(aPluginTag->MimeTypes()[i], disableFullPage)) { + shouldRegister = ePluginUnregister; + } else { + nsPluginTag* plugin = + FindNativePluginForType(aPluginTag->MimeTypes()[i], true); + shouldRegister = plugin ? ePluginRegister : ePluginUnregister; + } + + RegisterWithCategoryManager(aPluginTag->MimeTypes()[i], shouldRegister); + } + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) + obsService->NotifyObservers(nullptr, "plugin-info-updated", nullptr); +} + +// This function is not relevant for fake plugins. +void nsPluginHost::UpdatePluginInfo(nsPluginTag* aPluginTag) { + MOZ_ASSERT(XRE_IsParentProcess()); + + IncrementChromeEpoch(); + + UpdateInMemoryPluginInfo(aPluginTag); +} + +void nsPluginHost::RegisterWithCategoryManager(const nsCString& aMimeType, + nsRegisterType aType) { + PLUGIN_LOG( + PLUGIN_LOG_NORMAL, + ("nsPluginTag::RegisterWithCategoryManager type = %s, removing = %s\n", + aMimeType.get(), aType == ePluginUnregister ? "yes" : "no")); + + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return; + } + + constexpr auto contractId = + "@mozilla.org/content/plugin/document-loader-factory;1"_ns; + + if (aType == ePluginRegister) { + catMan->AddCategoryEntry("Gecko-Content-Viewers", aMimeType, contractId, + false, /* persist: broken by bug 193031 */ + mOverrideInternalTypes); + } else { + if (aType == ePluginMaybeUnregister) { + // Bail out if this type is still used by an enabled plugin + if (HavePluginForType(aMimeType)) { + return; + } + } else { + MOZ_ASSERT(aType == ePluginUnregister, "Unknown nsRegisterType"); + } + + // Only delete the entry if a plugin registered for it + nsCString value; + nsresult rv = + catMan->GetCategoryEntry("Gecko-Content-Viewers", aMimeType, value); + if (NS_SUCCEEDED(rv) && value == contractId) { + catMan->DeleteCategoryEntry("Gecko-Content-Viewers", aMimeType, true); + } + } +} + +nsresult nsPluginHost::NewPluginURLStream( + const nsString& aURL, nsNPAPIPluginInstance* aInstance, + nsNPAPIPluginStreamListener* aListener, nsIInputStream* aPostStream, + const char* aHeadersData, uint32_t aHeadersDataLen) { + nsCOMPtr<nsIURI> url; + nsAutoString absUrl; + + if (aURL.Length() <= 0) return NS_OK; + + // get the base URI for the plugin to create an absolute url + // in case aURL is relative + RefPtr<nsPluginInstanceOwner> owner = aInstance->GetOwner(); + if (owner) { + NS_MakeAbsoluteURI(absUrl, aURL, owner->GetBaseURI()); + } + + if (absUrl.IsEmpty()) absUrl.Assign(aURL); + + nsresult rv = NS_NewURI(getter_AddRefs(url), absUrl); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsPluginStreamListenerPeer> listenerPeer = + new nsPluginStreamListenerPeer(); + NS_ENSURE_TRUE(listenerPeer, NS_ERROR_OUT_OF_MEMORY); + + rv = listenerPeer->Initialize(url, aInstance, aListener); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<dom::Element> element; + nsCOMPtr<Document> doc; + if (owner) { + owner->GetDOMElement(getter_AddRefs(element)); + owner->GetDocument(getter_AddRefs(doc)); + } + + NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); + + nsCOMPtr<nsIChannel> channel; + // @arg loadgroup: + // do not add this internal plugin's channel on the + // load group otherwise this channel could be canceled + // form |nsDocShell::OnLinkClickSync| bug 166613 + rv = NS_NewChannel( + getter_AddRefs(channel), url, element, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT | + nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, + nsIContentPolicy::TYPE_OBJECT_SUBREQUEST, + nullptr, // aPerformanceStorage + nullptr, // aLoadGroup + listenerPeer, + nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_BYPASS_SERVICE_WORKER); + NS_ENSURE_SUCCESS(rv, rv); + + if (doc) { + // And if it's a script allow it to execute against the + // document's script context. + nsCOMPtr<nsIScriptChannel> scriptChannel(do_QueryInterface(channel)); + if (scriptChannel) { + scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); + // Plug-ins seem to depend on javascript: URIs running synchronously + scriptChannel->SetExecuteAsync(false); + } + } + + // deal with headers and post data + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + if (!aPostStream) { + // Only set the Referer header for GET requests because IIS throws + // errors about malformed requests if we include it in POSTs. See + // bug 724465. + nsCOMPtr<nsIURI> referer; + dom::ReferrerPolicy referrerPolicy = dom::ReferrerPolicy::_empty; + + nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(element); + if (olc) olc->GetSrcURI(getter_AddRefs(referer)); + + if (!referer) { + if (!doc) { + return NS_ERROR_FAILURE; + } + referer = doc->GetDocumentURIAsReferrer(); + referrerPolicy = doc->GetReferrerPolicy(); + } + nsCOMPtr<nsIReferrerInfo> referrerInfo = + new dom::ReferrerInfo(referer, referrerPolicy); + rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aPostStream) { + // XXX it's a bit of a hack to rewind the postdata stream + // here but it has to be done in case the post data is + // being reused multiple times. + nsCOMPtr<nsISeekableStream> postDataSeekable( + do_QueryInterface(aPostStream)); + if (postDataSeekable) + postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel"); + + uploadChannel->SetUploadStream(aPostStream, ""_ns, -1); + } + + if (aHeadersData) { + rv = AddHeadersToChannel(aHeadersData, aHeadersDataLen, httpChannel); + NS_ENSURE_SUCCESS(rv, rv); + } + } + rv = channel->AsyncOpen(listenerPeer); + if (NS_SUCCEEDED(rv)) listenerPeer->TrackRequest(channel); + return rv; +} + +nsresult nsPluginHost::AddHeadersToChannel(const char* aHeadersData, + uint32_t aHeadersDataLen, + nsIChannel* aGenericChannel) { + nsresult rv = NS_OK; + + nsCOMPtr<nsIHttpChannel> aChannel = do_QueryInterface(aGenericChannel); + if (!aChannel) { + return NS_ERROR_NULL_POINTER; + } + + // used during the manipulation of the String from the aHeadersData + nsAutoCString headersString; + nsAutoCString oneHeader; + nsAutoCString headerName; + nsAutoCString headerValue; + int32_t crlf = 0; + int32_t colon = 0; + + // Turn the char * buffer into an nsString. + headersString = aHeadersData; + + // Iterate over the nsString: for each "\r\n" delimited chunk, + // add the value as a header to the nsIHTTPChannel + while (true) { + crlf = headersString.Find("\r\n", true); + if (-1 == crlf) { + rv = NS_OK; + return rv; + } + headersString.Mid(oneHeader, 0, crlf); + headersString.Cut(0, crlf + 2); + oneHeader.StripWhitespace(); + colon = oneHeader.Find(":"); + if (-1 == colon) { + rv = NS_ERROR_NULL_POINTER; + return rv; + } + oneHeader.Left(headerName, colon); + colon++; + oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon); + + // FINALLY: we can set the header! + + rv = aChannel->SetRequestHeader(headerName, headerValue, true); + if (NS_FAILED(rv)) { + rv = NS_ERROR_NULL_POINTER; + return rv; + } + } +} + +nsresult nsPluginHost::StopPluginInstance(nsNPAPIPluginInstance* aInstance) { + AUTO_PROFILER_LABEL("nsPluginHost::StopPluginInstance", OTHER); + if (PluginDestructionGuard::DelayDestroy(aInstance)) { + return NS_OK; + } + + PLUGIN_LOG( + PLUGIN_LOG_NORMAL, + ("nsPluginHost::StopPluginInstance called instance=%p\n", aInstance)); + + if (aInstance->HasStartedDestroying()) { + return NS_OK; + } + + Telemetry::AutoTimer<Telemetry::PLUGIN_SHUTDOWN_MS> timer; + aInstance->Stop(); + + // if the instance does not want to be 'cached' just remove it + bool doCache = aInstance->ShouldCache(); + if (doCache) { + // try to get the max cached instances from a pref or use default + uint32_t cachedInstanceLimit = Preferences::GetUint( + NS_PREF_MAX_NUM_CACHED_INSTANCES, DEFAULT_NUMBER_OF_STOPPED_INSTANCES); + if (StoppedInstanceCount() >= cachedInstanceLimit) { + nsNPAPIPluginInstance* oldestInstance = FindOldestStoppedInstance(); + if (oldestInstance) { + nsPluginTag* pluginTag = TagForPlugin(oldestInstance->GetPlugin()); + oldestInstance->Destroy(); + mInstances.RemoveElement(oldestInstance); + // TODO: Remove this check once bug 752422 was investigated + if (pluginTag) { + OnPluginInstanceDestroyed(pluginTag); + } + } + } + } else { + nsPluginTag* pluginTag = TagForPlugin(aInstance->GetPlugin()); + aInstance->Destroy(); + mInstances.RemoveElement(aInstance); + // TODO: Remove this check once bug 752422 was investigated + if (pluginTag) { + OnPluginInstanceDestroyed(pluginTag); + } + } + + return NS_OK; +} + +nsresult nsPluginHost::NewPluginStreamListener( + nsIURI* aURI, nsNPAPIPluginInstance* aInstance, + nsIStreamListener** aStreamListener) { + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aStreamListener); + + RefPtr<nsPluginStreamListenerPeer> listener = + new nsPluginStreamListenerPeer(); + nsresult rv = listener->Initialize(aURI, aInstance, nullptr); + if (NS_FAILED(rv)) { + return rv; + } + + listener.forget(aStreamListener); + + return NS_OK; +} + +void nsPluginHost::CreateWidget(nsPluginInstanceOwner* aOwner) { + aOwner->CreateWidget(); + + // If we've got a native window, the let the plugin know about it. + aOwner->CallSetWindow(); +} + +NS_IMETHODIMP nsPluginHost::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) { + UnloadPlugins(); + } + if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) { + mPluginsDisabled = Preferences::GetBool("plugin.disable", false); + // Unload or load plugins as needed + if (mPluginsDisabled) { + UnloadPlugins(); + } else { + LoadPlugins(); + } + } + if (XRE_IsParentProcess() && !strcmp("plugin-blocklist-updated", aTopic)) { + // The blocklist has updated. Asynchronously get blocklist state for all + // items. The promise resolution handler takes care of checking if anything + // changed, and writing an updated state to file, as well as sending data to + // child processes. + nsPluginTag* plugin = mPlugins; + while (plugin) { + UpdatePluginBlocklistState(plugin); + plugin = plugin->mNext; + } + } + return NS_OK; +} + +nsresult nsPluginHost::ParsePostBufferToFixHeaders(const char* inPostData, + uint32_t inPostDataLen, + char** outPostData, + uint32_t* outPostDataLen) { + if (!inPostData || !outPostData || !outPostDataLen) + return NS_ERROR_NULL_POINTER; + + *outPostData = 0; + *outPostDataLen = 0; + + const char CR = '\r'; + const char LF = '\n'; + const char CRLFCRLF[] = {CR, LF, CR, LF, '\0'}; // C string"\r\n\r\n" + const char ContentLenHeader[] = "Content-length"; + + AutoTArray<const char*, 8> singleLF; + const char* pSCntlh = + 0; // pointer to start of ContentLenHeader in inPostData + const char* pSod = 0; // pointer to start of data in inPostData + const char* pEoh = 0; // pointer to end of headers in inPostData + const char* pEod = + inPostData + inPostDataLen; // pointer to end of inPostData + if (*inPostData == LF) { + // If no custom headers are required, simply add a blank + // line ('\n') to the beginning of the file or buffer. + // so *inPostData == '\n' is valid + pSod = inPostData + 1; + } else { + const char* s = inPostData; // tmp pointer to sourse inPostData + while (s < pEod) { + if (!pSCntlh && (*s == 'C' || *s == 'c') && + (s + sizeof(ContentLenHeader) - 1 < pEod) && + (!PL_strncasecmp(s, ContentLenHeader, + sizeof(ContentLenHeader) - 1))) { + // lets assume this is ContentLenHeader for now + const char* p = pSCntlh = s; + p += sizeof(ContentLenHeader) - 1; + // search for first CR or LF == end of ContentLenHeader + for (; p < pEod; p++) { + if (*p == CR || *p == LF) { + // got delimiter, + // one more check; if previous char is a digit + // most likely pSCntlh points to the start of ContentLenHeader + if (*(p - 1) >= '0' && *(p - 1) <= '9') { + s = p; + } + break; // for loop + } + } + if (pSCntlh == s) { // curret ptr is the same + pSCntlh = 0; // that was not ContentLenHeader + break; // there is nothing to parse, break *WHILE LOOP* here + } + } + + if (*s == CR) { + if (pSCntlh && // only if ContentLenHeader is found we are looking for + // end of headers + ((s + sizeof(CRLFCRLF) - 1) <= pEod) && + !memcmp(s, CRLFCRLF, sizeof(CRLFCRLF) - 1)) { + s += sizeof(CRLFCRLF) - 1; + pEoh = pSod = s; // data stars here + break; + } + } else if (*s == LF) { + if (*(s - 1) != CR) { + singleLF.AppendElement(s); + } + if (pSCntlh && (s + 1 < pEod) && (*(s + 1) == LF)) { + s++; + singleLF.AppendElement(s); + s++; + pEoh = pSod = s; // data stars here + break; + } + } + s++; + } + } + + // deal with output buffer + if (!pSod) { // lets assume whole buffer is a data + pSod = inPostData; + } + + uint32_t newBufferLen = 0; + uint32_t dataLen = pEod - pSod; + uint32_t headersLen = pEoh ? pSod - inPostData : 0; + + char* p; // tmp ptr into new output buf + if (headersLen) { // we got a headers + // this function does not make any assumption on correctness + // of ContentLenHeader value in this case. + + newBufferLen = dataLen + headersLen; + // in case there were single LFs in headers + // reserve an extra space for CR will be added before each single LF + int cntSingleLF = singleLF.Length(); + newBufferLen += cntSingleLF; + + *outPostData = p = (char*)moz_xmalloc(newBufferLen); + + // deal with single LF + const char* s = inPostData; + if (cntSingleLF) { + for (int i = 0; i < cntSingleLF; i++) { + const char* plf = singleLF.ElementAt(i); // ptr to single LF in headers + int n = plf - s; // bytes to copy + if (n) { // for '\n\n' there is nothing to memcpy + memcpy(p, s, n); + p += n; + } + *p++ = CR; + s = plf; + *p++ = *s++; + } + } + // are we done with headers? + headersLen = pEoh - s; + if (headersLen) { // not yet + memcpy(p, s, headersLen); // copy the rest + p += headersLen; + } + } else if (dataLen) { // no ContentLenHeader is found but there is a data + // make new output buffer big enough + // to keep ContentLenHeader+value followed by data + uint32_t l = sizeof(ContentLenHeader) + sizeof(CRLFCRLF) + 32; + newBufferLen = dataLen + l; + *outPostData = p = (char*)moz_xmalloc(newBufferLen); + headersLen = + snprintf(p, l, "%s: %u%s", ContentLenHeader, dataLen, CRLFCRLF); + if (headersLen == + l) { // if snprintf has ate all extra space consider this as an error + free(p); + *outPostData = 0; + return NS_ERROR_FAILURE; + } + p += headersLen; + newBufferLen = headersLen + dataLen; + } + // at this point we've done with headers. + // there is a possibility that input buffer has only headers info in it + // which already parsed and copied into output buffer. + // copy the data + if (dataLen) { + memcpy(p, pSod, dataLen); + } + + *outPostDataLen = newBufferLen; + + return NS_OK; +} + +nsresult nsPluginHost::NewPluginNativeWindow( + nsPluginNativeWindow** aPluginNativeWindow) { + return PLUG_NewPluginNativeWindow(aPluginNativeWindow); +} + +nsresult nsPluginHost::GetPluginName(nsNPAPIPluginInstance* aPluginInstance, + const char** aPluginName) { + nsNPAPIPluginInstance* instance = + static_cast<nsNPAPIPluginInstance*>(aPluginInstance); + if (!instance) return NS_ERROR_FAILURE; + + nsNPAPIPlugin* plugin = instance->GetPlugin(); + if (!plugin) return NS_ERROR_FAILURE; + + *aPluginName = TagForPlugin(plugin)->Name().get(); + + return NS_OK; +} + +nsresult nsPluginHost::GetPluginTagForInstance( + nsNPAPIPluginInstance* aPluginInstance, nsIPluginTag** aPluginTag) { + NS_ENSURE_ARG_POINTER(aPluginInstance); + NS_ENSURE_ARG_POINTER(aPluginTag); + + nsNPAPIPlugin* plugin = aPluginInstance->GetPlugin(); + if (!plugin) return NS_ERROR_FAILURE; + + *aPluginTag = TagForPlugin(plugin); + + NS_ADDREF(*aPluginTag); + return NS_OK; +} + +NS_IMETHODIMP nsPluginHost::Notify(nsITimer* timer) { + RefPtr<nsPluginTag> pluginTag = mPlugins; + while (pluginTag) { + if (pluginTag->mUnloadTimer == timer) { + if (!IsRunningPlugin(pluginTag)) { + pluginTag->TryUnloadPlugin(false); + } + return NS_OK; + } + pluginTag = pluginTag->mNext; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsPluginHost::GetName(nsACString& aName) { + aName.AssignLiteral("nsPluginHost"); + return NS_OK; +} + +#ifdef XP_WIN +// Re-enable any top level browser windows that were disabled by modal dialogs +// displayed by the crashed plugin. +static void CheckForDisabledWindows() { + nsCOMPtr<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!wm) return; + + nsCOMPtr<nsISimpleEnumerator> windowList; + wm->GetAppWindowEnumerator(nullptr, getter_AddRefs(windowList)); + if (!windowList) return; + + bool haveWindows; + do { + windowList->HasMoreElements(&haveWindows); + if (!haveWindows) return; + + nsCOMPtr<nsISupports> supportsWindow; + windowList->GetNext(getter_AddRefs(supportsWindow)); + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(supportsWindow)); + if (baseWin) { + nsCOMPtr<nsIWidget> widget; + baseWin->GetMainWidget(getter_AddRefs(widget)); + if (widget && !widget->GetParent() && widget->IsVisible() && + !widget->IsEnabled()) { + nsIWidget* child = widget->GetFirstChild(); + bool enable = true; + while (child) { + if (child->WindowType() == eWindowType_dialog) { + enable = false; + break; + } + child = child->GetNextSibling(); + } + if (enable) { + widget->Enable(true); + } + } + } + } while (haveWindows); +} +#endif + +void nsPluginHost::PluginCrashed(nsNPAPIPlugin* aPlugin, + const nsAString& aPluginDumpID, + const nsACString& aAdditionalMinidumps) { + nsPluginTag* crashedPluginTag = TagForPlugin(aPlugin); + MOZ_ASSERT(crashedPluginTag); + + // Notify the app's observer that a plugin crashed so it can submit + // a crashreport. + bool submittedCrashReport = false; + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + nsCOMPtr<nsIWritablePropertyBag2> propbag = + do_CreateInstance("@mozilla.org/hash-property-bag;1"); + if (obsService && propbag) { + uint32_t runID = 0; + PluginLibrary* library = aPlugin->GetLibrary(); + + if (!NS_WARN_IF(!library)) { + library->GetRunID(&runID); + } + propbag->SetPropertyAsUint32(u"runID"_ns, runID); + + nsCString pluginName; + crashedPluginTag->GetName(pluginName); + propbag->SetPropertyAsAString(u"pluginName"_ns, + NS_ConvertUTF8toUTF16(pluginName)); + propbag->SetPropertyAsAString(u"pluginDumpID"_ns, aPluginDumpID); + propbag->SetPropertyAsACString(u"additionalMinidumps"_ns, + aAdditionalMinidumps); + propbag->SetPropertyAsBool(u"submittedCrashReport"_ns, + submittedCrashReport); + obsService->NotifyObservers(propbag, "plugin-crashed", nullptr); + // see if an observer submitted a crash report. + propbag->GetPropertyAsBool(u"submittedCrashReport"_ns, + &submittedCrashReport); + } + + // Invalidate each nsPluginInstanceTag for the crashed plugin + + for (uint32_t i = mInstances.Length(); i > 0; i--) { + nsNPAPIPluginInstance* instance = mInstances[i - 1]; + if (instance->GetPlugin() == aPlugin) { + // notify the content node (nsIObjectLoadingContent) that the + // plugin has crashed + RefPtr<dom::Element> domElement; + instance->GetDOMElement(getter_AddRefs(domElement)); + nsCOMPtr<nsIObjectLoadingContent> objectContent( + do_QueryInterface(domElement)); + if (objectContent) { + objectContent->PluginCrashed(crashedPluginTag, aPluginDumpID, + submittedCrashReport); + } + + instance->Destroy(); + mInstances.RemoveElement(instance); + OnPluginInstanceDestroyed(crashedPluginTag); + } + } + + // Only after all instances have been invalidated is it safe to null + // out nsPluginTag.mPlugin. The next time we try to create an + // instance of this plugin we reload it (launch a new plugin process). + + crashedPluginTag->mPlugin = nullptr; + crashedPluginTag->mContentProcessRunningCount = 0; + +#ifdef XP_WIN + CheckForDisabledWindows(); +#endif +} + +nsNPAPIPluginInstance* nsPluginHost::FindInstance(const char* mimetype) { + for (uint32_t i = 0; i < mInstances.Length(); i++) { + nsNPAPIPluginInstance* instance = mInstances[i]; + + const char* mt; + nsresult rv = instance->GetMIMEType(&mt); + if (NS_FAILED(rv)) continue; + + if (PL_strcasecmp(mt, mimetype) == 0) return instance; + } + + return nullptr; +} + +nsNPAPIPluginInstance* nsPluginHost::FindOldestStoppedInstance() { + nsNPAPIPluginInstance* oldestInstance = nullptr; + TimeStamp oldestTime = TimeStamp::Now(); + for (uint32_t i = 0; i < mInstances.Length(); i++) { + nsNPAPIPluginInstance* instance = mInstances[i]; + if (instance->IsRunning()) continue; + + TimeStamp time = instance->StopTime(); + if (time < oldestTime) { + oldestTime = time; + oldestInstance = instance; + } + } + + return oldestInstance; +} + +uint32_t nsPluginHost::StoppedInstanceCount() { + uint32_t stoppedCount = 0; + for (uint32_t i = 0; i < mInstances.Length(); i++) { + nsNPAPIPluginInstance* instance = mInstances[i]; + if (!instance->IsRunning()) stoppedCount++; + } + return stoppedCount; +} + +nsTArray<RefPtr<nsNPAPIPluginInstance>>* nsPluginHost::InstanceArray() { + return &mInstances; +} + +void nsPluginHost::DestroyRunningInstances(nsPluginTag* aPluginTag) { + for (int32_t i = mInstances.Length(); i > 0; i--) { + nsNPAPIPluginInstance* instance = mInstances[i - 1]; + if (instance->IsRunning() && + (!aPluginTag || aPluginTag == TagForPlugin(instance->GetPlugin()))) { + instance->SetWindow(nullptr); + instance->Stop(); + + // Get rid of all the instances without the possibility of caching. + nsPluginTag* pluginTag = TagForPlugin(instance->GetPlugin()); + instance->SetWindow(nullptr); + + RefPtr<dom::Element> domElement; + instance->GetDOMElement(getter_AddRefs(domElement)); + nsCOMPtr<nsIObjectLoadingContent> objectContent = + do_QueryInterface(domElement); + + instance->Destroy(); + + mInstances.RemoveElement(instance); + OnPluginInstanceDestroyed(pluginTag); + + // Notify owning content that we destroyed its plugin out from under it + if (objectContent) { + objectContent->PluginDestroyed(); + } + } + } +} + +/* static */ +bool nsPluginHost::CanUsePluginForMIMEType(const nsACString& aMIMEType) { + // We only support flash as a plugin, so if the mime types don't match for + // those, exit before we start loading plugins. + // + // XXX: Remove test/java cases when bug 1351885 lands. + if (nsPluginHost::GetSpecialType(aMIMEType) == + nsPluginHost::eSpecialType_Flash || + MimeTypeIsAllowedForFakePlugin(NS_ConvertUTF8toUTF16(aMIMEType)) || + aMIMEType.LowerCaseEqualsLiteral("application/x-test")) { + return true; + } + + return false; +} + +// Runnable that does an async destroy of a plugin. + +class nsPluginDestroyRunnable + : public Runnable, + public mozilla::LinkedListElement<nsPluginDestroyRunnable> { + public: + explicit nsPluginDestroyRunnable(nsNPAPIPluginInstance* aInstance) + : Runnable("nsPluginDestroyRunnable"), mInstance(aInstance) { + sRunnableList.insertBack(this); + } + + ~nsPluginDestroyRunnable() override { this->remove(); } + + NS_IMETHOD Run() override { + RefPtr<nsNPAPIPluginInstance> instance; + + // Null out mInstance to make sure this code in another runnable + // will do the right thing even if someone was holding on to this + // runnable longer than we expect. + instance.swap(mInstance); + + if (PluginDestructionGuard::DelayDestroy(instance)) { + // It's still not safe to destroy the plugin, it's now up to the + // outermost guard on the stack to take care of the destruction. + return NS_OK; + } + + for (auto r : sRunnableList) { + if (r != this && r->mInstance == instance) { + // There's another runnable scheduled to tear down + // instance. Let it do the job. + return NS_OK; + } + } + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("Doing delayed destroy of instance %p\n", instance.get())); + + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + if (host) host->StopPluginInstance(instance); + + PLUGIN_LOG(PLUGIN_LOG_NORMAL, + ("Done with delayed destroy of instance %p\n", instance.get())); + + return NS_OK; + } + + protected: + RefPtr<nsNPAPIPluginInstance> mInstance; + + static mozilla::LinkedList<nsPluginDestroyRunnable> sRunnableList; +}; + +mozilla::LinkedList<nsPluginDestroyRunnable> + nsPluginDestroyRunnable::sRunnableList; + +mozilla::LinkedList<PluginDestructionGuard> PluginDestructionGuard::sList; + +PluginDestructionGuard::PluginDestructionGuard(nsNPAPIPluginInstance* aInstance) + : mInstance(aInstance) { + Init(); +} + +PluginDestructionGuard::PluginDestructionGuard(NPP npp) + : mInstance(npp ? static_cast<nsNPAPIPluginInstance*>(npp->ndata) + : nullptr) { + Init(); +} + +PluginDestructionGuard::~PluginDestructionGuard() { + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); + + this->remove(); + + if (mDelayedDestroy) { + // We've attempted to destroy the plugin instance we're holding on + // to while we were guarding it. Do the actual destroy now, off of + // a runnable. + RefPtr<nsPluginDestroyRunnable> evt = + new nsPluginDestroyRunnable(mInstance); + + NS_DispatchToMainThread(evt); + } +} + +// static +bool PluginDestructionGuard::DelayDestroy(nsNPAPIPluginInstance* aInstance) { + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); + NS_ASSERTION(aInstance, "Uh, I need an instance!"); + + // Find the first guard on the stack and make it do a delayed + // destroy upon destruction. + + for (auto g : sList) { + if (g->mInstance == aInstance) { + g->mDelayedDestroy = true; + + return true; + } + } + + return false; +} diff --git a/dom/plugins/base/nsPluginHost.h b/dom/plugins/base/nsPluginHost.h new file mode 100644 index 0000000000..e5b98b5705 --- /dev/null +++ b/dom/plugins/base/nsPluginHost.h @@ -0,0 +1,391 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPluginHost_h_ +#define nsPluginHost_h_ + +#include "mozilla/LinkedList.h" +#include "mozilla/StaticPtr.h" + +#include "nsIPluginHost.h" +#include "nsIObserver.h" +#include "nsCOMPtr.h" +#include "prlink.h" +#include "nsIPluginTag.h" +#include "nsPluginsDir.h" +#include "nsWeakReference.h" +#include "MainThreadUtils.h" +#include "nsTArray.h" +#include "nsINamed.h" +#include "nsTObserverArray.h" +#include "nsITimer.h" +#include "nsPluginTags.h" +#include "nsIEffectiveTLDService.h" +#include "nsIIDNService.h" +#include "nsCRT.h" +#include "mozilla/dom/PromiseNativeHandler.h" +#include "mozilla/plugins/PluginTypes.h" + +#ifdef XP_WIN +# include <minwindef.h> +# include "nsIWindowsRegKey.h" +#endif + +namespace mozilla { +namespace plugins { +class BlocklistPromiseHandler; +} // namespace plugins +namespace dom { +class ContentParent; +} // namespace dom +} // namespace mozilla + +class nsNPAPIPlugin; +class nsIFile; +class nsIChannel; +class nsPluginNativeWindow; +class nsObjectLoadingContent; +class nsPluginInstanceOwner; +class nsPluginUnloadRunnable; +class nsNPAPIPluginInstance; +class nsNPAPIPluginStreamListener; +class nsIPluginInstanceOwner; +class nsIInputStream; +class nsIStreamListener; +#ifndef npapi_h_ +struct _NPP; +typedef _NPP* NPP; +#endif + +class nsPluginHost final : public nsIPluginHost, + public nsIObserver, + public nsITimerCallback, + public nsSupportsWeakReference, + public nsINamed { + friend class nsPluginTag; + friend class nsFakePluginTag; + virtual ~nsPluginHost(); + + public: + nsPluginHost(); + + static already_AddRefed<nsPluginHost> GetInst(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPLUGINHOST + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + // Acts like a bitfield + enum PluginFilter { + eExcludeNone = nsIPluginHost::EXCLUDE_NONE, + eExcludeDisabled = nsIPluginHost::EXCLUDE_DISABLED, + eExcludeFake = nsIPluginHost::EXCLUDE_FAKE + }; + // FIXME-jsplugins comment about fake + bool HavePluginForType(const nsACString& aMimeType, + PluginFilter aFilter = eExcludeDisabled); + + // FIXME-jsplugins what if fake has different extensions + bool HavePluginForExtension(const nsACString& aExtension, + /* out */ nsACString& aMimeType, + PluginFilter aFilter = eExcludeDisabled); + + void GetPlugins(nsTArray<nsCOMPtr<nsIInternalPluginTag>>& aPluginArray, + bool aIncludeDisabled = false); + + nsresult GetURL(nsISupports* pluginInst, const char* url, const char* target, + nsNPAPIPluginStreamListener* streamListener, + const char* altHost, const char* referrer, + bool forceJSEnabled); + nsresult PostURL(nsISupports* pluginInst, const char* url, + uint32_t postDataLen, const char* postData, + const char* target, + nsNPAPIPluginStreamListener* streamListener, + const char* altHost, const char* referrer, + bool forceJSEnabled, uint32_t postHeadersLength, + const char* postHeaders); + + nsresult UserAgent(const char** retstring); + nsresult ParsePostBufferToFixHeaders(const char* inPostData, + uint32_t inPostDataLen, + char** outPostData, + uint32_t* outPostDataLen); + nsresult NewPluginNativeWindow(nsPluginNativeWindow** aPluginNativeWindow); + + void AddIdleTimeTarget(nsIPluginInstanceOwner* objectFrame, bool isVisible); + void RemoveIdleTimeTarget(nsIPluginInstanceOwner* objectFrame); + + nsresult GetPluginName(nsNPAPIPluginInstance* aPluginInstance, + const char** aPluginName); + nsresult StopPluginInstance(nsNPAPIPluginInstance* aInstance); + nsresult GetPluginTagForInstance(nsNPAPIPluginInstance* aPluginInstance, + nsIPluginTag** aPluginTag); + + nsresult NewPluginURLStream(const nsString& aURL, + nsNPAPIPluginInstance* aInstance, + nsNPAPIPluginStreamListener* aListener, + nsIInputStream* aPostStream = nullptr, + const char* aHeadersData = nullptr, + uint32_t aHeadersDataLen = 0); + + nsresult GetURLWithHeaders( + nsNPAPIPluginInstance* pluginInst, const char* url, + const char* target = nullptr, + nsNPAPIPluginStreamListener* streamListener = nullptr, + const char* altHost = nullptr, const char* referrer = nullptr, + bool forceJSEnabled = false, uint32_t getHeadersLength = 0, + const char* getHeaders = nullptr); + + nsresult AddHeadersToChannel(const char* aHeadersData, + uint32_t aHeadersDataLen, + nsIChannel* aGenericChannel); + + // Helper that checks if a type is whitelisted in plugin.allowed_types. + // Always returns true if plugin.allowed_types is not set + static bool IsTypeWhitelisted(const char* aType); + + /** + * Returns true if a plugin can be used to load the requested MIME type. Used + * for short circuiting before sending things to plugin code. + */ + static bool CanUsePluginForMIMEType(const nsACString& aMIMEType); + + // checks whether aType is a type we recognize for potential special handling + enum SpecialType { + eSpecialType_None, + // Needed to whitelist for async init support + eSpecialType_Test, + // Informs some decisions about OOP and quirks + eSpecialType_Flash + }; + static SpecialType GetSpecialType(const nsACString& aMIMEType); + + static nsresult PostPluginUnloadEvent(PRLibrary* aLibrary); + + void PluginCrashed(nsNPAPIPlugin* aPlugin, const nsAString& aPluginDumpID, + const nsACString& aAdditionalMinidumps); + + nsNPAPIPluginInstance* FindInstance(const char* mimetype); + nsNPAPIPluginInstance* FindOldestStoppedInstance(); + uint32_t StoppedInstanceCount(); + + nsTArray<RefPtr<nsNPAPIPluginInstance>>* InstanceArray(); + + // Return the tag for |aLibrary| if found, nullptr if not. + nsPluginTag* FindTagForLibrary(PRLibrary* aLibrary); + + // The last argument should be false if we already have an in-flight stream + // and don't need to set up a new stream. + nsresult InstantiatePluginInstance(const nsACString& aMimeType, nsIURI* aURL, + nsObjectLoadingContent* aContent, + nsPluginInstanceOwner** aOwner); + + // Does not accept nullptr and should never fail. + nsPluginTag* TagForPlugin(nsNPAPIPlugin* aPlugin); + + nsPluginTag* PluginWithId(uint32_t aId); + + nsresult GetPlugin(const nsACString& aMimeType, nsNPAPIPlugin** aPlugin); + nsresult GetPluginForContentProcess(uint32_t aPluginId, + nsNPAPIPlugin** aPlugin); + void NotifyContentModuleDestroyed(uint32_t aPluginId); + + nsresult NewPluginStreamListener(nsIURI* aURL, + nsNPAPIPluginInstance* aInstance, + nsIStreamListener** aStreamListener); + + void CreateWidget(nsPluginInstanceOwner* aOwner); + + nsresult EnumerateSiteData(const nsACString& domain, + const nsTArray<nsCString>& sites, + nsTArray<nsCString>& result, bool firstMatchOnly); + + nsresult UpdateCachedSerializablePluginList(); + nsresult SendPluginsToContent(mozilla::dom::ContentParent* parent); + nsresult SetPluginsInContent( + uint32_t aPluginEpoch, nsTArray<mozilla::plugins::PluginTag>& aPlugins, + nsTArray<mozilla::plugins::FakePluginTag>& aFakePlugins); + + void UpdatePluginBlocklistState(nsPluginTag* aPluginTag, + bool aShouldSoftblock = false); + + private: + nsresult LoadPlugins(); + nsresult UnloadPlugins(); + + nsresult SetUpPluginInstance(const nsACString& aMimeType, nsIURI* aURL, + nsPluginInstanceOwner* aOwner); + + friend class nsPluginUnloadRunnable; + friend class mozilla::plugins::BlocklistPromiseHandler; + + void DestroyRunningInstances(nsPluginTag* aPluginTag); + + // Writes updated plugins settings to disk and unloads the plugin + // if it is now disabled. Should only be called by the plugin tag in question + void UpdatePluginInfo(nsPluginTag* aPluginTag); + + nsresult TrySetUpPluginInstance(const nsACString& aMimeType, nsIURI* aURL, + nsPluginInstanceOwner* aOwner); + + // FIXME-jsplugins comment here about when things may be fake + nsPluginTag* FindPreferredPlugin(const nsTArray<nsPluginTag*>& matches); + + // Find a plugin for the given type. If aIncludeFake is true a fake plugin + // will be preferred if one exists; otherwise a fake plugin will never be + // returned. If aCheckEnabled is false, disabled plugins can be returned. + nsIInternalPluginTag* FindPluginForType(const nsACString& aMimeType, + bool aIncludeFake, + bool aCheckEnabled); + + // Find specifically a fake plugin for the given type. If aCheckEnabled is + // false, disabled plugins can be returned. + nsFakePluginTag* FindFakePluginForType(const nsACString& aMimeType, + bool aCheckEnabled); + + // Find specifically a fake plugin for the given extension. If aCheckEnabled + // is false, disabled plugins can be returned. aMimeType will be filled in + // with the MIME type the plugin is registered for. + nsFakePluginTag* FindFakePluginForExtension(const nsACString& aExtension, + /* out */ nsACString& aMimeType, + bool aCheckEnabled); + + // Find specifically a native (NPAPI) plugin for the given type. If + // aCheckEnabled is false, disabled plugins can be returned. + nsPluginTag* FindNativePluginForType(const nsACString& aMimeType, + bool aCheckEnabled); + + // Find specifically a native (NPAPI) plugin for the given extension. If + // aCheckEnabled is false, disabled plugins can be returned. aMimeType will + // be filled in with the MIME type the plugin is registered for. + nsPluginTag* FindNativePluginForExtension(const nsACString& aExtension, + /* out */ nsACString& aMimeType, + bool aCheckEnabled); + + nsresult FindStoppedPluginForURL(nsIURI* aURL, + nsIPluginInstanceOwner* aOwner); + + nsresult BroadcastPluginsToContent(); + + // FIXME revisit, no ns prefix + // Registers or unregisters the given mime type with the category manager + enum nsRegisterType { + ePluginRegister, + ePluginUnregister, + // Checks if this type should still be registered first + ePluginMaybeUnregister + }; + void RegisterWithCategoryManager(const nsCString& aMimeType, + nsRegisterType aType); + + void AddPluginTag(nsPluginTag* aPluginTag); + + nsresult EnsurePluginLoaded(nsPluginTag* aPluginTag); + + bool IsRunningPlugin(nsPluginTag* aPluginTag); + + // Checks to see if a tag object is in our list of live tags. + bool IsLiveTag(nsIPluginTag* tag); + + // Checks our list of live tags for an equivalent tag. + nsPluginTag* HaveSamePlugin(const nsPluginTag* aPluginTag); + + void OnPluginInstanceDestroyed(nsPluginTag* aPluginTag); + + // To be used by the chrome process whenever the set of plugins changes. + void IncrementChromeEpoch(); + + // To be used by the chrome process; returns the current epoch. + uint32_t ChromeEpoch(); + + // To be used by the content process to get/set the last observed epoch value + // from the chrome process. + uint32_t ChromeEpochForContent(); + void SetChromeEpochForContent(uint32_t aEpoch); + + void UpdateInMemoryPluginInfo(nsPluginTag* aPluginTag); + + void ClearNonRunningPlugins(); + nsresult ActuallyReloadPlugins(); + + void FindingFinished(); + + RefPtr<nsPluginTag> mPlugins; + + nsTArray<RefPtr<nsFakePluginTag>> mFakePlugins; + + AutoTArray<mozilla::plugins::PluginTag, 1> mSerializablePlugins; + nsTArray<mozilla::plugins::FakePluginTag> mSerializableFakePlugins; + + bool mPluginsLoaded; + + // set by pref plugin.override_internal_types + bool mOverrideInternalTypes; + + // set by pref plugin.disable + bool mPluginsDisabled; + + // Any instances in this array will have valid plugin objects via GetPlugin(). + // When removing an instance it might not die - be sure to null out it's + // plugin. + nsTArray<RefPtr<nsNPAPIPluginInstance>> mInstances; + + // An nsIFile for the pluginreg.dat file in the profile. +#ifdef XP_WIN + // In order to reload plugins when they change, we watch the registry via + // this object. + nsCOMPtr<nsIWindowsRegKey> mRegKeyHKLM; + nsCOMPtr<nsIWindowsRegKey> mRegKeyHKCU; +#endif + + nsCOMPtr<nsIEffectiveTLDService> mTLDService; + nsCOMPtr<nsIIDNService> mIDNService; + + // Helpers for ClearSiteData and SiteHasData. + nsresult NormalizeHostname(nsCString& host); + + nsWeakPtr mCurrentDocument; // weak reference, we use it to id document only + + // This epoch increases each time we load the list of plugins from disk. + // In the chrome process, this stores the actual epoch. + // In the content process, this stores the last epoch value observed + // when reading plugins from chrome. + uint32_t mPluginEpoch; + + static nsIFile* sPluginTempDir; + + // We need to hold a global ptr to ourselves because we register for + // two different CIDs for some reason... + static mozilla::StaticRefPtr<nsPluginHost> sInst; +}; + +class PluginDestructionGuard + : public mozilla::LinkedListElement<PluginDestructionGuard> { + public: + explicit PluginDestructionGuard(nsNPAPIPluginInstance* aInstance); + explicit PluginDestructionGuard(NPP npp); + + ~PluginDestructionGuard(); + + static bool DelayDestroy(nsNPAPIPluginInstance* aInstance); + + protected: + void Init() { + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); + + mDelayedDestroy = false; + + sList.insertBack(this); + } + + RefPtr<nsNPAPIPluginInstance> mInstance; + bool mDelayedDestroy; + + static mozilla::LinkedList<PluginDestructionGuard> sList; +}; + +#endif // nsPluginHost_h_ diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp new file mode 100644 index 0000000000..a1a7a2ab25 --- /dev/null +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -0,0 +1,3164 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef MOZ_X11 +# include <cairo-xlib.h> +# include "gfxXlibSurface.h" +/* X headers suck */ +enum { XKeyPress = KeyPress }; +# include "mozilla/X11Util.h" +using mozilla::DefaultXDisplay; +#endif + +#include "nsPluginInstanceOwner.h" + +#include "gfxUtils.h" +#include "nsIRunnable.h" +#include "nsContentUtils.h" +#include "nsRect.h" +#include "nsSize.h" +#include "nsDisplayList.h" +#include "ImageLayers.h" +#include "GLImages.h" +#include "nsPluginFrame.h" +#include "nsIPluginDocument.h" +#include "nsIStringStream.h" +#include "nsNetUtil.h" +#include "mozilla/Preferences.h" +#include "nsLayoutUtils.h" +#include "nsIPluginWidget.h" +#include "nsViewManager.h" +#include "nsIAppShell.h" +#include "nsIObjectLoadingContent.h" +#include "nsObjectLoadingContent.h" +#include "nsAttrName.h" +#include "nsIFocusManager.h" +#include "nsFocusManager.h" +#include "nsIProtocolHandler.h" +#include "nsIScrollableFrame.h" +#include "nsIDocShell.h" +#include "ImageContainer.h" +#include "GLContext.h" +#include "nsIContentInlines.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/PresShell.h" +#include "mozilla/TextEvents.h" +#include "mozilla/dom/DocumentInlines.h" +#include "mozilla/dom/DragEvent.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/HTMLObjectElementBinding.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/WheelEventBinding.h" +#include "nsFrameSelection.h" +#include "PuppetWidget.h" +#include "nsPIWindowRoot.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/TextComposition.h" +#include "mozilla/AutoRestore.h" + +#include "nsContentCID.h" +#include "nsWidgetsCID.h" +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + +#ifdef XP_WIN +# include <wtypes.h> +# include <winuser.h> +# include "mozilla/widget/WinMessages.h" +#endif // #ifdef XP_WIN + +#ifdef MOZ_WIDGET_GTK +# include <gdk/gdk.h> +# include <gtk/gtk.h> +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::layers; + +// special class for handeling DOM context menu events because for +// some reason it starves other mouse events if implemented on the +// same class +class nsPluginDOMContextMenuListener : public nsIDOMEventListener { + virtual ~nsPluginDOMContextMenuListener(); + + public: + explicit nsPluginDOMContextMenuListener(nsIContent* aContent); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + void Destroy(nsIContent* aContent); + + nsEventStatus ProcessEvent(const WidgetGUIEvent& anEvent) { + return nsEventStatus_eConsumeNoDefault; + } +}; + +class AsyncPaintWaitEvent : public Runnable { + public: + AsyncPaintWaitEvent(nsIContent* aContent, bool aFinished) + : Runnable("AsyncPaintWaitEvent"), + mContent(aContent), + mFinished(aFinished) {} + + NS_IMETHOD Run() override { + nsContentUtils::DispatchEventOnlyToChrome( + mContent->OwnerDoc(), mContent, + mFinished ? u"MozPaintWaitFinished"_ns : u"MozPaintWait"_ns, + CanBubble::eYes, Cancelable::eYes); + return NS_OK; + } + + private: + nsCOMPtr<nsIContent> mContent; + bool mFinished; +}; + +void nsPluginInstanceOwner::NotifyPaintWaiter(nsDisplayListBuilder* aBuilder) { + // This is notification for reftests about async plugin paint start + if (!mWaitingForPaint && !IsUpToDate() && + aBuilder->ShouldSyncDecodeImages()) { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + nsCOMPtr<nsIRunnable> event = new AsyncPaintWaitEvent(content, false); + // Run this event as soon as it's safe to do so, since listeners need to + // receive it immediately + nsContentUtils::AddScriptRunner(event); + mWaitingForPaint = true; + } +} + +bool nsPluginInstanceOwner::NeedsScrollImageLayer() { +#if defined(XP_WIN) + // If this is a windowed plugin and we're doing layout in the content + // process, force the creation of an image layer for the plugin. We'll + // paint to this when scrolling. + return XRE_IsContentProcess() && mPluginWindow && + mPluginWindow->type == NPWindowTypeWindow; +#else + return false; +#endif +} + +already_AddRefed<ImageContainer> nsPluginInstanceOwner::GetImageContainer() { + if (!mInstance) return nullptr; + + RefPtr<ImageContainer> container; + + if (NeedsScrollImageLayer()) { + // windowed plugin under e10s +#if defined(XP_WIN) + mInstance->GetScrollCaptureContainer(getter_AddRefs(container)); +#endif + } else { + // async windowless rendering + mInstance->GetImageContainer(getter_AddRefs(container)); + } + + return container.forget(); +} + +void nsPluginInstanceOwner::DidComposite() { + if (mInstance) { + mInstance->DidComposite(); + } +} + +void nsPluginInstanceOwner::SetBackgroundUnknown() { + if (mInstance) { + mInstance->SetBackgroundUnknown(); + } +} + +already_AddRefed<mozilla::gfx::DrawTarget> +nsPluginInstanceOwner::BeginUpdateBackground(const nsIntRect& aRect) { + nsIntRect rect = aRect; + RefPtr<DrawTarget> dt; + if (mInstance && NS_SUCCEEDED(mInstance->BeginUpdateBackground( + &rect, getter_AddRefs(dt)))) { + return dt.forget(); + } + return nullptr; +} + +void nsPluginInstanceOwner::EndUpdateBackground(const nsIntRect& aRect) { + nsIntRect rect = aRect; + if (mInstance) { + mInstance->EndUpdateBackground(&rect); + } +} + +bool nsPluginInstanceOwner::UseAsyncRendering() { +#ifdef XP_MACOSX + if (mUseAsyncRendering) { + return true; + } +#endif + + bool isOOP; + bool result = + (mInstance && NS_SUCCEEDED(mInstance->GetIsOOP(&isOOP)) && isOOP +#ifndef XP_MACOSX + && (!mPluginWindow || mPluginWindow->type == NPWindowTypeDrawable) +#endif + ); + +#ifdef XP_MACOSX + if (result) { + mUseAsyncRendering = true; + } +#endif + + return result; +} + +nsIntSize nsPluginInstanceOwner::GetCurrentImageSize() { + nsIntSize size(0, 0); + if (mInstance) { + mInstance->GetImageSize(&size); + } + return size; +} + +nsPluginInstanceOwner::nsPluginInstanceOwner() + : mPluginWindow(nullptr), mLastEventloopNestingLevel(0) { + // create nsPluginNativeWindow object, it is derived from NPWindow + // struct and allows to manipulate native window procedure + nsCOMPtr<nsIPluginHost> pluginHostCOM = + do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); + mPluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get()); + if (mPluginHost) mPluginHost->NewPluginNativeWindow(&mPluginWindow); + + mPluginFrame = nullptr; + mWidgetCreationComplete = false; +#ifdef XP_MACOSX + mSentInitialTopLevelWindowEvent = false; + mLastWindowIsActive = false; + mLastContentFocused = false; + mLastScaleFactor = 1.0; + mShouldBlurOnActivate = false; +#endif + mLastCSSZoomFactor = 1.0; + mContentFocused = false; + mWidgetVisible = true; + mPluginWindowVisible = false; + mPluginDocumentActiveState = true; + mLastMouseDownButtonType = -1; + +#ifdef XP_MACOSX +# ifndef NP_NO_CARBON + // We don't support Carbon, but it is still the default model for i386 NPAPI. + mEventModel = NPEventModelCarbon; +# else + mEventModel = NPEventModelCocoa; +# endif + mUseAsyncRendering = false; +#endif + + mWaitingForPaint = false; + +#ifdef XP_WIN + mGotCompositionData = false; + mSentStartComposition = false; + mPluginDidNotHandleIMEComposition = false; + // 3 is the Windows default for these values. + mWheelScrollLines = 3; + mWheelScrollChars = 3; +#endif +} + +nsPluginInstanceOwner::~nsPluginInstanceOwner() { + if (mWaitingForPaint) { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (content) { + // We don't care when the event is dispatched as long as it's "soon", + // since whoever needs it will be waiting for it. + nsCOMPtr<nsIRunnable> event = new AsyncPaintWaitEvent(content, true); + NS_DispatchToMainThread(event); + } + } + + mPluginFrame = nullptr; + + PLUG_DeletePluginNativeWindow(mPluginWindow); + mPluginWindow = nullptr; + + if (mInstance) { + mInstance->SetOwner(nullptr); + } +} + +NS_IMPL_ISUPPORTS(nsPluginInstanceOwner, nsIPluginInstanceOwner, + nsIDOMEventListener, nsIPrivacyTransitionObserver, + nsIKeyEventInPluginCallback, nsISupportsWeakReference) + +nsresult nsPluginInstanceOwner::SetInstance(nsNPAPIPluginInstance* aInstance) { + NS_ASSERTION(!mInstance || !aInstance, + "mInstance should only be set or unset!"); + + // If we're going to null out mInstance after use, be sure to call + // mInstance->SetOwner(nullptr) here, since it now won't be called + // from our destructor. This fixes bug 613376. + if (mInstance && !aInstance) { + mInstance->SetOwner(nullptr); + } + + mInstance = aInstance; + + nsCOMPtr<Document> doc; + GetDocument(getter_AddRefs(doc)); + if (doc) { + if (nsCOMPtr<nsPIDOMWindowOuter> domWindow = doc->GetWindow()) { + nsCOMPtr<nsIDocShell> docShell = domWindow->GetDocShell(); + if (docShell) docShell->AddWeakPrivacyTransitionObserver(this); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetWindow(NPWindow*& aWindow) { + NS_ASSERTION(mPluginWindow, + "the plugin window object being returned is null"); + aWindow = mPluginWindow; + return NS_OK; +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetMode(int32_t* aMode) { + nsCOMPtr<Document> doc; + nsresult rv = GetDocument(getter_AddRefs(doc)); + nsCOMPtr<nsIPluginDocument> pDoc(do_QueryInterface(doc)); + + if (pDoc) { + *aMode = NP_FULL; + } else { + *aMode = NP_EMBED; + } + + return rv; +} + +void nsPluginInstanceOwner::GetAttributes( + nsTArray<MozPluginParameter>& attributes) { + nsCOMPtr<nsIObjectLoadingContent> content = do_QueryReferent(mContent); + nsObjectLoadingContent* loadingContent = + static_cast<nsObjectLoadingContent*>(content.get()); + + loadingContent->GetPluginAttributes(attributes); +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetDOMElement(Element** result) { + return CallQueryReferent(mContent.get(), result); +} + +nsNPAPIPluginInstance* nsPluginInstanceOwner::GetInstance() { + return mInstance; +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetURL( + const char* aURL, const char* aTarget, nsIInputStream* aPostStream, + void* aHeadersData, uint32_t aHeadersDataLen, bool aDoCheckLoadURIChecks) { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (!content) { + return NS_ERROR_NULL_POINTER; + } + + if (content->IsEditable()) { + return NS_OK; + } + + Document* doc = content->GetComposedDoc(); + if (!doc) { + return NS_ERROR_FAILURE; + } + + nsPresContext* presContext = doc->GetPresContext(); + if (!presContext) { + return NS_ERROR_FAILURE; + } + + // the container of the pres context will give us the link handler + nsCOMPtr<nsIDocShell> container = presContext->GetDocShell(); + NS_ENSURE_TRUE(container, NS_ERROR_FAILURE); + + nsAutoString unitarget; + if ((0 == PL_strcmp(aTarget, "newwindow")) || + (0 == PL_strcmp(aTarget, "_new"))) { + unitarget.AssignLiteral("_blank"); + } else if (0 == PL_strcmp(aTarget, "_current")) { + unitarget.AssignLiteral("_self"); + } else { + unitarget.AssignASCII(aTarget); // XXX could this be nonascii? + } + + // Create an absolute URL + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, GetBaseURI()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsCOMPtr<nsIInputStream> headersDataStream; + if (aPostStream && aHeadersData) { + if (!aHeadersDataLen) return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIStringInputStream> sis = + do_CreateInstance("@mozilla.org/io/string-input-stream;1"); + if (!sis) return NS_ERROR_OUT_OF_MEMORY; + + rv = sis->SetData((char*)aHeadersData, aHeadersDataLen); + NS_ENSURE_SUCCESS(rv, rv); + headersDataStream = sis; + } + + int32_t blockPopups = + Preferences::GetInt("privacy.popups.disable_from_plugins"); + AutoPopupStatePusher popupStatePusher( + (PopupBlocker::PopupControlState)blockPopups); + + // if security checks (in particular CheckLoadURIWithPrincipal) needs + // to be skipped we are creating a contentPrincipal from the target URI + // to make sure that security checks succeed. + // Please note that we do not want to fall back to using the + // systemPrincipal, because that would also bypass ContentPolicy checks + // which should still be enforced. + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + if (!aDoCheckLoadURIChecks) { + mozilla::OriginAttributes attrs = + BasePrincipal::Cast(content->NodePrincipal())->OriginAttributesRef(); + triggeringPrincipal = BasePrincipal::CreateContentPrincipal(uri, attrs); + } else { + bool useParentContentPrincipal = false; + nsCOMPtr<nsINetUtil> netUtil = do_GetNetUtil(); + // For protocols loadable by anyone, it doesn't matter what principal + // we use for the security check. However, for external URIs, we check + // whether the browsing context in which they load can be accessed by + // the triggering principal that is doing the loading, to avoid certain + // types of spoofing attacks. In this case, the load would never be + // allowed with the newly minted null principal, when all the plugin is + // trying to do is load a URL in its own browsing context. So we use + // the content principal of the plugin's node in this case. + netUtil->ProtocolHasFlags(uri, + nsIProtocolHandler::URI_LOADABLE_BY_ANYONE | + nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, + &useParentContentPrincipal); + if (useParentContentPrincipal) { + triggeringPrincipal = content->NodePrincipal(); + } else { + triggeringPrincipal = NullPrincipal::CreateWithInheritedAttributes( + content->NodePrincipal()); + } + } + + nsCOMPtr<nsIContentSecurityPolicy> csp = content->GetCsp(); + + rv = nsDocShell::Cast(container)->OnLinkClick( + content, uri, unitarget, VoidString(), aPostStream, headersDataStream, + /* isUserTriggered */ false, /* isTrusted */ true, triggeringPrincipal, + csp); + + return rv; +} + +NS_IMETHODIMP nsPluginInstanceOwner::GetDocument(Document** aDocument) { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (!aDocument || !content) { + return NS_ERROR_NULL_POINTER; + } + + // XXX sXBL/XBL2 issue: current doc or owner doc? + // But keep in mind bug 322414 comment 33 + NS_ADDREF(*aDocument = content->OwnerDoc()); + return NS_OK; +} + +NS_IMETHODIMP nsPluginInstanceOwner::InvalidateRect(NPRect* invalidRect) { + // If our object frame has gone away, we won't be able to determine + // up-to-date-ness, so just fire off the event. + if (mWaitingForPaint && (!mPluginFrame || IsUpToDate())) { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + // We don't care when the event is dispatched as long as it's "soon", + // since whoever needs it will be waiting for it. + nsCOMPtr<nsIRunnable> event = new AsyncPaintWaitEvent(content, true); + NS_DispatchToMainThread(event); + mWaitingForPaint = false; + } + + if (!mPluginFrame || !invalidRect || !mWidgetVisible) return NS_ERROR_FAILURE; + +#if defined(XP_MACOSX) + // Each time an asynchronously-drawing plugin sends a new surface to display, + // the image in the ImageContainer is updated and InvalidateRect is called. + RefPtr<ImageContainer> container; + mInstance->GetImageContainer(getter_AddRefs(container)); +#endif + +#ifndef XP_MACOSX + // Invalidate for windowed plugins needs to work. + if (mWidget) { + mWidget->Invalidate( + LayoutDeviceIntRect(invalidRect->left, invalidRect->top, + invalidRect->right - invalidRect->left, + invalidRect->bottom - invalidRect->top)); + // Plugin instances also call invalidate when plugin windows are hidden + // during scrolling. In this case fall through so we invalidate the + // underlying layer. + if (!NeedsScrollImageLayer()) { + return NS_OK; + } + } +#endif + nsIntRect rect(invalidRect->left, invalidRect->top, + invalidRect->right - invalidRect->left, + invalidRect->bottom - invalidRect->top); + // invalidRect is in "display pixels". In non-HiDPI modes "display pixels" + // are device pixels. But in HiDPI modes each display pixel corresponds + // to more than one device pixel. + double scaleFactor = 1.0; + GetContentsScaleFactor(&scaleFactor); + rect.ScaleRoundOut(scaleFactor); + mPluginFrame->InvalidateLayer(DisplayItemType::TYPE_PLUGIN, &rect); + return NS_OK; +} + +NS_IMETHODIMP nsPluginInstanceOwner::InvalidateRegion(NPRegion invalidRegion) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPluginInstanceOwner::RedrawPlugin() { + if (mPluginFrame) { + mPluginFrame->InvalidateLayer(DisplayItemType::TYPE_PLUGIN); + } + return NS_OK; +} + +#if defined(XP_WIN) +nsIWidget* nsPluginInstanceOwner::GetContainingWidgetIfOffset() { + MOZ_ASSERT(mPluginFrame, "Caller should have checked for null mPluginFrame."); + + // This property is provided to allow a "windowless" plugin to determine the + // window it is drawing in, so it can translate mouse coordinates it receives + // directly from the operating system to coordinates relative to itself. + + // The original code returns the document's window, which is OK if the window + // the "windowless" plugin is drawing into has the same origin as the + // document's window, but this is not the case for "windowless" plugins inside + // of scrolling DIVs etc + + // To make sure "windowless" plugins always get the right origin for + // translating mouse coordinates, this code determines the window handle of + // the mozilla window containing the "windowless" plugin. + + // Given that this HWND may not be that of the document's window, there is a + // slight risk of confusing a plugin that is using this HWND for illicit + // purposes, but since the documentation does not suggest this HWND IS that of + // the document window, rather that of the window the plugin is drawn in, this + // seems like a safe fix. + + // we only attempt to get the nearest window if this really is a "windowless" + // plugin so as not to change any behaviour for the much more common windowed + // plugins, though why this method would even be being called for a windowed + // plugin escapes me. + if (!XRE_IsContentProcess() && mPluginWindow && + mPluginWindow->type == NPWindowTypeDrawable) { + // it turns out that flash also uses this window for determining focus, and + // is currently unable to show a caret correctly if we return the enclosing + // window. Therefore for now we only return the enclosing window when there + // is an actual offset which would otherwise cause coordinates to be offset + // incorrectly. (i.e. if the enclosing window if offset from the document + // window) + // + // fixing both the caret and ability to interact issues for a windowless + // control in a non document aligned windw does not seem to be possible + // without a change to the flash plugin + + nsIWidget* win = mPluginFrame->GetNearestWidget(); + if (win) { + nsView* view = nsView::GetViewFor(win); + NS_ASSERTION(view, "No view for widget"); + nsPoint offset = view->GetOffsetTo(nullptr); + + if (offset.x || offset.y) { + // in the case the two windows are offset from eachother, we do go ahead + // and return the correct enclosing window so that mouse co-ordinates + // are not messed up. + return win; + } + } + } + + return nullptr; +} + +static already_AddRefed<nsIWidget> GetRootWidgetForPluginFrame( + const nsPluginFrame* aPluginFrame) { + MOZ_ASSERT(aPluginFrame); + + nsViewManager* vm = + aPluginFrame->PresContext()->GetPresShell()->GetViewManager(); + if (!vm) { + NS_WARNING("Could not find view manager for plugin frame."); + return nullptr; + } + + nsCOMPtr<nsIWidget> rootWidget; + vm->GetRootWidget(getter_AddRefs(rootWidget)); + return rootWidget.forget(); +} +#endif + +NS_IMETHODIMP nsPluginInstanceOwner::GetNetscapeWindow(void* value) { + if (!mPluginFrame) { + NS_WARNING("plugin owner has no owner in getting doc's window handle"); + return NS_ERROR_FAILURE; + } + +#if defined(XP_WIN) + void** pvalue = (void**)value; + nsIWidget* offsetContainingWidget = GetContainingWidgetIfOffset(); + if (offsetContainingWidget) { + *pvalue = (void*)offsetContainingWidget->GetNativeData(NS_NATIVE_WINDOW); + if (*pvalue) { + return NS_OK; + } + } + + // simply return the topmost document window + nsCOMPtr<nsIWidget> widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (widget) { + *pvalue = widget->GetNativeData(NS_NATIVE_SHAREABLE_WINDOW); + } else { + NS_ASSERTION(widget, + "couldn't get doc's widget in getting doc's window handle"); + } + + return NS_OK; +#elif defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) + // X11 window managers want the toplevel window for WM_TRANSIENT_FOR. + nsIWidget* win = mPluginFrame->GetNearestWidget(); + if (!win) return NS_ERROR_FAILURE; + *static_cast<Window*>(value) = + (long unsigned int)win->GetNativeData(NS_NATIVE_SHAREABLE_WINDOW); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#if defined(XP_WIN) +void nsPluginInstanceOwner::SetWidgetWindowAsParent(HWND aWindowToAdopt) { + if (!mWidget) { + NS_ERROR("mWidget should exist before this gets called."); + return; + } + + mWidget->SetNativeData(NS_NATIVE_CHILD_WINDOW, + reinterpret_cast<uintptr_t>(aWindowToAdopt)); +} + +nsresult nsPluginInstanceOwner::SetNetscapeWindowAsParent(HWND aWindowToAdopt) { + if (!mPluginFrame) { + NS_WARNING("Plugin owner has no plugin frame."); + return NS_ERROR_FAILURE; + } + + // If there is a containing window that is offset then ask that to adopt. + nsIWidget* offsetWidget = GetContainingWidgetIfOffset(); + if (offsetWidget) { + offsetWidget->SetNativeData(NS_NATIVE_CHILD_WINDOW, + reinterpret_cast<uintptr_t>(aWindowToAdopt)); + return NS_OK; + } + + // Otherwise ask the topmost document window to adopt. + nsCOMPtr<nsIWidget> rootWidget = GetRootWidgetForPluginFrame(mPluginFrame); + if (!rootWidget) { + NS_ASSERTION(rootWidget, "Couldn't get topmost document's widget."); + return NS_ERROR_FAILURE; + } + + rootWidget->SetNativeData(NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW, + reinterpret_cast<uintptr_t>(aWindowToAdopt)); + return NS_OK; +} + +bool nsPluginInstanceOwner::GetCompositionString(uint32_t aType, + nsTArray<uint8_t>* aDist, + int32_t* aLength) { + // Mark pkugin calls ImmGetCompositionStringW correctly + mGotCompositionData = true; + + RefPtr<TextComposition> composition = GetTextComposition(); + if (NS_WARN_IF(!composition)) { + return false; + } + + switch (aType) { + case GCS_COMPSTR: { + if (!composition->IsComposing()) { + *aLength = 0; + return true; + } + + uint32_t len = composition->LastData().Length() * sizeof(char16_t); + if (len) { + aDist->SetLength(len); + memcpy(aDist->Elements(), composition->LastData().get(), len); + } + *aLength = len; + return true; + } + + case GCS_RESULTSTR: { + if (composition->IsComposing()) { + *aLength = 0; + return true; + } + + uint32_t len = composition->LastData().Length() * sizeof(char16_t); + if (len) { + aDist->SetLength(len); + memcpy(aDist->Elements(), composition->LastData().get(), len); + } + *aLength = len; + return true; + } + + case GCS_CURSORPOS: { + *aLength = 0; + TextRangeArray* ranges = composition->GetLastRanges(); + if (!ranges) { + return true; + } + *aLength = ranges->GetCaretPosition(); + if (*aLength < 0) { + return false; + } + return true; + } + + case GCS_COMPATTR: { + TextRangeArray* ranges = composition->GetLastRanges(); + if (!ranges || ranges->IsEmpty()) { + *aLength = 0; + return true; + } + + aDist->SetLength(composition->LastData().Length()); + memset(aDist->Elements(), ATTR_INPUT, aDist->Length()); + + for (TextRange& range : *ranges) { + uint8_t type = ATTR_INPUT; + switch (range.mRangeType) { + case TextRangeType::eRawClause: + type = ATTR_INPUT; + break; + case TextRangeType::eSelectedRawClause: + type = ATTR_TARGET_NOTCONVERTED; + break; + case TextRangeType::eConvertedClause: + type = ATTR_CONVERTED; + break; + case TextRangeType::eSelectedClause: + type = ATTR_TARGET_CONVERTED; + break; + default: + continue; + } + + size_t minLen = std::min<size_t>(range.mEndOffset, aDist->Length()); + for (size_t i = range.mStartOffset; i < minLen; i++) { + (*aDist)[i] = type; + } + } + *aLength = aDist->Length(); + return true; + } + + case GCS_COMPCLAUSE: { + RefPtr<TextRangeArray> ranges = composition->GetLastRanges(); + if (!ranges || ranges->IsEmpty()) { + aDist->SetLength(sizeof(uint32_t)); + memset(aDist->Elements(), 0, sizeof(uint32_t)); + *aLength = aDist->Length(); + return true; + } + AutoTArray<uint32_t, 16> clauses; + clauses.AppendElement(0); + for (TextRange& range : *ranges) { + if (!range.IsClause()) { + continue; + } + clauses.AppendElement(range.mEndOffset); + } + + aDist->SetLength(clauses.Length() * sizeof(uint32_t)); + memcpy(aDist->Elements(), clauses.Elements(), aDist->Length()); + *aLength = aDist->Length(); + return true; + } + + case GCS_RESULTREADSTR: { + // When returning error causes unexpected error, so we return 0 instead. + *aLength = 0; + return true; + } + + case GCS_RESULTCLAUSE: { + // When returning error causes unexpected error, so we return 0 instead. + *aLength = 0; + return true; + } + + default: + NS_WARNING( + nsPrintfCString( + "Unsupported type %x of ImmGetCompositionStringW hook", aType) + .get()); + break; + } + + return false; +} + +bool nsPluginInstanceOwner::RequestCommitOrCancel(bool aCommitted) { + nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset(); + if (!widget) { + widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (NS_WARN_IF(!widget)) { + return false; + } + } + + // Retrieve TextComposition for the widget with IMEStateManager instead of + // using GetTextComposition() because we cannot know whether the method + // failed due to no widget or no composition. + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(widget); + if (!composition) { + // If there is composition, we should just ignore this request since + // the composition may have been committed after the plugin process + // sent this request. + return true; + } + + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (content != composition->GetEventTargetNode()) { + // If the composition is handled in different node, that means that + // the composition for the plugin has gone and new composition has + // already started. So, request from the plugin should be ignored + // since user inputs different text now. + return true; + } + + // If active composition is being handled in the plugin, let's request to + // commit/cancel the composition via both IMEStateManager and TextComposition + // for avoid breaking the status management of composition. I.e., don't + // call nsIWidget::NotifyIME() directly from here. + IMEStateManager::NotifyIME(aCommitted ? widget::REQUEST_TO_COMMIT_COMPOSITION + : widget::REQUEST_TO_CANCEL_COMPOSITION, + widget, composition->GetBrowserParent()); + // FYI: This instance may have been destroyed. Be careful if you need to + // access members of this class. + return true; +} + +#endif // #ifdef XP_WIN + +void nsPluginInstanceOwner::HandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, bool aIsConsumed) { + if (NS_WARN_IF(!mInstance)) { + return; + } + DebugOnly<nsresult> rv = + mInstance->HandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HandledWindowedPluginKeyEvent fail"); +} + +void nsPluginInstanceOwner::OnWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData) { + if (NS_WARN_IF(!mPluginFrame)) { + // Notifies the plugin process of the key event being not consumed by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return; + } + + nsCOMPtr<nsIWidget> widget = mPluginFrame->PresContext()->GetRootWidget(); + if (NS_WARN_IF(!widget)) { + // Notifies the plugin process of the key event being not consumed by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return; + } + + nsresult rv = widget->OnWindowedPluginKeyEvent(aKeyEventData, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Notifies the plugin process of the key event being not consumed by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return; + } + + // If the key event is posted to another process, we need to wait a call + // of HandledWindowedPluginKeyEvent(). So, nothing to do here in this case. + if (rv == NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY) { + return; + } + + // Otherwise, the key event is handled synchronously. Let's notify the + // plugin process of the key event's result. + bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED); + HandledWindowedPluginKeyEvent(aKeyEventData, consumed); +} + +NS_IMETHODIMP nsPluginInstanceOwner::SetEventModel(int32_t eventModel) { +#ifdef XP_MACOSX + mEventModel = static_cast<NPEventModel>(eventModel); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#ifdef XP_MACOSX +NPBool nsPluginInstanceOwner::ConvertPointPuppet(PuppetWidget* widget, + nsPluginFrame* pluginFrame, + double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, + double* destX, double* destY, + NPCoordinateSpace destSpace) { + NS_ENSURE_TRUE(widget && widget->GetOwningBrowserChild() && pluginFrame, + false); + // Caller has to want a result. + NS_ENSURE_TRUE(destX || destY, false); + + if (sourceSpace == destSpace) { + if (destX) { + *destX = sourceX; + } + if (destY) { + *destY = sourceY; + } + return true; + } + + nsPresContext* presContext = pluginFrame->PresContext(); + CSSToLayoutDeviceScale scaleFactor( + double(AppUnitsPerCSSPixel()) / + presContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()); + + PuppetWidget* puppetWidget = static_cast<PuppetWidget*>(widget); + PuppetWidget* rootWidget = + static_cast<PuppetWidget*>(widget->GetTopLevelWidget()); + if (!rootWidget) { + return false; + } + CSSIntPoint chromeSize = + CSSIntPoint::Truncate(rootWidget->GetChromeOffset() / scaleFactor); + nsIntSize intScreenDims = rootWidget->GetScreenDimensions(); + CSSIntSize screenDims = CSSIntSize::Truncate( + LayoutDeviceIntSize::FromUnknownSize(intScreenDims) / scaleFactor); + int32_t screenH = screenDims.height; + CSSIntPoint windowPosition = + CSSIntPoint::Truncate(rootWidget->GetWindowPosition() / scaleFactor); + + // Window size is tab size + chrome size. + LayoutDeviceIntRect tabContentBounds = puppetWidget->GetBounds(); + tabContentBounds.ScaleInverseRoundOut(scaleFactor.scale); + int32_t windowH = tabContentBounds.height + int(chromeSize.y); + + CSSIntPoint pluginPosition = pluginFrame->GetScreenRect().TopLeft(); + + // Convert (sourceX, sourceY) to 'real' (not PuppetWidget) screen space. + // In OSX, the Y-axis increases upward, which is the reverse of ours. + // We want OSX coordinates for window and screen so those equations are + // swapped. + CSSIntPoint sourcePoint = CSSIntPoint::Truncate(sourceX, sourceY); + CSSIntPoint screenPoint; + switch (sourceSpace) { + case NPCoordinateSpacePlugin: + screenPoint = sourcePoint + pluginPosition + + CSSIntPoint::Truncate(CSSPoint::FromAppUnits( + pluginFrame->GetContentRectRelativeToSelf().TopLeft())); + break; + case NPCoordinateSpaceWindow: + screenPoint = + CSSIntPoint(sourcePoint.x, windowH - sourcePoint.y) + windowPosition; + break; + case NPCoordinateSpaceFlippedWindow: + screenPoint = sourcePoint + windowPosition; + break; + case NPCoordinateSpaceScreen: + screenPoint = CSSIntPoint(sourcePoint.x, screenH - sourcePoint.y); + break; + case NPCoordinateSpaceFlippedScreen: + screenPoint = sourcePoint; + break; + default: + return false; + } + + // Convert from screen to dest space. + CSSIntPoint destPoint; + switch (destSpace) { + case NPCoordinateSpacePlugin: + destPoint = screenPoint - pluginPosition - + CSSIntPoint::Truncate(CSSPoint::FromAppUnits( + pluginFrame->GetContentRectRelativeToSelf().TopLeft())); + break; + case NPCoordinateSpaceWindow: + destPoint = screenPoint - windowPosition; + destPoint.y = windowH - destPoint.y; + break; + case NPCoordinateSpaceFlippedWindow: + destPoint = screenPoint - windowPosition; + break; + case NPCoordinateSpaceScreen: + destPoint = CSSIntPoint(screenPoint.x, screenH - screenPoint.y); + break; + case NPCoordinateSpaceFlippedScreen: + destPoint = screenPoint; + break; + default: + return false; + } + + if (destX) { + *destX = destPoint.x; + } + if (destY) { + *destY = destPoint.y; + } + + return true; +} + +NPBool nsPluginInstanceOwner::ConvertPointNoPuppet( + nsIWidget* widget, nsPluginFrame* pluginFrame, double sourceX, + double sourceY, NPCoordinateSpace sourceSpace, double* destX, double* destY, + NPCoordinateSpace destSpace) { + NS_ENSURE_TRUE(widget && pluginFrame, false); + // Caller has to want a result. + NS_ENSURE_TRUE(destX || destY, false); + + if (sourceSpace == destSpace) { + if (destX) { + *destX = sourceX; + } + if (destY) { + *destY = sourceY; + } + return true; + } + + nsPresContext* presContext = pluginFrame->PresContext(); + double scaleFactor = + double(AppUnitsPerCSSPixel()) / + presContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom(); + + nsCOMPtr<nsIScreen> screen = widget->GetWidgetScreen(); + if (!screen) { + return false; + } + + int32_t screenX, screenY, screenWidth, screenHeight; + screen->GetRect(&screenX, &screenY, &screenWidth, &screenHeight); + screenHeight /= scaleFactor; + + LayoutDeviceIntRect windowScreenBounds = widget->GetScreenBounds(); + windowScreenBounds.ScaleInverseRoundOut(scaleFactor); + int32_t windowX = windowScreenBounds.x; + int32_t windowY = windowScreenBounds.y; + int32_t windowHeight = windowScreenBounds.height; + + CSSIntRect pluginScreenRect = pluginFrame->GetScreenRect(); + + double screenXGecko, screenYGecko; + switch (sourceSpace) { + case NPCoordinateSpacePlugin: + screenXGecko = pluginScreenRect.x + sourceX; + screenYGecko = pluginScreenRect.y + sourceY; + break; + case NPCoordinateSpaceWindow: + screenXGecko = windowX + sourceX; + screenYGecko = windowY + (windowHeight - sourceY); + break; + case NPCoordinateSpaceFlippedWindow: + screenXGecko = windowX + sourceX; + screenYGecko = windowY + sourceY; + break; + case NPCoordinateSpaceScreen: + screenXGecko = sourceX; + screenYGecko = screenHeight - sourceY; + break; + case NPCoordinateSpaceFlippedScreen: + screenXGecko = sourceX; + screenYGecko = sourceY; + break; + default: + return false; + } + + double destXCocoa, destYCocoa; + switch (destSpace) { + case NPCoordinateSpacePlugin: + destXCocoa = screenXGecko - pluginScreenRect.x; + destYCocoa = screenYGecko - pluginScreenRect.y; + break; + case NPCoordinateSpaceWindow: + destXCocoa = screenXGecko - windowX; + destYCocoa = windowHeight - (screenYGecko - windowY); + break; + case NPCoordinateSpaceFlippedWindow: + destXCocoa = screenXGecko - windowX; + destYCocoa = screenYGecko - windowY; + break; + case NPCoordinateSpaceScreen: + destXCocoa = screenXGecko; + destYCocoa = screenHeight - screenYGecko; + break; + case NPCoordinateSpaceFlippedScreen: + destXCocoa = screenXGecko; + destYCocoa = screenYGecko; + break; + default: + return false; + } + + if (destX) { + *destX = destXCocoa; + } + if (destY) { + *destY = destYCocoa; + } + + return true; +} +#endif // XP_MACOSX + +NPBool nsPluginInstanceOwner::ConvertPoint(double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, + double* destX, double* destY, + NPCoordinateSpace destSpace) { +#ifdef XP_MACOSX + if (!mPluginFrame) { + return false; + } + + MOZ_ASSERT(mPluginFrame->GetNearestWidget()); + + if (nsIWidget::UsePuppetWidgets()) { + return ConvertPointPuppet( + static_cast<PuppetWidget*>(mPluginFrame->GetNearestWidget()), + mPluginFrame, sourceX, sourceY, sourceSpace, destX, destY, destSpace); + } + + return ConvertPointNoPuppet(mPluginFrame->GetNearestWidget(), mPluginFrame, + sourceX, sourceY, sourceSpace, destX, destY, + destSpace); +#else + return false; +#endif +} + +NPError nsPluginInstanceOwner::InitAsyncSurface(NPSize* size, + NPImageFormat format, + void* initData, + NPAsyncSurface* surface) { + return NPERR_INCOMPATIBLE_VERSION_ERROR; +} + +NPError nsPluginInstanceOwner::FinalizeAsyncSurface(NPAsyncSurface*) { + return NPERR_INCOMPATIBLE_VERSION_ERROR; +} + +void nsPluginInstanceOwner::SetCurrentAsyncSurface(NPAsyncSurface*, NPRect*) {} + +NS_IMETHODIMP nsPluginInstanceOwner::GetTagType(nsPluginTagType* result) { + NS_ENSURE_ARG_POINTER(result); + + *result = nsPluginTagType_Unknown; + + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (content->IsHTMLElement(nsGkAtoms::embed)) + *result = nsPluginTagType_Embed; + else if (content->IsHTMLElement(nsGkAtoms::object)) + *result = nsPluginTagType_Object; + + return NS_OK; +} + +void nsPluginInstanceOwner::GetParameters( + nsTArray<MozPluginParameter>& parameters) { + nsCOMPtr<nsIObjectLoadingContent> content = do_QueryReferent(mContent); + nsObjectLoadingContent* loadingContent = + static_cast<nsObjectLoadingContent*>(content.get()); + + loadingContent->GetPluginParameters(parameters); +} + +#ifdef XP_MACOSX + +static void InitializeNPCocoaEvent(NPCocoaEvent* event) { + memset(event, 0, sizeof(NPCocoaEvent)); +} + +NPDrawingModel nsPluginInstanceOwner::GetDrawingModel() { +# ifndef NP_NO_QUICKDRAW + // We don't support the Quickdraw drawing model any more but it's still + // the default model for i386 per NPAPI. + NPDrawingModel drawingModel = NPDrawingModelQuickDraw; +# else + NPDrawingModel drawingModel = NPDrawingModelCoreGraphics; +# endif + + if (!mInstance) return drawingModel; + + mInstance->GetDrawingModel((int32_t*)&drawingModel); + return drawingModel; +} + +bool nsPluginInstanceOwner::IsRemoteDrawingCoreAnimation() { + if (!mInstance) return false; + + bool coreAnimation; + if (!NS_SUCCEEDED(mInstance->IsRemoteDrawingCoreAnimation(&coreAnimation))) + return false; + + return coreAnimation; +} + +NPEventModel nsPluginInstanceOwner::GetEventModel() { return mEventModel; } + +# define DEFAULT_REFRESH_RATE 20 // 50 FPS +StaticRefPtr<nsITimer> nsPluginInstanceOwner::sCATimer; +nsTArray<nsPluginInstanceOwner*>* nsPluginInstanceOwner::sCARefreshListeners = + nullptr; + +void nsPluginInstanceOwner::CARefresh(nsITimer* aTimer, void* aClosure) { + if (!sCARefreshListeners) { + return; + } + for (size_t i = 0; i < sCARefreshListeners->Length(); i++) { + nsPluginInstanceOwner* instanceOwner = (*sCARefreshListeners)[i]; + NPWindow* window; + instanceOwner->GetWindow(window); + if (!window) { + continue; + } + NPRect r; + r.left = 0; + r.top = 0; + r.right = window->width; + r.bottom = window->height; + instanceOwner->InvalidateRect(&r); + } +} + +void nsPluginInstanceOwner::AddToCARefreshTimer() { + if (!mInstance) { + return; + } + + // Flash invokes InvalidateRect for us. + const char* mime = nullptr; + if (NS_SUCCEEDED(mInstance->GetMIMEType(&mime)) && mime && + nsPluginHost::GetSpecialType(nsDependentCString(mime)) == + nsPluginHost::eSpecialType_Flash) { + return; + } + + if (!sCARefreshListeners) { + sCARefreshListeners = new nsTArray<nsPluginInstanceOwner*>(); + } + + if (sCARefreshListeners->Contains(this)) { + return; + } + + sCARefreshListeners->AppendElement(this); + + if (sCARefreshListeners->Length() == 1) { + nsCOMPtr<nsITimer> timer; + NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), CARefresh, nullptr, DEFAULT_REFRESH_RATE, + nsITimer::TYPE_REPEATING_SLACK, "nsPluginInstanceOwner::CARefresh"); + sCATimer = timer.forget(); + } +} + +void nsPluginInstanceOwner::RemoveFromCARefreshTimer() { + if (!sCARefreshListeners || sCARefreshListeners->Contains(this) == false) { + return; + } + + sCARefreshListeners->RemoveElement(this); + + if (sCARefreshListeners->Length() == 0) { + if (sCATimer) { + sCATimer->Cancel(); + sCATimer = nullptr; + } + delete sCARefreshListeners; + sCARefreshListeners = nullptr; + } +} + +void nsPluginInstanceOwner::SetPluginPort() { + void* pluginPort = GetPluginPort(); + if (!pluginPort || !mPluginWindow) return; + mPluginWindow->window = pluginPort; +} +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +nsresult nsPluginInstanceOwner::ContentsScaleFactorChanged( + double aContentsScaleFactor) { + if (!mInstance) { + return NS_ERROR_NULL_POINTER; + } + return mInstance->ContentsScaleFactorChanged(aContentsScaleFactor); +} +#endif + +// static +uint32_t nsPluginInstanceOwner::GetEventloopNestingLevel() { + nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID); + uint32_t currentLevel = 0; + if (appShell) { + appShell->GetEventloopNestingLevel(¤tLevel); +#ifdef XP_MACOSX + // Cocoa widget code doesn't process UI events through the normal + // appshell event loop, so it needs an additional count here. + currentLevel++; +#endif + } + + // No idea how this happens... but Linux doesn't consistently + // process UI events through the appshell event loop. If we get a 0 + // here on any platform we increment the level just in case so that + // we make sure we always tear the plugin down eventually. + if (!currentLevel) { + currentLevel++; + } + + return currentLevel; +} + +nsresult nsPluginInstanceOwner::DispatchFocusToPlugin(Event* aFocusEvent) { +#ifndef XP_MACOSX + if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) { + // continue only for cases without child window + aFocusEvent->PreventDefault(); // consume event + return NS_OK; + } +#endif + + WidgetEvent* theEvent = aFocusEvent->WidgetEventPtr(); + if (theEvent) { + WidgetGUIEvent focusEvent(theEvent->IsTrusted(), theEvent->mMessage, + nullptr); + nsEventStatus rv = ProcessEvent(focusEvent); + if (nsEventStatus_eConsumeNoDefault == rv) { + aFocusEvent->PreventDefault(); + aFocusEvent->StopPropagation(); + } + } + + return NS_OK; +} + +nsresult nsPluginInstanceOwner::ProcessKeyPress(Event* aKeyEvent) { + // ProcessKeyPress() may be called twice with same eKeyPress event. One is + // by the event listener in the default event group and the other is by the + // event listener in the system event group. When this is called in the + // latter case and the event must be fired in the default event group too, + // we don't need to do nothing anymore. + // XXX Do we need to check whether the document is in chrome? In strictly + // speaking, it must be yes. However, our UI must not use plugin in + // chrome. + if (!aKeyEvent->WidgetEventPtr()->mFlags.mOnlySystemGroupDispatchInContent && + aKeyEvent->WidgetEventPtr()->mFlags.mInSystemGroup) { + return NS_OK; + } + +#ifdef XP_MACOSX + return DispatchKeyToPlugin(aKeyEvent); +#else + if (SendNativeEvents()) DispatchKeyToPlugin(aKeyEvent); + + if (mInstance) { + // If this event is going to the plugin, we want to kill it. + // Not actually sending keypress to the plugin, since we didn't before. + aKeyEvent->PreventDefault(); + aKeyEvent->StopPropagation(); + } + return NS_OK; +#endif +} + +nsresult nsPluginInstanceOwner::DispatchKeyToPlugin(Event* aKeyEvent) { +#if !defined(XP_MACOSX) + if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) { + aKeyEvent->PreventDefault(); // consume event + return NS_OK; + } + // continue only for cases without child window +#endif + + if (mInstance) { + WidgetKeyboardEvent* keyEvent = + aKeyEvent->WidgetEventPtr()->AsKeyboardEvent(); + if (keyEvent && keyEvent->mClass == eKeyboardEventClass) { + nsEventStatus rv = ProcessEvent(*keyEvent); + if (nsEventStatus_eConsumeNoDefault == rv) { + aKeyEvent->PreventDefault(); + aKeyEvent->StopPropagation(); + } + } + } + + return NS_OK; +} + +nsresult nsPluginInstanceOwner::ProcessMouseDown(Event* aMouseEvent) { +#if !defined(XP_MACOSX) + if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) { + aMouseEvent->PreventDefault(); // consume event + return NS_OK; + } + // continue only for cases without child window +#endif + + // if the plugin is windowless, we need to set focus ourselves + // otherwise, we might not get key events + if (mPluginFrame && mPluginWindow && + mPluginWindow->type == NPWindowTypeDrawable) { + if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { + nsCOMPtr<Element> elem = do_QueryReferent(mContent); + fm->SetFocus(elem, 0); + } + } + + WidgetMouseEvent* mouseEvent = aMouseEvent->WidgetEventPtr()->AsMouseEvent(); + if (mouseEvent && mouseEvent->mClass == eMouseEventClass) { + mLastMouseDownButtonType = mouseEvent->mButton; + nsEventStatus rv = ProcessEvent(*mouseEvent); + if (nsEventStatus_eConsumeNoDefault == rv) { + aMouseEvent->PreventDefault(); // consume event + return NS_OK; + } + } + + return NS_OK; +} + +nsresult nsPluginInstanceOwner::DispatchMouseToPlugin(Event* aMouseEvent, + bool aAllowPropagate) { +#if !defined(XP_MACOSX) + if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) { + aMouseEvent->PreventDefault(); // consume event + return NS_OK; + } + // continue only for cases without child window +#endif + // don't send mouse events if we are hidden + if (!mWidgetVisible) return NS_OK; + + WidgetMouseEvent* mouseEvent = aMouseEvent->WidgetEventPtr()->AsMouseEvent(); + if (mouseEvent && mouseEvent->mClass == eMouseEventClass) { + nsEventStatus rv = ProcessEvent(*mouseEvent); + if (nsEventStatus_eConsumeNoDefault == rv) { + aMouseEvent->PreventDefault(); + if (!aAllowPropagate) { + aMouseEvent->StopPropagation(); + } + } + if (mouseEvent->mMessage == eMouseUp) { + mLastMouseDownButtonType = -1; + } + } + return NS_OK; +} + +#ifdef XP_WIN +already_AddRefed<TextComposition> nsPluginInstanceOwner::GetTextComposition() { + if (NS_WARN_IF(!mPluginFrame)) { + return nullptr; + } + + nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset(); + if (!widget) { + widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (NS_WARN_IF(!widget)) { + return nullptr; + } + } + + RefPtr<TextComposition> composition = + IMEStateManager::GetTextCompositionFor(widget); + if (NS_WARN_IF(!composition)) { + return nullptr; + } + + return composition.forget(); +} + +void nsPluginInstanceOwner::HandleNoConsumedCompositionMessage( + WidgetCompositionEvent* aCompositionEvent, const NPEvent* aPluginEvent) { + nsCOMPtr<nsIWidget> widget = GetContainingWidgetIfOffset(); + if (!widget) { + widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (NS_WARN_IF(!widget)) { + return; + } + } + + if (aPluginEvent->lParam & GCS_RESULTSTR) { + return; + } + if (!mSentStartComposition) { + mSentStartComposition = true; + } +} +#endif + +nsresult nsPluginInstanceOwner::DispatchCompositionToPlugin(Event* aEvent) { +#ifdef XP_WIN + if (!mPluginWindow) { + // CompositionEvent isn't cancellable. So it is unnecessary to call + // PreventDefaults() to consume event + return NS_OK; + } + WidgetCompositionEvent* compositionEvent = + aEvent->WidgetEventPtr()->AsCompositionEvent(); + if (NS_WARN_IF(!compositionEvent)) { + return NS_ERROR_INVALID_ARG; + } + + if (compositionEvent->mMessage == eCompositionChange) { + RefPtr<TextComposition> composition = GetTextComposition(); + if (NS_WARN_IF(!composition)) { + return NS_ERROR_FAILURE; + } + TextComposition::CompositionChangeEventHandlingMarker + compositionChangeEventHandlingMarker(composition, compositionEvent); + } + + const NPEvent* pPluginEvent = + static_cast<const NPEvent*>(compositionEvent->mPluginEvent); + if (pPluginEvent && pPluginEvent->event == WM_IME_COMPOSITION && + mPluginDidNotHandleIMEComposition) { + // This is a workaround when running windowed and windowless Flash on + // same process. + // Flash with protected mode calls IMM APIs on own render process. This + // is a bug of Flash's protected mode. + // ImmGetCompositionString with GCS_RESULTSTR returns *LAST* committed + // string. So when windowed mode Flash handles IME composition, + // windowless plugin can get windowed mode's commited string by that API. + // So we never post WM_IME_COMPOSITION when plugin doesn't call + // ImmGetCompositionString() during WM_IME_COMPOSITION correctly. + HandleNoConsumedCompositionMessage(compositionEvent, pPluginEvent); + aEvent->StopImmediatePropagation(); + return NS_OK; + } + + // Protected mode Flash returns noDefault by NPP_HandleEvent, but + // composition information into plugin is invalid because plugin's bug. + // So if plugin doesn't get composition data by WM_IME_COMPOSITION, we + // recongnize it isn't handled + AutoRestore<bool> restore(mGotCompositionData); + mGotCompositionData = false; + + nsEventStatus status = ProcessEvent(*compositionEvent); + aEvent->StopImmediatePropagation(); + + // Composition event isn't handled by plugin, so we have to call default proc. + + if (NS_WARN_IF(!pPluginEvent)) { + return NS_OK; + } + + if (pPluginEvent->event == WM_IME_STARTCOMPOSITION) { + if (nsEventStatus_eConsumeNoDefault != status) { + mSentStartComposition = true; + } else { + mSentStartComposition = false; + } + mPluginDidNotHandleIMEComposition = false; + return NS_OK; + } + + if (pPluginEvent->event == WM_IME_ENDCOMPOSITION) { + return NS_OK; + } + + if (pPluginEvent->event == WM_IME_COMPOSITION && !mGotCompositionData) { + // If plugin doesn't handle WM_IME_COMPOSITION correctly, we don't send + // composition event until end composition. + mPluginDidNotHandleIMEComposition = true; + + HandleNoConsumedCompositionMessage(compositionEvent, pPluginEvent); + } +#endif // #ifdef XP_WIN + return NS_OK; +} + +nsresult nsPluginInstanceOwner::HandleEvent(Event* aEvent) { + NS_ASSERTION(mInstance, + "Should have a valid plugin instance or not receive events."); + + nsAutoString eventType; + aEvent->GetType(eventType); + +#ifdef XP_MACOSX + if (eventType.EqualsLiteral("activate") || + eventType.EqualsLiteral("deactivate")) { + WindowFocusMayHaveChanged(); + return NS_OK; + } + if (eventType.EqualsLiteral("MozPerformDelayedBlur")) { + if (mShouldBlurOnActivate) { + WidgetGUIEvent blurEvent(true, eBlur, nullptr); + ProcessEvent(blurEvent); + mShouldBlurOnActivate = false; + } + return NS_OK; + } +#endif + + if (eventType.EqualsLiteral("focus")) { + mContentFocused = true; + return DispatchFocusToPlugin(aEvent); + } + if (eventType.EqualsLiteral("blur")) { + mContentFocused = false; + return DispatchFocusToPlugin(aEvent); + } + if (eventType.EqualsLiteral("mousedown")) { + return ProcessMouseDown(aEvent); + } + if (eventType.EqualsLiteral("mouseup")) { + return DispatchMouseToPlugin(aEvent); + } + if (eventType.EqualsLiteral("mousemove")) { + return DispatchMouseToPlugin(aEvent, true); + } + if (eventType.EqualsLiteral("click") || eventType.EqualsLiteral("dblclick") || + eventType.EqualsLiteral("mouseover") || + eventType.EqualsLiteral("mouseout")) { + return DispatchMouseToPlugin(aEvent); + } + if (eventType.EqualsLiteral("keydown") || eventType.EqualsLiteral("keyup")) { + return DispatchKeyToPlugin(aEvent); + } + if (eventType.EqualsLiteral("keypress")) { + return ProcessKeyPress(aEvent); + } + if (eventType.EqualsLiteral("compositionstart") || + eventType.EqualsLiteral("compositionend") || + eventType.EqualsLiteral("text")) { + return DispatchCompositionToPlugin(aEvent); + } + + DragEvent* dragEvent = aEvent->AsDragEvent(); + if (dragEvent && mInstance) { + WidgetEvent* ievent = aEvent->WidgetEventPtr(); + if (ievent && ievent->IsTrusted() && ievent->mMessage != eDragEnter && + ievent->mMessage != eDragOver) { + aEvent->PreventDefault(); + } + + // Let the plugin handle drag events. + aEvent->StopPropagation(); + } + return NS_OK; +} + +#ifdef MOZ_X11 +static unsigned int XInputEventState(const WidgetInputEvent& anEvent) { + unsigned int state = 0; + if (anEvent.IsShift()) state |= ShiftMask; + if (anEvent.IsControl()) state |= ControlMask; + if (anEvent.IsAlt()) state |= Mod1Mask; + if (anEvent.IsMeta()) state |= Mod4Mask; + return state; +} +#endif + +#ifdef XP_MACOSX + +// Returns whether or not content is the content that is or would be +// focused if the top-level chrome window was active. +static bool ContentIsFocusedWithinWindow(nsIContent* aContent) { + nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow(); + if (!outerWindow) { + return false; + } + + nsPIDOMWindowOuter* rootWindow = outerWindow->GetPrivateRoot(); + if (!rootWindow) { + return false; + } + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return false; + } + + nsCOMPtr<nsPIDOMWindowOuter> focusedFrame; + nsCOMPtr<nsIContent> focusedContent = nsFocusManager::GetFocusedDescendant( + rootWindow, nsFocusManager::eIncludeAllDescendants, + getter_AddRefs(focusedFrame)); + return (focusedContent.get() == aContent); +} + +static NPCocoaEventType CocoaEventTypeForEvent(const WidgetGUIEvent& anEvent, + nsIFrame* aObjectFrame) { + const NPCocoaEvent* event = + static_cast<const NPCocoaEvent*>(anEvent.mPluginEvent); + if (event) { + return event->type; + } + + switch (anEvent.mMessage) { + case eMouseOver: + return NPCocoaEventMouseEntered; + case eMouseOut: + return NPCocoaEventMouseExited; + case eMouseMove: { + // We don't know via information on events from the widget code whether or + // not we're dragging. The widget code just generates mouse move events + // from native drag events. If anybody is capturing, this is a drag event. + if (PresShell::GetCapturingContent()) { + return NPCocoaEventMouseDragged; + } + + return NPCocoaEventMouseMoved; + } + case eMouseDown: + return NPCocoaEventMouseDown; + case eMouseUp: + return NPCocoaEventMouseUp; + case eKeyDown: + return NPCocoaEventKeyDown; + case eKeyUp: + return NPCocoaEventKeyUp; + case eFocus: + case eBlur: + return NPCocoaEventFocusChanged; + case eLegacyMouseLineOrPageScroll: + return NPCocoaEventScrollWheel; + default: + return (NPCocoaEventType)0; + } +} + +static NPCocoaEvent TranslateToNPCocoaEvent(WidgetGUIEvent* anEvent, + nsIFrame* aObjectFrame) { + NPCocoaEvent cocoaEvent; + InitializeNPCocoaEvent(&cocoaEvent); + cocoaEvent.type = CocoaEventTypeForEvent(*anEvent, aObjectFrame); + + if (anEvent->mMessage == eMouseMove || anEvent->mMessage == eMouseDown || + anEvent->mMessage == eMouseUp || + anEvent->mMessage == eLegacyMouseLineOrPageScroll || + anEvent->mMessage == eMouseOver || anEvent->mMessage == eMouseOut) { + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo( + anEvent, RelativeTo{aObjectFrame}) - + aObjectFrame->GetContentRectRelativeToSelf().TopLeft(); + nsPresContext* presContext = aObjectFrame->PresContext(); + // Plugin event coordinates need to be translated from device pixels + // into "display pixels" in HiDPI modes. + double scaleFactor = double(AppUnitsPerCSSPixel()) / + aObjectFrame->PresContext() + ->DeviceContext() + ->AppUnitsPerDevPixelAtUnitFullZoom(); + size_t intScaleFactor = ceil(scaleFactor); + nsIntPoint ptPx(presContext->AppUnitsToDevPixels(pt.x) / intScaleFactor, + presContext->AppUnitsToDevPixels(pt.y) / intScaleFactor); + cocoaEvent.data.mouse.pluginX = double(ptPx.x); + cocoaEvent.data.mouse.pluginY = double(ptPx.y); + } + + switch (anEvent->mMessage) { + case eMouseDown: + case eMouseUp: { + WidgetMouseEvent* mouseEvent = anEvent->AsMouseEvent(); + if (mouseEvent) { + switch (mouseEvent->mButton) { + case MouseButton::ePrimary: + cocoaEvent.data.mouse.buttonNumber = 0; + break; + case MouseButton::eSecondary: + cocoaEvent.data.mouse.buttonNumber = 1; + break; + case MouseButton::eMiddle: + cocoaEvent.data.mouse.buttonNumber = 2; + break; + default: + NS_WARNING("Mouse mButton we don't know about?"); + } + cocoaEvent.data.mouse.clickCount = mouseEvent->mClickCount; + } else { + NS_WARNING("eMouseUp/DOWN is not a WidgetMouseEvent?"); + } + break; + } + case eLegacyMouseLineOrPageScroll: { + WidgetWheelEvent* wheelEvent = anEvent->AsWheelEvent(); + if (wheelEvent) { + cocoaEvent.data.mouse.deltaX = wheelEvent->mLineOrPageDeltaX; + cocoaEvent.data.mouse.deltaY = wheelEvent->mLineOrPageDeltaY; + } else { + NS_WARNING( + "eLegacyMouseLineOrPageScroll is not a WidgetWheelEvent? " + "(could be, haven't checked)"); + } + break; + } + case eKeyDown: + case eKeyUp: + break; + case eFocus: + case eBlur: + cocoaEvent.data.focus.hasFocus = (anEvent->mMessage == eFocus); + break; + default: + break; + } + return cocoaEvent; +} + +void nsPluginInstanceOwner::PerformDelayedBlurs() { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + nsCOMPtr<EventTarget> windowRoot = + content->OwnerDoc()->GetWindow()->GetTopWindowRoot(); + nsContentUtils::DispatchEventOnlyToChrome( + content->OwnerDoc(), windowRoot, u"MozPerformDelayedBlur"_ns, + CanBubble::eNo, Cancelable::eNo, nullptr); +} + +#endif + +nsEventStatus nsPluginInstanceOwner::ProcessEvent( + const WidgetGUIEvent& anEvent) { + nsEventStatus rv = nsEventStatus_eIgnore; + + if (!mInstance || !mPluginFrame) { + return nsEventStatus_eIgnore; + } + +#ifdef XP_MACOSX + NPEventModel eventModel = GetEventModel(); + if (eventModel != NPEventModelCocoa) { + return nsEventStatus_eIgnore; + } + + // In the Cocoa event model, focus is per-window. Don't tell a plugin it lost + // focus unless it lost focus within the window. For example, ignore a blur + // event if it's coming due to the plugin's window deactivating. + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (anEvent.mMessage == eBlur && ContentIsFocusedWithinWindow(content)) { + mShouldBlurOnActivate = true; + return nsEventStatus_eIgnore; + } + + // Also, don't tell the plugin it gained focus again after we've already given + // it focus. This might happen if it has focus, its window is blurred, then + // the window is made active again. The plugin never lost in-window focus, so + // it shouldn't get a focus event again. + if (anEvent.mMessage == eFocus && mLastContentFocused == true) { + mShouldBlurOnActivate = false; + return nsEventStatus_eIgnore; + } + + // Now, if we're going to send a focus event, update mLastContentFocused and + // tell any plugins in our window that we have taken focus, so they should + // perform any delayed blurs. + if (anEvent.mMessage == eFocus || anEvent.mMessage == eBlur) { + mLastContentFocused = (anEvent.mMessage == eFocus); + mShouldBlurOnActivate = false; + PerformDelayedBlurs(); + } + + NPCocoaEvent cocoaEvent = TranslateToNPCocoaEvent( + const_cast<WidgetGUIEvent*>(&anEvent), mPluginFrame); + if (cocoaEvent.type == (NPCocoaEventType)0) { + return nsEventStatus_eIgnore; + } + + if (cocoaEvent.type == NPCocoaEventTextInput) { + mInstance->HandleEvent(&cocoaEvent, nullptr); + return nsEventStatus_eConsumeNoDefault; + } + + int16_t response = kNPEventNotHandled; + mInstance->HandleEvent(&cocoaEvent, &response, + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + + bool handled = (response == kNPEventHandled || response == kNPEventStartIME); + bool leftMouseButtonDown = + (anEvent.mMessage == eMouseDown) && + (anEvent.AsMouseEvent()->mButton == MouseButton::ePrimary); + if (handled && !(leftMouseButtonDown && !mContentFocused)) { + rv = nsEventStatus_eConsumeNoDefault; + } +#endif + +#ifdef XP_WIN + // this code supports windowless plugins + const NPEvent* pPluginEvent = + static_cast<const NPEvent*>(anEvent.mPluginEvent); + // we can get synthetic events from the EventStateManager... these + // have no pluginEvent + NPEvent pluginEvent; + if (anEvent.mClass == eMouseEventClass || + anEvent.mClass == eWheelEventClass) { + if (!pPluginEvent) { + // XXX Should extend this list to synthesize events for more event + // types + pluginEvent.event = 0; + bool initWParamWithCurrentState = true; + switch (anEvent.mMessage) { + case eMouseMove: { + pluginEvent.event = WM_MOUSEMOVE; + break; + } + case eMouseDown: { + static const int downMsgs[] = {WM_LBUTTONDOWN, WM_MBUTTONDOWN, + WM_RBUTTONDOWN}; + static const int dblClickMsgs[] = {WM_LBUTTONDBLCLK, WM_MBUTTONDBLCLK, + WM_RBUTTONDBLCLK}; + const WidgetMouseEvent* mouseEvent = anEvent.AsMouseEvent(); + if (mouseEvent->mClickCount == 2) { + pluginEvent.event = dblClickMsgs[mouseEvent->mButton]; + } else { + pluginEvent.event = downMsgs[mouseEvent->mButton]; + } + break; + } + case eMouseUp: { + static const int upMsgs[] = {WM_LBUTTONUP, WM_MBUTTONUP, + WM_RBUTTONUP}; + const WidgetMouseEvent* mouseEvent = anEvent.AsMouseEvent(); + pluginEvent.event = upMsgs[mouseEvent->mButton]; + break; + } + // For plugins which don't support high-resolution scroll, we should + // generate legacy resolution wheel messages. I.e., the delta value + // should be WHEEL_DELTA * n. + case eWheel: { + const WidgetWheelEvent* wheelEvent = anEvent.AsWheelEvent(); + int32_t delta = 0; + if (wheelEvent->mLineOrPageDeltaY) { + switch (wheelEvent->mDeltaMode) { + case WheelEvent_Binding::DOM_DELTA_PAGE: + pluginEvent.event = WM_MOUSEWHEEL; + delta = -WHEEL_DELTA * wheelEvent->mLineOrPageDeltaY; + break; + case WheelEvent_Binding::DOM_DELTA_LINE: { + UINT linesPerWheelDelta = mWheelScrollLines; + if (!linesPerWheelDelta) { + break; + } + pluginEvent.event = WM_MOUSEWHEEL; + delta = -WHEEL_DELTA / linesPerWheelDelta; + delta *= wheelEvent->mLineOrPageDeltaY; + break; + } + case WheelEvent_Binding::DOM_DELTA_PIXEL: + default: + // We don't support WM_GESTURE with this path. + MOZ_ASSERT(!pluginEvent.event); + break; + } + } else if (wheelEvent->mLineOrPageDeltaX) { + switch (wheelEvent->mDeltaMode) { + case WheelEvent_Binding::DOM_DELTA_PAGE: + pluginEvent.event = WM_MOUSEHWHEEL; + delta = -WHEEL_DELTA * wheelEvent->mLineOrPageDeltaX; + break; + case WheelEvent_Binding::DOM_DELTA_LINE: { + pluginEvent.event = WM_MOUSEHWHEEL; + UINT charsPerWheelDelta = mWheelScrollChars; + if (!charsPerWheelDelta) { + break; + } + delta = WHEEL_DELTA / charsPerWheelDelta; + delta *= wheelEvent->mLineOrPageDeltaX; + break; + } + case WheelEvent_Binding::DOM_DELTA_PIXEL: + default: + // We don't support WM_GESTURE with this path. + MOZ_ASSERT(!pluginEvent.event); + break; + } + } + + if (!pluginEvent.event) { + break; + } + + initWParamWithCurrentState = false; + int32_t modifiers = + (wheelEvent->IsControl() ? MK_CONTROL : 0) | + (wheelEvent->IsShift() ? MK_SHIFT : 0) | + (wheelEvent->IsLeftButtonPressed() ? MK_LBUTTON : 0) | + (wheelEvent->IsMiddleButtonPressed() ? MK_MBUTTON : 0) | + (wheelEvent->IsRightButtonPressed() ? MK_RBUTTON : 0) | + (wheelEvent->Is4thButtonPressed() ? MK_XBUTTON1 : 0) | + (wheelEvent->Is5thButtonPressed() ? MK_XBUTTON2 : 0); + pluginEvent.wParam = MAKEWPARAM(modifiers, delta); + pPluginEvent = &pluginEvent; + break; + } + // don't synthesize anything for eMouseDoubleClick, since that + // is a synthetic event generated on mouse-up, and Windows WM_*DBLCLK + // messages are sent on mouse-down + default: + break; + } + if (pluginEvent.event && initWParamWithCurrentState) { + // We created one of the messages caught above but didn't fill in + // wParam. Mark it with an invalid wParam value so that HandleEvent can + // figure it out. + pPluginEvent = &pluginEvent; + pluginEvent.wParam = NPAPI_INVALID_WPARAM; + } + } + if (pPluginEvent) { + // Make event coordinates relative to our enclosing widget, + // not the widget they were received on. + // See use of NPEvent in widget/windows/nsWindow.cpp + // for why this assert should be safe + NS_ASSERTION( + anEvent.mMessage == eMouseDown || anEvent.mMessage == eMouseUp || + anEvent.mMessage == eMouseDoubleClick || + anEvent.mMessage == eMouseAuxClick || + anEvent.mMessage == eMouseOver || anEvent.mMessage == eMouseOut || + anEvent.mMessage == eMouseMove || anEvent.mMessage == eWheel, + "Incorrect event type for coordinate translation"); + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo( + &anEvent, RelativeTo{mPluginFrame}) - + mPluginFrame->GetContentRectRelativeToSelf().TopLeft(); + nsPresContext* presContext = mPluginFrame->PresContext(); + nsIntPoint ptPx(presContext->AppUnitsToDevPixels(pt.x), + presContext->AppUnitsToDevPixels(pt.y)); + nsIntPoint widgetPtPx = + ptPx + mPluginFrame->GetWindowOriginInPixels(true); + const_cast<NPEvent*>(pPluginEvent)->lParam = + MAKELPARAM(widgetPtPx.x, widgetPtPx.y); + } + } else if (!pPluginEvent) { + switch (anEvent.mMessage) { + case eFocus: + pluginEvent.event = WM_SETFOCUS; + pluginEvent.wParam = 0; + pluginEvent.lParam = 0; + pPluginEvent = &pluginEvent; + break; + case eBlur: + pluginEvent.event = WM_KILLFOCUS; + pluginEvent.wParam = 0; + pluginEvent.lParam = 0; + pPluginEvent = &pluginEvent; + break; + default: + break; + } + } + + if (pPluginEvent && !pPluginEvent->event) { + // Don't send null events to plugins. + NS_WARNING( + "nsPluginFrame ProcessEvent: trying to send null event to plugin."); + return rv; + } + + // We don't need to tell the plugin about changes to the scroll wheel + // settings but we do need to remember them for future mouse move + // calculations. We put the scroll wheel setting in the lParam field. + if (pPluginEvent && pPluginEvent->event == WM_SETTINGCHANGE) { + switch (pPluginEvent->wParam) { + case SPI_SETWHEELSCROLLLINES: + mWheelScrollLines = static_cast<uint32_t>(pPluginEvent->lParam); + break; + case SPI_SETWHEELSCROLLCHARS: + mWheelScrollChars = static_cast<uint32_t>(pPluginEvent->lParam); + break; + default: + break; + } + return nsEventStatus_eConsumeNoDefault; + } + + if (pPluginEvent) { + int16_t response = kNPEventNotHandled; + mInstance->HandleEvent(const_cast<NPEvent*>(pPluginEvent), &response, + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + if (response == kNPEventHandled) rv = nsEventStatus_eConsumeNoDefault; + } +#endif + +#ifdef MOZ_X11 + // this code supports windowless plugins + nsIWidget* widget = anEvent.mWidget; + XEvent pluginEvent = XEvent(); + pluginEvent.type = 0; + + switch (anEvent.mClass) { + case eMouseEventClass: { + switch (anEvent.mMessage) { + case eMouseClick: + case eMouseDoubleClick: + case eMouseAuxClick: + // Button up/down events sent instead. + return rv; + default: + break; + } + + // Get reference point relative to plugin origin. + const nsPresContext* presContext = mPluginFrame->PresContext(); + nsPoint appPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo( + &anEvent, RelativeTo{mPluginFrame}) - + mPluginFrame->GetContentRectRelativeToSelf().TopLeft(); + nsIntPoint pluginPoint(presContext->AppUnitsToDevPixels(appPoint.x), + presContext->AppUnitsToDevPixels(appPoint.y)); + const WidgetMouseEvent& mouseEvent = *anEvent.AsMouseEvent(); + // Get reference point relative to screen: + LayoutDeviceIntPoint rootPoint(-1, -1); + if (widget) { + rootPoint = anEvent.mRefPoint + widget->WidgetToScreenOffset(); + } +# ifdef MOZ_WIDGET_GTK + Window root = GDK_ROOT_WINDOW(); +# else + Window root = X11None; // Could XQueryTree, but this is not important. +# endif + + switch (anEvent.mMessage) { + case eMouseOver: + case eMouseOut: { + XCrossingEvent& event = pluginEvent.xcrossing; + event.type = + anEvent.mMessage == eMouseOver ? EnterNotify : LeaveNotify; + event.root = root; + event.time = anEvent.mTime; + event.x = pluginPoint.x; + event.y = pluginPoint.y; + event.x_root = rootPoint.x; + event.y_root = rootPoint.y; + event.state = XInputEventState(mouseEvent); + // information lost + event.subwindow = X11None; + event.mode = -1; + event.detail = NotifyDetailNone; + event.same_screen = X11True; + event.focus = mContentFocused; + } break; + case eMouseMove: { + XMotionEvent& event = pluginEvent.xmotion; + event.type = MotionNotify; + event.root = root; + event.time = anEvent.mTime; + event.x = pluginPoint.x; + event.y = pluginPoint.y; + event.x_root = rootPoint.x; + event.y_root = rootPoint.y; + event.state = XInputEventState(mouseEvent); + // information lost + event.subwindow = X11None; + event.is_hint = NotifyNormal; + event.same_screen = X11True; + } break; + case eMouseDown: + case eMouseUp: { + XButtonEvent& event = pluginEvent.xbutton; + event.type = + anEvent.mMessage == eMouseDown ? ButtonPress : ButtonRelease; + event.root = root; + event.time = anEvent.mTime; + event.x = pluginPoint.x; + event.y = pluginPoint.y; + event.x_root = rootPoint.x; + event.y_root = rootPoint.y; + event.state = XInputEventState(mouseEvent); + switch (mouseEvent.mButton) { + case MouseButton::eMiddle: + event.button = 2; + break; + case MouseButton::eSecondary: + event.button = 3; + break; + default: // MouseButton::eLeft; + event.button = 1; + break; + } + // information lost: + event.subwindow = X11None; + event.same_screen = X11True; + } break; + default: + break; + } + } break; + + // XXX case eMouseScrollEventClass: not received. + + case eKeyboardEventClass: + if (anEvent.mPluginEvent) { + XKeyEvent& event = pluginEvent.xkey; +# ifdef MOZ_WIDGET_GTK + event.root = GDK_ROOT_WINDOW(); + event.time = anEvent.mTime; + const GdkEventKey* gdkEvent = + static_cast<const GdkEventKey*>(anEvent.mPluginEvent); + event.keycode = gdkEvent->hardware_keycode; + event.state = gdkEvent->state; + switch (anEvent.mMessage) { + case eKeyDown: + // Handle eKeyDown for modifier key presses + // For non-modifiers we get eKeyPress + if (gdkEvent->is_modifier) event.type = XKeyPress; + break; + case eKeyPress: + event.type = XKeyPress; + break; + case eKeyUp: + event.type = KeyRelease; + break; + default: + break; + } +# endif + + // Information that could be obtained from pluginEvent but we may not + // want to promise to provide: + event.subwindow = X11None; + event.x = 0; + event.y = 0; + event.x_root = -1; + event.y_root = -1; + event.same_screen = X11False; + } else { + // If we need to send synthesized key events, then + // DOMKeyCodeToGdkKeyCode(keyEvent.keyCode) and + // gdk_keymap_get_entries_for_keyval will be useful, but the + // mappings will not be unique. + NS_WARNING("Synthesized key event not sent to plugin"); + } + break; + + default: + switch (anEvent.mMessage) { + case eFocus: + case eBlur: { + XFocusChangeEvent& event = pluginEvent.xfocus; + event.type = anEvent.mMessage == eFocus ? FocusIn : FocusOut; + // information lost: + event.mode = -1; + event.detail = NotifyDetailNone; + } break; + default: + break; + } + } + + if (!pluginEvent.type) { + return rv; + } + + // Fill in (useless) generic event information. + XAnyEvent& event = pluginEvent.xany; + event.display = + widget ? static_cast<Display*>(widget->GetNativeData(NS_NATIVE_DISPLAY)) + : nullptr; + event.window = X11None; // not a real window + // information lost: + event.serial = 0; + event.send_event = X11False; + + int16_t response = kNPEventNotHandled; + mInstance->HandleEvent(&pluginEvent, &response, + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); + if (response == kNPEventHandled) rv = nsEventStatus_eConsumeNoDefault; +#endif + + return rv; +} + +nsresult nsPluginInstanceOwner::Destroy() { + SetFrame(nullptr); + +#ifdef XP_MACOSX + RemoveFromCARefreshTimer(); +#endif + + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + + // unregister context menu listener + if (mCXMenuListener) { + mCXMenuListener->Destroy(content); + mCXMenuListener = nullptr; + } + + content->RemoveEventListener(u"focus"_ns, this, false); + content->RemoveEventListener(u"blur"_ns, this, false); + content->RemoveEventListener(u"mouseup"_ns, this, false); + content->RemoveEventListener(u"mousedown"_ns, this, false); + content->RemoveEventListener(u"mousemove"_ns, this, false); + content->RemoveEventListener(u"click"_ns, this, false); + content->RemoveEventListener(u"dblclick"_ns, this, false); + content->RemoveEventListener(u"mouseover"_ns, this, false); + content->RemoveEventListener(u"mouseout"_ns, this, false); + content->RemoveEventListener(u"keypress"_ns, this, true); + content->RemoveSystemEventListener(u"keypress"_ns, this, true); + content->RemoveEventListener(u"keydown"_ns, this, true); + content->RemoveEventListener(u"keyup"_ns, this, true); + content->RemoveEventListener(u"drop"_ns, this, true); + content->RemoveEventListener(u"drag"_ns, this, true); + content->RemoveEventListener(u"dragenter"_ns, this, true); + content->RemoveEventListener(u"dragover"_ns, this, true); + content->RemoveEventListener(u"dragleave"_ns, this, true); + content->RemoveEventListener(u"dragexit"_ns, this, true); + content->RemoveEventListener(u"dragstart"_ns, this, true); + content->RemoveEventListener(u"dragend"_ns, this, true); + content->RemoveSystemEventListener(u"compositionstart"_ns, this, true); + content->RemoveSystemEventListener(u"compositionend"_ns, this, true); + content->RemoveSystemEventListener(u"text"_ns, this, true); + + if (mWidget) { + if (mPluginWindow) { + mPluginWindow->SetPluginWidget(nullptr); + } + + nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget); + if (pluginWidget) { + pluginWidget->SetPluginInstanceOwner(nullptr); + } + mWidget->Destroy(); + } + + return NS_OK; +} + +// Paints are handled differently, so we just simulate an update event. + +#ifdef XP_MACOSX +void nsPluginInstanceOwner::Paint(const gfxRect& aDirtyRect, + CGContextRef cgContext) { + if (!mInstance || !mPluginFrame) return; + + gfxRect dirtyRectCopy = aDirtyRect; + double scaleFactor = 1.0; + GetContentsScaleFactor(&scaleFactor); + if (scaleFactor != 1.0) { + ::CGContextScaleCTM(cgContext, scaleFactor, scaleFactor); + // Convert aDirtyRect from device pixels to "display pixels" + // for HiDPI modes + dirtyRectCopy.ScaleRoundOut(1.0 / scaleFactor); + } + + DoCocoaEventDrawRect(dirtyRectCopy, cgContext); +} + +void nsPluginInstanceOwner::DoCocoaEventDrawRect(const gfxRect& aDrawRect, + CGContextRef cgContext) { + if (!mInstance || !mPluginFrame) return; + + // The context given here is only valid during the HandleEvent call. + NPCocoaEvent updateEvent; + InitializeNPCocoaEvent(&updateEvent); + updateEvent.type = NPCocoaEventDrawRect; + updateEvent.data.draw.context = cgContext; + updateEvent.data.draw.x = aDrawRect.X(); + updateEvent.data.draw.y = aDrawRect.Y(); + updateEvent.data.draw.width = aDrawRect.Width(); + updateEvent.data.draw.height = aDrawRect.Height(); + + mInstance->HandleEvent(&updateEvent, nullptr); +} +#endif + +#ifdef XP_WIN +void nsPluginInstanceOwner::Paint(const RECT& aDirty, HDC aDC) { + if (!mInstance || !mPluginFrame) return; + + NPEvent pluginEvent; + pluginEvent.event = WM_PAINT; + pluginEvent.wParam = WPARAM(aDC); + pluginEvent.lParam = LPARAM(&aDirty); + mInstance->HandleEvent(&pluginEvent, nullptr); +} +#endif + +#if defined(MOZ_X11) +void nsPluginInstanceOwner::Paint(gfxContext* aContext, + const gfxRect& aFrameRect, + const gfxRect& aDirtyRect) { + if (!mInstance || !mPluginFrame) return; + + // to provide crisper and faster drawing. + gfxRect pluginRect = aFrameRect; + if (aContext->UserToDevicePixelSnapped(pluginRect)) { + pluginRect = aContext->DeviceToUser(pluginRect); + } + + // Round out the dirty rect to plugin pixels to ensure the plugin draws + // enough pixels for interpolation to device pixels. + gfxRect dirtyRect = aDirtyRect - pluginRect.TopLeft(); + dirtyRect.RoundOut(); + + // Plugins can only draw an integer number of pixels. + // + // With translation-only transformation matrices, pluginRect is already + // pixel-aligned. + // + // With more complex transformations, modifying the scales in the + // transformation matrix could retain subpixel accuracy and let the plugin + // draw a suitable number of pixels for interpolation to device pixels in + // Renderer::Draw, but such cases are not common enough to warrant the + // effort now. + nsIntSize pluginSize(NS_lround(pluginRect.width), + NS_lround(pluginRect.height)); + + // Determine what the plugin needs to draw. + nsIntRect pluginDirtyRect(int32_t(dirtyRect.x), int32_t(dirtyRect.y), + int32_t(dirtyRect.width), + int32_t(dirtyRect.height)); + if (!pluginDirtyRect.IntersectRect( + nsIntRect(0, 0, pluginSize.width, pluginSize.height), + pluginDirtyRect)) + return; + + NPWindow* window; + GetWindow(window); + + uint32_t rendererFlags = 0; + if (!mFlash10Quirks) { + rendererFlags |= Renderer::DRAW_SUPPORTS_CLIP_RECT | + Renderer::DRAW_SUPPORTS_ALTERNATE_VISUAL; + } + + bool transparent; + mInstance->IsTransparent(&transparent); + if (!transparent) rendererFlags |= Renderer::DRAW_IS_OPAQUE; + + // Renderer::Draw() draws a rectangle with top-left at the aContext origin. + gfxContextAutoSaveRestore autoSR(aContext); + aContext->SetMatrixDouble( + aContext->CurrentMatrixDouble().PreTranslate(pluginRect.TopLeft())); + + Renderer renderer(window, this, pluginSize, pluginDirtyRect); + + Display* dpy = mozilla::DefaultXDisplay(); + Screen* screen = DefaultScreenOfDisplay(dpy); + Visual* visual = DefaultVisualOfScreen(screen); + + renderer.Draw(aContext, nsIntSize(window->width, window->height), + rendererFlags, screen, visual); +} +nsresult nsPluginInstanceOwner::Renderer::DrawWithXlib( + cairo_surface_t* xsurface, nsIntPoint offset, nsIntRect* clipRects, + uint32_t numClipRects) { + Screen* screen = cairo_xlib_surface_get_screen(xsurface); + Colormap colormap; + Visual* visual; + if (!gfxXlibSurface::GetColormapAndVisual(xsurface, &colormap, &visual)) { + NS_ERROR("Failed to get visual and colormap"); + return NS_ERROR_UNEXPECTED; + } + + nsNPAPIPluginInstance* instance = mInstanceOwner->mInstance; + if (!instance) return NS_ERROR_FAILURE; + + // See if the plugin must be notified of new window parameters. + bool doupdatewindow = false; + + if (mWindow->x != offset.x || mWindow->y != offset.y) { + mWindow->x = offset.x; + mWindow->y = offset.y; + doupdatewindow = true; + } + + if (nsIntSize(mWindow->width, mWindow->height) != mPluginSize) { + mWindow->width = mPluginSize.width; + mWindow->height = mPluginSize.height; + doupdatewindow = true; + } + + // The clip rect is relative to drawable top-left. + NS_ASSERTION(numClipRects <= 1, "We don't support multiple clip rectangles!"); + nsIntRect clipRect; + if (numClipRects) { + clipRect.x = clipRects[0].x; + clipRect.y = clipRects[0].y; + clipRect.width = clipRects[0].width; + clipRect.height = clipRects[0].height; + // NPRect members are unsigned, but clip rectangles should be contained by + // the surface. + NS_ASSERTION(clipRect.x >= 0 && clipRect.y >= 0, + "Clip rectangle offsets are negative!"); + } else { + clipRect.x = offset.x; + clipRect.y = offset.y; + clipRect.width = mWindow->width; + clipRect.height = mWindow->height; + // Don't ask the plugin to draw outside the drawable. + // This also ensures that the unsigned clip rectangle offsets won't be -ve. + clipRect.IntersectRect( + clipRect, nsIntRect(0, 0, cairo_xlib_surface_get_width(xsurface), + cairo_xlib_surface_get_height(xsurface))); + } + + NPRect newClipRect; + newClipRect.left = clipRect.x; + newClipRect.top = clipRect.y; + newClipRect.right = clipRect.XMost(); + newClipRect.bottom = clipRect.YMost(); + if (mWindow->clipRect.left != newClipRect.left || + mWindow->clipRect.top != newClipRect.top || + mWindow->clipRect.right != newClipRect.right || + mWindow->clipRect.bottom != newClipRect.bottom) { + mWindow->clipRect = newClipRect; + doupdatewindow = true; + } + + NPSetWindowCallbackStruct* ws_info = + static_cast<NPSetWindowCallbackStruct*>(mWindow->ws_info); +# ifdef MOZ_X11 + if (ws_info->visual != visual || ws_info->colormap != colormap) { + ws_info->visual = visual; + ws_info->colormap = colormap; + ws_info->depth = gfxXlibSurface::DepthOfVisual(screen, visual); + doupdatewindow = true; + } +# endif + + { + if (doupdatewindow) instance->SetWindow(mWindow); + } + + // Translate the dirty rect to drawable coordinates. + nsIntRect dirtyRect = mDirtyRect + offset; + if (mInstanceOwner->mFlash10Quirks) { + // Work around a bug in Flash up to 10.1 d51 at least, where expose event + // top left coordinates within the plugin-rect and not at the drawable + // origin are misinterpreted. (We can move the top left coordinate + // provided it is within the clipRect.) + dirtyRect.SetRect(offset.x, offset.y, mDirtyRect.XMost(), + mDirtyRect.YMost()); + } + // Intersect the dirty rect with the clip rect to ensure that it lies within + // the drawable. + if (!dirtyRect.IntersectRect(dirtyRect, clipRect)) return NS_OK; + + { + XEvent pluginEvent = XEvent(); + XGraphicsExposeEvent& exposeEvent = pluginEvent.xgraphicsexpose; + // set the drawing info + exposeEvent.type = GraphicsExpose; + exposeEvent.display = DisplayOfScreen(screen); + exposeEvent.drawable = cairo_xlib_surface_get_drawable(xsurface); + exposeEvent.x = dirtyRect.x; + exposeEvent.y = dirtyRect.y; + exposeEvent.width = dirtyRect.width; + exposeEvent.height = dirtyRect.height; + exposeEvent.count = 0; + // information not set: + exposeEvent.serial = 0; + exposeEvent.send_event = X11False; + exposeEvent.major_code = 0; + exposeEvent.minor_code = 0; + + instance->HandleEvent(&pluginEvent, nullptr); + } + return NS_OK; +} +#endif + +nsresult nsPluginInstanceOwner::Init(nsIContent* aContent) { + mLastEventloopNestingLevel = GetEventloopNestingLevel(); + + mContent = do_GetWeakReference(aContent); + + // Get a frame, don't reflow. If a reflow was necessary it should have been + // done at a higher level than this (content). + nsIFrame* frame = aContent->GetPrimaryFrame(); + nsIObjectFrame* iObjFrame = do_QueryFrame(frame); + nsPluginFrame* objFrame = static_cast<nsPluginFrame*>(iObjFrame); + if (objFrame) { + SetFrame(objFrame); + // Some plugins require a specific sequence of shutdown and startup when + // a page is reloaded. Shutdown happens usually when the last instance + // is destroyed. Here we make sure the plugin instance in the old + // document is destroyed before we try to create the new one. + objFrame->PresContext()->EnsureVisible(); + } else { + MOZ_ASSERT_UNREACHABLE("Should not be initializing plugin without a frame"); + return NS_ERROR_FAILURE; + } + + // register context menu listener + mCXMenuListener = new nsPluginDOMContextMenuListener(aContent); + + aContent->AddEventListener(u"focus"_ns, this, false, false); + aContent->AddEventListener(u"blur"_ns, this, false, false); + aContent->AddEventListener(u"mouseup"_ns, this, false, false); + aContent->AddEventListener(u"mousedown"_ns, this, false, false); + aContent->AddEventListener(u"mousemove"_ns, this, false, false); + aContent->AddEventListener(u"click"_ns, this, false, false); + aContent->AddEventListener(u"dblclick"_ns, this, false, false); + aContent->AddEventListener(u"mouseover"_ns, this, false, false); + aContent->AddEventListener(u"mouseout"_ns, this, false, false); + // "keypress" event should be handled when it's in the default event group + // if the event is fired in content. Otherwise, it should be handled when + // it's in the system event group. + aContent->AddEventListener(u"keypress"_ns, this, true); + aContent->AddSystemEventListener(u"keypress"_ns, this, true); + aContent->AddEventListener(u"keydown"_ns, this, true); + aContent->AddEventListener(u"keyup"_ns, this, true); + aContent->AddEventListener(u"drop"_ns, this, true); + aContent->AddEventListener(u"drag"_ns, this, true); + aContent->AddEventListener(u"dragenter"_ns, this, true); + aContent->AddEventListener(u"dragover"_ns, this, true); + aContent->AddEventListener(u"dragleave"_ns, this, true); + aContent->AddEventListener(u"dragexit"_ns, this, true); + aContent->AddEventListener(u"dragstart"_ns, this, true); + aContent->AddEventListener(u"dragend"_ns, this, true); + aContent->AddSystemEventListener(u"compositionstart"_ns, this, true); + aContent->AddSystemEventListener(u"compositionend"_ns, this, true); + aContent->AddSystemEventListener(u"text"_ns, this, true); + + return NS_OK; +} + +void* nsPluginInstanceOwner::GetPluginPort() { + void* result = nullptr; + if (mWidget) { +#ifdef XP_WIN + if (!mPluginWindow || mPluginWindow->type == NPWindowTypeWindow) +#endif + result = + mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT); // HWND/gdk window + } + + return result; +} + +void nsPluginInstanceOwner::ReleasePluginPort(void* pluginPort) {} + +NS_IMETHODIMP nsPluginInstanceOwner::CreateWidget(void) { + NS_ENSURE_TRUE(mPluginWindow, NS_ERROR_NULL_POINTER); + + // Can't call this twice! + if (mWidget) { + NS_WARNING("Trying to create a plugin widget twice!"); + return NS_ERROR_FAILURE; + } + + bool windowless = false; + mInstance->IsWindowless(&windowless); + if (!windowless) { +#ifndef XP_WIN + // Only Windows supports windowed mode! + MOZ_ASSERT_UNREACHABLE(); + return NS_ERROR_FAILURE; +#else + // Try to get a parent widget, on some platforms widget creation will fail + // without a parent. + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIWidget> parentWidget; + Document* doc = nullptr; + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (content) { + doc = content->OwnerDoc(); + parentWidget = nsContentUtils::WidgetForDocument(doc); + // If we're running in the content process, we need a remote widget + // created in chrome. + if (XRE_IsContentProcess()) { + if (nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow()) { + if (nsCOMPtr<nsPIDOMWindowOuter> topWindow = + window->GetInProcessTop()) { + dom::BrowserChild* tc = dom::BrowserChild::GetFrom(topWindow); + if (tc) { + // This returns a PluginWidgetProxy which remotes a number of + // calls. + rv = tc->CreatePluginWidget(parentWidget.get(), + getter_AddRefs(mWidget)); + if (NS_FAILED(rv)) { + return rv; + } + } + } + } + } + } + + // A failure here is terminal since we can't fall back on the non-e10s code + // path below. + if (!mWidget && XRE_IsContentProcess()) { + return NS_ERROR_UNEXPECTED; + } + + if (!mWidget) { + // native (single process) + mWidget = nsIWidget::CreateChildWindow(); + nsWidgetInitData initData; + initData.mWindowType = eWindowType_plugin; + initData.clipChildren = true; + initData.clipSiblings = true; + rv = mWidget->Create(parentWidget.get(), nullptr, + LayoutDeviceIntRect(0, 0, 0, 0), &initData); + if (NS_FAILED(rv)) { + mWidget->Destroy(); + mWidget = nullptr; + return rv; + } + } + + mWidget->EnableDragDrop(true); + mWidget->Show(false); + mWidget->Enable(false); +#endif // XP_WIN + } + + if (mPluginFrame) { + // nullptr widget is fine, will result in windowless setup. + mPluginFrame->PrepForDrawing(mWidget); + } + + if (windowless) { + mPluginWindow->type = NPWindowTypeDrawable; + + // this needs to be a HDC according to the spec, but I do + // not see the right way to release it so let's postpone + // passing HDC till paint event when it is really + // needed. Change spec? + mPluginWindow->window = nullptr; +#ifdef MOZ_X11 + // Fill in the display field. + NPSetWindowCallbackStruct* ws_info = + static_cast<NPSetWindowCallbackStruct*>(mPluginWindow->ws_info); + ws_info->display = DefaultXDisplay(); + + nsAutoCString description; + GetPluginDescription(description); + constexpr auto flash10Head = "Shockwave Flash 10."_ns; + mFlash10Quirks = StringBeginsWith(description, flash10Head); +#endif + } else if (mWidget) { + // mPluginWindow->type is used in |GetPluginPort| so it must + // be initialized first + mPluginWindow->type = NPWindowTypeWindow; + mPluginWindow->window = GetPluginPort(); + // tell the plugin window about the widget + mPluginWindow->SetPluginWidget(mWidget); + + // tell the widget about the current plugin instance owner. + nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget); + if (pluginWidget) { + pluginWidget->SetPluginInstanceOwner(this); + } + } + +#ifdef XP_MACOSX + if (GetDrawingModel() == NPDrawingModelCoreAnimation) { + AddToCARefreshTimer(); + } +#endif + + mWidgetCreationComplete = true; + + return NS_OK; +} + +// Mac specific code to fix up the port location and clipping region +#ifdef XP_MACOSX + +void nsPluginInstanceOwner::FixUpPluginWindow(int32_t inPaintState) { + if (!mPluginWindow || !mInstance || !mPluginFrame) { + return; + } + + SetPluginPort(); + + LayoutDeviceIntSize widgetClip = mPluginFrame->GetWidgetlessClipRect().Size(); + + mPluginWindow->x = 0; + mPluginWindow->y = 0; + + NPRect oldClipRect = mPluginWindow->clipRect; + + // fix up the clipping region + mPluginWindow->clipRect.top = 0; + mPluginWindow->clipRect.left = 0; + + if (inPaintState == ePluginPaintDisable) { + mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top; + mPluginWindow->clipRect.right = mPluginWindow->clipRect.left; + } else if (inPaintState == ePluginPaintEnable) { + mPluginWindow->clipRect.bottom = + mPluginWindow->clipRect.top + widgetClip.height; + mPluginWindow->clipRect.right = + mPluginWindow->clipRect.left + widgetClip.width; + } + + // if the clip rect changed, call SetWindow() + // (RealPlayer needs this to draw correctly) + if (mPluginWindow->clipRect.left != oldClipRect.left || + mPluginWindow->clipRect.top != oldClipRect.top || + mPluginWindow->clipRect.right != oldClipRect.right || + mPluginWindow->clipRect.bottom != oldClipRect.bottom) { + if (UseAsyncRendering()) { + mInstance->AsyncSetWindow(mPluginWindow); + } else { + mPluginWindow->CallSetWindow(mInstance); + } + } + + // After the first NPP_SetWindow call we need to send an initial + // top-level window focus event. + if (!mSentInitialTopLevelWindowEvent) { + // Set this before calling ProcessEvent to avoid endless recursion. + mSentInitialTopLevelWindowEvent = true; + + bool isActive = WindowIsActive(); + SendWindowFocusChanged(isActive); + mLastWindowIsActive = isActive; + } +} + +void nsPluginInstanceOwner::WindowFocusMayHaveChanged() { + if (!mSentInitialTopLevelWindowEvent) { + return; + } + + bool isActive = WindowIsActive(); + if (isActive != mLastWindowIsActive) { + SendWindowFocusChanged(isActive); + mLastWindowIsActive = isActive; + } +} + +bool nsPluginInstanceOwner::WindowIsActive() { + if (!mPluginFrame) { + return false; + } + + EventStates docState = + mPluginFrame->GetContent()->OwnerDoc()->GetDocumentState(); + return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE); +} + +void nsPluginInstanceOwner::SendWindowFocusChanged(bool aIsActive) { + if (!mInstance) { + return; + } + + NPCocoaEvent cocoaEvent; + InitializeNPCocoaEvent(&cocoaEvent); + cocoaEvent.type = NPCocoaEventWindowFocusChanged; + cocoaEvent.data.focus.hasFocus = aIsActive; + mInstance->HandleEvent(&cocoaEvent, nullptr, + NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO); +} + +void nsPluginInstanceOwner::HidePluginWindow() { + if (!mPluginWindow || !mInstance) { + return; + } + + mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top; + mPluginWindow->clipRect.right = mPluginWindow->clipRect.left; + mWidgetVisible = false; + if (UseAsyncRendering()) { + mInstance->AsyncSetWindow(mPluginWindow); + } else { + mInstance->SetWindow(mPluginWindow); + } +} + +#else // XP_MACOSX + +void nsPluginInstanceOwner::UpdateWindowPositionAndClipRect(bool aSetWindow) { + if (!mPluginWindow) return; + + // For windowless plugins a non-empty clip rectangle will be + // passed to the plugin during paint, an additional update + // of the the clip rectangle here is not required + if (aSetWindow && !mWidget && mPluginWindowVisible && !UseAsyncRendering()) + return; + + const NPWindow oldWindow = *mPluginWindow; + + bool windowless = (mPluginWindow->type == NPWindowTypeDrawable); + nsIntPoint origin = mPluginFrame->GetWindowOriginInPixels(windowless); + + mPluginWindow->x = origin.x; + mPluginWindow->y = origin.y; + + mPluginWindow->clipRect.left = 0; + mPluginWindow->clipRect.top = 0; + + if (mPluginWindowVisible && mPluginDocumentActiveState) { + mPluginWindow->clipRect.right = mPluginWindow->width; + mPluginWindow->clipRect.bottom = mPluginWindow->height; + } else { + mPluginWindow->clipRect.right = 0; + mPluginWindow->clipRect.bottom = 0; + } + + if (!aSetWindow) return; + + if (mPluginWindow->x != oldWindow.x || mPluginWindow->y != oldWindow.y || + mPluginWindow->clipRect.left != oldWindow.clipRect.left || + mPluginWindow->clipRect.top != oldWindow.clipRect.top || + mPluginWindow->clipRect.right != oldWindow.clipRect.right || + mPluginWindow->clipRect.bottom != oldWindow.clipRect.bottom) { + CallSetWindow(); + } +} + +void nsPluginInstanceOwner::UpdateWindowVisibility(bool aVisible) { + mPluginWindowVisible = aVisible; + UpdateWindowPositionAndClipRect(true); +} +#endif // XP_MACOSX + +void nsPluginInstanceOwner::ResolutionMayHaveChanged() { +#if defined(XP_MACOSX) || defined(XP_WIN) + double scaleFactor = 1.0; + GetContentsScaleFactor(&scaleFactor); + if (scaleFactor != mLastScaleFactor) { + ContentsScaleFactorChanged(scaleFactor); + mLastScaleFactor = scaleFactor; + } +#endif + float zoomFactor = 1.0; + GetCSSZoomFactor(&zoomFactor); + if (zoomFactor != mLastCSSZoomFactor) { + if (mInstance) { + mInstance->CSSZoomFactorChanged(zoomFactor); + } + mLastCSSZoomFactor = zoomFactor; + } +} + +void nsPluginInstanceOwner::UpdateDocumentActiveState(bool aIsActive) { + AUTO_PROFILER_LABEL("nsPluginInstanceOwner::UpdateDocumentActiveState", + OTHER); + + mPluginDocumentActiveState = aIsActive; +#ifndef XP_MACOSX + UpdateWindowPositionAndClipRect(true); + + // We don't have a connection to PluginWidgetParent in the chrome + // process when dealing with tab visibility changes, so this needs + // to be forwarded over after the active state is updated. If we + // don't hide plugin widgets in hidden tabs, the native child window + // in chrome will remain visible after a tab switch. + if (mWidget && XRE_IsContentProcess()) { + mWidget->Show(aIsActive); + mWidget->Enable(aIsActive); + } +#endif // #ifndef XP_MACOSX +} + +NS_IMETHODIMP +nsPluginInstanceOwner::CallSetWindow() { + if (!mWidgetCreationComplete) { + // No widget yet, we can't run this code + return NS_OK; + } + if (mPluginFrame) { + mPluginFrame->CallSetWindow(false); + } else if (mInstance) { + if (UseAsyncRendering()) { + mInstance->AsyncSetWindow(mPluginWindow); + } else { + mInstance->SetWindow(mPluginWindow); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPluginInstanceOwner::GetContentsScaleFactor(double* result) { + NS_ENSURE_ARG_POINTER(result); + double scaleFactor = 1.0; + // On Mac, device pixels need to be translated to (and from) "display pixels" + // for plugins. On other platforms, plugin coordinates are always in device + // pixels. +#if defined(XP_MACOSX) || defined(XP_WIN) + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + PresShell* presShell = + nsContentUtils::FindPresShellForDocument(content->OwnerDoc()); + if (presShell) { + scaleFactor = double(AppUnitsPerCSSPixel()) / + presShell->GetPresContext() + ->DeviceContext() + ->AppUnitsPerDevPixelAtUnitFullZoom(); + } +#endif + *result = scaleFactor; + return NS_OK; +} + +void nsPluginInstanceOwner::GetCSSZoomFactor(float* result) { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + PresShell* presShell = + nsContentUtils::FindPresShellForDocument(content->OwnerDoc()); + if (presShell) { + *result = presShell->GetPresContext()->DeviceContext()->GetFullZoom(); + } else { + *result = 1.0; + } +} + +void nsPluginInstanceOwner::SetFrame(nsPluginFrame* aFrame) { + // Don't do anything if the frame situation hasn't changed. + if (mPluginFrame == aFrame) { + return; + } + + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + + // If we already have a frame that is changing or going away... + if (mPluginFrame) { + if (content && content->OwnerDoc()->GetWindow()) { + nsCOMPtr<EventTarget> windowRoot = + content->OwnerDoc()->GetWindow()->GetTopWindowRoot(); + if (windowRoot) { + windowRoot->RemoveEventListener(u"activate"_ns, this, false); + windowRoot->RemoveEventListener(u"deactivate"_ns, this, false); + windowRoot->RemoveEventListener(u"MozPerformDelayedBlur"_ns, this, + false); + } + } + + // Make sure the old frame isn't holding a reference to us. + mPluginFrame->SetInstanceOwner(nullptr); + } + + // Swap in the new frame (or no frame) + mPluginFrame = aFrame; + + // Set up a new frame + if (mPluginFrame) { + mPluginFrame->SetInstanceOwner(this); + // Can only call PrepForDrawing on an object frame once. Don't do it here + // unless widget creation is complete. Doesn't matter if we actually have a + // widget. + if (mWidgetCreationComplete) { + mPluginFrame->PrepForDrawing(mWidget); + } + mPluginFrame->FixupWindow( + mPluginFrame->GetContentRectRelativeToSelf().Size()); + mPluginFrame->InvalidateFrame(); + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + const nsIContent* content = aFrame->GetContent(); + if (fm && content) { + mContentFocused = (content == fm->GetFocusedElement()); + } + + // Register for widget-focus events on the window root. + if (content && content->OwnerDoc()->GetWindow()) { + nsCOMPtr<EventTarget> windowRoot = + content->OwnerDoc()->GetWindow()->GetTopWindowRoot(); + if (windowRoot) { + windowRoot->AddEventListener(u"activate"_ns, this, false, false); + windowRoot->AddEventListener(u"deactivate"_ns, this, false, false); + windowRoot->AddEventListener(u"MozPerformDelayedBlur"_ns, this, false, + false); + } + } + } +} + +nsPluginFrame* nsPluginInstanceOwner::GetFrame() { return mPluginFrame; } + +NS_IMETHODIMP nsPluginInstanceOwner::PrivateModeChanged(bool aEnabled) { + return mInstance ? mInstance->PrivateModeStateChanged(aEnabled) : NS_OK; +} + +nsIURI* nsPluginInstanceOwner::GetBaseURI() const { + nsCOMPtr<nsIContent> content = do_QueryReferent(mContent); + if (!content) { + return nullptr; + } + return content->GetBaseURI(); +} + +// static +void nsPluginInstanceOwner::GeneratePluginEvent( + const WidgetCompositionEvent* aSrcCompositionEvent, + WidgetCompositionEvent* aDistCompositionEvent) { +#ifdef XP_WIN + NPEvent newEvent; + switch (aDistCompositionEvent->mMessage) { + case eCompositionChange: { + newEvent.event = WM_IME_COMPOSITION; + newEvent.wParam = 0; + if (aSrcCompositionEvent && + (aSrcCompositionEvent->mMessage == eCompositionCommit || + aSrcCompositionEvent->mMessage == eCompositionCommitAsIs)) { + newEvent.lParam = GCS_RESULTSTR; + } else { + newEvent.lParam = GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE; + } + TextRangeArray* ranges = aDistCompositionEvent->mRanges; + if (ranges && ranges->HasCaret()) { + newEvent.lParam |= GCS_CURSORPOS; + } + break; + } + + case eCompositionStart: + newEvent.event = WM_IME_STARTCOMPOSITION; + newEvent.wParam = 0; + newEvent.lParam = 0; + break; + + case eCompositionEnd: + newEvent.event = WM_IME_ENDCOMPOSITION; + newEvent.wParam = 0; + newEvent.lParam = 0; + break; + + default: + return; + } + aDistCompositionEvent->mPluginEvent.Copy(newEvent); +#endif +} + +// nsPluginDOMContextMenuListener class implementation + +nsPluginDOMContextMenuListener::nsPluginDOMContextMenuListener( + nsIContent* aContent) { + aContent->AddEventListener(u"contextmenu"_ns, this, true); +} + +nsPluginDOMContextMenuListener::~nsPluginDOMContextMenuListener() = default; + +NS_IMPL_ISUPPORTS(nsPluginDOMContextMenuListener, nsIDOMEventListener) + +NS_IMETHODIMP +nsPluginDOMContextMenuListener::HandleEvent(Event* aEvent) { + aEvent->PreventDefault(); // consume event + + return NS_OK; +} + +void nsPluginDOMContextMenuListener::Destroy(nsIContent* aContent) { + // Unregister context menu listener + aContent->RemoveEventListener(u"contextmenu"_ns, this, true); +} diff --git a/dom/plugins/base/nsPluginInstanceOwner.h b/dom/plugins/base/nsPluginInstanceOwner.h new file mode 100644 index 0000000000..a868c22f49 --- /dev/null +++ b/dom/plugins/base/nsPluginInstanceOwner.h @@ -0,0 +1,396 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPluginInstanceOwner_h_ +#define nsPluginInstanceOwner_h_ + +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/HTMLObjectElementBinding.h" +#include "npapi.h" +#include "nsCOMPtr.h" +#include "nsIKeyEventInPluginCallback.h" +#include "nsIPluginInstanceOwner.h" +#include "nsIPrivacyTransitionObserver.h" +#include "nsIDOMEventListener.h" +#include "nsPluginHost.h" +#include "nsPluginNativeWindow.h" +#include "nsWeakReference.h" +#include "gfxRect.h" + +#ifdef XP_MACOSX +# include "mozilla/gfx/QuartzSupport.h" +# include <ApplicationServices/ApplicationServices.h> +#endif + +class nsIInputStream; +class nsPluginDOMContextMenuListener; +class nsPluginFrame; +class nsDisplayListBuilder; + +#if defined(MOZ_X11) +class gfxContext; +#endif + +namespace mozilla { +class TextComposition; +namespace dom { +class Element; +class Event; +} // namespace dom +namespace widget { +class PuppetWidget; +} // namespace widget +} // namespace mozilla + +using mozilla::widget::PuppetWidget; + +#ifdef MOZ_X11 +# include "gfxXlibNativeRenderer.h" +#endif + +class nsPluginInstanceOwner final : public nsIPluginInstanceOwner, + public nsIDOMEventListener, + public nsIPrivacyTransitionObserver, + public nsIKeyEventInPluginCallback, + public nsSupportsWeakReference { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + nsPluginInstanceOwner(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPLUGININSTANCEOWNER + NS_DECL_NSIPRIVACYTRANSITIONOBSERVER + + NS_IMETHOD GetURL(const char* aURL, const char* aTarget, + nsIInputStream* aPostStream, void* aHeadersData, + uint32_t aHeadersDataLen, + bool aDoCheckLoadURIChecks) override; + + NPBool ConvertPoint(double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace) override; + + NPError InitAsyncSurface(NPSize* size, NPImageFormat format, void* initData, + NPAsyncSurface* surface) override; + NPError FinalizeAsyncSurface(NPAsyncSurface* surface) override; + void SetCurrentAsyncSurface(NPAsyncSurface* surface, + NPRect* changed) override; + + /** + * Get the type of the HTML tag that was used to instantiate this + * plugin. Currently supported tags are EMBED or OBJECT. + */ + NS_IMETHOD GetTagType(nsPluginTagType* aResult); + + void GetParameters(nsTArray<mozilla::dom::MozPluginParameter>& parameters); + void GetAttributes(nsTArray<mozilla::dom::MozPluginParameter>& attributes); + + /** + * Returns the DOM element corresponding to the tag which references + * this plugin in the document. + * + * @param aDOMElement - resulting DOM element + * @result - NS_OK if this operation was successful + */ + NS_IMETHOD GetDOMElement(mozilla::dom::Element** aResult); + + // nsIDOMEventListener interfaces + NS_DECL_NSIDOMEVENTLISTENER + + MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult + ProcessMouseDown(mozilla::dom::Event* aMouseEvent); + nsresult ProcessKeyPress(mozilla::dom::Event* aKeyEvent); + nsresult Destroy(); + +#ifdef XP_WIN + void Paint(const RECT& aDirty, HDC aDC); +#elif defined(XP_MACOSX) + void Paint(const gfxRect& aDirtyRect, CGContextRef cgContext); + void RenderCoreAnimation(CGContextRef aCGContext, int aWidth, int aHeight); + void DoCocoaEventDrawRect(const gfxRect& aDrawRect, CGContextRef cgContext); +#elif defined(MOZ_X11) + void Paint(gfxContext* aContext, const gfxRect& aFrameRect, + const gfxRect& aDirtyRect); +#endif + + // locals + + nsresult Init(nsIContent* aContent); + + void* GetPluginPort(); + void ReleasePluginPort(void* pluginPort); + + nsEventStatus ProcessEvent(const mozilla::WidgetGUIEvent& anEvent); + + static void GeneratePluginEvent( + const mozilla::WidgetCompositionEvent* aSrcCompositionEvent, + mozilla::WidgetCompositionEvent* aDistCompositionEvent); + +#if defined(XP_WIN) + void SetWidgetWindowAsParent(HWND aWindowToAdopt); + nsresult SetNetscapeWindowAsParent(HWND aWindowToAdopt); +#endif + +#ifdef XP_MACOSX + enum {ePluginPaintEnable, ePluginPaintDisable}; + + void WindowFocusMayHaveChanged(); + + bool WindowIsActive(); + void SendWindowFocusChanged(bool aIsActive); + NPDrawingModel GetDrawingModel(); + bool IsRemoteDrawingCoreAnimation(); + + NPEventModel GetEventModel(); + static void CARefresh(nsITimer* aTimer, void* aClosure); + void AddToCARefreshTimer(); + void RemoveFromCARefreshTimer(); + // This calls into the plugin (NPP_SetWindow) and can run script. + void FixUpPluginWindow(int32_t inPaintState); + void HidePluginWindow(); + // Set plugin port info in the plugin (in the 'window' member of the + // NPWindow structure passed to the plugin by SetWindow()). + void SetPluginPort(); +#else // XP_MACOSX + void UpdateWindowPositionAndClipRect(bool aSetWindow); + void UpdateWindowVisibility(bool aVisible); +#endif // XP_MACOSX + + void ResolutionMayHaveChanged(); +#if defined(XP_MACOSX) || defined(XP_WIN) + nsresult ContentsScaleFactorChanged(double aContentsScaleFactor); +#endif + + void UpdateDocumentActiveState(bool aIsActive); + + void SetFrame(nsPluginFrame* aFrame); + nsPluginFrame* GetFrame(); + + uint32_t GetLastEventloopNestingLevel() const { + return mLastEventloopNestingLevel; + } + + static uint32_t GetEventloopNestingLevel(); + + void ConsiderNewEventloopNestingLevel() { + uint32_t currentLevel = GetEventloopNestingLevel(); + + if (currentLevel < mLastEventloopNestingLevel) { + mLastEventloopNestingLevel = currentLevel; + } + } + + const char* GetPluginName() { + if (mInstance && mPluginHost) { + const char* name = nullptr; + if (NS_SUCCEEDED(mPluginHost->GetPluginName(mInstance, &name)) && name) + return name; + } + return ""; + } + +#ifdef MOZ_X11 + void GetPluginDescription(nsACString& aDescription) { + aDescription.Truncate(); + if (mInstance && mPluginHost) { + nsCOMPtr<nsIPluginTag> pluginTag; + + mPluginHost->GetPluginTagForInstance(mInstance, + getter_AddRefs(pluginTag)); + if (pluginTag) { + pluginTag->GetDescription(aDescription); + } + } + } +#endif + + bool SendNativeEvents() { +#ifdef XP_WIN + // XXX we should remove the plugin name check + return mPluginWindow->type == NPWindowTypeDrawable && + (MatchPluginName("Shockwave Flash") || + MatchPluginName("Test Plug-in")); +#elif defined(MOZ_X11) || defined(XP_MACOSX) + return true; +#else + return false; +#endif + } + + bool MatchPluginName(const char* aPluginName) { + return strncmp(GetPluginName(), aPluginName, strlen(aPluginName)) == 0; + } + + void NotifyPaintWaiter(nsDisplayListBuilder* aBuilder); + + // Returns the image container that has our currently displayed image. + already_AddRefed<mozilla::layers::ImageContainer> GetImageContainer(); + // Returns true if this is windowed plugin that can return static captures + // for scroll operations. + bool NeedsScrollImageLayer(); + + void DidComposite(); + + /** + * Returns the bounds of the current async-rendered surface. This can only + * change in response to messages received by the event loop (i.e. not during + * painting). + */ + nsIntSize GetCurrentImageSize(); + + // Methods to update the background image we send to async plugins. + // The eventual target of these operations is PluginInstanceParent, + // but it takes several hops to get there. + void SetBackgroundUnknown(); + already_AddRefed<DrawTarget> BeginUpdateBackground(const nsIntRect& aRect); + void EndUpdateBackground(const nsIntRect& aRect); + + bool UseAsyncRendering(); + + nsIURI* GetBaseURI() const; + + bool GetCompositionString(uint32_t aIndex, nsTArray<uint8_t>* aString, + int32_t* aLength); + bool RequestCommitOrCancel(bool aCommitted); + + // See nsIKeyEventInPluginCallback + virtual void HandledWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, bool aIsConsumed) override; + + /** + * OnWindowedPluginKeyEvent() is called when the plugin process receives + * native key event directly. + * + * @param aNativeKeyData The key event which was received by the + * plugin process directly. + */ + void OnWindowedPluginKeyEvent(const mozilla::NativeEventData& aNativeKeyData); + + void GetCSSZoomFactor(float* result); + + private: + virtual ~nsPluginInstanceOwner(); + + // return FALSE if LayerSurface dirty (newly created and don't have valid + // plugin content yet) + bool IsUpToDate() { + nsIntSize size; + return NS_SUCCEEDED(mInstance->GetImageSize(&size)) && + size == nsIntSize(mPluginWindow->width, mPluginWindow->height); + } + +#if defined(XP_WIN) + nsIWidget* GetContainingWidgetIfOffset(); + already_AddRefed<mozilla::TextComposition> GetTextComposition(); + void HandleNoConsumedCompositionMessage( + mozilla::WidgetCompositionEvent* aCompositionEvent, + const NPEvent* aPluginEvent); + bool mGotCompositionData; + bool mSentStartComposition; + bool mPluginDidNotHandleIMEComposition; + uint32_t mWheelScrollLines; + uint32_t mWheelScrollChars; +#endif + + nsPluginNativeWindow* mPluginWindow; + RefPtr<nsNPAPIPluginInstance> mInstance; + nsPluginFrame* mPluginFrame; + nsWeakPtr mContent; // WEAK, content owns us + nsCString mDocumentBase; + bool mWidgetCreationComplete; + nsCOMPtr<nsIWidget> mWidget; + RefPtr<nsPluginHost> mPluginHost; + +#ifdef XP_MACOSX + static mozilla::StaticRefPtr<nsITimer> sCATimer; + static nsTArray<nsPluginInstanceOwner*>* sCARefreshListeners; + bool mSentInitialTopLevelWindowEvent; + bool mLastWindowIsActive; + bool mLastContentFocused; + // True if, the next time the window is activated, we should blur ourselves. + bool mShouldBlurOnActivate; +#endif + double mLastScaleFactor; + double mLastCSSZoomFactor; + // Initially, the event loop nesting level we were created on, it's updated + // if we detect the appshell is on a lower level as long as we're not stopped. + // We delay DoStopPlugin() until the appshell reaches this level or lower. + uint32_t mLastEventloopNestingLevel; + bool mContentFocused; + bool mWidgetVisible; // used on Mac to store our widget's visible state +#ifdef MOZ_X11 + // Used with windowless plugins only, initialized in CreateWidget(). + bool mFlash10Quirks; +#endif + bool mPluginWindowVisible; + bool mPluginDocumentActiveState; + +#ifdef XP_MACOSX + NPEventModel mEventModel; + // This is a hack! UseAsyncRendering() can incorrectly return false + // when we don't have an object frame (possible as of bug 90268). + // We hack around this by always returning true if we've ever + // returned true. + bool mUseAsyncRendering; +#endif + + // pointer to wrapper for nsIDOMContextMenuListener + RefPtr<nsPluginDOMContextMenuListener> mCXMenuListener; + + nsresult DispatchKeyToPlugin(mozilla::dom::Event* aKeyEvent); + nsresult DispatchMouseToPlugin(mozilla::dom::Event* aMouseEvent, + bool aAllowPropagate = false); + nsresult DispatchFocusToPlugin(mozilla::dom::Event* aFocusEvent); + nsresult DispatchCompositionToPlugin(mozilla::dom::Event* aEvent); + +#ifdef XP_WIN + void CallDefaultProc(const mozilla::WidgetGUIEvent* aEvent); +#endif + +#ifdef XP_MACOSX + static NPBool ConvertPointPuppet(PuppetWidget* widget, + nsPluginFrame* pluginFrame, double sourceX, + double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace); + static NPBool ConvertPointNoPuppet(nsIWidget* widget, + nsPluginFrame* pluginFrame, double sourceX, + double sourceY, + NPCoordinateSpace sourceSpace, + double* destX, double* destY, + NPCoordinateSpace destSpace); + void PerformDelayedBlurs(); +#endif // XP_MACOSX + + int mLastMouseDownButtonType; + +#ifdef MOZ_X11 + class Renderer : public gfxXlibNativeRenderer { + public: + Renderer(NPWindow* aWindow, nsPluginInstanceOwner* aInstanceOwner, + const nsIntSize& aPluginSize, const nsIntRect& aDirtyRect) + : mWindow(aWindow), + mInstanceOwner(aInstanceOwner), + mPluginSize(aPluginSize), + mDirtyRect(aDirtyRect) {} + virtual nsresult DrawWithXlib(cairo_surface_t* surface, nsIntPoint offset, + nsIntRect* clipRects, + uint32_t numClipRects) override; + + private: + NPWindow* mWindow; + nsPluginInstanceOwner* mInstanceOwner; + const nsIntSize& mPluginSize; + const nsIntRect& mDirtyRect; + }; +#endif + + bool mWaitingForPaint; +}; + +#endif // nsPluginInstanceOwner_h_ diff --git a/dom/plugins/base/nsPluginLogging.h b/dom/plugins/base/nsPluginLogging.h new file mode 100644 index 0000000000..53c76bb25d --- /dev/null +++ b/dom/plugins/base/nsPluginLogging.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ + +/* Plugin Module Logging usage instructions and includes */ +//////////////////////////////////////////////////////////////////////////////// +#ifndef nsPluginLogging_h__ +#define nsPluginLogging_h__ + +#include "mozilla/Logging.h" + +//////////////////////////////////////////////////////////////////////////////// +// Basic Plugin Logging Usage Instructions +// +// 1. Set this environment variable: MOZ_LOG=<name>:<level> + +// Choose the <name> and <level> from this list (no quotes): + +// Log Names <name> +#define NPN_LOG_NAME "PluginNPN" +#define NPP_LOG_NAME "PluginNPP" +#define PLUGIN_LOG_NAME "Plugin" + +// Levels <level> +#define PLUGIN_LOG_ALWAYS mozilla::LogLevel::Error +#define PLUGIN_LOG_BASIC mozilla::LogLevel::Info +#define PLUGIN_LOG_NORMAL mozilla::LogLevel::Debug +#define PLUGIN_LOG_NOISY mozilla::LogLevel::Verbose + +// 2. You can combine logs and levels by separating them with a comma: +// My favorite Win32 Example: SET MOZ_LOG=Plugin:5,PluginNPP:5,PluginNPN:5 + +// 3. Instead of output going to the console, you can log to a file. +// Additionally, set the MOZ_LOG_FILE environment variable to point to the +// full path of a file. +// My favorite Win32 Example: SET MOZ_LOG_FILE=c:\temp\pluginLog.txt + +// 4. For complete information see the Gecko Developer guide: +// https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Gecko_Logging + +class nsPluginLogging { + public: + static mozilla::LazyLogModule gNPNLog; // 4.x NP API, calls into navigator + static mozilla::LazyLogModule gNPPLog; // 4.x NP API, calls into plugin + static mozilla::LazyLogModule gPluginLog; // general plugin log +}; + +// Quick-use macros +#define NPN_PLUGIN_LOG(a, b) MOZ_LOG(nsPluginLogging::gNPNLog, a, b) +#define NPP_PLUGIN_LOG(a, b) MOZ_LOG(nsPluginLogging::gNPPLog, a, b) +#define PLUGIN_LOG(a, b) MOZ_LOG(nsPluginLogging::gPluginLog, a, b) + +#endif // nsPluginLogging_h__ diff --git a/dom/plugins/base/nsPluginManifestLineReader.h b/dom/plugins/base/nsPluginManifestLineReader.h new file mode 100644 index 0000000000..4e2f6f3d54 --- /dev/null +++ b/dom/plugins/base/nsPluginManifestLineReader.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPluginManifestLineReader_h_ +#define nsPluginManifestLineReader_h_ + +#include "nspr.h" +#include "nsDebug.h" + +#ifdef XP_WIN +# define PLUGIN_REGISTRY_FIELD_DELIMITER '|' +#else +# define PLUGIN_REGISTRY_FIELD_DELIMITER ':' +#endif + +#define PLUGIN_REGISTRY_END_OF_LINE_MARKER '$' + +class nsPluginManifestLineReader { + public: + nsPluginManifestLineReader() : mLength(0) { + mBase = mCur = mNext = mLimit = 0; + } + ~nsPluginManifestLineReader() { + if (mBase) delete[] mBase; + mBase = 0; + } + + char* Init(uint32_t flen) { + mBase = mCur = mNext = new char[flen + 1]; + if (mBase) { + mLimit = mBase + flen; + *mLimit = 0; + } + mLength = 0; + return mBase; + } + + bool NextLine() { + if (mNext >= mLimit) return false; + + mCur = mNext; + mLength = 0; + + char* lastDelimiter = 0; + while (mNext < mLimit) { + if (IsEOL(*mNext)) { + if (lastDelimiter) { + if (lastDelimiter && + *(mNext - 1) != PLUGIN_REGISTRY_END_OF_LINE_MARKER) + return false; + *lastDelimiter = '\0'; + } else { + *mNext = '\0'; + } + + for (++mNext; mNext < mLimit; ++mNext) { + if (!IsEOL(*mNext)) break; + } + return true; + } + if (*mNext == PLUGIN_REGISTRY_FIELD_DELIMITER) lastDelimiter = mNext; + ++mNext; + ++mLength; + } + return false; + } + + int ParseLine(char** chunks, int maxChunks) { + NS_ASSERTION(mCur && maxChunks && chunks, "bad call to ParseLine"); + int found = 0; + chunks[found++] = mCur; + + if (found < maxChunks) { + for (char* cur = mCur; *cur; cur++) { + if (*cur == PLUGIN_REGISTRY_FIELD_DELIMITER) { + *cur = 0; + chunks[found++] = cur + 1; + if (found == maxChunks) break; + } + } + } + return found; + } + + char* LinePtr() { return mCur; } + uint32_t LineLength() { return mLength; } + + bool IsEOL(char c) { return c == '\n' || c == '\r'; } + + char* mBase; + + private: + char* mCur; + uint32_t mLength; + char* mNext; + char* mLimit; +}; + +#endif diff --git a/dom/plugins/base/nsPluginNativeWindow.cpp b/dom/plugins/base/nsPluginNativeWindow.cpp new file mode 100644 index 0000000000..73da098cd4 --- /dev/null +++ b/dom/plugins/base/nsPluginNativeWindow.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file is the default implementation of plugin native window + * empty stubs, it should be replaced with real platform implementation + * for every platform + */ + +#include "nsDebug.h" +#include "nsPluginNativeWindow.h" + +class nsPluginNativeWindowImpl : public nsPluginNativeWindow { + public: + nsPluginNativeWindowImpl(); + virtual ~nsPluginNativeWindowImpl(); + +#if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) + NPSetWindowCallbackStruct mWsInfo; +#endif +}; + +nsPluginNativeWindowImpl::nsPluginNativeWindowImpl() : nsPluginNativeWindow() { + // initialize the struct fields + window = nullptr; + x = 0; + y = 0; + width = 0; + height = 0; + memset(&clipRect, 0, sizeof(clipRect)); + type = NPWindowTypeWindow; + +#if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) + ws_info = &mWsInfo; + mWsInfo.type = 0; + mWsInfo.display = nullptr; + mWsInfo.visual = nullptr; + mWsInfo.colormap = 0; + mWsInfo.depth = 0; +#elif defined(XP_UNIX) && !defined(XP_MACOSX) + ws_info = nullptr; +#endif +} + +nsPluginNativeWindowImpl::~nsPluginNativeWindowImpl() = default; + +nsresult PLUG_NewPluginNativeWindow( + nsPluginNativeWindow** aPluginNativeWindow) { + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + *aPluginNativeWindow = new nsPluginNativeWindowImpl(); + return NS_OK; +} + +nsresult PLUG_DeletePluginNativeWindow( + nsPluginNativeWindow* aPluginNativeWindow) { + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + delete static_cast<nsPluginNativeWindowImpl*>(aPluginNativeWindow); + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginNativeWindow.h b/dom/plugins/base/nsPluginNativeWindow.h new file mode 100644 index 0000000000..b880a74535 --- /dev/null +++ b/dom/plugins/base/nsPluginNativeWindow.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsPluginNativeWindow_h_ +#define _nsPluginNativeWindow_h_ + +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsISupportsImpl.h" +#include "nsNPAPIPluginInstance.h" +#include "npapi.h" +#include "nsIWidget.h" + +/** + * base class for native plugin window implementations + */ +class nsPluginNativeWindow : public NPWindow { + public: + nsPluginNativeWindow() : NPWindow() { MOZ_COUNT_CTOR(nsPluginNativeWindow); } + + MOZ_COUNTED_DTOR_VIRTUAL(nsPluginNativeWindow) + + /** + * !!! CAUTION !!! + * + * The base class |nsPluginWindow| is defined as a struct in nsplugindefs.h, + * thus it does not have a destructor of its own. + * One should never attempt to delete |nsPluginNativeWindow| object instance + * (or derivatives) using a pointer of |nsPluginWindow *| type. Should such + * necessity occur it must be properly casted first. + */ + + public: + nsresult GetPluginInstance(RefPtr<nsNPAPIPluginInstance>& aPluginInstance) { + aPluginInstance = mPluginInstance; + return NS_OK; + } + nsresult SetPluginInstance(nsNPAPIPluginInstance* aPluginInstance) { + if (mPluginInstance != aPluginInstance) mPluginInstance = aPluginInstance; + return NS_OK; + } + + nsresult GetPluginWidget(nsIWidget** aWidget) const { + NS_IF_ADDREF(*aWidget = mWidget); + return NS_OK; + } + nsresult SetPluginWidget(nsIWidget* aWidget) { + mWidget = aWidget; + return NS_OK; + } + + public: + virtual nsresult CallSetWindow( + RefPtr<nsNPAPIPluginInstance>& aPluginInstance) { + // null aPluginInstance means that we want to call SetWindow(null) + if (aPluginInstance) + aPluginInstance->SetWindow(this); + else if (mPluginInstance) + mPluginInstance->SetWindow(nullptr); + + SetPluginInstance(aPluginInstance); + return NS_OK; + } + + protected: + RefPtr<nsNPAPIPluginInstance> mPluginInstance; + nsCOMPtr<nsIWidget> mWidget; +}; + +nsresult PLUG_NewPluginNativeWindow(nsPluginNativeWindow** aPluginNativeWindow); +nsresult PLUG_DeletePluginNativeWindow( + nsPluginNativeWindow* aPluginNativeWindow); + +#endif //_nsPluginNativeWindow_h_ diff --git a/dom/plugins/base/nsPluginNativeWindowWin.cpp b/dom/plugins/base/nsPluginNativeWindowWin.cpp new file mode 100644 index 0000000000..f8e6e44140 --- /dev/null +++ b/dom/plugins/base/nsPluginNativeWindowWin.cpp @@ -0,0 +1,658 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/BasicEvents.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/WeakPtr.h" + +#include "windows.h" +#include "windowsx.h" + +// XXXbz windowsx.h defines GetFirstChild, GetNextSibling, +// GetPrevSibling are macros, apparently... Eeevil. We have functions +// called that on some classes, so undef them. +#undef GetFirstChild +#undef GetNextSibling +#undef GetPrevSibling + +#include "nsDebug.h" + +#include "nsWindowsDllInterceptor.h" +#include "nsPluginNativeWindow.h" +#include "nsThreadUtils.h" +#include "nsCrashOnException.h" + +using namespace mozilla; + +#define NP_POPUP_API_VERSION 16 + +#define nsMajorVersion(v) (((int32_t)(v) >> 16) & 0xffff) +#define nsMinorVersion(v) ((int32_t)(v)&0xffff) +#define versionOK(suppliedV, requiredV) \ + (nsMajorVersion(suppliedV) == nsMajorVersion(requiredV) && \ + nsMinorVersion(suppliedV) >= nsMinorVersion(requiredV)) + +#define NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION \ + TEXT("MozillaPluginWindowPropertyAssociation") +#define NS_PLUGIN_CUSTOM_MSG_ID TEXT("MozFlashUserRelay") +#define WM_USER_FLASH WM_USER + 1 +static UINT sWM_FLASHBOUNCEMSG = 0; + +class nsPluginNativeWindowWin; + +/** + * PLEvent handling code + */ +class PluginWindowEvent : public Runnable { + public: + PluginWindowEvent(); + void Init(WeakPtr<nsPluginNativeWindowWin> aRef, HWND aWnd, UINT aMsg, + WPARAM aParam, LPARAM aLParam); + void Clear(); + HWND GetWnd() { return mWnd; }; + UINT GetMsg() { return mMsg; }; + WPARAM GetWParam() { return mWParam; }; + LPARAM GetLParam() { return mLParam; }; + bool InUse() { return mWnd != nullptr; }; + + NS_DECL_NSIRUNNABLE + + protected: + WeakPtr<nsPluginNativeWindowWin> mPluginWindowRef; + HWND mWnd; + UINT mMsg; + WPARAM mWParam; + LPARAM mLParam; +}; + +PluginWindowEvent::PluginWindowEvent() : Runnable("PluginWindowEvent") { + Clear(); +} + +void PluginWindowEvent::Clear() { + mWnd = nullptr; + mMsg = 0; + mWParam = 0; + mLParam = 0; +} + +void PluginWindowEvent::Init(WeakPtr<nsPluginNativeWindowWin> aRef, HWND aWnd, + UINT aMsg, WPARAM aWParam, LPARAM aLParam) { + NS_ASSERTION(aWnd != nullptr, "invalid plugin event value"); + NS_ASSERTION(mWnd == nullptr, "event already in use"); + mPluginWindowRef = aRef; + mWnd = aWnd; + mMsg = aMsg; + mWParam = aWParam; + mLParam = aLParam; +} + +/** + * nsPluginNativeWindow Windows specific class declaration + */ + +class nsPluginNativeWindowWin : public nsPluginNativeWindow, + public SupportsWeakPtr { + public: + nsPluginNativeWindowWin(); + + virtual nsresult CallSetWindow( + RefPtr<nsNPAPIPluginInstance>& aPluginInstance); + + private: + nsresult SubclassAndAssociateWindow(); + nsresult UndoSubclassAndAssociateWindow(); + + public: + // locals + WNDPROC GetPrevWindowProc(); + void SetPrevWindowProc(WNDPROC proc) { mPluginWinProc = proc; } + WNDPROC GetWindowProc(); + PluginWindowEvent* GetPluginWindowEvent(HWND aWnd, UINT aMsg, WPARAM aWParam, + LPARAM aLParam); + + private: + WNDPROC mPluginWinProc; + WNDPROC mPrevWinProc; + WeakPtr<nsPluginNativeWindowWin> mWeakRef; + RefPtr<PluginWindowEvent> mCachedPluginWindowEvent; + + HWND mParentWnd; + LONG_PTR mParentProc; + + public: + nsPluginHost::SpecialType mPluginType; +}; + +static bool sInPreviousMessageDispatch = false; + +static bool ProcessFlashMessageDelayed(nsPluginNativeWindowWin* aWin, + nsNPAPIPluginInstance* aInst, HWND hWnd, + UINT msg, WPARAM wParam, LPARAM lParam) { + NS_ENSURE_TRUE(aWin, false); + NS_ENSURE_TRUE(aInst, false); + + if (msg == sWM_FLASHBOUNCEMSG) { + // See PluginWindowEvent::Run() below. + NS_ASSERTION((sWM_FLASHBOUNCEMSG != 0), + "RegisterWindowMessage failed in flash plugin WM_USER message " + "handling!"); + ::CallWindowProc((WNDPROC)aWin->GetWindowProc(), hWnd, WM_USER_FLASH, + wParam, lParam); + return true; + } + + if (msg != WM_USER_FLASH) return false; // no need to delay + + // do stuff + nsCOMPtr<nsIRunnable> pwe = + aWin->GetPluginWindowEvent(hWnd, msg, wParam, lParam); + if (pwe) { + NS_DispatchToCurrentThread(pwe); + return true; + } + return false; +} + +class nsDelayedPopupsEnabledEvent : public Runnable { + public: + explicit nsDelayedPopupsEnabledEvent(nsNPAPIPluginInstance* inst) + : Runnable("nsDelayedPopupsEnabledEvent"), mInst(inst) {} + + NS_DECL_NSIRUNNABLE + + private: + RefPtr<nsNPAPIPluginInstance> mInst; +}; + +NS_IMETHODIMP nsDelayedPopupsEnabledEvent::Run() { + mInst->PushPopupsEnabledState(false); + return NS_OK; +} + +static LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam); + +/** + * New plugin window procedure + * + * e10s note - this subclass, and the hooks we set below using + * WindowsDllInterceptor are currently not in use when running with e10s. + * (Utility calls like CallSetWindow are still in use in the content process.) + * We would like to keep things this away, essentially making all the hacks here + * obsolete. Some of the mitigation work here has already been supplanted by + * code in PluginInstanceChild. The rest we eventually want to rip out. + */ +static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, + WPARAM wParam, LPARAM lParam) { + nsPluginNativeWindowWin* win = (nsPluginNativeWindowWin*)::GetProp( + hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + if (!win) return TRUE; + + // The DispatchEvent(ePluginActivate) below can trigger a reentrant focus + // event which might destroy us. Hold a strong ref on the plugin instance + // to prevent that, bug 374229. + RefPtr<nsNPAPIPluginInstance> inst; + win->GetPluginInstance(inst); + + bool enablePopups = false; + + // Activate/deactivate mouse capture on the plugin widget + // here, before we pass the Windows event to the plugin + // because its possible our widget won't get paired events + // (see bug 131007) and we'll look frozen. Note that this + // is also done in ChildWindow::DispatchMouseEvent. + switch (msg) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: { + nsCOMPtr<nsIWidget> widget; + win->GetPluginWidget(getter_AddRefs(widget)); + if (widget) widget->CaptureMouse(true); + break; + } + case WM_LBUTTONUP: + enablePopups = true; + + // fall through + case WM_MBUTTONUP: + case WM_RBUTTONUP: { + nsCOMPtr<nsIWidget> widget; + win->GetPluginWidget(getter_AddRefs(widget)); + if (widget) widget->CaptureMouse(false); + break; + } + case WM_KEYDOWN: + // Ignore repeating keydown messages... + if ((lParam & 0x40000000) != 0) { + break; + } + + // fall through + case WM_KEYUP: + enablePopups = true; + + break; + + case WM_MOUSEACTIVATE: { + // If a child window of this plug-in is already focused, + // don't focus the parent to avoid focus dance. We'll + // receive a follow up WM_SETFOCUS which will notify + // the appropriate window anyway. + HWND focusedWnd = ::GetFocus(); + if (!::IsChild((HWND)win->window, focusedWnd)) { + // Notify the dom / focus manager the plugin has focus when one of + // it's child windows receives it. OOPP specific - this code is + // critical in notifying the dom of focus changes when the plugin + // window in the child process receives focus via a mouse click. + // WM_MOUSEACTIVATE is sent by nsWindow via a custom window event + // sent from PluginInstanceParent in response to focus events sent + // from the child. (bug 540052) Note, this gui event could also be + // sent directly from widget. + nsCOMPtr<nsIWidget> widget; + win->GetPluginWidget(getter_AddRefs(widget)); + if (widget) { + WidgetGUIEvent event(true, ePluginActivate, widget); + nsEventStatus status; + widget->DispatchEvent(&event, status); + } + } + } break; + + case WM_SETFOCUS: + case WM_KILLFOCUS: { + // Make sure setfocus and killfocus get through to the widget procedure + // even if they are eaten by the plugin. Also make sure we aren't calling + // recursively. + WNDPROC prevWndProc = win->GetPrevWindowProc(); + if (prevWndProc && !sInPreviousMessageDispatch) { + sInPreviousMessageDispatch = true; + ::CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam); + sInPreviousMessageDispatch = false; + } + break; + } + } + + // Macromedia Flash plugin may flood the message queue with some special + // messages (WM_USER+1) causing 100% CPU consumption and GUI freeze, see + // mozilla bug 132759; we can prevent this from happening by delaying the + // processing such messages; + if (win->mPluginType == nsPluginHost::eSpecialType_Flash) { + if (ProcessFlashMessageDelayed(win, inst, hWnd, msg, wParam, lParam)) + return TRUE; + } + + if (enablePopups && inst) { + uint16_t apiVersion; + if (NS_SUCCEEDED(inst->GetPluginAPIVersion(&apiVersion)) && + !versionOK(apiVersion, NP_POPUP_API_VERSION)) { + inst->PushPopupsEnabledState(true); + } + } + + LRESULT res; + WNDPROC proc = (WNDPROC)win->GetWindowProc(); + if (PluginWndProc == proc) { + NS_WARNING( + "Previous plugin window procedure references PluginWndProc! " + "Report this bug!"); + res = CallWindowProc(DefWindowProc, hWnd, msg, wParam, lParam); + } else { + res = CallWindowProc(proc, hWnd, msg, wParam, lParam); + } + + if (inst) { + // Popups are enabled (were enabled before the call to + // CallWindowProc()). Some plugins (at least the flash player) + // post messages from their key handlers etc that delay the actual + // processing, so we need to delay the disabling of popups so that + // popups remain enabled when the flash player ends up processing + // the actual key handlers. We do this by posting an event that + // does the disabling, this way our disabling will happen after + // the handlers in the plugin are done. + + // Note that it's not fatal if any of this fails (which won't + // happen unless we're out of memory anyways) since the plugin + // code will pop any popup state pushed by this plugin on + // destruction. + + nsCOMPtr<nsIRunnable> event = new nsDelayedPopupsEnabledEvent(inst); + if (event) NS_DispatchToCurrentThread(event); + } + + return res; +} + +static LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT msg, WPARAM wParam, + LPARAM lParam) { + return mozilla::CallWindowProcCrashProtected(PluginWndProcInternal, hWnd, msg, + wParam, lParam); +} + +/* + * Flash will reset the subclass of our widget at various times. + * (Notably when entering and exiting full screen mode.) This + * occurs independent of the main plugin window event procedure. + * We trap these subclass calls to prevent our subclass hook from + * getting dropped. + * Note, ascii versions can be nixed once flash versions < 10.1 + * are considered obsolete. + */ +static WindowsDllInterceptor sUser32Intercept; + +#ifdef _WIN64 +typedef LONG_PTR(WINAPI* User32SetWindowLongPtrA)(HWND hWnd, int nIndex, + LONG_PTR dwNewLong); +typedef LONG_PTR(WINAPI* User32SetWindowLongPtrW)(HWND hWnd, int nIndex, + LONG_PTR dwNewLong); +static 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 +static inline bool SetWindowLongHookCheck(HWND hWnd, int nIndex, + LONG_PTR newLong) { + nsPluginNativeWindowWin* win = (nsPluginNativeWindowWin*)GetProp( + hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + if (!win || (win && win->mPluginType != nsPluginHost::eSpecialType_Flash) || + (nIndex == GWLP_WNDPROC && + newLong == reinterpret_cast<LONG_PTR>(PluginWndProc))) + return true; + return false; +} + +#ifdef _WIN64 +LONG_PTR WINAPI SetWindowLongPtrAHook(HWND hWnd, int nIndex, LONG_PTR newLong) +#else +LONG WINAPI SetWindowLongAHook(HWND hWnd, int nIndex, LONG newLong) +#endif +{ + if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) + return sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); + + // Set flash's new subclass to get the result. + LONG_PTR proc = sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); + + // We already checked this in SetWindowLongHookCheck + nsPluginNativeWindowWin* win = (nsPluginNativeWindowWin*)GetProp( + hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + + // Hook our subclass back up, just like we do on setwindow. + win->SetPrevWindowProc( + reinterpret_cast<WNDPROC>(sUser32SetWindowLongWHookStub( + hWnd, nIndex, reinterpret_cast<LONG_PTR>(PluginWndProc)))); + return proc; +} + +#ifdef _WIN64 +LONG_PTR WINAPI SetWindowLongPtrWHook(HWND hWnd, int nIndex, LONG_PTR newLong) +#else +LONG WINAPI SetWindowLongWHook(HWND hWnd, int nIndex, LONG newLong) +#endif +{ + if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) + return sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); + + // Set flash's new subclass to get the result. + LONG_PTR proc = sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); + + // We already checked this in SetWindowLongHookCheck + nsPluginNativeWindowWin* win = (nsPluginNativeWindowWin*)GetProp( + hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + + // Hook our subclass back up, just like we do on setwindow. + win->SetPrevWindowProc( + reinterpret_cast<WNDPROC>(sUser32SetWindowLongWHookStub( + hWnd, nIndex, reinterpret_cast<LONG_PTR>(PluginWndProc)))); + return proc; +} + +static void HookSetWindowLongPtr() { + sUser32Intercept.Init("user32.dll"); +#ifdef _WIN64 + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongPtrA", + &SetWindowLongPtrAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongPtrW", + &SetWindowLongPtrWHook); +#else + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongA", + &SetWindowLongAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongW", + &SetWindowLongWHook); +#endif +} + +/** + * nsPluginNativeWindowWin implementation + */ +nsPluginNativeWindowWin::nsPluginNativeWindowWin() : nsPluginNativeWindow() { + // initialize the struct fields + window = nullptr; + x = 0; + y = 0; + width = 0; + height = 0; + type = NPWindowTypeWindow; + + mPrevWinProc = nullptr; + mPluginWinProc = nullptr; + mPluginType = nsPluginHost::eSpecialType_None; + + mParentWnd = nullptr; + mParentProc = 0; +} + +WNDPROC nsPluginNativeWindowWin::GetPrevWindowProc() { return mPrevWinProc; } + +WNDPROC nsPluginNativeWindowWin::GetWindowProc() { return mPluginWinProc; } + +NS_IMETHODIMP PluginWindowEvent::Run() { + nsPluginNativeWindowWin* win = mPluginWindowRef; + if (!win) return NS_OK; + + HWND hWnd = GetWnd(); + if (!hWnd) return NS_OK; + + RefPtr<nsNPAPIPluginInstance> inst; + win->GetPluginInstance(inst); + + if (GetMsg() == WM_USER_FLASH) { + // XXX Unwind issues related to runnable event callback depth for this + // event and destruction of the plugin. (Bug 493601) + ::PostMessage(hWnd, sWM_FLASHBOUNCEMSG, GetWParam(), GetLParam()); + } else { + // Currently not used, but added so that processing events here + // is more generic. + ::CallWindowProc(win->GetWindowProc(), hWnd, GetMsg(), GetWParam(), + GetLParam()); + } + + Clear(); + return NS_OK; +} + +PluginWindowEvent* nsPluginNativeWindowWin::GetPluginWindowEvent( + HWND aWnd, UINT aMsg, WPARAM aWParam, LPARAM aLParam) { + if (!mWeakRef) { + mWeakRef = this; + if (!mWeakRef) return nullptr; + } + + PluginWindowEvent* event; + + // We have the ability to alloc if needed in case in the future some plugin + // should post multiple PostMessages. However, this could lead to many + // alloc's per second which could become a performance issue. See bug 169247. + if (!mCachedPluginWindowEvent) { + event = new PluginWindowEvent(); + mCachedPluginWindowEvent = event; + } else if (mCachedPluginWindowEvent->InUse()) { + event = new PluginWindowEvent(); + } else { + event = mCachedPluginWindowEvent; + } + + event->Init(mWeakRef, aWnd, aMsg, aWParam, aLParam); + return event; +} + +nsresult nsPluginNativeWindowWin::CallSetWindow( + RefPtr<nsNPAPIPluginInstance>& aPluginInstance) { + // Note, 'window' can be null + + // check the incoming instance, null indicates that window is going away and + // we are not interested in subclassing business any more, undo and don't + // subclass + if (!aPluginInstance) { + UndoSubclassAndAssociateWindow(); + // release plugin instance + SetPluginInstance(nullptr); + nsPluginNativeWindow::CallSetWindow(aPluginInstance); + return NS_OK; + } + + // check plugin mime type and cache it if it will need special treatment later + if (mPluginType == nsPluginHost::eSpecialType_None) { + const char* mimetype = nullptr; + if (NS_SUCCEEDED(aPluginInstance->GetMIMEType(&mimetype)) && mimetype) { + mPluginType = nsPluginHost::GetSpecialType(nsDependentCString(mimetype)); + } + } + + // With e10s we execute in the content process and as such we don't + // have access to native widgets. CallSetWindow and skip native widget + // subclassing. + if (!XRE_IsParentProcess()) { + nsPluginNativeWindow::CallSetWindow(aPluginInstance); + return NS_OK; + } + + if (!sWM_FLASHBOUNCEMSG) { + sWM_FLASHBOUNCEMSG = ::RegisterWindowMessage(NS_PLUGIN_CUSTOM_MSG_ID); + } + + if (window) { + // grab the widget procedure before the plug-in does a subclass in + // setwindow. We'll use this in PluginWndProc for forwarding focus + // events to the widget. + WNDPROC currentWndProc = + (WNDPROC)::GetWindowLongPtr((HWND)window, GWLP_WNDPROC); + if (!mPrevWinProc && currentWndProc != PluginWndProc) + mPrevWinProc = currentWndProc; + } + + nsPluginNativeWindow::CallSetWindow(aPluginInstance); + + SubclassAndAssociateWindow(); + + if (window && mPluginType == nsPluginHost::eSpecialType_Flash && + !GetPropW((HWND)window, L"PluginInstanceParentProperty")) { + HookSetWindowLongPtr(); + } + + return NS_OK; +} + +nsresult nsPluginNativeWindowWin::SubclassAndAssociateWindow() { + if (type != NPWindowTypeWindow || !window) return NS_ERROR_FAILURE; + + HWND hWnd = (HWND)window; + + // check if we need to subclass + WNDPROC currentWndProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + if (currentWndProc == PluginWndProc) return NS_OK; + + // If the plugin reset the subclass, set it back. + if (mPluginWinProc) { +#ifdef DEBUG + NS_WARNING("A plugin cleared our subclass - resetting."); + if (currentWndProc != mPluginWinProc) { + NS_WARNING("Procedures do not match up, discarding old subclass value."); + } + if (mPrevWinProc && currentWndProc == mPrevWinProc) { + NS_WARNING("The new procedure is our widget procedure?"); + } +#endif + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PluginWndProc); + return NS_OK; + } + + LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE); + // Out of process plugins must not have the WS_CLIPCHILDREN style set on their + // parent windows or else synchronous paints (via UpdateWindow() and others) + // will cause deadlocks. + if (::GetPropW(hWnd, L"PluginInstanceParentProperty")) + style &= ~WS_CLIPCHILDREN; + else + style |= WS_CLIPCHILDREN; + SetWindowLongPtr(hWnd, GWL_STYLE, style); + + mPluginWinProc = + (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PluginWndProc); + if (!mPluginWinProc) return NS_ERROR_FAILURE; + + DebugOnly<nsPluginNativeWindowWin*> win = (nsPluginNativeWindowWin*)::GetProp( + hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + NS_ASSERTION(!win || (win == this), + "plugin window already has property and this is not us"); + + if (!::SetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION, (HANDLE)this)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult nsPluginNativeWindowWin::UndoSubclassAndAssociateWindow() { + // remove window property + HWND hWnd = (HWND)window; + if (IsWindow(hWnd)) ::RemoveProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); + + // restore the original win proc + // but only do this if this were us last time + if (mPluginWinProc) { + WNDPROC currentWndProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + if (currentWndProc == PluginWndProc) + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)mPluginWinProc); + mPluginWinProc = nullptr; + + LONG_PTR style = GetWindowLongPtr(hWnd, GWL_STYLE); + style &= ~WS_CLIPCHILDREN; + SetWindowLongPtr(hWnd, GWL_STYLE, style); + } + + if (mPluginType == nsPluginHost::eSpecialType_Flash && mParentWnd) { + ::SetWindowLongPtr(mParentWnd, GWLP_WNDPROC, mParentProc); + mParentWnd = nullptr; + mParentProc = 0; + } + + return NS_OK; +} + +nsresult PLUG_NewPluginNativeWindow( + nsPluginNativeWindow** aPluginNativeWindow) { + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + + *aPluginNativeWindow = new nsPluginNativeWindowWin(); + return NS_OK; +} + +nsresult PLUG_DeletePluginNativeWindow( + nsPluginNativeWindow* aPluginNativeWindow) { + NS_ENSURE_ARG_POINTER(aPluginNativeWindow); + nsPluginNativeWindowWin* p = (nsPluginNativeWindowWin*)aPluginNativeWindow; + delete p; + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginStreamListenerPeer.cpp b/dom/plugins/base/nsPluginStreamListenerPeer.cpp new file mode 100644 index 0000000000..df54a66d67 --- /dev/null +++ b/dom/plugins/base/nsPluginStreamListenerPeer.cpp @@ -0,0 +1,606 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPluginStreamListenerPeer.h" +#include "nsIContentPolicy.h" +#include "nsContentPolicyUtils.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIFileChannel.h" +#include "nsMimeTypes.h" +#include "nsNetCID.h" +#include "nsPluginInstanceOwner.h" +#include "nsPluginLogging.h" +#include "nsIURI.h" +#include "nsPluginHost.h" +#include "nsIMultiPartChannel.h" +#include "nsPrintfCString.h" +#include "nsIScriptGlobalObject.h" +#include "mozilla/dom/Document.h" +#include "nsIWebNavigation.h" +#include "nsContentUtils.h" +#include "nsNetUtil.h" +#include "nsPluginNativeWindow.h" +#include "GeckoProfiler.h" +#include "nsPluginInstanceOwner.h" +#include "nsDataHashtable.h" +#include "mozilla/NullPrincipal.h" + +// nsPluginStreamListenerPeer + +NS_IMPL_ISUPPORTS(nsPluginStreamListenerPeer, nsIStreamListener, + nsIRequestObserver, nsIHttpHeaderVisitor, + nsISupportsWeakReference, nsIInterfaceRequestor, + nsIChannelEventSink) + +nsPluginStreamListenerPeer::nsPluginStreamListenerPeer() : mLength(0) { + mStreamType = NP_NORMAL; + mStartBinding = false; + mRequestFailed = false; + + mPendingRequests = 0; + mHaveFiredOnStartRequest = false; + + mUseLocalCache = false; + mModified = 0; + mStreamOffset = 0; + mStreamComplete = 0; +} + +nsPluginStreamListenerPeer::~nsPluginStreamListenerPeer() { +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginStreamListenerPeer::dtor this=%p, url=%s\n", this, + mURLSpec.get())); +#endif + + if (mPStreamListener) { + mPStreamListener->SetStreamListenerPeer(nullptr); + } +} + +// Called as a result of GetURL and PostURL, or by the host in the case of the +// initial plugin stream. +nsresult nsPluginStreamListenerPeer::Initialize( + nsIURI* aURL, nsNPAPIPluginInstance* aInstance, + nsNPAPIPluginStreamListener* aListener) { +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, + ("nsPluginStreamListenerPeer::Initialize instance=%p, url=%s\n", + aInstance, aURL ? aURL->GetSpecOrDefault().get() : "")); + + PR_LogFlush(); +#endif + + // Not gonna work out + if (!aInstance) { + return NS_ERROR_FAILURE; + } + + mURL = aURL; + + NS_ASSERTION( + mPluginInstance == nullptr, + "nsPluginStreamListenerPeer::Initialize mPluginInstance != nullptr"); + mPluginInstance = aInstance; + + // If the plugin did not request this stream, e.g. the initial stream, we wont + // have a nsNPAPIPluginStreamListener yet - this will be handled by + // SetUpStreamListener + if (aListener) { + mPStreamListener = aListener; + mPStreamListener->SetStreamListenerPeer(this); + } + + mPendingRequests = 1; + + return NS_OK; +} + +NS_IMETHODIMP +nsPluginStreamListenerPeer::OnStartRequest(nsIRequest* request) { + nsresult rv = NS_OK; + AUTO_PROFILER_LABEL("nsPluginStreamListenerPeer::OnStartRequest", OTHER); + + if (mRequests.IndexOfObject(request) == -1) { + NS_ASSERTION(mRequests.Count() == 0, + "Only our initial stream should be unknown!"); + TrackRequest(request); + } + + if (mHaveFiredOnStartRequest) { + return NS_OK; + } + + mHaveFiredOnStartRequest = true; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + // deal with 404 (Not Found) HTTP response, + // just return, this causes the request to be ignored. + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + uint32_t responseCode = 0; + rv = httpChannel->GetResponseStatus(&responseCode); + if (NS_FAILED(rv)) { + // NPP_Notify() will be called from OnStopRequest + // in nsNPAPIPluginStreamListener::CleanUpStream + // return error will cancel this request + // ...and we also need to tell the plugin that + mRequestFailed = true; + return NS_ERROR_FAILURE; + } + + if (responseCode > 206) { // not normal + uint32_t wantsAllNetworkStreams = 0; + + // We don't always have an instance here already, but if we do, check + // to see if it wants all streams. + if (mPluginInstance) { + rv = mPluginInstance->GetValueFromPlugin( + NPPVpluginWantsAllNetworkStreams, &wantsAllNetworkStreams); + // If the call returned an error code make sure we still use our default + // value. + if (NS_FAILED(rv)) { + wantsAllNetworkStreams = 0; + } + } + + if (!wantsAllNetworkStreams) { + mRequestFailed = true; + return NS_ERROR_FAILURE; + } + } + } + + nsAutoCString contentType; + rv = channel->GetContentType(contentType); + if (NS_FAILED(rv)) return rv; + + // Check ShouldProcess with content policy + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + + int16_t shouldLoad = nsIContentPolicy::ACCEPT; + rv = NS_CheckContentProcessPolicy(mURL, loadInfo, contentType, &shouldLoad); + if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { + mRequestFailed = true; + return NS_ERROR_CONTENT_BLOCKED; + } + + // Get the notification callbacks from the channel and save it as + // week ref we'll use it in nsPluginStreamInfo::RequestRead() when + // we'll create channel for byte range request. + nsCOMPtr<nsIInterfaceRequestor> callbacks; + channel->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) mWeakPtrChannelCallbacks = do_GetWeakReference(callbacks); + + nsCOMPtr<nsILoadGroup> loadGroup; + channel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) mWeakPtrChannelLoadGroup = do_GetWeakReference(loadGroup); + + int64_t length; + rv = channel->GetContentLength(&length); + + // it's possible for the server to not send a Content-Length. + // we should still work in this case. + if (NS_FAILED(rv) || length < 0 || length > UINT32_MAX) { + // check out if this is file channel + nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel); + if (fileChannel) { + // file does not exist + mRequestFailed = true; + return NS_ERROR_FAILURE; + } + mLength = 0; + } else { + mLength = uint32_t(length); + } + + nsCOMPtr<nsIURI> aURL; + rv = channel->GetURI(getter_AddRefs(aURL)); + if (NS_FAILED(rv)) return rv; + + aURL->GetSpec(mURLSpec); + + if (!contentType.IsEmpty()) mContentType = contentType; + +#ifdef PLUGIN_LOGGING + MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NOISY, + ("nsPluginStreamListenerPeer::OnStartRequest this=%p request=%p " + "mime=%s, url=%s\n", + this, request, contentType.get(), mURLSpec.get())); + + PR_LogFlush(); +#endif + + // Set up the stream listener... + rv = SetUpStreamListener(request, aURL); + if (NS_FAILED(rv)) { + return rv; + } + + return rv; +} + +NS_IMETHODIMP nsPluginStreamListenerPeer::OnProgress(nsIRequest* request, + int64_t aProgress, + int64_t aProgressMax) { + nsresult rv = NS_OK; + return rv; +} + +NS_IMETHODIMP nsPluginStreamListenerPeer::OnStatus(nsIRequest* request, + nsresult aStatus, + const char16_t* aStatusArg) { + return NS_OK; +} + +nsresult nsPluginStreamListenerPeer::GetContentType(char** result) { + *result = const_cast<char*>(mContentType.get()); + return NS_OK; +} + +nsresult nsPluginStreamListenerPeer::GetLength(uint32_t* result) { + *result = mLength; + return NS_OK; +} + +nsresult nsPluginStreamListenerPeer::GetLastModified(uint32_t* result) { + *result = mModified; + return NS_OK; +} + +nsresult nsPluginStreamListenerPeer::GetURL(const char** result) { + *result = mURLSpec.get(); + return NS_OK; +} + +nsresult nsPluginStreamListenerPeer::GetStreamOffset(int32_t* result) { + *result = mStreamOffset; + return NS_OK; +} + +nsresult nsPluginStreamListenerPeer::SetStreamOffset(int32_t value) { + mStreamOffset = value; + return NS_OK; +} + +NS_IMETHODIMP nsPluginStreamListenerPeer::OnDataAvailable( + nsIRequest* request, nsIInputStream* aIStream, uint64_t sourceOffset, + uint32_t aLength) { + if (mRequests.IndexOfObject(request) == -1) { + MOZ_ASSERT(false, "Received OnDataAvailable for untracked request."); + return NS_ERROR_UNEXPECTED; + } + + if (mRequestFailed) return NS_ERROR_FAILURE; + + nsresult rv = NS_OK; + + if (!mPStreamListener) return NS_ERROR_FAILURE; + + const char* url = nullptr; + GetURL(&url); + + PLUGIN_LOG(PLUGIN_LOG_NOISY, + ("nsPluginStreamListenerPeer::OnDataAvailable this=%p request=%p, " + "offset=%" PRIu64 ", length=%u, url=%s\n", + this, request, sourceOffset, aLength, url ? url : "no url set")); + + nsCOMPtr<nsIInputStream> stream = aIStream; + rv = mPStreamListener->OnDataAvailable(this, stream, aLength); + + // if a plugin returns an error, the peer must kill the stream + // else the stream and PluginStreamListener leak + if (NS_FAILED(rv)) { + request->Cancel(rv); + } + + return rv; +} + +NS_IMETHODIMP nsPluginStreamListenerPeer::OnStopRequest(nsIRequest* request, + nsresult aStatus) { + nsresult rv = NS_OK; + + nsCOMPtr<nsIMultiPartChannel> mp = do_QueryInterface(request); + if (!mp) { + bool found = mRequests.RemoveObject(request); + if (!found) { + NS_ERROR("Received OnStopRequest for untracked request."); + } + } + + PLUGIN_LOG( + PLUGIN_LOG_NOISY, + ("nsPluginStreamListenerPeer::OnStopRequest this=%p aStatus=%" PRIu32 + " request=%p\n", + this, static_cast<uint32_t>(aStatus), request)); + + // if we still have pending stuff to do, lets not close the plugin socket. + if (--mPendingRequests > 0) return NS_OK; + + if (!mPStreamListener) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if (!channel) return NS_ERROR_FAILURE; + // Set the content type to ensure we don't pass null to the plugin + nsAutoCString aContentType; + rv = channel->GetContentType(aContentType); + if (NS_FAILED(rv) && !mRequestFailed) return rv; + + if (!aContentType.IsEmpty()) mContentType = aContentType; + + // set error status if stream failed so we notify the plugin + if (mRequestFailed) aStatus = NS_ERROR_FAILURE; + + if (NS_FAILED(aStatus)) { + // on error status cleanup the stream + // and return w/o OnFileAvailable() + mPStreamListener->OnStopBinding(this, aStatus); + return NS_OK; + } + + if (mStartBinding) { + // On start binding has been called + mPStreamListener->OnStopBinding(this, aStatus); + } else { + // OnStartBinding hasn't been called, so complete the action. + mPStreamListener->OnStartBinding(this); + mPStreamListener->OnStopBinding(this, aStatus); + } + + if (NS_SUCCEEDED(aStatus)) { + mStreamComplete = true; + } + + return NS_OK; +} + +nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest* request, + nsIURI* aURL) { + nsresult rv = NS_OK; + + // If we don't yet have a stream listener, we need to get + // one from the plugin. + // NOTE: this should only happen when a stream was NOT created + // with GetURL or PostURL (i.e. it's the initial stream we + // send to the plugin as determined by the SRC or DATA attribute) + if (!mPStreamListener) { + if (!mPluginInstance) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsNPAPIPluginStreamListener> streamListener; + rv = mPluginInstance->NewStreamListener(nullptr, nullptr, + getter_AddRefs(streamListener)); + if (NS_FAILED(rv) || !streamListener) { + return NS_ERROR_FAILURE; + } + + mPStreamListener = + static_cast<nsNPAPIPluginStreamListener*>(streamListener.get()); + } + + mPStreamListener->SetStreamListenerPeer(this); + + // get httpChannel to retrieve some info we need for nsIPluginStreamInfo setup + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel); + + /* + * Assumption + * By the time nsPluginStreamListenerPeer::OnDataAvailable() gets + * called, all the headers have been read. + */ + if (httpChannel) { + // Reassemble the HTTP response status line and provide it to our + // listener. Would be nice if we could get the raw status line, + // but nsIHttpChannel doesn't currently provide that. + // Status code: required; the status line isn't useful without it. + uint32_t statusNum; + if (NS_SUCCEEDED(httpChannel->GetResponseStatus(&statusNum)) && + statusNum < 1000) { + // HTTP version: provide if available. Defaults to empty string. + nsCString ver; + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(channel); + if (httpChannelInternal) { + uint32_t major, minor; + if (NS_SUCCEEDED( + httpChannelInternal->GetResponseVersion(&major, &minor))) { + ver = nsPrintfCString("/%" PRIu32 ".%" PRIu32, major, minor); + } + } + + // Status text: provide if available. Defaults to "OK". + nsCString statusText; + if (NS_FAILED(httpChannel->GetResponseStatusText(statusText))) { + statusText = "OK"; + } + + // Assemble everything and pass to listener. + nsPrintfCString status("HTTP%s %" PRIu32 " %s", ver.get(), statusNum, + statusText.get()); + static_cast<nsIHTTPHeaderListener*>(mPStreamListener) + ->StatusLine(status.get()); + } + + // Also provide all HTTP response headers to our listener. + rv = httpChannel->VisitResponseHeaders(this); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + // we require a content len + // get Last-Modified header for plugin info + nsAutoCString lastModified; + if (NS_SUCCEEDED( + httpChannel->GetResponseHeader("last-modified"_ns, lastModified)) && + !lastModified.IsEmpty()) { + PRTime time64; + PR_ParseTimeString(lastModified.get(), true, + &time64); // convert string time to integer time + + // Convert PRTime to unix-style time_t, i.e. seconds since the epoch + double fpTime = double(time64); + mModified = (uint32_t)(fpTime * 1e-6 + 0.5); + } + } + + MOZ_ASSERT(!mRequest); + mRequest = request; + + rv = mPStreamListener->OnStartBinding(this); + + mStartBinding = true; + + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +NS_IMETHODIMP +nsPluginStreamListenerPeer::VisitHeader(const nsACString& header, + const nsACString& value) { + return mPStreamListener->NewResponseHeader(PromiseFlatCString(header).get(), + PromiseFlatCString(value).get()); +} + +nsresult nsPluginStreamListenerPeer::GetInterfaceGlobal(const nsIID& aIID, + void** result) { + if (!mPluginInstance) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsPluginInstanceOwner> owner = mPluginInstance->GetOwner(); + if (!owner) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<mozilla::dom::Document> doc; + nsresult rv = owner->GetDocument(getter_AddRefs(doc)); + if (NS_FAILED(rv) || !doc) { + return NS_ERROR_FAILURE; + } + + nsPIDOMWindowOuter* window = doc->GetWindow(); + if (!window) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window); + nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(webNav); + if (!ir) { + return NS_ERROR_FAILURE; + } + + return ir->GetInterface(aIID, result); +} + +NS_IMETHODIMP +nsPluginStreamListenerPeer::GetInterface(const nsIID& aIID, void** result) { + // Provide nsIChannelEventSink ourselves, otherwise let our document's + // script global object owner provide the interface. + if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { + return QueryInterface(aIID, result); + } + + return GetInterfaceGlobal(aIID, result); +} + +/** + * Proxy class which forwards async redirect notifications back to the necko + * callback, keeping nsPluginStreamListenerPeer::mRequests in sync with + * which channel is active. + */ +class ChannelRedirectProxyCallback : public nsIAsyncVerifyRedirectCallback { + public: + ChannelRedirectProxyCallback(nsPluginStreamListenerPeer* listener, + nsIAsyncVerifyRedirectCallback* parent, + nsIChannel* oldChannel, nsIChannel* newChannel) + : mWeakListener( + do_GetWeakReference(static_cast<nsIStreamListener*>(listener))), + mParent(parent), + mOldChannel(oldChannel), + mNewChannel(newChannel) {} + + ChannelRedirectProxyCallback() = default; + + NS_DECL_ISUPPORTS + + NS_IMETHOD OnRedirectVerifyCallback(nsresult aResult) override { + if (NS_SUCCEEDED(aResult)) { + nsCOMPtr<nsIStreamListener> listener = do_QueryReferent(mWeakListener); + if (listener) + static_cast<nsPluginStreamListenerPeer*>(listener.get()) + ->ReplaceRequest(mOldChannel, mNewChannel); + } + return mParent->OnRedirectVerifyCallback(aResult); + } + + private: + virtual ~ChannelRedirectProxyCallback() = default; + + nsWeakPtr mWeakListener; + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mParent; + nsCOMPtr<nsIChannel> mOldChannel; + nsCOMPtr<nsIChannel> mNewChannel; +}; + +NS_IMPL_ISUPPORTS(ChannelRedirectProxyCallback, nsIAsyncVerifyRedirectCallback) + +NS_IMETHODIMP +nsPluginStreamListenerPeer::AsyncOnChannelRedirect( + nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags, + nsIAsyncVerifyRedirectCallback* callback) { + // Disallow redirects if we don't have a stream listener. + if (!mPStreamListener) { + return NS_ERROR_FAILURE; + } + + // Don't allow cross-origin 307/308 POST redirects. + nsCOMPtr<nsIHttpChannel> oldHttpChannel(do_QueryInterface(oldChannel)); + if (oldHttpChannel) { + uint32_t responseStatus; + nsresult rv = oldHttpChannel->GetResponseStatus(&responseStatus); + if (NS_FAILED(rv)) { + return rv; + } + if (responseStatus == 307 || responseStatus == 308) { + nsAutoCString method; + rv = oldHttpChannel->GetRequestMethod(method); + if (NS_FAILED(rv)) { + return rv; + } + if (method.EqualsLiteral("POST")) { + rv = nsContentUtils::CheckSameOrigin(oldChannel, newChannel); + if (NS_FAILED(rv)) { + return rv; + } + } + } + } + + nsCOMPtr<nsIAsyncVerifyRedirectCallback> proxyCallback = + new ChannelRedirectProxyCallback(this, callback, oldChannel, newChannel); + + // Give NPAPI a chance to control redirects. + bool notificationHandled = mPStreamListener->HandleRedirectNotification( + oldChannel, newChannel, proxyCallback); + if (notificationHandled) { + return NS_OK; + } + + // Fall back to channel event sink for window. + nsCOMPtr<nsIChannelEventSink> channelEventSink; + nsresult rv = GetInterfaceGlobal(NS_GET_IID(nsIChannelEventSink), + getter_AddRefs(channelEventSink)); + if (NS_FAILED(rv)) { + return rv; + } + + return channelEventSink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, + proxyCallback); +} diff --git a/dom/plugins/base/nsPluginStreamListenerPeer.h b/dom/plugins/base/nsPluginStreamListenerPeer.h new file mode 100644 index 0000000000..73a956ee23 --- /dev/null +++ b/dom/plugins/base/nsPluginStreamListenerPeer.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPluginStreamListenerPeer_h_ +#define nsPluginStreamListenerPeer_h_ + +#include "nscore.h" +#include "nsIFile.h" +#include "nsIRequest.h" +#include "nsIStreamListener.h" +#include "nsIProgressEventSink.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsWeakReference.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsNPAPIPluginInstance.h" +#include "nsIInterfaceRequestor.h" +#include "nsIChannelEventSink.h" + +class nsIChannel; + +/** + * When a plugin requests opens multiple requests to the same URL and + * the request must be satified by saving a file to disk, each stream + * listener holds a reference to the backing file: the file is only removed + * when all the listeners are done. + */ +class CachedFileHolder { + public: + explicit CachedFileHolder(nsIFile* cacheFile); + ~CachedFileHolder(); + + void AddRef(); + void Release(); + + nsIFile* file() const { return mFile; } + + private: + nsAutoRefCnt mRefCnt; + nsCOMPtr<nsIFile> mFile; +}; + +class nsPluginStreamListenerPeer : public nsIStreamListener, + public nsIProgressEventSink, + public nsIHttpHeaderVisitor, + public nsSupportsWeakReference, + public nsIInterfaceRequestor, + public nsIChannelEventSink { + virtual ~nsPluginStreamListenerPeer(); + + public: + nsPluginStreamListenerPeer(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROGRESSEVENTSINK + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIHTTPHEADERVISITOR + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + + // Called by GetURL and PostURL (via NewStream) or by the host in the case of + // the initial plugin stream. + nsresult Initialize(nsIURI* aURL, nsNPAPIPluginInstance* aInstance, + nsNPAPIPluginStreamListener* aListener); + + nsNPAPIPluginInstance* GetPluginInstance() { return mPluginInstance; } + + nsresult GetLength(uint32_t* result); + nsresult GetURL(const char** result); + nsresult GetLastModified(uint32_t* result); + nsresult GetContentType(char** result); + nsresult GetStreamOffset(int32_t* result); + nsresult SetStreamOffset(int32_t value); + + void TrackRequest(nsIRequest* request) { mRequests.AppendObject(request); } + + void ReplaceRequest(nsIRequest* oldRequest, nsIRequest* newRequest) { + int32_t i = mRequests.IndexOfObject(oldRequest); + if (i == -1) { + NS_ASSERTION(mRequests.Count() == 0, + "Only our initial stream should be unknown!"); + mRequests.AppendObject(oldRequest); + } else { + mRequests.ReplaceObjectAt(newRequest, i); + } + } + + void CancelRequests(nsresult status) { + // Copy the array to avoid modification during the loop. + nsCOMArray<nsIRequest> requestsCopy(mRequests); + for (int32_t i = 0; i < requestsCopy.Count(); ++i) + requestsCopy[i]->Cancel(status); + } + + void SuspendRequests() { + nsCOMArray<nsIRequest> requestsCopy(mRequests); + for (int32_t i = 0; i < requestsCopy.Count(); ++i) + requestsCopy[i]->Suspend(); + } + + void ResumeRequests() { + nsCOMArray<nsIRequest> requestsCopy(mRequests); + for (int32_t i = 0; i < requestsCopy.Count(); ++i) + requestsCopy[i]->Resume(); + } + + private: + nsresult SetUpStreamListener(nsIRequest* request, nsIURI* aURL); + nsresult GetInterfaceGlobal(const nsIID& aIID, void** result); + + nsCOMPtr<nsIURI> mURL; + nsCString + mURLSpec; // Have to keep this member because GetURL hands out char* + RefPtr<nsNPAPIPluginStreamListener> mPStreamListener; + + // Set to true if we request failed (like with a HTTP response of 404) + bool mRequestFailed; + + /* + * Set to true after nsNPAPIPluginStreamListener::OnStartBinding() has + * been called. Checked in ::OnStopRequest so we can call the + * plugin's OnStartBinding if, for some reason, it has not already + * been called. + */ + bool mStartBinding; + bool mHaveFiredOnStartRequest; + // these get passed to the plugin stream listener + uint32_t mLength; + int32_t mStreamType; + + nsCString mContentType; + bool mUseLocalCache; + nsCOMPtr<nsIRequest> mRequest; + uint32_t mModified; + RefPtr<nsNPAPIPluginInstance> mPluginInstance; + int32_t mStreamOffset; + bool mStreamComplete; + + public: + int32_t mPendingRequests; + nsWeakPtr mWeakPtrChannelCallbacks; + nsWeakPtr mWeakPtrChannelLoadGroup; + nsCOMArray<nsIRequest> mRequests; +}; + +#endif // nsPluginStreamListenerPeer_h_ diff --git a/dom/plugins/base/nsPluginTags.cpp b/dom/plugins/base/nsPluginTags.cpp new file mode 100644 index 0000000000..df4c83b881 --- /dev/null +++ b/dom/plugins/base/nsPluginTags.cpp @@ -0,0 +1,926 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPluginTags.h" + +#include "prlink.h" +#include "plstr.h" +#include "nsPluginsDir.h" +#include "nsPluginHost.h" +#include "nsIBlocklistService.h" +#include "nsPluginLogging.h" +#include "nsNPAPIPlugin.h" +#include "nsCharSeparatedTokenizer.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "nsNetUtil.h" +#include <cctype> +#include "mozilla/Encoding.h" +#include "mozilla/dom/FakePluginTagInitBinding.h" +#include "mozilla/StaticPrefs_plugin.h" + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +# include "nsCocoaFeatures.h" +#endif + +using mozilla::dom::FakePluginTagInit; +using namespace mozilla; + +// These legacy flags are used in the plugin registry. The states are now +// stored in prefs, but we still need to be able to import them. +#define NS_PLUGIN_FLAG_ENABLED 0x0001 // is this plugin enabled? +// no longer used 0x0002 // reuse only if regenerating +// pluginreg.dat +#define NS_PLUGIN_FLAG_FROMCACHE \ + 0x0004 // this plugintag info was loaded from cache +// no longer used 0x0008 // reuse only if regenerating +// pluginreg.dat +#define NS_PLUGIN_FLAG_CLICKTOPLAY 0x0020 // this is a click-to-play plugin + +static const char kPrefDefaultEnabledState[] = "plugin.default.state"; + +// The defaults here will be read from prefs and overwritten +#if defined(MOZ_SANDBOX) +# if defined(XP_WIN) || defined(XP_MACOSX) +static int32_t sFlashSandboxLevel = 3; +static int32_t sDefaultSandboxLevel = 0; +# endif +# if defined(XP_MACOSX) +static bool sEnableSandboxLogging = false; +# endif +#endif /* MOZ_SANDBOX */ +static bool sInitializedSandboxingInfo = false; + +// check comma delimited extensions +static bool ExtensionInList(const nsCString& aExtensionList, + const nsACString& aExtension) { + for (const nsACString& extension : + nsCCharSeparatedTokenizer(aExtensionList, ',').ToRange()) { + if (extension.Equals(aExtension, nsCaseInsensitiveCStringComparator)) { + return true; + } + } + return false; +} + +// Search for an extension in an extensions array, and return its +// matching mime type +static bool SearchExtensions(const nsTArray<nsCString>& aExtensions, + const nsTArray<nsCString>& aMimeTypes, + const nsACString& aFindExtension, + nsACString& aMatchingType) { + uint32_t mimes = aMimeTypes.Length(); + MOZ_ASSERT(mimes == aExtensions.Length(), + "These arrays should have matching elements"); + + aMatchingType.Truncate(); + + for (uint32_t i = 0; i < mimes; i++) { + if (ExtensionInList(aExtensions[i], aFindExtension)) { + aMatchingType = aMimeTypes[i]; + return true; + } + } + + return false; +} + +static nsCString MakeNiceFileName(const nsCString& aFileName) { + nsCString niceName = aFileName; + int32_t niceNameLength = aFileName.RFind("."); + NS_ASSERTION(niceNameLength != kNotFound, "aFileName doesn't have a '.'?"); + while (niceNameLength > 0) { + char chr = aFileName[niceNameLength - 1]; + if (!std::isalpha(chr)) + niceNameLength--; + else + break; + } + + // If it turns out that niceNameLength <= 0, we'll fall back and use the + // entire aFileName (which we've already taken care of, a few lines back). + if (niceNameLength > 0) { + niceName.Truncate(niceNameLength); + } + + ToLowerCase(niceName); + return niceName; +} + +static nsCString MakePrefNameForPlugin(const char* const subname, + nsIInternalPluginTag* aTag) { + nsCString pref; + nsAutoCString pluginName(aTag->GetNiceFileName()); + + if (pluginName.IsEmpty()) { + // Use filename if nice name fails + pluginName = aTag->FileName(); + if (pluginName.IsEmpty()) { + MOZ_ASSERT_UNREACHABLE("Plugin with no filename or nice name in list"); + pluginName.AssignLiteral("unknown-plugin-name"); + } + } + + pref.AssignLiteral("plugin."); + pref.Append(subname); + pref.Append('.'); + pref.Append(pluginName); + + return pref; +} + +static nsCString GetStatePrefNameForPlugin(nsIInternalPluginTag* aTag) { + return MakePrefNameForPlugin("state", aTag); +} + +static nsresult IsEnabledStateLockedForPlugin(nsIInternalPluginTag* aTag, + bool* aIsEnabledStateLocked) { + *aIsEnabledStateLocked = false; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + if (NS_WARN_IF(!prefs)) { + return NS_ERROR_FAILURE; + } + + Unused << prefs->PrefIsLocked(GetStatePrefNameForPlugin(aTag).get(), + aIsEnabledStateLocked); + + return NS_OK; +} + +/* nsIInternalPluginTag */ + +uint32_t nsIInternalPluginTag::sNextId; + +nsIInternalPluginTag::nsIInternalPluginTag() = default; + +nsIInternalPluginTag::nsIInternalPluginTag(const char* aName, + const char* aDescription, + const char* aFileName, + const char* aVersion) + : mName(aName), + mDescription(aDescription), + mFileName(aFileName), + mVersion(aVersion) {} + +nsIInternalPluginTag::nsIInternalPluginTag( + const char* aName, const char* aDescription, const char* aFileName, + const char* aVersion, const nsTArray<nsCString>& aMimeTypes, + const nsTArray<nsCString>& aMimeDescriptions, + const nsTArray<nsCString>& aExtensions) + : mName(aName), + mDescription(aDescription), + mFileName(aFileName), + mVersion(aVersion), + mMimeTypes(aMimeTypes.Clone()), + mMimeDescriptions(aMimeDescriptions.Clone()), + mExtensions(aExtensions.Clone()) {} + +nsIInternalPluginTag::~nsIInternalPluginTag() = default; + +bool nsIInternalPluginTag::HasExtension(const nsACString& aExtension, + nsACString& aMatchingType) const { + return SearchExtensions(mExtensions, mMimeTypes, aExtension, aMatchingType); +} + +bool nsIInternalPluginTag::HasMimeType(const nsACString& aMimeType) const { + return mMimeTypes.Contains(aMimeType, + nsCaseInsensitiveCStringArrayComparator()); +} + +/* nsPluginTag */ + +nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo, int64_t aLastModifiedTime, + uint32_t aBlocklistState) + : nsIInternalPluginTag(aPluginInfo->fName, aPluginInfo->fDescription, + aPluginInfo->fFileName, aPluginInfo->fVersion), + mId(sNextId++), + mContentProcessRunningCount(0), + mHadLocalInstance(false), + mLibrary(nullptr), + mIsFlashPlugin(false), + mSupportsAsyncRender(false), + mFullPath(aPluginInfo->fFullPath), + mLastModifiedTime(aLastModifiedTime), + mSandboxLevel(0), + mIsSandboxLoggingEnabled(false), + mBlocklistState(aBlocklistState) { + InitMime(aPluginInfo->fMimeTypeArray, aPluginInfo->fMimeDescriptionArray, + aPluginInfo->fExtensionArray, aPluginInfo->fVariantCount); + InitSandboxLevel(); + EnsureMembersAreUTF8(); + FixupVersion(); +} + +nsPluginTag::nsPluginTag(const char* aName, const char* aDescription, + const char* aFileName, const char* aFullPath, + const char* aVersion, const char* const* aMimeTypes, + const char* const* aMimeDescriptions, + const char* const* aExtensions, int32_t aVariants, + int64_t aLastModifiedTime, uint32_t aBlocklistState, + bool aArgsAreUTF8) + : nsIInternalPluginTag(aName, aDescription, aFileName, aVersion), + mId(sNextId++), + mContentProcessRunningCount(0), + mHadLocalInstance(false), + mLibrary(nullptr), + mIsFlashPlugin(false), + mSupportsAsyncRender(false), + mFullPath(aFullPath), + mLastModifiedTime(aLastModifiedTime), + mSandboxLevel(0), + mIsSandboxLoggingEnabled(false), + mBlocklistState(aBlocklistState) { + InitMime(aMimeTypes, aMimeDescriptions, aExtensions, + static_cast<uint32_t>(aVariants)); + InitSandboxLevel(); + if (!aArgsAreUTF8) EnsureMembersAreUTF8(); + FixupVersion(); +} + +nsPluginTag::nsPluginTag(uint32_t aId, const char* aName, + const char* aDescription, const char* aFileName, + const char* aFullPath, const char* aVersion, + nsTArray<nsCString> aMimeTypes, + nsTArray<nsCString> aMimeDescriptions, + nsTArray<nsCString> aExtensions, bool aIsFlashPlugin, + bool aSupportsAsyncRender, int64_t aLastModifiedTime, + int32_t aSandboxLevel, uint32_t aBlocklistState) + : nsIInternalPluginTag(aName, aDescription, aFileName, aVersion, aMimeTypes, + aMimeDescriptions, aExtensions), + mId(aId), + mContentProcessRunningCount(0), + mHadLocalInstance(false), + mLibrary(nullptr), + mIsFlashPlugin(aIsFlashPlugin), + mSupportsAsyncRender(aSupportsAsyncRender), + mLastModifiedTime(aLastModifiedTime), + mSandboxLevel(aSandboxLevel), + mIsSandboxLoggingEnabled(false), + mNiceFileName(), + mBlocklistState(aBlocklistState) {} + +nsPluginTag::~nsPluginTag() { + NS_ASSERTION(!mNext, "Risk of exhausting the stack space, bug 486349"); +} + +NS_IMPL_ISUPPORTS(nsPluginTag, nsPluginTag, nsIInternalPluginTag, nsIPluginTag) + +void nsPluginTag::InitMime(const char* const* aMimeTypes, + const char* const* aMimeDescriptions, + const char* const* aExtensions, + uint32_t aVariantCount) { + if (!aMimeTypes) { + return; + } + + for (uint32_t i = 0; i < aVariantCount; i++) { + if (!aMimeTypes[i]) { + continue; + } + + nsAutoCString mimeType(aMimeTypes[i]); + + // Convert the MIME type, which is case insensitive, to lowercase in order + // to properly handle a mixed-case type. + ToLowerCase(mimeType); + + // Look for certain special plugins. + switch (nsPluginHost::GetSpecialType(mimeType)) { + case nsPluginHost::eSpecialType_Flash: + // VLC sometimes claims to implement the Flash MIME type, and we want + // to allow users to control that separately from Adobe Flash. + if (Name().EqualsLiteral("Shockwave Flash")) { + mIsFlashPlugin = true; + } + break; + case nsPluginHost::eSpecialType_Test: + case nsPluginHost::eSpecialType_None: + default: + break; + } + + // Fill in our MIME type array. + mMimeTypes.AppendElement(mimeType); + + // Now fill in the MIME descriptions. + if (aMimeDescriptions && aMimeDescriptions[i]) { + // we should cut off the list of suffixes which the mime + // description string may have, see bug 53895 + // it is usually in form "some description (*.sf1, *.sf2)" + // so we can search for the opening round bracket + char cur = '\0'; + char pre = '\0'; + char* p = PL_strrchr(aMimeDescriptions[i], '('); + if (p && (p != aMimeDescriptions[i])) { + if ((p - 1) && *(p - 1) == ' ') { + pre = *(p - 1); + *(p - 1) = '\0'; + } else { + cur = *p; + *p = '\0'; + } + } + mMimeDescriptions.AppendElement(nsCString(aMimeDescriptions[i])); + // restore the original string + if (cur != '\0') { + *p = cur; + } + if (pre != '\0') { + *(p - 1) = pre; + } + } else { + mMimeDescriptions.AppendElement(nsCString()); + } + + // Now fill in the extensions. + if (aExtensions && aExtensions[i]) { + mExtensions.AppendElement(nsCString(aExtensions[i])); + } else { + mExtensions.AppendElement(nsCString()); + } + } +} + +void nsPluginTag::InitSandboxLevel() { + MOZ_ASSERT(sInitializedSandboxingInfo, + "Should have initialized global sandboxing info"); +#if defined(MOZ_SANDBOX) +# if defined(XP_MACOSX) + mSandboxLevel = mIsFlashPlugin ? sFlashSandboxLevel : sDefaultSandboxLevel; + mIsSandboxLoggingEnabled = mIsFlashPlugin && sEnableSandboxLogging; +# elif defined(XP_WIN) + mSandboxLevel = mIsFlashPlugin ? sFlashSandboxLevel : sDefaultSandboxLevel; +# endif /* defined(XP_MACOSX) / defined(XP_WIN) */ +#endif /* defined(MOZ_SANDBOX) */ +} + +#if !defined(XP_WIN) && !defined(XP_MACOSX) +static void ConvertToUTF8(nsCString& aString) { + Unused << UTF_8_ENCODING->DecodeWithoutBOMHandling(aString, aString); +} +#endif + +nsresult nsPluginTag::EnsureMembersAreUTF8() { +#if defined(XP_WIN) || defined(XP_MACOSX) + return NS_OK; +#else + ConvertToUTF8(mFileName); + ConvertToUTF8(mFullPath); + ConvertToUTF8(mName); + ConvertToUTF8(mDescription); + for (uint32_t i = 0; i < mMimeDescriptions.Length(); ++i) { + ConvertToUTF8(mMimeDescriptions[i]); + } + return NS_OK; +#endif +} + +void nsPluginTag::FixupVersion() { +#if defined(XP_LINUX) + if (mIsFlashPlugin) { + mVersion.ReplaceChar(',', '.'); + } +#endif +} + +NS_IMETHODIMP +nsPluginTag::GetDescription(nsACString& aDescription) { + aDescription = mDescription; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetIsFlashPlugin(bool* aIsFlash) { + *aIsFlash = mIsFlashPlugin; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetFilename(nsACString& aFileName) { + aFileName = mFileName; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetFullpath(nsACString& aFullPath) { + aFullPath = mFullPath; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetVersion(nsACString& aVersion) { + aVersion = mVersion; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetName(nsACString& aName) { + aName = mName; + return NS_OK; +} + +bool nsPluginTag::IsActive() { return IsEnabled() && !IsBlocklisted(); } + +NS_IMETHODIMP +nsPluginTag::GetActive(bool* aResult) { + *aResult = IsActive(); + return NS_OK; +} + +bool nsPluginTag::IsEnabled() { + const PluginState state = GetPluginState(); + return (state == ePluginState_Enabled) || (state == ePluginState_Clicktoplay); +} + +NS_IMETHODIMP +nsPluginTag::GetDisabled(bool* aDisabled) { + *aDisabled = !IsEnabled(); + return NS_OK; +} + +bool nsPluginTag::IsBlocklisted() { + return mBlocklistState == nsIBlocklistService::STATE_BLOCKED; +} + +NS_IMETHODIMP +nsPluginTag::GetBlocklisted(bool* aBlocklisted) { + *aBlocklisted = IsBlocklisted(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetIsEnabledStateLocked(bool* aIsEnabledStateLocked) { + return IsEnabledStateLockedForPlugin(this, aIsEnabledStateLocked); +} + +bool nsPluginTag::IsClicktoplay() { + const PluginState state = GetPluginState(); + return (state == ePluginState_Clicktoplay); +} + +NS_IMETHODIMP +nsPluginTag::GetClicktoplay(bool* aClicktoplay) { + *aClicktoplay = IsClicktoplay(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetEnabledState(uint32_t* aEnabledState) { + int32_t enabledState; + nsresult rv = NS_OK; + if (mIsFlashPlugin) { + enabledState = StaticPrefs::plugin_state_flash(); + if (enabledState == nsIPluginTag::STATE_ENABLED) { + enabledState = nsIPluginTag::STATE_CLICKTOPLAY; + } + } else { + rv = Preferences::GetInt(GetStatePrefNameForPlugin(this).get(), + &enabledState); + } + if (NS_SUCCEEDED(rv) && enabledState >= nsIPluginTag::STATE_DISABLED && + enabledState <= nsIPluginTag::STATE_ENABLED) { + *aEnabledState = (uint32_t)enabledState; + return rv; + } + + // Something went wrong fetching the plugin's state (e.g. it wasn't flash + // and the preference was not present) - use the default state: + enabledState = Preferences::GetInt(kPrefDefaultEnabledState, + nsIPluginTag::STATE_ENABLED); + if (enabledState == nsIPluginTag::STATE_ENABLED && mIsFlashPlugin) { + enabledState = nsIPluginTag::STATE_CLICKTOPLAY; + } + if (enabledState >= nsIPluginTag::STATE_DISABLED && + enabledState <= nsIPluginTag::STATE_ENABLED) { + *aEnabledState = (uint32_t)enabledState; + return NS_OK; + } + + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsPluginTag::SetEnabledState(uint32_t aEnabledState) { + if (aEnabledState >= ePluginState_MaxValue) return NS_ERROR_ILLEGAL_VALUE; + if (aEnabledState == nsIPluginTag::STATE_ENABLED && mIsFlashPlugin) { + aEnabledState = nsIPluginTag::STATE_CLICKTOPLAY; + } + uint32_t oldState = nsIPluginTag::STATE_DISABLED; + GetEnabledState(&oldState); + if (oldState != aEnabledState) { + Preferences::SetInt(GetStatePrefNameForPlugin(this).get(), aEnabledState); + if (RefPtr<nsPluginHost> host = nsPluginHost::GetInst()) { + host->UpdatePluginInfo(this); + } + } + return NS_OK; +} + +nsPluginTag::PluginState nsPluginTag::GetPluginState() { + uint32_t enabledState = nsIPluginTag::STATE_DISABLED; + GetEnabledState(&enabledState); + return (PluginState)enabledState; +} + +void nsPluginTag::SetPluginState(PluginState state) { + static_assert((uint32_t)nsPluginTag::ePluginState_Disabled == + nsIPluginTag::STATE_DISABLED, + "nsPluginTag::ePluginState_Disabled must match " + "nsIPluginTag::STATE_DISABLED"); + static_assert((uint32_t)nsPluginTag::ePluginState_Clicktoplay == + nsIPluginTag::STATE_CLICKTOPLAY, + "nsPluginTag::ePluginState_Clicktoplay must match " + "nsIPluginTag::STATE_CLICKTOPLAY"); + static_assert((uint32_t)nsPluginTag::ePluginState_Enabled == + nsIPluginTag::STATE_ENABLED, + "nsPluginTag::ePluginState_Enabled must match " + "nsIPluginTag::STATE_ENABLED"); + SetEnabledState((uint32_t)state); +} + +NS_IMETHODIMP +nsPluginTag::GetMimeTypes(nsTArray<nsCString>& aResults) { + aResults = mMimeTypes.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetMimeDescriptions(nsTArray<nsCString>& aResults) { + aResults = mMimeDescriptions.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetExtensions(nsTArray<nsCString>& aResults) { + aResults = mExtensions.Clone(); + return NS_OK; +} + +bool nsPluginTag::HasSameNameAndMimes(const nsPluginTag* aPluginTag) const { + NS_ENSURE_TRUE(aPluginTag, false); + + if ((!mName.Equals(aPluginTag->mName)) || + (mMimeTypes.Length() != aPluginTag->mMimeTypes.Length())) { + return false; + } + + for (uint32_t i = 0; i < mMimeTypes.Length(); i++) { + if (!mMimeTypes[i].Equals(aPluginTag->mMimeTypes[i])) { + return false; + } + } + + return true; +} + +NS_IMETHODIMP +nsPluginTag::GetLoaded(bool* aIsLoaded) { + *aIsLoaded = !!mPlugin; + return NS_OK; +} + +void nsPluginTag::TryUnloadPlugin(bool inShutdown) { + // We never want to send NPP_Shutdown to an in-process plugin unless + // this process is shutting down. + if (!mPlugin) { + return; + } + if (inShutdown || mPlugin->GetLibrary()->IsOOP()) { + mPlugin->Shutdown(); + mPlugin = nullptr; + } +} + +/* static */ void nsPluginTag::EnsureSandboxInformation() { + if (sInitializedSandboxingInfo) { + return; + } + MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread."); +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + Preferences::GetInt("dom.ipc.plugins.sandbox-level.default", + &sDefaultSandboxLevel); + Preferences::GetInt("dom.ipc.plugins.sandbox-level.flash", + &sFlashSandboxLevel); +# if defined(_AMD64_) + // Level 3 is now the default NPAPI sandbox level for 64-bit flash. + // We permit the user to drop the sandbox level by at most 1. This should + // be kept up to date with the default value in the firefox.js pref file. + sFlashSandboxLevel = std::max(sFlashSandboxLevel, 2); +# endif +#elif defined(XP_MACOSX) && defined(MOZ_SANDBOX) + int legacyOSMinorMax = Preferences::GetInt( + "dom.ipc.plugins.sandbox-level.flash.max-legacy-os-minor", 10); + const char* levelPref = "dom.ipc.plugins.sandbox-level.flash"; + + if (PR_GetEnv("MOZ_DISABLE_NPAPI_SANDBOX")) { + // Flash sandbox disabled + sFlashSandboxLevel = 0; + } else if (nsCocoaFeatures::macOSVersionMajor() == 10 && + nsCocoaFeatures::macOSVersionMinor() <= legacyOSMinorMax) { + const char* legacyLevelPref = "dom.ipc.plugins.sandbox-level.flash.legacy"; + int32_t compatLevel = Preferences::GetInt(legacyLevelPref, 0); + int32_t level = Preferences::GetInt(levelPref, 0); + sFlashSandboxLevel = std::min(compatLevel, level); + } else { + sFlashSandboxLevel = Preferences::GetInt(levelPref, 0); + } + sFlashSandboxLevel = ClampFlashSandboxLevel(sFlashSandboxLevel); + + // At present, Flash is the only supported plugin on macOS. + // Other test plugins are used during testing and they will use + // the default plugin sandbox level. + Preferences::GetInt("dom.ipc.plugins.sandbox-level.default", + &sDefaultSandboxLevel); + + // Enable sandbox logging in the plugin process if it has + // been turned on via prefs or environment variables. + sEnableSandboxLogging = + sFlashSandboxLevel > 0 && + (Preferences::GetBool("security.sandbox.logging.enabled") || + PR_GetEnv("MOZ_SANDBOX_LOGGING") || + PR_GetEnv("MOZ_SANDBOX_MAC_FLASH_LOGGING")); +#endif + sInitializedSandboxingInfo = true; +} + +const nsCString& nsPluginTag::GetNiceFileName() { + if (!mNiceFileName.IsEmpty()) { + return mNiceFileName; + } + + if (mIsFlashPlugin) { + mNiceFileName.AssignLiteral("flash"); + return mNiceFileName; + } + + mNiceFileName = MakeNiceFileName(mFileName); + return mNiceFileName; +} + +NS_IMETHODIMP +nsPluginTag::GetNiceName(nsACString& aResult) { + aResult = GetNiceFileName(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetBlocklistState(uint32_t* aResult) { + *aResult = mBlocklistState; + return NS_OK; +} + +void nsPluginTag::SetBlocklistState(uint32_t aBlocklistState) { + mBlocklistState = aBlocklistState; +} + +uint32_t nsPluginTag::BlocklistState() { return mBlocklistState; } + +NS_IMETHODIMP +nsPluginTag::GetLastModifiedTime(PRTime* aLastModifiedTime) { + MOZ_ASSERT(aLastModifiedTime); + *aLastModifiedTime = mLastModifiedTime; + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetId(uint32_t* aId) { + *aId = mId; + return NS_OK; +} + +/* nsFakePluginTag */ + +nsFakePluginTag::nsFakePluginTag() + : mId(sNextId++), mState(nsPluginTag::ePluginState_Disabled) {} + +nsFakePluginTag::nsFakePluginTag(uint32_t aId, + already_AddRefed<nsIURI>&& aHandlerURI, + const char* aName, const char* aDescription, + const nsTArray<nsCString>& aMimeTypes, + const nsTArray<nsCString>& aMimeDescriptions, + const nsTArray<nsCString>& aExtensions, + const nsCString& aNiceName, + const nsString& aSandboxScript) + : nsIInternalPluginTag(aName, aDescription, nullptr, nullptr, aMimeTypes, + aMimeDescriptions, aExtensions), + mId(aId), + mHandlerURI(aHandlerURI), + mNiceName(aNiceName), + mSandboxScript(aSandboxScript), + mState(nsPluginTag::ePluginState_Enabled) {} + +nsFakePluginTag::~nsFakePluginTag() = default; + +NS_IMPL_ADDREF(nsFakePluginTag) +NS_IMPL_RELEASE(nsFakePluginTag) +NS_INTERFACE_TABLE_HEAD(nsFakePluginTag) + NS_INTERFACE_TABLE_BEGIN + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsFakePluginTag, nsIPluginTag, + nsIInternalPluginTag) + NS_INTERFACE_TABLE_ENTRY(nsFakePluginTag, nsIInternalPluginTag) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsFakePluginTag, nsISupports, + nsIInternalPluginTag) + NS_INTERFACE_TABLE_ENTRY(nsFakePluginTag, nsIFakePluginTag) + NS_INTERFACE_TABLE_END +NS_INTERFACE_TABLE_TAIL + +/* static */ +nsresult nsFakePluginTag::Create(const FakePluginTagInit& aInitDictionary, + nsFakePluginTag** aPluginTag) { + NS_ENSURE_TRUE(sNextId <= PR_INT32_MAX, NS_ERROR_OUT_OF_MEMORY); + NS_ENSURE_TRUE(!aInitDictionary.mMimeEntries.IsEmpty(), NS_ERROR_INVALID_ARG); + + RefPtr<nsFakePluginTag> tag = new nsFakePluginTag(); + nsresult rv = + NS_NewURI(getter_AddRefs(tag->mHandlerURI), aInitDictionary.mHandlerURI); + NS_ENSURE_SUCCESS(rv, rv); + + CopyUTF16toUTF8(aInitDictionary.mNiceName, tag->mNiceName); + CopyUTF16toUTF8(aInitDictionary.mFullPath, tag->mFullPath); + CopyUTF16toUTF8(aInitDictionary.mName, tag->mName); + CopyUTF16toUTF8(aInitDictionary.mDescription, tag->mDescription); + CopyUTF16toUTF8(aInitDictionary.mFileName, tag->mFileName); + CopyUTF16toUTF8(aInitDictionary.mVersion, tag->mVersion); + tag->mSandboxScript = aInitDictionary.mSandboxScript; + + for (const mozilla::dom::FakePluginMimeEntry& mimeEntry : + aInitDictionary.mMimeEntries) { + CopyUTF16toUTF8(mimeEntry.mType, *tag->mMimeTypes.AppendElement()); + CopyUTF16toUTF8(mimeEntry.mDescription, + *tag->mMimeDescriptions.AppendElement()); + CopyUTF16toUTF8(mimeEntry.mExtension, *tag->mExtensions.AppendElement()); + } + + tag.forget(aPluginTag); + return NS_OK; +} + +bool nsFakePluginTag::HandlerURIMatches(nsIURI* aURI) { + bool equals = false; + return NS_SUCCEEDED(mHandlerURI->Equals(aURI, &equals)) && equals; +} + +NS_IMETHODIMP +nsFakePluginTag::GetHandlerURI(nsIURI** aResult) { + NS_IF_ADDREF(*aResult = mHandlerURI); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetSandboxScript(nsAString& aSandboxScript) { + aSandboxScript = mSandboxScript; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetDescription(/* utf-8 */ nsACString& aResult) { + aResult = mDescription; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetIsFlashPlugin(bool* aIsFlash) { + *aIsFlash = false; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetFilename(/* utf-8 */ nsACString& aResult) { + aResult = mFileName; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetFullpath(/* utf-8 */ nsACString& aResult) { + aResult = mFullPath; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetVersion(/* utf-8 */ nsACString& aResult) { + aResult = mVersion; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetName(/* utf-8 */ nsACString& aResult) { + aResult = mName; + return NS_OK; +} + +const nsCString& nsFakePluginTag::GetNiceFileName() { + // We don't try to mimic the special-cased flash/java names if the fake plugin + // claims one of their MIME types, but do allow directly setting niceName if + // emulating those is desired. + if (mNiceName.IsEmpty() && !mFileName.IsEmpty()) { + mNiceName = MakeNiceFileName(mFileName); + } + + return mNiceName; +} + +NS_IMETHODIMP +nsFakePluginTag::GetNiceName(/* utf-8 */ nsACString& aResult) { + aResult = GetNiceFileName(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetBlocklistState(uint32_t* aResult) { + // Fake tags don't currently support blocklisting + *aResult = nsIBlocklistService::STATE_NOT_BLOCKED; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetBlocklisted(bool* aBlocklisted) { + // Fake tags can't be blocklisted + *aBlocklisted = false; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetIsEnabledStateLocked(bool* aIsEnabledStateLocked) { + return IsEnabledStateLockedForPlugin(this, aIsEnabledStateLocked); +} + +bool nsFakePluginTag::IsEnabled() { + return mState == nsPluginTag::ePluginState_Enabled || + mState == nsPluginTag::ePluginState_Clicktoplay; +} + +NS_IMETHODIMP +nsFakePluginTag::GetDisabled(bool* aDisabled) { + *aDisabled = !IsEnabled(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetClicktoplay(bool* aClicktoplay) { + *aClicktoplay = (mState == nsPluginTag::ePluginState_Clicktoplay); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetEnabledState(uint32_t* aEnabledState) { + *aEnabledState = (uint32_t)mState; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::SetEnabledState(uint32_t aEnabledState) { + // There are static asserts above enforcing that this enum matches + mState = (nsPluginTag::PluginState)aEnabledState; + // FIXME-jsplugins update + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetMimeTypes(nsTArray<nsCString>& aResults) { + aResults = mMimeTypes.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetMimeDescriptions(nsTArray<nsCString>& aResults) { + aResults = mMimeDescriptions.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetExtensions(nsTArray<nsCString>& aResults) { + aResults = mExtensions.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetActive(bool* aResult) { + // Fake plugins can't be blocklisted, so this is just !Disabled + *aResult = IsEnabled(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetLastModifiedTime(PRTime* aLastModifiedTime) { + // FIXME-jsplugins What should this return, if anything? + MOZ_ASSERT(aLastModifiedTime); + *aLastModifiedTime = 0; + return NS_OK; +} + +// We don't load fake plugins out of a library, so they should always be there. +NS_IMETHODIMP +nsFakePluginTag::GetLoaded(bool* ret) { + *ret = true; + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetId(uint32_t* aId) { + *aId = mId; + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginTags.h b/dom/plugins/base/nsPluginTags.h new file mode 100644 index 0000000000..73e597cb2f --- /dev/null +++ b/dom/plugins/base/nsPluginTags.h @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPluginTags_h_ +#define nsPluginTags_h_ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIPluginTag.h" +#include "nsITimer.h" +#include "nsString.h" + +class nsIURI; +struct PRLibrary; +struct nsPluginInfo; +class nsNPAPIPlugin; + +namespace mozilla { +namespace dom { +struct FakePluginTagInit; +} // namespace dom +} // namespace mozilla + +// An interface representing plugin tags internally. +#define NS_IINTERNALPLUGINTAG_IID \ + { \ + 0xe8fdd227, 0x27da, 0x46ee, { \ + 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 \ + } \ + } + +#define NS_PLUGINTAG_IID \ + { \ + 0xcce2e8b9, 0x9702, 0x4d4b, { \ + 0xbe, 0xa4, 0x7c, 0x1e, 0x13, 0x1f, 0xaf, 0x78 \ + } \ + } +class nsIInternalPluginTag : public nsIPluginTag { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINTERNALPLUGINTAG_IID) + + nsIInternalPluginTag(); + nsIInternalPluginTag(const char* aName, const char* aDescription, + const char* aFileName, const char* aVersion); + nsIInternalPluginTag(const char* aName, const char* aDescription, + const char* aFileName, const char* aVersion, + const nsTArray<nsCString>& aMimeTypes, + const nsTArray<nsCString>& aMimeDescriptions, + const nsTArray<nsCString>& aExtensions); + + virtual bool IsEnabled() = 0; + virtual const nsCString& GetNiceFileName() = 0; + + const nsCString& Name() const { return mName; } + const nsCString& Description() const { return mDescription; } + + const nsTArray<nsCString>& MimeTypes() const { return mMimeTypes; } + + const nsTArray<nsCString>& MimeDescriptions() const { + return mMimeDescriptions; + } + + const nsTArray<nsCString>& Extensions() const { return mExtensions; } + + const nsCString& FileName() const { return mFileName; } + + const nsCString& Version() const { return mVersion; } + + // Returns true if this plugin claims it supports this MIME type. The + // comparison is done ASCII-case-insensitively. + bool HasMimeType(const nsACString& aMimeType) const; + + // Returns true if this plugin claims it supports the given extension. In + // that case, aMatchingType is set to the MIME type the plugin claims + // corresponds to this extension. The match on aExtension is done + // ASCII-case-insensitively. + bool HasExtension(const nsACString& aExtension, + /* out */ nsACString& aMatchingType) const; + + protected: + ~nsIInternalPluginTag(); + + nsCString mName; // UTF-8 + nsCString mDescription; // UTF-8 + nsCString mFileName; // UTF-8 + nsCString mVersion; // UTF-8 + nsTArray<nsCString> mMimeTypes; // UTF-8 + nsTArray<nsCString> mMimeDescriptions; // UTF-8 + nsTArray<nsCString> mExtensions; // UTF-8 + + static uint32_t sNextId; +}; +NS_DEFINE_STATIC_IID_ACCESSOR(nsIInternalPluginTag, NS_IINTERNALPLUGINTAG_IID) + +// A linked-list of plugin information that is used for instantiating plugins +// and reflecting plugin information into JavaScript. +class nsPluginTag final : public nsIInternalPluginTag { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_PLUGINTAG_IID) + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPLUGINTAG + + // These must match the STATE_* values in nsIPluginTag.idl + enum PluginState { + ePluginState_Disabled = 0, + ePluginState_Clicktoplay = 1, + ePluginState_Enabled = 2, + ePluginState_MaxValue = 3, + }; + + nsPluginTag(nsPluginInfo* aPluginInfo, int64_t aLastModifiedTime, + uint32_t aBlocklistState); + nsPluginTag(const char* aName, const char* aDescription, + const char* aFileName, const char* aFullPath, + const char* aVersion, const char* const* aMimeTypes, + const char* const* aMimeDescriptions, + const char* const* aExtensions, int32_t aVariants, + int64_t aLastModifiedTime, uint32_t aBlocklistState, + bool aArgsAreUTF8 = false); + nsPluginTag(uint32_t aId, const char* aName, const char* aDescription, + const char* aFileName, const char* aFullPath, + const char* aVersion, nsTArray<nsCString> aMimeTypes, + nsTArray<nsCString> aMimeDescriptions, + nsTArray<nsCString> aExtensions, bool aIsFlashPlugin, + bool aSupportsAsyncRender, int64_t aLastModifiedTime, + int32_t aSandboxLevel, uint32_t aBlocklistState); + + void TryUnloadPlugin(bool inShutdown); + + static void EnsureSandboxInformation(); + + // plugin is enabled and not blocklisted + bool IsActive(); + + bool IsEnabled() override; + void SetEnabled(bool enabled); + bool IsClicktoplay(); + bool IsBlocklisted(); + uint32_t BlocklistState(); + + PluginState GetPluginState(); + void SetPluginState(PluginState state); + void SetBlocklistState(uint32_t aBlocklistState); + + bool HasSameNameAndMimes(const nsPluginTag* aPluginTag) const; + const nsCString& GetNiceFileName() override; + + RefPtr<nsPluginTag> mNext; + uint32_t mId; + + // Number of PluginModuleParents living in all content processes. + size_t mContentProcessRunningCount; + + // True if we've ever created an instance of this plugin in the current + // process. + bool mHadLocalInstance; + + PRLibrary* mLibrary; + RefPtr<nsNPAPIPlugin> mPlugin; + bool mIsFlashPlugin; + bool mSupportsAsyncRender; + nsCString mFullPath; // UTF-8 + int64_t mLastModifiedTime; + nsCOMPtr<nsITimer> mUnloadTimer; + int32_t mSandboxLevel; + bool mIsSandboxLoggingEnabled; + + private: + virtual ~nsPluginTag(); + + nsCString mNiceFileName; // UTF-8 + uint32_t mBlocklistState; + + void InitMime(const char* const* aMimeTypes, + const char* const* aMimeDescriptions, + const char* const* aExtensions, uint32_t aVariantCount); + void InitSandboxLevel(); + nsresult EnsureMembersAreUTF8(); + void FixupVersion(); +}; +NS_DEFINE_STATIC_IID_ACCESSOR(nsPluginTag, NS_PLUGINTAG_IID) + +// A class representing "fake" plugin tags; that is plugin tags not +// corresponding to actual NPAPI plugins. In practice these are all +// JS-implemented plugins; maybe we want a better name for this class? +class nsFakePluginTag : public nsIInternalPluginTag, public nsIFakePluginTag { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPLUGINTAG + NS_DECL_NSIFAKEPLUGINTAG + + static nsresult Create(const mozilla::dom::FakePluginTagInit& aInitDictionary, + nsFakePluginTag** aPluginTag); + nsFakePluginTag(uint32_t aId, already_AddRefed<nsIURI>&& aHandlerURI, + const char* aName, const char* aDescription, + const nsTArray<nsCString>& aMimeTypes, + const nsTArray<nsCString>& aMimeDescriptions, + const nsTArray<nsCString>& aExtensions, + const nsCString& aNiceName, const nsString& aSandboxScript); + + bool IsEnabled() override; + const nsCString& GetNiceFileName() override; + + bool HandlerURIMatches(nsIURI* aURI); + + nsIURI* HandlerURI() const { return mHandlerURI; } + + uint32_t Id() const { return mId; } + + const nsString& SandboxScript() const { return mSandboxScript; } + + static const int32_t NOT_JSPLUGIN = -1; + + private: + nsFakePluginTag(); + virtual ~nsFakePluginTag(); + + // A unique id for this JS-implemented plugin. Registering a plugin through + // nsPluginHost::RegisterFakePlugin assigns a new id. The id is transferred + // through IPC when getting the list of JS-implemented plugins from child + // processes, so it should be consistent across processes. + // 0 is a valid id. + uint32_t mId; + + // The URI of the handler for our fake plugin. + // FIXME-jsplugins do we need to sanity check these? + nsCOMPtr<nsIURI> mHandlerURI; + + nsCString mFullPath; + nsCString mNiceName; + + nsString mSandboxScript; + + nsPluginTag::PluginState mState; +}; + +#endif // nsPluginTags_h_ diff --git a/dom/plugins/base/nsPluginsCID.h b/dom/plugins/base/nsPluginsCID.h new file mode 100644 index 0000000000..14fb48642e --- /dev/null +++ b/dom/plugins/base/nsPluginsCID.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPluginsCID_h_ +#define nsPluginsCID_h_ + +#define NS_PLUGIN_HOST_CID \ + { \ + 0x23E8FD98, 0xA625, 0x4B08, { \ + 0xBE, 0x1A, 0xF7, 0xCC, 0x18, 0xA5, 0xB1, 0x06 \ + } \ + } + +#endif // nsPluginsCID_h_ diff --git a/dom/plugins/base/nsPluginsDir.h b/dom/plugins/base/nsPluginsDir.h new file mode 100644 index 0000000000..42a24f8382 --- /dev/null +++ b/dom/plugins/base/nsPluginsDir.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPluginsDir_h_ +#define nsPluginsDir_h_ + +#include "nsError.h" +#include "nsIFile.h" + +/** + * nsPluginsDir is nearly obsolete. Directory Service should be used instead. + * It exists for the sake of one static function. + */ + +class nsPluginsDir { + public: + /** + * Determines whether or not the given file is actually a plugin file. + */ + static bool IsPluginFile(nsIFile* file); +}; + +struct PRLibrary; + +struct nsPluginInfo { + char* fName; // name of the plugin + char* fDescription; // etc. + uint32_t fVariantCount; + char** fMimeTypeArray; + char** fMimeDescriptionArray; + char** fExtensionArray; + char* fFileName; + char* fFullPath; + char* fVersion; + bool fSupportsAsyncRender; +}; + +/** + * Provides cross-platform access to a plugin file. Deals with reading + * properties from the plugin file, and loading the plugin's shared + * library. Insulates core nsIPluginHost implementations from these + * details. + */ +class nsPluginFile { +#ifndef XP_WIN + PRLibrary* pLibrary; +#endif + nsCOMPtr<nsIFile> mPlugin; + + public: + /** + * If spec corresponds to a valid plugin file, constructs a reference + * to a plugin file on disk. Plugins are typically located using the + * nsPluginsDir class. + */ + explicit nsPluginFile(nsIFile* spec); + virtual ~nsPluginFile(); + + /** + * Loads the plugin into memory using NSPR's shared-library loading + * mechanism. Handles platform differences in loading shared libraries. + */ + nsresult LoadPlugin(PRLibrary** outLibrary); + + /** + * Obtains all of the information currently available for this plugin. + * Has a library outparam which will be non-null if a library load was + * required. + */ + nsresult GetPluginInfo(nsPluginInfo& outPluginInfo, PRLibrary** outLibrary); + + /** + * Should be called after GetPluginInfo to free all allocated stuff + */ + nsresult FreePluginInfo(nsPluginInfo& PluginInfo); +}; + +#endif /* nsPluginsDir_h_ */ diff --git a/dom/plugins/base/nsPluginsDirDarwin.cpp b/dom/plugins/base/nsPluginsDirDarwin.cpp new file mode 100644 index 0000000000..8efe40f698 --- /dev/null +++ b/dom/plugins/base/nsPluginsDirDarwin.cpp @@ -0,0 +1,522 @@ +/* -*- 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/. */ + +/* + nsPluginsDirDarwin.cpp + + Mac OS X implementation of the nsPluginsDir/nsPluginsFile classes. + + by Patrick C. Beard. + */ + +#include "GeckoChildProcessHost.h" +#include "base/process_util.h" + +#include "prlink.h" +#include "prnetdb.h" +#include "nsXPCOM.h" + +#include "nsPluginsDir.h" +#include "nsNPAPIPlugin.h" +#include "nsPluginsDirUtils.h" + +#include "mozilla/UniquePtr.h" + +#include "nsCocoaFeatures.h" +#include "nsExceptionHandler.h" + +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +#include <Carbon/Carbon.h> +#include <CoreServices/CoreServices.h> +#include <mach-o/loader.h> +#include <mach-o/fat.h> + +typedef NS_NPAPIPLUGIN_CALLBACK(const char*, NP_GETMIMEDESCRIPTION)(); +typedef NS_NPAPIPLUGIN_CALLBACK(OSErr, BP_GETSUPPORTEDMIMETYPES)( + BPSupportedMIMETypes* mimeInfo, UInt32 flags); + +/* +** Returns a CFBundleRef if the path refers to a Mac OS X bundle directory. +** The caller is responsible for calling CFRelease() to deallocate. +*/ +static CFBundleRef getPluginBundle(const char* path) { + CFBundleRef bundle = nullptr; + CFStringRef pathRef = + ::CFStringCreateWithCString(nullptr, path, kCFStringEncodingUTF8); + if (pathRef) { + CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath( + nullptr, pathRef, kCFURLPOSIXPathStyle, true); + if (bundleURL) { + bundle = ::CFBundleCreate(nullptr, bundleURL); + ::CFRelease(bundleURL); + } + ::CFRelease(pathRef); + } + return bundle; +} + +bool nsPluginsDir::IsPluginFile(nsIFile* file) { + nsCString fileName; + file->GetNativeLeafName(fileName); + /* + * Don't load the VDP fake plugin, to avoid tripping a bad bug in OS X + * 10.5.3 (see bug 436575). + */ + if (!strcmp(fileName.get(), "VerifiedDownloadPlugin.plugin")) { + NS_WARNING( + "Preventing load of VerifiedDownloadPlugin.plugin (see bug 436575)"); + return false; + } + return true; +} + +// Caller is responsible for freeing returned buffer. +static char* CFStringRefToUTF8Buffer(CFStringRef cfString) { + const char* buffer = ::CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8); + if (buffer) { + return PL_strdup(buffer); + } + + int64_t bufferLength = + ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(cfString), + kCFStringEncodingUTF8) + + 1; + char* newBuffer = static_cast<char*>(moz_xmalloc(bufferLength)); + + if (!::CFStringGetCString(cfString, newBuffer, bufferLength, + kCFStringEncodingUTF8)) { + free(newBuffer); + return nullptr; + } + + newBuffer = + static_cast<char*>(moz_xrealloc(newBuffer, strlen(newBuffer) + 1)); + return newBuffer; +} + +class AutoCFTypeObject { + public: + explicit AutoCFTypeObject(CFTypeRef aObject) { mObject = aObject; } + ~AutoCFTypeObject() { ::CFRelease(mObject); } + + private: + CFTypeRef mObject; +}; + +static Boolean MimeTypeEnabled(CFDictionaryRef mimeDict) { + if (!mimeDict) { + return true; + } + + CFTypeRef value; + if (::CFDictionaryGetValueIfPresent(mimeDict, CFSTR("WebPluginTypeEnabled"), + &value)) { + if (value && ::CFGetTypeID(value) == ::CFBooleanGetTypeID()) { + return ::CFBooleanGetValue(static_cast<CFBooleanRef>(value)); + } + } + return true; +} + +static CFDictionaryRef ParsePlistForMIMETypesFilename(CFBundleRef bundle) { + CFTypeRef mimeFileName = ::CFBundleGetValueForInfoDictionaryKey( + bundle, CFSTR("WebPluginMIMETypesFilename")); + if (!mimeFileName || ::CFGetTypeID(mimeFileName) != ::CFStringGetTypeID()) { + return nullptr; + } + + FSRef homeDir; + if (::FSFindFolder(kUserDomain, kCurrentUserFolderType, kDontCreateFolder, + &homeDir) != noErr) { + return nullptr; + } + + CFURLRef userDirURL = ::CFURLCreateFromFSRef(kCFAllocatorDefault, &homeDir); + if (!userDirURL) { + return nullptr; + } + + AutoCFTypeObject userDirURLAutorelease(userDirURL); + CFStringRef mimeFilePath = ::CFStringCreateWithFormat( + kCFAllocatorDefault, nullptr, CFSTR("Library/Preferences/%@"), + static_cast<CFStringRef>(mimeFileName)); + if (!mimeFilePath) { + return nullptr; + } + + AutoCFTypeObject mimeFilePathAutorelease(mimeFilePath); + CFURLRef mimeFileURL = ::CFURLCreateWithFileSystemPathRelativeToBase( + kCFAllocatorDefault, mimeFilePath, kCFURLPOSIXPathStyle, false, + userDirURL); + if (!mimeFileURL) { + return nullptr; + } + + AutoCFTypeObject mimeFileURLAutorelease(mimeFileURL); + SInt32 errorCode = 0; + CFDataRef mimeFileData = nullptr; + Boolean result = ::CFURLCreateDataAndPropertiesFromResource( + kCFAllocatorDefault, mimeFileURL, &mimeFileData, nullptr, nullptr, + &errorCode); + if (!result) { + return nullptr; + } + + AutoCFTypeObject mimeFileDataAutorelease(mimeFileData); + if (errorCode != 0) { + return nullptr; + } + + CFPropertyListRef propertyList = ::CFPropertyListCreateFromXMLData( + kCFAllocatorDefault, mimeFileData, kCFPropertyListImmutable, nullptr); + if (!propertyList) { + return nullptr; + } + + AutoCFTypeObject propertyListAutorelease(propertyList); + if (::CFGetTypeID(propertyList) != ::CFDictionaryGetTypeID()) { + return nullptr; + } + + CFTypeRef mimeTypes = ::CFDictionaryGetValue( + static_cast<CFDictionaryRef>(propertyList), CFSTR("WebPluginMIMETypes")); + if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || + ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) { + return nullptr; + } + + return static_cast<CFDictionaryRef>(::CFRetain(mimeTypes)); +} + +static void ParsePlistPluginInfo(nsPluginInfo& info, CFBundleRef bundle) { + CFDictionaryRef mimeDict = ParsePlistForMIMETypesFilename(bundle); + + if (!mimeDict) { + CFTypeRef mimeTypes = ::CFBundleGetValueForInfoDictionaryKey( + bundle, CFSTR("WebPluginMIMETypes")); + if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || + ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) + return; + mimeDict = static_cast<CFDictionaryRef>(::CFRetain(mimeTypes)); + } + + AutoCFTypeObject mimeDictAutorelease(mimeDict); + int mimeDictKeyCount = ::CFDictionaryGetCount(mimeDict); + + // Allocate memory for mime data + int mimeDataArraySize = mimeDictKeyCount * sizeof(char*); + info.fMimeTypeArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + memset(info.fMimeTypeArray, 0, mimeDataArraySize); + info.fExtensionArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + memset(info.fExtensionArray, 0, mimeDataArraySize); + info.fMimeDescriptionArray = + static_cast<char**>(moz_xmalloc(mimeDataArraySize)); + memset(info.fMimeDescriptionArray, 0, mimeDataArraySize); + + // Allocate memory for mime dictionary keys and values + mozilla::UniquePtr<CFTypeRef[]> keys(new CFTypeRef[mimeDictKeyCount]); + mozilla::UniquePtr<CFTypeRef[]> values(new CFTypeRef[mimeDictKeyCount]); + + info.fVariantCount = 0; + + ::CFDictionaryGetKeysAndValues(mimeDict, keys.get(), values.get()); + for (int i = 0; i < mimeDictKeyCount; i++) { + CFTypeRef mimeString = keys[i]; + if (!mimeString || ::CFGetTypeID(mimeString) != ::CFStringGetTypeID()) { + continue; + } + CFTypeRef mimeDict = values[i]; + if (mimeDict && ::CFGetTypeID(mimeDict) == ::CFDictionaryGetTypeID()) { + if (!MimeTypeEnabled(static_cast<CFDictionaryRef>(mimeDict))) { + continue; + } + info.fMimeTypeArray[info.fVariantCount] = + CFStringRefToUTF8Buffer(static_cast<CFStringRef>(mimeString)); + if (!info.fMimeTypeArray[info.fVariantCount]) { + continue; + } + CFTypeRef extensions = ::CFDictionaryGetValue( + static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginExtensions")); + if (extensions && ::CFGetTypeID(extensions) == ::CFArrayGetTypeID()) { + int extensionCount = + ::CFArrayGetCount(static_cast<CFArrayRef>(extensions)); + CFMutableStringRef extensionList = + ::CFStringCreateMutable(kCFAllocatorDefault, 0); + for (int j = 0; j < extensionCount; j++) { + CFTypeRef extension = + ::CFArrayGetValueAtIndex(static_cast<CFArrayRef>(extensions), j); + if (extension && ::CFGetTypeID(extension) == ::CFStringGetTypeID()) { + if (j > 0) ::CFStringAppend(extensionList, CFSTR(",")); + ::CFStringAppend(static_cast<CFMutableStringRef>(extensionList), + static_cast<CFStringRef>(extension)); + } + } + info.fExtensionArray[info.fVariantCount] = + CFStringRefToUTF8Buffer(static_cast<CFStringRef>(extensionList)); + ::CFRelease(extensionList); + } + CFTypeRef description = + ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), + CFSTR("WebPluginTypeDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fMimeDescriptionArray[info.fVariantCount] = + CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description)); + } + info.fVariantCount++; + } +} + +nsPluginFile::nsPluginFile(nsIFile* spec) : pLibrary(nullptr), mPlugin(spec) {} + +nsPluginFile::~nsPluginFile() {} + +nsresult nsPluginFile::LoadPlugin(PRLibrary** outLibrary) { + if (!mPlugin) return NS_ERROR_NULL_POINTER; + + // 64-bit NSPR does not (yet) support bundles. So in 64-bit builds we need + // (for now) to load the bundle's executable. However this can cause + // problems: CFBundleCreate() doesn't run the bundle's executable's + // initialization code, while NSAddImage() and dlopen() do run it. So using + // NSPR's dyld loading mechanisms here (NSAddImage() or dlopen()) can cause + // a bundle's initialization code to run earlier than expected, and lead to + // crashes. See bug 577967. +#ifdef __LP64__ + char executablePath[PATH_MAX]; + executablePath[0] = '\0'; + nsAutoCString bundlePath; + mPlugin->GetNativePath(bundlePath); + CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, bundlePath.get(), + kCFStringEncodingUTF8); + if (pathRef) { + CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath( + nullptr, pathRef, kCFURLPOSIXPathStyle, true); + if (bundleURL) { + CFBundleRef bundle = ::CFBundleCreate(nullptr, bundleURL); + if (bundle) { + CFURLRef executableURL = ::CFBundleCopyExecutableURL(bundle); + if (executableURL) { + if (!::CFURLGetFileSystemRepresentation( + executableURL, true, (UInt8*)&executablePath, PATH_MAX)) + executablePath[0] = '\0'; + ::CFRelease(executableURL); + } + ::CFRelease(bundle); + } + ::CFRelease(bundleURL); + } + ::CFRelease(pathRef); + } +#else + nsAutoCString bundlePath; + mPlugin->GetNativePath(bundlePath); + const char* executablePath = bundlePath.get(); +#endif + + *outLibrary = PR_LoadLibrary(executablePath); + pLibrary = *outLibrary; + if (!pLibrary) { + return NS_ERROR_FAILURE; + } +#ifdef DEBUG + printf("[loaded plugin %s]\n", bundlePath.get()); +#endif + return NS_OK; +} + +static char* p2cstrdup(StringPtr pstr) { + int len = pstr[0]; + char* cstr = static_cast<char*>(moz_xmalloc(len + 1)); + memmove(cstr, pstr + 1, len); + cstr[len] = '\0'; + return cstr; +} + +static char* GetNextPluginStringFromHandle(Handle h, short* index) { + char* ret = p2cstrdup((unsigned char*)(*h + *index)); + *index += (ret ? strlen(ret) : 0) + 1; + return ret; +} + +/** + * Obtains all of the information currently available for this plugin. + */ +nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, + PRLibrary** outLibrary) { + *outLibrary = nullptr; + + nsresult rv = NS_OK; + + // clear out the info, except for the first field. + memset(&info, 0, sizeof(info)); + + // Try to get a bundle reference. + nsAutoCString path; + if (NS_FAILED(rv = mPlugin->GetNativePath(path))) return rv; + CFBundleRef bundle = getPluginBundle(path.get()); + + // fill in full path + info.fFullPath = PL_strdup(path.get()); + + // fill in file name + nsAutoCString fileName; + if (NS_FAILED(rv = mPlugin->GetNativeLeafName(fileName))) return rv; + info.fFileName = PL_strdup(fileName.get()); + + // Get fName + if (bundle) { + CFTypeRef name = + ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName")); + if (name && ::CFGetTypeID(name) == ::CFStringGetTypeID()) + info.fName = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(name)); + } + + // Get fDescription + if (bundle) { + CFTypeRef description = ::CFBundleGetValueForInfoDictionaryKey( + bundle, CFSTR("WebPluginDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fDescription = + CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description)); + } + + // Get fVersion + if (bundle) { + // Look for the release version first + CFTypeRef version = ::CFBundleGetValueForInfoDictionaryKey( + bundle, CFSTR("CFBundleShortVersionString")); + if (!version) // try the build version + version = + ::CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey); + if (version && ::CFGetTypeID(version) == ::CFStringGetTypeID()) + info.fVersion = + CFStringRefToUTF8Buffer(static_cast<CFStringRef>(version)); + } + + // The last thing we need to do is get MIME data + // fVariantCount, fMimeTypeArray, fExtensionArray, fMimeDescriptionArray + + // First look for data in a bundle plist + if (bundle) { + ParsePlistPluginInfo(info, bundle); + ::CFRelease(bundle); + if (info.fVariantCount > 0) return NS_OK; + } + + // Don't load "fbplugin" or any plugins whose name starts with "fbplugin_" + // (Facebook plugins) if we're running on OS X 10.10 (Yosemite) or later. + // A "fbplugin" file crashes on load, in the call to LoadPlugin() below. + // See bug 1086977. + if (fileName.EqualsLiteral("fbplugin") || + StringBeginsWith(fileName, "fbplugin_"_ns)) { + nsAutoCString msg; + msg.AppendPrintf("Preventing load of %s (see bug 1086977)", fileName.get()); + NS_WARNING(msg.get()); + return NS_ERROR_FAILURE; + } + + // The block above assumes that "fbplugin" is the filename of the plugin + // to be blocked, or that the filename starts with "fbplugin_". But we + // don't yet know for sure if this is always true. So for the time being + // record extra information in our crash logs. + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::Bug_1086977, + fileName); + + // It's possible that our plugin has 2 entry points that'll give us mime type + // info. Quicktime does this to get around the need of having admin rights to + // change mime info in the resource fork. We need to use this info instead of + // the resource. See bug 113464. + + // Sadly we have to load the library for this to work. + rv = LoadPlugin(outLibrary); + + // If we didn't crash in LoadPlugin(), remove the annotation so we don't + // sow confusion. + CrashReporter::RemoveCrashReportAnnotation( + CrashReporter::Annotation::Bug_1086977); + + if (NS_FAILED(rv)) return rv; + + // Try to get data from NP_GetMIMEDescription + if (pLibrary) { + NP_GETMIMEDESCRIPTION pfnGetMimeDesc = + (NP_GETMIMEDESCRIPTION)PR_FindFunctionSymbol( + pLibrary, NP_GETMIMEDESCRIPTION_NAME); + if (pfnGetMimeDesc) ParsePluginMimeDescription(pfnGetMimeDesc(), info); + if (info.fVariantCount) return NS_OK; + } + + // We'll fill this in using BP_GetSupportedMIMETypes and/or resource fork data + BPSupportedMIMETypes mi = {kBPSupportedMIMETypesStructVers_1, nullptr, + nullptr}; + + // Try to get data from BP_GetSupportedMIMETypes + if (pLibrary) { + BP_GETSUPPORTEDMIMETYPES pfnMime = + (BP_GETSUPPORTEDMIMETYPES)PR_FindFunctionSymbol( + pLibrary, "BP_GetSupportedMIMETypes"); + if (pfnMime && noErr == pfnMime(&mi, 0) && mi.typeStrings) { + info.fVariantCount = (**(short**)mi.typeStrings) / 2; + ::HLock(mi.typeStrings); + if (mi.infoStrings) // it's possible some plugins have infoStrings + // missing + ::HLock(mi.infoStrings); + } + } + + // Fill in the info struct based on the data in the BPSupportedMIMETypes + // struct + int variantCount = info.fVariantCount; + info.fMimeTypeArray = + static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + info.fExtensionArray = + static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + if (mi.infoStrings) { + info.fMimeDescriptionArray = + static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*))); + } + short mimeIndex = 2; + short descriptionIndex = 2; + for (int i = 0; i < variantCount; i++) { + info.fMimeTypeArray[i] = + GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex); + info.fExtensionArray[i] = + GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex); + if (mi.infoStrings) + info.fMimeDescriptionArray[i] = + GetNextPluginStringFromHandle(mi.infoStrings, &descriptionIndex); + } + + ::HUnlock(mi.typeStrings); + ::DisposeHandle(mi.typeStrings); + if (mi.infoStrings) { + ::HUnlock(mi.infoStrings); + ::DisposeHandle(mi.infoStrings); + } + + return NS_OK; +} + +nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info) { + free(info.fName); + free(info.fDescription); + int variantCount = info.fVariantCount; + for (int i = 0; i < variantCount; i++) { + free(info.fMimeTypeArray[i]); + free(info.fExtensionArray[i]); + free(info.fMimeDescriptionArray[i]); + } + free(info.fMimeTypeArray); + free(info.fMimeDescriptionArray); + free(info.fExtensionArray); + free(info.fFileName); + free(info.fFullPath); + free(info.fVersion); + + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginsDirUnix.cpp b/dom/plugins/base/nsPluginsDirUnix.cpp new file mode 100644 index 0000000000..62df31daea --- /dev/null +++ b/dom/plugins/base/nsPluginsDirUnix.cpp @@ -0,0 +1,188 @@ +/* -*- 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/. */ + +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsPluginsDir.h" +#include "nsPluginsDirUtils.h" +#include "prenv.h" +#include "prerror.h" +#include "prio.h" +#include <sys/stat.h> +#include "nsString.h" +#include "nsIFile.h" + +#define LOCAL_PLUGIN_DLL_SUFFIX ".so" +#if defined(__hpux) +# define DEFAULT_X11_PATH "/usr/lib/X11R6/" +# undef LOCAL_PLUGIN_DLL_SUFFIX +# define LOCAL_PLUGIN_DLL_SUFFIX ".sl" +# define LOCAL_PLUGIN_DLL_ALT_SUFFIX ".so" +#elif defined(_AIX) +# define DEFAULT_X11_PATH "/usr/lib" +# define LOCAL_PLUGIN_DLL_ALT_SUFFIX ".a" +#elif defined(SOLARIS) +# define DEFAULT_X11_PATH "/usr/openwin/lib/" +#elif defined(LINUX) +# define DEFAULT_X11_PATH "/usr/X11R6/lib/" +#elif defined(__APPLE__) +# define DEFAULT_X11_PATH "/usr/X11R6/lib" +# undef LOCAL_PLUGIN_DLL_SUFFIX +# define LOCAL_PLUGIN_DLL_SUFFIX ".dylib" +# define LOCAL_PLUGIN_DLL_ALT_SUFFIX ".so" +#else +# define DEFAULT_X11_PATH "" +#endif + +/* nsPluginsDir implementation */ + +bool nsPluginsDir::IsPluginFile(nsIFile* file) { + nsAutoCString filename; + if (NS_FAILED(file->GetNativeLeafName(filename))) return false; + + constexpr auto dllSuffix = nsLiteralCString{LOCAL_PLUGIN_DLL_SUFFIX}; + if (filename.Length() > dllSuffix.Length() && + StringEndsWith(filename, dllSuffix)) + return true; + +#ifdef LOCAL_PLUGIN_DLL_ALT_SUFFIX + constexpr auto dllAltSuffix = nsLiteralCString{LOCAL_PLUGIN_DLL_ALT_SUFFIX}; + if (filename.Length() > dllAltSuffix.Length() && + StringEndsWith(filename, dllAltSuffix)) + return true; +#endif + return false; +} + +/* nsPluginFile implementation */ + +nsPluginFile::nsPluginFile(nsIFile* file) : mPlugin(file) {} + +nsPluginFile::~nsPluginFile() = default; + +nsresult nsPluginFile::LoadPlugin(PRLibrary** outLibrary) { + PRLibSpec libSpec; + libSpec.type = PR_LibSpec_Pathname; + bool exists = false; + mPlugin->Exists(&exists); + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + nsresult rv; + nsAutoCString path; + rv = mPlugin->GetNativePath(path); + if (NS_FAILED(rv)) return rv; + + libSpec.value.pathname = path.get(); + + *outLibrary = PR_LoadLibraryWithFlags(libSpec, 0); + pLibrary = *outLibrary; + +#ifdef DEBUG + printf("LoadPlugin() %s returned %lx\n", libSpec.value.pathname, + (unsigned long)pLibrary); +#endif + + if (!pLibrary) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, + PRLibrary** outLibrary) { + *outLibrary = nullptr; + + info.fVersion = nullptr; + + // Sadly we have to load the library for this to work. + nsresult rv = LoadPlugin(outLibrary); + if (NS_FAILED(rv)) return rv; + + const char* (*npGetPluginVersion)() = + (const char* (*)())PR_FindFunctionSymbol(pLibrary, "NP_GetPluginVersion"); + if (npGetPluginVersion) { + info.fVersion = PL_strdup(npGetPluginVersion()); + } + + const char* (*npGetMIMEDescription)() = + (const char* (*)())PR_FindFunctionSymbol(pLibrary, + "NP_GetMIMEDescription"); + if (!npGetMIMEDescription) { + return NS_ERROR_FAILURE; + } + + const char* mimedescr = npGetMIMEDescription(); + if (!mimedescr) { + return NS_ERROR_FAILURE; + } + + rv = ParsePluginMimeDescription(mimedescr, info); + if (NS_FAILED(rv)) { + return rv; + } + + nsAutoCString path; + if (NS_FAILED(rv = mPlugin->GetNativePath(path))) return rv; + info.fFullPath = PL_strdup(path.get()); + + nsAutoCString fileName; + if (NS_FAILED(rv = mPlugin->GetNativeLeafName(fileName))) return rv; + info.fFileName = PL_strdup(fileName.get()); + + NP_GetValueFunc npGetValue = + (NP_GetValueFunc)PR_FindFunctionSymbol(pLibrary, "NP_GetValue"); + if (!npGetValue) { + return NS_ERROR_FAILURE; + } + + const char* name = nullptr; + npGetValue(nullptr, NPPVpluginNameString, &name); + if (name) { + info.fName = PL_strdup(name); + } else { + info.fName = PL_strdup(fileName.get()); + } + + const char* description = nullptr; + npGetValue(nullptr, NPPVpluginDescriptionString, &description); + if (description) { + info.fDescription = PL_strdup(description); + } else { + info.fDescription = PL_strdup(""); + } + + return NS_OK; +} + +nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info) { + if (info.fName != nullptr) PL_strfree(info.fName); + + if (info.fDescription != nullptr) PL_strfree(info.fDescription); + + for (uint32_t i = 0; i < info.fVariantCount; i++) { + if (info.fMimeTypeArray[i] != nullptr) PL_strfree(info.fMimeTypeArray[i]); + + if (info.fMimeDescriptionArray[i] != nullptr) + PL_strfree(info.fMimeDescriptionArray[i]); + + if (info.fExtensionArray[i] != nullptr) PL_strfree(info.fExtensionArray[i]); + } + + free(info.fMimeTypeArray); + info.fMimeTypeArray = nullptr; + free(info.fMimeDescriptionArray); + info.fMimeDescriptionArray = nullptr; + free(info.fExtensionArray); + info.fExtensionArray = nullptr; + + if (info.fFullPath != nullptr) PL_strfree(info.fFullPath); + + if (info.fFileName != nullptr) PL_strfree(info.fFileName); + + if (info.fVersion != nullptr) PL_strfree(info.fVersion); + + return NS_OK; +} diff --git a/dom/plugins/base/nsPluginsDirUtils.h b/dom/plugins/base/nsPluginsDirUtils.h new file mode 100644 index 0000000000..e4d929d1fc --- /dev/null +++ b/dom/plugins/base/nsPluginsDirUtils.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsPluginsDirUtils_h___ +#define nsPluginsDirUtils_h___ + +#include "nsPluginsDir.h" +#include "nsTArray.h" + +/////////////////////////////////////////////////////////////////////////////// +// Output format from NPP_GetMIMEDescription: "...mime +// type[;version]:[extension]:[desecription];..." The ambiguity of mime +// description could cause the browser fail to parse the MIME types correctly. +// E.g. "mime type::desecription;" // correct w/o ext +// "mime type:desecription;" // wrong w/o ext +// +static nsresult ParsePluginMimeDescription(const char* mdesc, + nsPluginInfo& info) { + nsresult rv = NS_ERROR_FAILURE; + if (!mdesc || !*mdesc) return rv; + + char* mdescDup = + PL_strdup(mdesc); // make a dup of intput string we'll change it content + char anEmptyString[] = ""; + AutoTArray<char*, 8> tmpMimeTypeArr; + char delimiters[] = {':', ':', ';'}; + int mimeTypeVariantCount = 0; + char* p = mdescDup; // make a dup of intput string we'll change it content + while (p) { + char* ptrMimeArray[] = {anEmptyString, anEmptyString, anEmptyString}; + + // It's easy to point out ptrMimeArray[0] to the string sounds like + // "Mime type is not specified, plugin will not function properly." + // and show this on "about:plugins" page, but we have to mark this + // particular mime type of given plugin as disable on "about:plugins" page, + // which feature is not implemented yet. + // So we'll ignore, without any warnings, an empty description strings, + // in other words, if after parsing ptrMimeArray[0] == anEmptyString is + // true. It is possible do not to registry a plugin at all if it returns an + // empty string on GetMIMEDescription() call, e.g. plugger returns "" if + // pluggerrc file is not found. + + char* s = p; + int i; + for (i = 0; + i < (int)sizeof(delimiters) && (p = PL_strchr(s, delimiters[i])); + i++) { + ptrMimeArray[i] = s; // save start ptr + *p++ = 0; // overwrite delimiter + s = p; // move forward + } + if (i == 2) ptrMimeArray[i] = s; + // fill out the temp array + // the order is important, it should be the same in for loop below + if (ptrMimeArray[0] != anEmptyString) { + tmpMimeTypeArr.AppendElement(ptrMimeArray[0]); + tmpMimeTypeArr.AppendElement(ptrMimeArray[1]); + tmpMimeTypeArr.AppendElement(ptrMimeArray[2]); + mimeTypeVariantCount++; + } + } + + // fill out info structure + if (mimeTypeVariantCount) { + info.fVariantCount = mimeTypeVariantCount; + // we can do these 3 mallocs at once, later on code cleanup + info.fMimeTypeArray = (char**)malloc(mimeTypeVariantCount * sizeof(char*)); + info.fMimeDescriptionArray = + (char**)malloc(mimeTypeVariantCount * sizeof(char*)); + info.fExtensionArray = (char**)malloc(mimeTypeVariantCount * sizeof(char*)); + + int j, i; + for (j = i = 0; i < mimeTypeVariantCount; i++) { + // the order is important, do not change it + // we can get rid of PL_strdup here, later on code cleanup + info.fMimeTypeArray[i] = PL_strdup(tmpMimeTypeArr.ElementAt(j++)); + info.fExtensionArray[i] = PL_strdup(tmpMimeTypeArr.ElementAt(j++)); + info.fMimeDescriptionArray[i] = PL_strdup(tmpMimeTypeArr.ElementAt(j++)); + } + rv = NS_OK; + } + if (mdescDup) PL_strfree(mdescDup); + return rv; +} + +#endif /* nsPluginsDirUtils_h___ */ diff --git a/dom/plugins/base/nsPluginsDirWin.cpp b/dom/plugins/base/nsPluginsDirWin.cpp new file mode 100644 index 0000000000..04e20f46d0 --- /dev/null +++ b/dom/plugins/base/nsPluginsDirWin.cpp @@ -0,0 +1,349 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + nsPluginsDirWin.cpp + + Windows implementation of the nsPluginsDir/nsPluginsFile classes. + + by Alex Musil + */ + +#include "mozilla/ArrayUtils.h" // ArrayLength +#include "mozilla/DebugOnly.h" +#include "mozilla/Printf.h" + +#include "nsPluginsDir.h" +#include "prlink.h" +#include "plstr.h" + +#include "windows.h" +#include "winbase.h" + +#include "nsString.h" +#include "nsIFile.h" +#include "nsUnicharUtils.h" + +using namespace mozilla; + +/* Local helper functions */ + +static char* GetKeyValue(void* verbuf, const WCHAR* key, UINT language, + UINT codepage) { + WCHAR keybuf[64]; // plenty for the template below, with the longest key + // we use (currently "FileDescription") + const WCHAR keyFormat[] = L"\\StringFileInfo\\%04X%04X\\%ls"; + WCHAR* buf = nullptr; + UINT blen; + + if (_snwprintf_s(keybuf, ArrayLength(keybuf), _TRUNCATE, keyFormat, language, + codepage, key) < 0) { + MOZ_ASSERT_UNREACHABLE("plugin info key too long for buffer!"); + return nullptr; + } + + if (::VerQueryValueW(verbuf, keybuf, (void**)&buf, &blen) == 0 || + buf == nullptr || blen == 0) { + return nullptr; + } + + return PL_strdup(NS_ConvertUTF16toUTF8(buf, blen).get()); +} + +static char* GetVersion(void* verbuf) { + VS_FIXEDFILEINFO* fileInfo; + UINT fileInfoLen; + + ::VerQueryValueW(verbuf, L"\\", (void**)&fileInfo, &fileInfoLen); + + if (fileInfo) { + return mozilla::Smprintf("%ld.%ld.%ld.%ld", + HIWORD(fileInfo->dwFileVersionMS), + LOWORD(fileInfo->dwFileVersionMS), + HIWORD(fileInfo->dwFileVersionLS), + LOWORD(fileInfo->dwFileVersionLS)) + .release(); + } + + return nullptr; +} + +// Returns a boolean indicating if the key's value contains a string +// entry equal to "1" or "0". No entry for the key returns false. +static bool GetBooleanFlag(void* verbuf, const WCHAR* key, UINT language, + UINT codepage) { + char* flagStr = GetKeyValue(verbuf, key, language, codepage); + if (!flagStr) { + return false; + } + bool result = (PL_strncmp("1", flagStr, 1) == 0); + PL_strfree(flagStr); + return result; +} + +static uint32_t CalculateVariantCount(char* mimeTypes) { + uint32_t variants = 1; + + if (!mimeTypes) return 0; + + char* index = mimeTypes; + while (*index) { + if (*index == '|') variants++; + + ++index; + } + return variants; +} + +static char** MakeStringArray(uint32_t variants, char* data) { + // The number of variants has been calculated based on the mime + // type array. Plugins are not explicitely required to match + // this number in two other arrays: file extention array and mime + // description array, and some of them actually don't. + // We should handle such situations gracefully + + if ((variants <= 0) || !data) return nullptr; + + char** array = (char**)calloc(variants, sizeof(char*)); + if (!array) return nullptr; + + char* start = data; + + for (uint32_t i = 0; i < variants; i++) { + char* p = PL_strchr(start, '|'); + if (p) *p = 0; + + array[i] = PL_strdup(start); + + if (!p) { + // nothing more to look for, fill everything left + // with empty strings and break + while (++i < variants) array[i] = PL_strdup(""); + + break; + } + + start = ++p; + } + return array; +} + +static void FreeStringArray(uint32_t variants, char** array) { + if ((variants == 0) || !array) return; + + for (uint32_t i = 0; i < variants; i++) { + if (array[i]) { + PL_strfree(array[i]); + array[i] = nullptr; + } + } + free(array); +} + +static bool CanLoadPlugin(char16ptr_t aBinaryPath) { +#if defined(_M_IX86) || defined(_M_X64) || defined(_M_IA64) + bool canLoad = false; + + HANDLE file = + CreateFileW(aBinaryPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file != INVALID_HANDLE_VALUE) { + HANDLE map = CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, + GetFileSize(file, nullptr), nullptr); + if (map != nullptr) { + LPVOID mapView = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0); + if (mapView != nullptr) { + if (((IMAGE_DOS_HEADER*)mapView)->e_magic == IMAGE_DOS_SIGNATURE) { + long peImageHeaderStart = (((IMAGE_DOS_HEADER*)mapView)->e_lfanew); + if (peImageHeaderStart != 0L) { + DWORD arch = + (((IMAGE_NT_HEADERS*)((LPBYTE)mapView + peImageHeaderStart)) + ->FileHeader.Machine); +# ifdef _M_IX86 + canLoad = (arch == IMAGE_FILE_MACHINE_I386); +# elif defined(_M_X64) + canLoad = (arch == IMAGE_FILE_MACHINE_AMD64); +# elif defined(_M_IA64) + canLoad = (arch == IMAGE_FILE_MACHINE_IA64); +# endif + } + } + UnmapViewOfFile(mapView); + } + CloseHandle(map); + } + CloseHandle(file); + } + + return canLoad; +#else + // Assume correct binaries for unhandled cases. + return true; +#endif +} + +/* nsPluginsDir implementation */ + +// The file name must be in the form "np*.dll" +bool nsPluginsDir::IsPluginFile(nsIFile* file) { + nsAutoString path; + if (NS_FAILED(file->GetPath(path))) return false; + + // this is most likely a path, so skip to the filename + auto filename = Substring(path, path.RFindChar('\\') + 1); + // The file name must have at least one character between "np" and ".dll". + if (filename.Length() < 7) { + return false; + } + + ToLowerCase(filename); + if (StringBeginsWith(filename, u"np"_ns) && + StringEndsWith(filename, u".dll"_ns)) { + // don't load OJI-based Java plugins + if (StringBeginsWith(filename, u"npoji"_ns) || + StringBeginsWith(filename, u"npjava"_ns)) + return false; + return true; + } + + return false; +} + +/* nsPluginFile implementation */ + +nsPluginFile::nsPluginFile(nsIFile* file) : mPlugin(file) { + // nada +} + +nsPluginFile::~nsPluginFile() { + // nada +} + +/** + * Loads the plugin into memory using NSPR's shared-library loading + * mechanism. Handles platform differences in loading shared libraries. + */ +nsresult nsPluginFile::LoadPlugin(PRLibrary** outLibrary) { + if (!mPlugin) return NS_ERROR_NULL_POINTER; + + nsAutoString pluginFilePath; + mPlugin->GetPath(pluginFilePath); + + nsAutoString pluginFolderPath = pluginFilePath; + int32_t idx = pluginFilePath.RFindChar('\\'); + pluginFolderPath.SetLength(idx); + + BOOL restoreOrigDir = FALSE; + WCHAR aOrigDir[MAX_PATH + 1]; + DWORD dwCheck = GetCurrentDirectoryW(MAX_PATH, aOrigDir); + NS_ASSERTION(dwCheck <= MAX_PATH + 1, "Error in Loading plugin"); + + if (dwCheck <= MAX_PATH + 1) { + restoreOrigDir = SetCurrentDirectoryW(pluginFolderPath.get()); + NS_ASSERTION(restoreOrigDir, "Error in Loading plugin"); + } + + // Temporarily add the current directory back to the DLL load path. + SetDllDirectory(nullptr); + + nsresult rv = mPlugin->Load(outLibrary); + if (NS_FAILED(rv)) *outLibrary = nullptr; + + SetDllDirectory(L""); + + if (restoreOrigDir) { + DebugOnly<BOOL> bCheck = SetCurrentDirectoryW(aOrigDir); + NS_ASSERTION(bCheck, "Error in Loading plugin"); + } + + return rv; +} + +/** + * Obtains all of the information currently available for this plugin. + */ +nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, + PRLibrary** outLibrary) { + *outLibrary = nullptr; + + nsresult rv = NS_OK; + DWORD zerome, versionsize; + void* verbuf = nullptr; + + if (!mPlugin) return NS_ERROR_NULL_POINTER; + + nsAutoString fullPath; + if (NS_FAILED(rv = mPlugin->GetPath(fullPath))) return rv; + + if (!CanLoadPlugin(fullPath.get())) return NS_ERROR_FAILURE; + + nsAutoString fileName; + if (NS_FAILED(rv = mPlugin->GetLeafName(fileName))) return rv; + + LPCWSTR lpFilepath = fullPath.get(); + + versionsize = ::GetFileVersionInfoSizeW(lpFilepath, &zerome); + + if (versionsize > 0) verbuf = malloc(versionsize); + if (!verbuf) return NS_ERROR_OUT_OF_MEMORY; + + if (::GetFileVersionInfoW(lpFilepath, 0, versionsize, verbuf)) { + // TODO: get appropriately-localized info from plugin file + UINT lang = 1033; // language = English, 0x409 + UINT cp = 1252; // codepage = Western, 0x4E4 + info.fName = GetKeyValue(verbuf, L"ProductName", lang, cp); + info.fDescription = GetKeyValue(verbuf, L"FileDescription", lang, cp); + info.fSupportsAsyncRender = + GetBooleanFlag(verbuf, L"AsyncDrawingSupport", lang, cp); + + char* mimeType = GetKeyValue(verbuf, L"MIMEType", lang, cp); + char* mimeDescription = GetKeyValue(verbuf, L"FileOpenName", lang, cp); + char* extensions = GetKeyValue(verbuf, L"FileExtents", lang, cp); + + info.fVariantCount = CalculateVariantCount(mimeType); + info.fMimeTypeArray = MakeStringArray(info.fVariantCount, mimeType); + info.fMimeDescriptionArray = + MakeStringArray(info.fVariantCount, mimeDescription); + info.fExtensionArray = MakeStringArray(info.fVariantCount, extensions); + info.fFullPath = PL_strdup(NS_ConvertUTF16toUTF8(fullPath).get()); + info.fFileName = PL_strdup(NS_ConvertUTF16toUTF8(fileName).get()); + info.fVersion = GetVersion(verbuf); + + PL_strfree(mimeType); + PL_strfree(mimeDescription); + PL_strfree(extensions); + } else { + rv = NS_ERROR_FAILURE; + } + + free(verbuf); + + return rv; +} + +nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info) { + if (info.fName) PL_strfree(info.fName); + + if (info.fDescription) PL_strfree(info.fDescription); + + if (info.fMimeTypeArray) + FreeStringArray(info.fVariantCount, info.fMimeTypeArray); + + if (info.fMimeDescriptionArray) + FreeStringArray(info.fVariantCount, info.fMimeDescriptionArray); + + if (info.fExtensionArray) + FreeStringArray(info.fVariantCount, info.fExtensionArray); + + if (info.fFullPath) PL_strfree(info.fFullPath); + + if (info.fFileName) PL_strfree(info.fFileName); + + if (info.fVersion) free(info.fVersion); + + ZeroMemory((void*)&info, sizeof(info)); + + return NS_OK; +} diff --git a/dom/plugins/base/nspluginroot.idl b/dom/plugins/base/nspluginroot.idl new file mode 100644 index 0000000000..6c931d3711 --- /dev/null +++ b/dom/plugins/base/nspluginroot.idl @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +native REFNSIID(REFNSIID); +native nativeVoid(void *); +native nativeChar(const char * *); +[ptr] native constVoidPtr(const void); +[ref] native PRUint32Ref(uint32_t); +[ref] native PRUint16Ref(uint16_t); +[ref] native constCharStarConstStar(const char* const*); +[ptr] native constCharPtr(const char); +[ref] native constCharStarRef(const char *); + +native NPWindowType(NPWindowType); +native NPWindow(NPWindow); +[ptr] native NPWindowPtr(NPWindow); +[ref] native NPWindowStarRef(NPWindow *); +[ptr] native NPPrintPtr(NPPrint); +native NPByteRange(NPByteRange); +[ptr] native NPByteRangePtr(NPByteRange); +native NPPVariable(NPPVariable); +native NPNVariable(NPNVariable); +[ptr] native NPRectPtr(NPRect); +native NPRegion(NPRegion); +native NPDrawingModel(NPDrawingModel); +native NPEventModel(NPEventModel); + +[ptr] native JRIEnvPtr(JRIEnv); +native jref(jref); diff --git a/dom/plugins/ipc/AStream.h b/dom/plugins/ipc/AStream.h new file mode 100644 index 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"] diff --git a/dom/plugins/test/crashtests/110650-1.html b/dom/plugins/test/crashtests/110650-1.html new file mode 100644 index 0000000000..9826227b03 --- /dev/null +++ b/dom/plugins/test/crashtests/110650-1.html @@ -0,0 +1,11 @@ +<HTML> +<HEAD> +<TITLE>123246 testcase</TITLE> +</HEAD> +<BODY> +<object align="right" width=100> +</object> +</BODY > +</HTML> + + diff --git a/dom/plugins/test/crashtests/41276-1.html b/dom/plugins/test/crashtests/41276-1.html new file mode 100644 index 0000000000..ba35c34fa0 --- /dev/null +++ b/dom/plugins/test/crashtests/41276-1.html @@ -0,0 +1,28 @@ +<HTML><HEAD><TITLE>Plugin Limit</TITLE></HEAD>
+<BODY>
+
+Mozilla has a hardcoded limit of 10 simultaneously embedded plugins.<br>
+If that limit is exceeded, plugin instances are prematurely destroyed (see the empty boxes below).<br>
+Leave or reload a page that has a prematurely destroyed plugin and mozilla will crash.<br>
+Sometimes, just loading this page will cause mozilla to crash.<br>
+
+
+<br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<br>
+</BODY></HTML>
diff --git a/dom/plugins/test/crashtests/48856-1.html b/dom/plugins/test/crashtests/48856-1.html new file mode 100644 index 0000000000..cd0de2ab94 --- /dev/null +++ b/dom/plugins/test/crashtests/48856-1.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/REC-html40/loose.dtd"> +<HTML> + <HEAD> + <TITLE>Mozilla Bug 48856</TITLE> + </HEAD> + <BODY> + <EMBED></EMBED> + </BODY> +</HTML> diff --git a/dom/plugins/test/crashtests/539897-1.html b/dom/plugins/test/crashtests/539897-1.html new file mode 100644 index 0000000000..f280e62e6e --- /dev/null +++ b/dom/plugins/test/crashtests/539897-1.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> +<script type="text/javascript"> +function crashplugin() { + var plugin = document.getElementById('p'); + plugin.reinitWidget(); + plugin.reinitWidget(); +} + +function getTestCases() { + return [ + { testPassed: + (function () { + var plugin = document.getElementById('p'); + try { + plugin.getPaintCount(); + return true; + } catch (e) { + return false; + } + }), + testDescription: + (function () { + return "plugin should not crash"; + }) + } + ]; +} +</script> +</head> +<body onload="crashplugin();"> +<embed id="p" type="application/x-test" wmode="window"/> +</body> +</html> diff --git a/dom/plugins/test/crashtests/540114-1.html b/dom/plugins/test/crashtests/540114-1.html new file mode 100644 index 0000000000..8243649aa3 --- /dev/null +++ b/dom/plugins/test/crashtests/540114-1.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function crashplugin() { + var plugin = document.getElementById('removeme'); + var flush_reflow = plugin.offsetHeight; // this may not be necessary + document.body.removeChild(plugin); + // Give the plugin time to crash + setTimeout(function() { document.documentElement.removeAttribute('class') }, + 1000); +} + +function getTestCases() { + return [ + { testPassed: + (function () { + // Assuming the same process is used for removeme and checkme + var plugin = document.getElementById('checkme'); + try { + plugin.getPaintCount(); + return true; + } catch (e) { + return false; + } + }), + testDescription: + (function () { + return "plugin should not crash"; + }) + } + ]; +} +</script> +</head> +<body onload="crashplugin();"> +<embed id="checkme" type="application/x-test"/> +<embed id="removeme" type="application/x-test" wmode="window" cleanupwidget="false"/> +</body> +</html> diff --git a/dom/plugins/test/crashtests/570884.html b/dom/plugins/test/crashtests/570884.html new file mode 100644 index 0000000000..7af5cdba53 --- /dev/null +++ b/dom/plugins/test/crashtests/570884.html @@ -0,0 +1,8 @@ +<body> +<embed type="application/x-test" width="20"></embed> +<embed type="application/x-test" width="10"></embed> +<embed type="application/x-test" width="0"></embed> +<embed type="application/x-test" width="20" height="20"></embed> +<embed type="application/x-test" width="10" height="10"></embed> +<embed type="application/x-test" width="0" height="0"></embed> +</body> diff --git a/dom/plugins/test/crashtests/598862.html b/dom/plugins/test/crashtests/598862.html new file mode 100644 index 0000000000..02ffb05428 --- /dev/null +++ b/dom/plugins/test/crashtests/598862.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> + <head> + <script> +var unusedScreenX; +function XSync() { + unusedScreenX = window.screenX; +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +function cleanup() { + try { + document.getElementById("plugin").crash(); + } catch (dontcare) { + } + window.setTimeout(finish, 100); +} + +function scrollOfDeath() { + // Add a listener for the MozAfterPaint after the scrollTo below. + // If we don't crash during the scroll, we'll get the + // MozAfterPaint. + window.addEventListener("MozAfterPaint", cleanup); + window.scrollTo(0, 50); +} + +// +// The sequence of events we expect is +// +// load (including initial paints of plugin that are cached) +// destroy X resources +// [X server has time to observe resource destruction] +// scrollTo +// [repaint using cached plugin surface: BOOM if buggy] +// MozAfterPaint +// cleanup +// +// However, this test is fundamentally nondeterministic. There are +// two main "failure" modes +// (1) X server doesn't have time to observer resource destruction +// before paint-after-scroll +// (2) plugin's crash notification arrives before +// paint-after-scroll +// Both result in spurious passes. +// +// This test is anal about cleanup because it must be pretty sure that +// the plugin subprocess is gone before starting the next test. We +// double-check that the plugin is gone by the time we're done by +// trying to crash it again, after we expect it to have crashed already. +// +function runTest() { + // Have the plugin throw away its X resources, one of which is + // probably a drawable to which we hold a reference + document.getElementById("plugin").destroySharedGfxStuff(); + + // Do something that's (hopefully) equivalent to an XSync() to allow + // the resource destruction to propagate to the server + XSync(); + + // Set up a scroll to happen soon + window.setTimeout(scrollOfDeath, 100); +} + +window.addEventListener("MozReftestInvalidate", runTest); + </script> + </head> + + <body style="width: 400px; height: 10000px;"> + <embed id="plugin" type="application/x-test" + style="position:absolute; + top:100px; left:50px; width:200px; height:200px;"> + </embed> + </body> +</html> diff --git a/dom/plugins/test/crashtests/626602-1.html b/dom/plugins/test/crashtests/626602-1.html new file mode 100644 index 0000000000..0a878bbd1d --- /dev/null +++ b/dom/plugins/test/crashtests/626602-1.html @@ -0,0 +1,109 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:4; +} +#two { + position:absolute; + top:100px; left:100px; + background-color:rgb(0,0,0,0); + z-index:3; +} +#three { + position:absolute; + left:100px; top:100px; + width:200px; height:200px; + background-color: rgb(255,0,0); + opacity:0.6; + z-index:2; +} +#four { + position:absolute; + top:100px; left:100px; + z-index:1; +} + </style> + <script type="text/javascript"> +var plugin, div, canvas; +function start() { + plugin = document.getElementById("four"); + div = document.getElementById("three"); + canvas = document.getElementById("two"); + paintCanvas(); + + requestAnimationFrame(moveSomething); +} + +function paintCanvas() { + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgba(255,0,0, 0.6)"; + ctx.fillRect(0,0, 200,200); +} + +var i = 0, numLoops = 20; +var pluginIn = true, divIn = true, canvasIn = true; +function moveSomething() { + var didSomething = (0 === (i % 2)) ? moveSomethingOut() : moveSomethingIn(); + if (!didSomething && ++i >= numLoops) { + return finish(); + } + + requestAnimationFrame(moveSomething); +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +function moveSomethingOut() { + if (pluginIn) { + plugin.style.left = "400px"; + pluginIn = false; + } else if (divIn) { + div.style.left = "400px"; + divIn = false; + } else if (canvasIn) { + canvas.style.left = "400px"; + canvasIn = false; + } else { + return false; + } + return true; +} + +function moveSomethingIn() { + if (!pluginIn) { + plugin.style.left = "100px"; + pluginIn = true; + } else if (!divIn) { + div.style.left = "100px"; + divIn = true; + } else if (!canvasIn) { + canvas.style.left = "100px"; + canvasIn = true; + } else { + return false; + } + return true; +} + +function reset() { + +} + </script> +</style> +</head> +<body onload="start();"> + <embed id="four" type="application/x-test" width="200" height="200" + drawmode="solid" color="FFFF0000"></embed> + <div id="three"></div> + <canvas id="two" width="200" height="200"></canvas> + <embed id="one" type="application/x-test" width="400" height="400" + drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/crashtests/752340.html b/dom/plugins/test/crashtests/752340.html new file mode 100644 index 0000000000..c4c8c464f5 --- /dev/null +++ b/dom/plugins/test/crashtests/752340.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> +<head> + <script type="text/javascript"> + // Failures in this file can manifest as ###!!! ASSERTION: scope has non-empty map: '0 == mWrappedNativeMap->Count()' + // followed by an Assertion failure: allocated() crash during the next GC. + // It can also manifest as a leak. + function breakthings() { + var e = document.createElement("embed"); + var i = document.getElementById("i"); + i.contentDocument.body.appendChild(e); + i.src = "about:blank"; + } + </script> +</head> +<body onload="javascript:breakthings();"> +<iframe id="i" /> +</body> +</html> diff --git a/dom/plugins/test/crashtests/843086.xhtml b/dom/plugins/test/crashtests/843086.xhtml new file mode 100644 index 0000000000..a88e2193fc --- /dev/null +++ b/dom/plugins/test/crashtests/843086.xhtml @@ -0,0 +1 @@ +<applet xmlns="http://www.w3.org/1999/xhtml" /> diff --git a/dom/plugins/test/crashtests/crashtests.list b/dom/plugins/test/crashtests/crashtests.list new file mode 100644 index 0000000000..4cc18e13a0 --- /dev/null +++ b/dom/plugins/test/crashtests/crashtests.list @@ -0,0 +1,14 @@ +HTTP load 41276-1.html +HTTP load 48856-1.html +HTTP load 110650-1.html +skip-if(!haveTestPlugin) HTTP script 539897-1.html +asserts-if(winWidget&&browserIsRemote,0-1) skip-if(!haveTestPlugin) HTTP script 540114-1.html +skip-if(!haveTestPlugin) HTTP load 570884.html +# This test relies on the reading of screenX/Y forcing a round trip to +# the X server, which is a bad assumption for <browser remote>. +# Plugin arch is going to change anyway with OOP content so skipping +# this test for now is OK. +skip-if(!haveTestPlugin||http.platform!="X11") HTTP load 598862.html +skip-if(!haveTestPlugin) HTTP load 626602-1.html +HTTP load 752340.html +HTTP load 843086.xhtml diff --git a/dom/plugins/test/mochitest/.eslintrc.js b/dom/plugins/test/mochitest/.eslintrc.js new file mode 100644 index 0000000000..317abe7b48 --- /dev/null +++ b/dom/plugins/test/mochitest/.eslintrc.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + extends: [ + "plugin:mozilla/browser-test", + "plugin:mozilla/chrome-test", + "plugin:mozilla/mochitest-test", + ], +}; diff --git a/dom/plugins/test/mochitest/307-xo-redirect.sjs b/dom/plugins/test/mochitest/307-xo-redirect.sjs new file mode 100644 index 0000000000..b880978cea --- /dev/null +++ b/dom/plugins/test/mochitest/307-xo-redirect.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) +{ + response.setStatusLine(request.httpVersion, 307, "Moved temporarily"); + response.setHeader("Location", "http://example.org/tests/dom/plugins/test/mochitest/loremipsum.txt"); + response.setHeader("Content-Type", "text/html"); +} diff --git a/dom/plugins/test/mochitest/block_all_plugins.html b/dom/plugins/test/mochitest/block_all_plugins.html new file mode 100644 index 0000000000..3ccdda1373 --- /dev/null +++ b/dom/plugins/test/mochitest/block_all_plugins.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <object id="object" type="application/x-shockwave-flash"></object> + <embed id="embed" type="application/x-shockwave-flash"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/browser.ini b/dom/plugins/test/mochitest/browser.ini new file mode 100644 index 0000000000..32aaf9b576 --- /dev/null +++ b/dom/plugins/test/mochitest/browser.ini @@ -0,0 +1,17 @@ +[DEFAULT] +prefs = + plugin.load_flash_only=false +support-files = + block_all_plugins.html + head.js + plugin_test.html + plugin_subframe_test.html + plugin_no_scroll_div.html + +[browser_blockallplugins.js] +[browser_bug1163570.js] +skip-if = true # Bug 1249878 +[browser_tabswitchbetweenplugins.js] +skip-if = true #Bug 1538425 +[browser_pluginscroll.js] +skip-if = (true || !e10s || os != "win") # Bug 1213631 diff --git a/dom/plugins/test/mochitest/browser_blockallplugins.js b/dom/plugins/test/mochitest/browser_blockallplugins.js new file mode 100644 index 0000000000..e847f2cd23 --- /dev/null +++ b/dom/plugins/test/mochitest/browser_blockallplugins.js @@ -0,0 +1,66 @@ +var gTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); + +add_task(async function() { + registerCleanupFunction(function() { + gBrowser.removeCurrentTab(); + window.focus(); + }); +}); + +// simple tab load helper, pilfered from browser plugin tests +function promiseTabLoadEvent(tab, url) { + info("Wait tab event: load"); + + function handle(loadedUrl) { + if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) { + info(`Skipping spurious load event for ${loadedUrl}`); + return false; + } + + info("Tab event received: load"); + return true; + } + + let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle); + + if (url) { + BrowserTestUtils.loadURI(tab.linkedBrowser, url); + } + + return loaded; +} + +add_task(async function() { + let pluginTab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser)); + await promiseTabLoadEvent(pluginTab, gTestRoot + "block_all_plugins.html"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() { + let doc = content.document; + + let objectElt = doc.getElementById("object"); + Assert.ok(!!objectElt, "object should exist"); + Assert.ok( + objectElt instanceof Ci.nsIObjectLoadingContent, + "object should be an nsIObjectLoadingContent" + ); + Assert.ok( + objectElt.pluginFallbackType == + Ci.nsIObjectLoadingContent.PLUGIN_BLOCK_ALL, + "object should be blocked" + ); + + let embedElt = doc.getElementById("embed"); + Assert.ok(!!embedElt, "embed should exist"); + Assert.ok( + embedElt instanceof Ci.nsIObjectLoadingContent, + "embed should be an nsIObjectLoadingContent" + ); + Assert.ok( + embedElt.pluginFallbackType == + Ci.nsIObjectLoadingContent.PLUGIN_BLOCK_ALL, + "embed should be blocked" + ); + }); +}); diff --git a/dom/plugins/test/mochitest/browser_bug1163570.js b/dom/plugins/test/mochitest/browser_bug1163570.js new file mode 100644 index 0000000000..6a77728c77 --- /dev/null +++ b/dom/plugins/test/mochitest/browser_bug1163570.js @@ -0,0 +1,118 @@ +var gTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); + +// simple tab load helper, pilfered from browser plugin tests +function promiseTabLoad(tab, url, eventType = "load") { + return new Promise(resolve => { + function handle(event) { + if ( + event.originalTarget != tab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank" || + (url && event.target.location.href != url) + ) { + return; + } + tab.linkedBrowser.removeEventListener(eventType, handle, true); + resolve(event); + } + + tab.linkedBrowser.addEventListener(eventType, handle, true, true); + if (url) { + tab.linkedBrowser.loadURI(url); + } + }); +} + +// dom event listener helper +function promiseWaitForEvent( + object, + eventName, + capturing = false, + chrome = false +) { + return new Promise(resolve => { + function listener(event) { + object.removeEventListener(eventName, listener, capturing, chrome); + resolve(event); + } + object.addEventListener(eventName, listener, capturing, chrome); + }); +} + +add_task(async function() { + registerCleanupFunction(function() { + window.focus(); + }); +}); + +add_task(async function() { + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser)); + let prefTab = BrowserTestUtils.addTab(gBrowser); + + await promiseTabLoad(pluginTab, gTestRoot + "plugin_test.html"); + await promiseTabLoad(prefTab, "about:preferences"); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + Assert.ok(!!plugin, "plugin is loaded"); + }); + + let ppromise = promiseWaitForEvent(window, "MozAfterPaint"); + gBrowser.selectedTab = prefTab; + await ppromise; + + // We're going to switch tabs using actual mouse clicks, which helps + // reproduce this bug. + let tabStripContainer = document.getElementById("tabbrowser-tabs"); + + // diagnosis if front end layout changes + info("-> " + tabStripContainer.tagName); // tabs + info("-> " + tabStripContainer.firstChild.tagName); // tab + info("-> " + tabStripContainer.childNodes[0].label); // test harness tab + info("-> " + tabStripContainer.childNodes[1].label); // plugin tab + info("-> " + tabStripContainer.childNodes[2].label); // preferences tab + + for (let iteration = 0; iteration < 5; iteration++) { + ppromise = promiseWaitForEvent(window, "MozAfterPaint"); + EventUtils.synthesizeMouseAtCenter( + tabStripContainer.childNodes[1], + {}, + window + ); + await ppromise; + + await SpecialPowers.spawn(pluginTab.linkedBrowser, [], async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + Assert.ok( + XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(), + "plugin is visible" + ); + }); + + ppromise = promiseWaitForEvent(window, "MozAfterPaint"); + EventUtils.synthesizeMouseAtCenter( + tabStripContainer.childNodes[2], + {}, + window + ); + await ppromise; + + await SpecialPowers.spawn(pluginTab.linkedBrowser, [], async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + Assert.ok( + !XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(), + "plugin is hidden" + ); + }); + } + + gBrowser.removeTab(prefTab); + gBrowser.removeTab(pluginTab); +}); diff --git a/dom/plugins/test/mochitest/browser_pluginscroll.js b/dom/plugins/test/mochitest/browser_pluginscroll.js new file mode 100644 index 0000000000..a55651b1cc --- /dev/null +++ b/dom/plugins/test/mochitest/browser_pluginscroll.js @@ -0,0 +1,417 @@ +var gTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); + +const { Preferences } = ChromeUtils.import( + "resource://gre/modules/Preferences.jsm" +); + +/** + * tests for plugin windows and scroll + */ + +function coordinatesRelativeToWindow(aX, aY, aElement) { + var targetWindow = aElement.ownerGlobal; + var scale = targetWindow.devicePixelRatio; + var rect = aElement.getBoundingClientRect(); + return { + x: targetWindow.mozInnerScreenX + (rect.left + aX) * scale, + y: targetWindow.mozInnerScreenY + (rect.top + aY) * scale, + }; +} + +var apzEnabled = + Services.appinfo.fissionAutostart || + Preferences.get("layers.async-pan-zoom.enabled", false); +var pluginHideEnabled = Preferences.get( + "gfx.e10s.hide-plugins-for-scroll", + true +); + +add_task(async function() { + registerCleanupFunction(function() { + setTestPluginEnabledState( + Ci.nsIPluginTag.STATE_CLICKTOPLAY, + "Test Plug-in" + ); + }); +}); + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["general.smoothScroll", true], + ["general.smoothScroll.other", true], + ["general.smoothScroll.mouseWheel", true], + ["general.smoothScroll.other.durationMaxMS", 2000], + ["general.smoothScroll.other.durationMinMS", 1999], + ["general.smoothScroll.mouseWheel.durationMaxMS", 2000], + ["general.smoothScroll.mouseWheel.durationMinMS", 1999], + ], + }); +}); + +/* + * test plugin visibility when scrolling with scroll wheel and apz in a top level document. + */ + +add_task(async function() { + let result; + + if (!apzEnabled) { + ok(true, "nothing to test, need apz"); + return; + } + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin is loaded"); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + let nativeId = nativeVerticalWheelEventMsg(); + let utils = SpecialPowers.getDOMWindowUtils(window); + let screenCoords = coordinatesRelativeToWindow( + 10, + 10, + gBrowser.selectedBrowser + ); + utils.sendNativeMouseScrollEvent( + screenCoords.x, + screenCoords.y, + nativeId, + 0, + -50, + 0, + 0, + 0, + gBrowser.selectedBrowser + ); + + await waitScrollStart(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); + +/* + * test plugin visibility when scrolling with scroll wheel and apz in a sub document. + */ + +add_task(async function() { + let result; + + if (!apzEnabled) { + ok(true, "nothing to test, need apz"); + return; + } + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_subframe_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin is loaded"); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + let nativeId = nativeVerticalWheelEventMsg(); + let utils = SpecialPowers.getDOMWindowUtils(window); + let screenCoords = coordinatesRelativeToWindow( + 10, + 10, + gBrowser.selectedBrowser + ); + utils.sendNativeMouseScrollEvent( + screenCoords.x, + screenCoords.y, + nativeId, + 0, + -50, + 0, + 0, + 0, + gBrowser.selectedBrowser + ); + + await waitScrollStart(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); + +/* + * test visibility when scrolling with keyboard shortcuts for a top level document. + * This circumvents apz and relies on dom scroll, which is what we want to target + * for this test. + */ + +add_task(async function() { + let result; + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin is loaded"); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + EventUtils.synthesizeKey("KEY_End"); + + await waitScrollStart(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + EventUtils.synthesizeKey("KEY_Home"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); + +/* + * test visibility when scrolling with keyboard shortcuts for a sub document. + */ + +add_task(async function() { + let result; + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_subframe_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin is loaded"); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + EventUtils.synthesizeKey("KEY_End"); + + await waitScrollStart(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + EventUtils.synthesizeKey("KEY_Home"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); diff --git a/dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js b/dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js new file mode 100644 index 0000000000..c6b7e10796 --- /dev/null +++ b/dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js @@ -0,0 +1,141 @@ +var gTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); + +function waitForPluginVisibility(browser, shouldBeVisible, errorMessage) { + return new Promise((resolve, reject) => { + let windowUtils = window.windowUtils; + let lastTransactionId = windowUtils.lastTransactionId; + let listener = async event => { + let visibility = await SpecialPowers.spawn(browser, [], async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + + if (visibility == shouldBeVisible) { + window.removeEventListener("MozAfterPaint", listener); + resolve(); + } else if (event && event.transactionId > lastTransactionId) { + // We want to allow for one failed check since we call listener + // directly, but if we get a MozAfterPaint notification and we + // still don't have the correct visibility, that's likely a + // problem. + reject(new Error("MozAfterPaint had a mismatched plugin visibility")); + } + }; + window.addEventListener("MozAfterPaint", listener); + listener(null); + }); +} + +// tests that we get plugin updates when we flip between tabs that +// have the same plugin in the same position in the page. + +add_task(async function() { + let result, tabSwitchedPromise; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let testTab = gBrowser.selectedTab; + let pluginTab1 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_test.html" + ); + let pluginTab2 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab1.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin1 is loaded"); + + result = await SpecialPowers.spawn( + pluginTab2.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin2 is loaded"); + + // plugin tab 2 should be selected + is(gBrowser.selectedTab == pluginTab2, true, "plugin2 is selected"); + + await waitForPluginVisibility( + pluginTab1.linkedBrowser, + false, + "plugin1 should be hidden" + ); + + await waitForPluginVisibility( + pluginTab2.linkedBrowser, + true, + "plugin2 should be visible" + ); + + // select plugin1 tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = pluginTab1; + await tabSwitchedPromise; + + await waitForPluginVisibility( + pluginTab1.linkedBrowser, + true, + "plugin1 should be visible" + ); + + await waitForPluginVisibility( + pluginTab2.linkedBrowser, + false, + "plugin2 should be hidden" + ); + + // select plugin2 tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = pluginTab2; + await tabSwitchedPromise; + + await waitForPluginVisibility( + pluginTab1.linkedBrowser, + false, + "plugin1 should be hidden" + ); + + await waitForPluginVisibility( + pluginTab2.linkedBrowser, + true, + "plugin2 should be visible" + ); + + // select test tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = testTab; + await tabSwitchedPromise; + + await waitForPluginVisibility( + pluginTab1.linkedBrowser, + false, + "plugin1 should be hidden" + ); + + await waitForPluginVisibility( + pluginTab2.linkedBrowser, + false, + "plugin2 should be hidden" + ); + + gBrowser.removeTab(pluginTab1); + gBrowser.removeTab(pluginTab2); +}); diff --git a/dom/plugins/test/mochitest/file_authident.js b/dom/plugins/test/mochitest/file_authident.js new file mode 100644 index 0000000000..d067cff223 --- /dev/null +++ b/dom/plugins/test/mochitest/file_authident.js @@ -0,0 +1,14 @@ +var am = Cc["@mozilla.org/network/http-auth-manager;1"].getService( + Ci.nsIHttpAuthManager +); +am.setAuthIdentity( + "http", + "mochi.test", + 8888, + "basic", + "testrealm", + "", + "mochi.test", + "user1", + "password1" +); diff --git a/dom/plugins/test/mochitest/file_bug771202.html b/dom/plugins/test/mochitest/file_bug771202.html new file mode 100644 index 0000000000..935be65b25 --- /dev/null +++ b/dom/plugins/test/mochitest/file_bug771202.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> + <embed id="pluginElement" type="application/x-test" width="200" height="200"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/file_bug863792.html b/dom/plugins/test/mochitest/file_bug863792.html new file mode 100644 index 0000000000..4a6889a563 --- /dev/null +++ b/dom/plugins/test/mochitest/file_bug863792.html @@ -0,0 +1,43 @@ +<!doctype html> +<html> +<head> + <title>File for Bug 863792</title> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <base href="chrome://browser/content/"> +</head> +<body> +<script type="application/javascript"> + +// A plugin that removes itself from the document and inactivates said document +// inside NPP_New. We should not leak the instance. See also test_bug854082 + +var outerwindow = window; +var i = document.createElement("iframe"); +i.width = 500; +i.height = 500; +var ob = document.body; +document.body.appendChild(i); +i.addEventListener("load", function loaded() { + var id = i.contentDocument; + var e = id.createElement("embed"); + e.width = 200; + e.height = 200; + e.type = "application/x-test"; + e.__defineSetter__("pluginFoundElement", function() { + window.console.log("pluginFoundElement"); + e.style.display = "none"; + e.clientTop; + i.removeEventListener("load", loaded); + ob.removeChild(i); + id.body.clientTop; + id.body.removeChild(e); + }); + id.body.appendChild(e); + e.clientTop; + e = id = i = ob = null; + SpecialPowers.forceCC(); SpecialPowers.forceGC(); +}); +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/head.js b/dom/plugins/test/mochitest/head.js new file mode 100644 index 0000000000..b9242f2932 --- /dev/null +++ b/dom/plugins/test/mochitest/head.js @@ -0,0 +1,147 @@ +/** + * Waits for a tab switch. + */ +function waitTabSwitched() { + return new Promise(resolve => { + gBrowser.addEventListener( + "TabSwitchDone", + function() { + executeSoon(resolve); + }, + { once: true } + ); + }); +} + +/** + * Waits a specified number of miliseconds. + * + * Usage: + * let wait = yield waitForMs(2000); + * ok(wait, "2 seconds should now have elapsed"); + * + * @param aMs the number of miliseconds to wait for + * @returns a Promise that resolves to true after the time has elapsed + */ +function waitForMs(aMs) { + return new Promise(resolve => { + setTimeout(done, aMs); + function done() { + resolve(true); + } + }); +} + +/** + * Platform string helper for nativeVerticalWheelEventMsg + */ +function getPlatform() { + if (navigator.platform.indexOf("Win") == 0) { + return "windows"; + } + if (navigator.platform.indexOf("Mac") == 0) { + return "mac"; + } + if (navigator.platform.indexOf("Linux") == 0) { + return "linux"; + } + return "unknown"; +} + +/** + * Returns a native wheel scroll event id for dom window + * uitls sendNativeMouseScrollEvent. + */ +function nativeVerticalWheelEventMsg() { + switch (getPlatform()) { + case "windows": + return 0x020a; // WM_MOUSEWHEEL + case "mac": + return 0; // value is unused, can be anything + case "linux": + return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway + } + throw new Error( + "Native wheel events not supported on platform " + getPlatform() + ); +} + +/** + * Waits for the first dom "scroll" event. + */ +function waitScrollStart(aTarget) { + return new Promise((resolve, reject) => { + aTarget.addEventListener( + "scroll", + function(event) { + resolve(event); + }, + { capture: true, once: true } + ); + }); +} + +/** + * Waits for the last dom "scroll" event which generally indicates + * a scroll operation is complete. To detect this the helper waits + * 1 second intervals checking for scroll events from aTarget. If + * a scroll event is not received during that time, it considers + * the scroll operation complete. Not super accurate, be careful. + */ +function waitScrollFinish(aTarget) { + return new Promise((resolve, reject) => { + let recent = false; + let count = 0; + function listener(event) { + recent = true; + } + aTarget.addEventListener("scroll", listener, true); + setInterval(function() { + // one second passed and we didn't receive a scroll event. + if (!recent) { + aTarget.removeEventListener("scroll", listener, true); + resolve(); + return; + } + recent = false; + // ten seconds + if (count > 10) { + aTarget.removeEventListener("scroll", listener, true); + reject(); + } + }, 1000); + }); +} + +/** + * Set a plugin activation state. See nsIPluginTag for + * supported states. Affected plugin default to the first + * test plugin. + */ +function setTestPluginEnabledState(aState, aPluginName) { + let name = aPluginName || "Test Plug-in"; + let resolved = false; + SpecialPowers.setTestPluginEnabledState(aState, name).then(() => { + resolved = true; + }); + SpecialPowers.Services.tm.spinEventLoopUntil(() => resolved); +} + +/** + * Returns the chrome side nsIPluginTag for this plugin, helper for + * setTestPluginEnabledState. + */ +function getTestPlugin(aName) { + let pluginName = aName || "Test Plug-in"; + let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let tags = ph.getPluginTags(); + + // Find the test plugin + for (let i = 0; i < tags.length; i++) { + if (tags[i].name == pluginName) { + return tags[i]; + } + } + ok(false, "Unable to find plugin"); + return null; +} diff --git a/dom/plugins/test/mochitest/large-pic.jpg b/dom/plugins/test/mochitest/large-pic.jpg Binary files differnew file mode 100644 index 0000000000..b167f6b9ba --- /dev/null +++ b/dom/plugins/test/mochitest/large-pic.jpg diff --git a/dom/plugins/test/mochitest/loremipsum.txt b/dom/plugins/test/mochitest/loremipsum.txt new file mode 100644 index 0000000000..c5becca596 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum.xtest b/dom/plugins/test/mochitest/loremipsum.xtest new file mode 100644 index 0000000000..c5becca596 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum.xtest @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum.xtest^headers^ b/dom/plugins/test/mochitest/loremipsum.xtest^headers^ new file mode 100644 index 0000000000..8dd7784af4 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum.xtest^headers^ @@ -0,0 +1 @@ +Content-Type: application/x-test diff --git a/dom/plugins/test/mochitest/loremipsum_file.txt b/dom/plugins/test/mochitest/loremipsum_file.txt new file mode 100644 index 0000000000..c5becca596 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum_file.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum_nocache.txt b/dom/plugins/test/mochitest/loremipsum_nocache.txt new file mode 100644 index 0000000000..c5becca596 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum_nocache.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ b/dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ new file mode 100644 index 0000000000..12a01c4a22 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store
diff --git a/dom/plugins/test/mochitest/mixed_case_mime.sjs b/dom/plugins/test/mochitest/mixed_case_mime.sjs new file mode 100644 index 0000000000..1901bb74d8 --- /dev/null +++ b/dom/plugins/test/mochitest/mixed_case_mime.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) +{ + response.processAsync(); + response.setHeader("Content-Type", "image/pNG", false); + + response.write("Hello world.\n"); + response.finish(); +} diff --git a/dom/plugins/test/mochitest/mochitest.ini b/dom/plugins/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..79ed62167a --- /dev/null +++ b/dom/plugins/test/mochitest/mochitest.ini @@ -0,0 +1,45 @@ +[DEFAULT] +prefs = + plugin.load_flash_only=false +skip-if = headless # crash on shutdown, no other failures +support-files = + 307-xo-redirect.sjs + file_authident.js + file_bug771202.html + file_bug863792.html + large-pic.jpg + loremipsum.txt + loremipsum.xtest + loremipsum.xtest^headers^ + loremipsum_file.txt + loremipsum_nocache.txt + loremipsum_nocache.txt^headers^ + mixed_case_mime.sjs + neverending.sjs + npruntime_identifiers_subpage.html + plugin-stream-referer.sjs + plugin_window.html + pluginstream.js + post.sjs + plugin-utils.js + +[test_hanging.html] +skip-if = !crashreporter || e10s +[test_mixed_case_mime.html] +skip-if = (processor == 'aarch64' && os == 'win') +reason = Plugins are not supported on Windows/AArch64 +[test_plugin_fallback_focus.html] +[test_plugin_scroll_painting.html] +skip-if = true # Bug 596491 +[test_pluginstream_geturl.html] +skip-if = true # Bug 1267432 +[test_pluginstream_geturlnotify.html] +skip-if = true # Bug 1267432 +[test_positioning.html] +skip-if = true # disabled due to oddness, perhaps scrolling of the mochitest window? +[test_queryContentsScaleFactor.html] +skip-if = (toolkit != "cocoa") || (os != "win") +[test_queryContentsScaleFactorWindowed.html] +skip-if = (toolkit != "cocoa") || (os != "win") +[test_refresh_navigator_plugins.html] +skip-if = e10s # Bug 1090576 diff --git a/dom/plugins/test/mochitest/neverending.sjs b/dom/plugins/test/mochitest/neverending.sjs new file mode 100644 index 0000000000..1576ce344c --- /dev/null +++ b/dom/plugins/test/mochitest/neverending.sjs @@ -0,0 +1,16 @@ +var timer = null; // declare timer outside to prevent premature GC
+function handleRequest(request, response)
+{
+ response.processAsync();
+ response.setHeader("Content-Type", "text/plain", false);
+
+ for (var i = 0; i < 1000; ++i)
+ response.write("Hello... ");
+
+ timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(function() {
+ response.write("world.\n");
+ response.finish();
+ }, 10 * 1000 /* 10 secs */, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/dom/plugins/test/mochitest/npruntime_identifiers_subpage.html b/dom/plugins/test/mochitest/npruntime_identifiers_subpage.html new file mode 100644 index 0000000000..38c62e017b --- /dev/null +++ b/dom/plugins/test/mochitest/npruntime_identifiers_subpage.html @@ -0,0 +1,4 @@ +<html> + <body> + <embed id="plugin1" type="application/x-test" width="400" height="100"> + </embed> diff --git a/dom/plugins/test/mochitest/plugin-stream-referer.sjs b/dom/plugins/test/mochitest/plugin-stream-referer.sjs new file mode 100644 index 0000000000..a1c9692c95 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin-stream-referer.sjs @@ -0,0 +1,12 @@ +function handleRequest(request, response) +{ + response.setHeader('Content-Type', 'text/plain', false); + response.setHeader('Cache-Control', 'no-cache', false); + response.setHeader('Content-Type', 'application/x-test', false); + if (request.hasHeader('Referer')) { + response.write('Referer found: ' + request.getHeader('Referer')); + } + else { + response.write('No Referer found'); + } +} diff --git a/dom/plugins/test/mochitest/plugin-utils.js b/dom/plugins/test/mochitest/plugin-utils.js new file mode 100644 index 0000000000..1d35d2b049 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin-utils.js @@ -0,0 +1,147 @@ +function paintCountIs(plugin, expected, msg) { + var count = plugin.getPaintCount(); + var realExpected = expected; + ++realExpected; // extra paint at startup for all async-rendering plugins + ok( + realExpected == count, + msg + + " (expected " + + expected + + " independent paints, expected " + + realExpected + + " logged paints, got " + + count + + " actual paints)" + ); +} + +function getTestPlugin(pluginName) { + var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"].getService( + SpecialPowers.Ci.nsIPluginHost + ); + var tags = ph.getPluginTags(); + var name = pluginName || "Test Plug-in"; + for (var tag of tags) { + if (tag.name == name) { + return tag; + } + } + + ok(false, "Could not find plugin tag with plugin name '" + name + "'"); + return null; +} + +// call this to set the test plugin(s) initially expected enabled state. +// it will automatically be reset to it's previous value after the test +// ends +function setTestPluginEnabledState(newEnabledState, pluginName) { + var oldEnabledState = SpecialPowers.setTestPluginEnabledState( + newEnabledState, + pluginName + ); + var plugin = getTestPlugin(pluginName); + // Run a nested event loop to wait for the preference change to + // propagate to the child. Yuck! + SpecialPowers.Services.tm.spinEventLoopUntil(() => { + return plugin.enabledState == newEnabledState; + }); + SimpleTest.registerCleanupFunction(async function() { + return SpecialPowers.setTestPluginEnabledState( + await oldEnabledState, + pluginName + ); + }); +} + +function crashAndGetCrashServiceRecord(crashMethodName, callback) { + var crashMan = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm") + .Services.crashmanager; + + // First, clear the crash record store. + info("Waiting for pruneOldCrashes"); + var future = new Date(Date.now() + 1000 * 60 * 60 * 24); + crashMan.pruneOldCrashes(future).then( + function() { + var iframe = document.getElementById("iframe1"); + var p = iframe.contentDocument.getElementById("plugin1"); + + var crashDateMS = Date.now(); + try { + p[crashMethodName](); + ok(false, "p." + crashMethodName + "() should throw an exception"); + } catch (e) { + ok(true, "p." + crashMethodName + "() should throw an exception"); + } + + // The crash record store is written and read back asyncly, so poll for + // the new record. + function tryGetCrash() { + info("Waiting for getCrashes"); + crashMan.getCrashes().then( + SpecialPowers.wrapCallback(function(crashes) { + if (crashes.length) { + is(crashes.length, 1, "There should be only one record"); + var crash = SpecialPowers.wrap(crashes[0]); + ok(!!crash.id, "Record should have an ID"); + ok(!!crash.crashDate, "Record should have a crash date"); + var dateMS = crash.crashDate.valueOf(); + var twoMin = 1000 * 60 * 2; + ok( + crashDateMS - twoMin <= dateMS && + dateMS <= crashDateMS + twoMin, + "Record's crash date should be nowish: " + + "now=" + + crashDateMS + + " recordDate=" + + dateMS + ); + callback(crashMan, crash); + } else { + setTimeout(tryGetCrash, 1000); + } + }), + function(err) { + ok(false, "Error getting crashes: " + err); + SimpleTest.finish(); + } + ); + } + setTimeout(tryGetCrash, 1000); + }, + function() { + ok(false, "pruneOldCrashes error"); + SimpleTest.finish(); + } + ); +} + +/** + * Returns a promise which resolves on `mozFullScreenChange`. + */ +function promiseFullScreenChange() { + return new Promise(resolve => { + document.addEventListener( + "fullscreenchange", + function(e) { + resolve(); + }, + { once: true } + ); + }); +} + +/** + * Crashes target plugin. Returns a promise; resolves on successful crash, + * rejects otherwise. + * @param plugin Target plugin to attempt to crash. + */ +function crashPlugin(plugin) { + return new Promise((resolve, reject) => { + try { + plugin.crash(); + reject(); + } catch (e) { + resolve(); + } + }); +} diff --git a/dom/plugins/test/mochitest/plugin_no_scroll_div.html b/dom/plugins/test/mochitest/plugin_no_scroll_div.html new file mode 100644 index 0000000000..b28f6d6ffb --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_no_scroll_div.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <embed id="testplugin" type="application/x-test" drawmode="solid" color="ff00ff00" wmode="window" + style="position:absolute; top:5px; left:5px; width:500px; height:250px"> +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_subframe_test.html b/dom/plugins/test/mochitest/plugin_subframe_test.html new file mode 100644 index 0000000000..598521d57e --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_subframe_test.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <iframe id="subframe" style="width:510px; height:260px;" src="plugin_no_scroll_div.html"></iframe> + <div style="display:block; height:3000px;"></div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_test.html b/dom/plugins/test/mochitest/plugin_test.html new file mode 100644 index 0000000000..88b70e8ee6 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_test.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <embed id="testplugin" type="application/x-test" drawmode="solid" color="ff00ff00" wmode="window" + style="position:absolute; top:50px; left:50px; width:500px; height:250px"> +<div style="display:block; height:3000px;"></div> + +<iframe id="subf" src="about:blank" width="300" height="300"></iframe> + +<a href="about:blank" id="aboutlink">Navigate to about:blank</a> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_window.html b/dom/plugins/test/mochitest/plugin_window.html new file mode 100644 index 0000000000..d3a298e89c --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_window.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>NPAPI Stream Tests</title> +</head> +<body onload="doTest()"> +<p id="display"></p> +<div id="content" style="display: none"> +This is a plugin test window. +</div> +<div id="test"> +<script class="testbody" type="text/javascript"> + +function doTest() { + window.opener.continueTest(); +} + +</script> +</div> +</body> + +</html> + diff --git a/dom/plugins/test/mochitest/pluginstream.js b/dom/plugins/test/mochitest/pluginstream.js new file mode 100644 index 0000000000..c4ab769d51 --- /dev/null +++ b/dom/plugins/test/mochitest/pluginstream.js @@ -0,0 +1,46 @@ +SimpleTest.waitForExplicitFinish(); + +function frameLoaded(finishWhenCalled = true, lastObject = false) { + var testframe = document.getElementById("testframe"); + function getNode(list) { + if (list.length === 0) { + return undefined; + } + return lastObject ? list[list.length - 1] : list[0]; + } + var embed = getNode(document.getElementsByTagName("embed")); + if (undefined === embed) { + embed = getNode(document.getElementsByTagName("object")); + } + + // In the file:// URI case, this ends up being cross-origin. + // Skip these checks in that case. + if (testframe.contentDocument) { + var content = testframe.contentDocument.body.innerHTML; + if (!content.length) { + return; + } + + var filename = + embed.getAttribute("src") || + embed.getAttribute("geturl") || + embed.getAttribute("geturlnotify") || + embed.getAttribute("data"); + + var req = new XMLHttpRequest(); + req.open("GET", filename, false); + req.overrideMimeType("text/plain; charset=x-user-defined"); + req.send(null); + is(req.status, 200, "bad XMLHttpRequest status"); + is( + content, + req.responseText.replace(/\r\n/g, "\n"), + "content doesn't match" + ); + } + + is(embed.getError(), "pass", "plugin reported error"); + if (finishWhenCalled) { + SimpleTest.finish(); + } +} diff --git a/dom/plugins/test/mochitest/post.sjs b/dom/plugins/test/mochitest/post.sjs new file mode 100644 index 0000000000..b391dbdd81 --- /dev/null +++ b/dom/plugins/test/mochitest/post.sjs @@ -0,0 +1,17 @@ +const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ var body = "";
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var bytes = [], avail = 0;
+ while ((avail = bodyStream.available()) > 0)
+ body += String.fromCharCode.apply(String, bodyStream.readByteArray(avail));
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(body);
+}
+
diff --git a/dom/plugins/test/mochitest/test_hanging.html b/dom/plugins/test/mochitest/test_hanging.html new file mode 100644 index 0000000000..b6dd1b12a7 --- /dev/null +++ b/dom/plugins/test/mochitest/test_hanging.html @@ -0,0 +1,59 @@ +<head> + <title>Plugin hanging</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + window.frameLoaded = function frameLoaded_toCrash() { + SimpleTest.expectChildProcessCrash(); + + // the default timeout is annoying high for mochitest runs + var timeoutPref = "dom.ipc.plugins.timeoutSecs"; + SpecialPowers.setIntPref(timeoutPref, 5); + + var iframe = document.getElementById("iframe1"); + var p = iframe.contentDocument.getElementById("plugin1"); + + p.setColor("FFFF00FF"); + + try { + p.hang(); + ok(false, "p.hang() should throw an exception"); + } catch (e) { + ok(true, "p.hang() should throw an exception"); + } + + try { + p.setColor("FFFF0000"); + ok(false, "p.setColor should throw after the plugin crashes"); + } catch (e) { + ok(true, "p.setColor should throw after the plugin crashes"); + } + + window.frameLoaded = function reloaded() { + var p1 = iframe.contentDocument.getElementById("plugin1"); + try { + p1.setColor("FF00FF00"); + ok(true, "Reloading worked"); + } catch (e) { + ok(false, "Reloading didn't give us a usable plugin"); + } + + try { + SpecialPowers.clearUserPref(timeoutPref); + } catch (e) { + ok(false, "Couldn't reset timeout pref"); + } + + SimpleTest.finish(); + }; + + iframe.contentWindow.location.reload(); + }; + + </script> + <iframe id="iframe1" src="crashing_subpage.html" width="600" height="600"></iframe> diff --git a/dom/plugins/test/mochitest/test_mixed_case_mime.html b/dom/plugins/test/mochitest/test_mixed_case_mime.html new file mode 100644 index 0000000000..85e5f19ac9 --- /dev/null +++ b/dom/plugins/test/mochitest/test_mixed_case_mime.html @@ -0,0 +1,25 @@ +<body> +<head> + <title>Test mixed case mimetype for plugins</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<script> + SimpleTest.expectAssertions(0, 1); + + SimpleTest.waitForExplicitFinish(); + + function frameLoaded() { + var contentDocument = document.getElementById("testframe").contentDocument; + ok(contentDocument.body.innerHTML.length > 0, "Frame content shouldn't be empty."); + ok(contentDocument.images.length > 0, "Frame content should have an image."); + SimpleTest.finish(); + } +</script> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()" src="mixed_case_mime.sjs"></iframe> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_plugin_fallback_focus.html b/dom/plugins/test/mochitest/test_plugin_fallback_focus.html new file mode 100644 index 0000000000..e89abb44df --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_fallback_focus.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test that plugins reject focus</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<div id="content"> + <object id="obj_elt" type="application/x-shockwave-flash"></object> + <object tabindex='0' id="obj_elt_with_idx" type="application/x-shockwave-flash"></object> + <embed id="embed_elt" type="application/x-shockwave-flash"></embed> +</div> +<script type="application/javascript"> +var objElt = document.getElementById('obj_elt'); +var objEltWithIdx = document.getElementById('obj_elt_with_idx'); +var embedElt = document.getElementById('embed_elt'); + +function checkHasFocus(expected, typeOfElts, elt) { + ok((document.activeElement == elt) == expected, + typeOfElts + " element should " + (expected ? "" : "not ") + "accept focus"); +} + +function checkNoneHasFocus(typeOfElts) { + checkHasFocus(false, typeOfElts + " <object>", objElt); + checkHasFocus(false, typeOfElts + " <object> with tabindex", objEltWithIdx); + checkHasFocus(false, typeOfElts + " <embed>", embedElt); +} + +function checkFocusable(expected, typeOfElts, elt) { + elt.focus(); + checkHasFocus(expected, typeOfElts, elt); +} + +// As plugins, object and embed elements are not given focus +ok(objElt != null, "object element should exist"); +ok(objEltWithIdx != null, "object element with tabindex should exist"); +ok(embedElt != null, "embed element should exist"); + +// As plugins, obj/embed_elt can not take focus +checkNoneHasFocus("plugin"); + +// Switch obj/embed_elt attributes from plugin to image +objElt.data = "large-pic.jpg"; +objElt.width = 100; +objElt.height = 100; +objElt.type = "image/jpg"; +objEltWithIdx.data = "large-pic.jpg"; +objEltWithIdx.width = 100; +objEltWithIdx.height = 100; +objEltWithIdx.type = "image/jpg"; +embedElt.src = "large-pic.jpg"; +embedElt.width = 100; +embedElt.height = 100; +embedElt.type = "image/jpg"; + +// As images, obj/embed_elt can take focus as image +// object image elements require a tabindex to accept focus. +// embed elements must be reparented before new type is recognized. +checkFocusable(false, "<object> image", objElt); +checkFocusable(true, "<object> image with tabindex", objEltWithIdx); +checkFocusable(false, "<embed> plugin with image attribs before reparenting", embedElt); +embedElt.parentNode.appendChild(embedElt); +checkFocusable(true, "<embed> image", embedElt); + +// Switch obj/embed_elt attributes from image to plugin +objElt.type = "application/x-shockwave-flash"; +embedElt.type = "application/x-shockwave-flash"; + +// embed elements must be reparented before new type is recognized. +checkFocusable(true, "<embed> image with plugin attribs", embedElt); +embedElt.parentNode.appendChild(embedElt); +checkNoneHasFocus("plugin"); +</script> +</body> +</html> + + diff --git a/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html b/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html new file mode 100644 index 0000000000..5fdd2a7b6c --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for plugin child widgets not being invalidated by scrolling</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="initialize()"> +<script type="application/javascript"> +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, + "Test Plug-in"); +</script> + +<p id="display"> + <iframe id="i" src="plugin_scroll_invalidation.html" + width="50" height="50" scrolling="no"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +var scrolling; +var scrolling_plugins = []; +var paint_waiter; +var last_paint_counts; + +function initialize() { + scrolling = document.getElementById("i").contentWindow; + scrolling_plugins = scrolling.document.querySelectorAll("embed.scrolling"); + paint_waiter = scrolling.document.getElementById("paint-waiter"); + + scrolling.scrollTo(50, 45); + + is(paint_waiter.getPaintCount(), 0, "zero-sized plugin not painted"); + + waitForPaint(scrollAround); +} + +function scrollAround() { + var paints = getPaintCounts(); + + for (var i = 0; i < paints.length; ++i) { + isnot(paints[i], 0, "embed " + scrolling_plugins[i].id + " is painted"); + } + + last_paint_counts = paints; + + scrolling.scrollBy(-5, 5); + scrolling.scrollBy(5, 5); + scrolling.scrollBy(5, -5); + scrolling.scrollBy(-5, -5); + + scrolling.scrollTo(45, 45); + scrolling.scrollBy(10, 0); + scrolling.scrollBy(0, 10); + scrolling.scrollBy(-10, 0); + scrolling.scrollBy(0, -10); + + waitForPaint(done); +} + +function done() { + var paints = getPaintCounts(); + for (var i = 0; i < paints.length; ++i) { + is(paints[i], last_paint_counts[i], "embed " + scrolling_plugins[i].id + " is not painted on scroll"); + } + SimpleTest.finish(); +} + +// Waits for the paint_waiter plugin to be repainted and then +// calls 'func' to continue. +function waitForPaint(func) { + paint_waiter.last_paint_count = paint_waiter.getPaintCount(); + + paint_waiter.style.left = scrolling.scrollX + "px"; + paint_waiter.style.top = scrolling.scrollY + "px"; + + // Fiddle with the style in a way that should force some repainting + paint_waiter.style.width = + (paint_waiter.getBoundingClientRect().width + 1) + "px"; + paint_waiter.style.height = "1px"; + + function waitForPaintHelper() { + if (paint_waiter.getPaintCount() != paint_waiter.last_paint_count) { + setTimeout(func, 0); + return; + } + setTimeout(waitForPaintHelper, 0); + } + waitForPaintHelper(); +} + +function getPaintCounts() { + var result = []; + for (var i = 0; i < scrolling_plugins.length; ++i) { + result[i] = scrolling_plugins[i].getPaintCount(); + } + return result; +} + +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_plugin_scroll_painting.html b/dom/plugins/test/mochitest/test_plugin_scroll_painting.html new file mode 100644 index 0000000000..1041b948da --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_scroll_painting.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that scrolling a windowless plugin doesn't force us to repaint it</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTest()"> +<p id="display"></p> + <embed id="plugin" type="application/x-test" style="width:50px; height:10px; margin-top:20px;"></embed> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +var container = document.documentElement; +container.scrollTop = 0; +var plugin = document.getElementById("plugin"); +var pluginTop; +var beforeScrollPaintCount; + +function waitForScroll() { + if (plugin.getEdge(1) >= pluginTop) { + setTimeout(waitForScroll, 0); + return; + } + + is(plugin.getPaintCount(), beforeScrollPaintCount, "plugin should not paint due to scrolling"); + SimpleTest.finish(); +} + +function waitForInitialScroll() { + if (plugin.getEdge(1) >= pluginTop) { + setTimeout(waitForInitialScroll, 0); + return; + } + + pluginTop = plugin.getEdge(1); + beforeScrollPaintCount = plugin.getPaintCount(); + container.scrollTop = 20; + waitForScroll(); +} + +function runTest() { + document.body.offsetTop; + pluginTop = plugin.getEdge(1); + container.scrollTop = 10; + waitForInitialScroll(); +} +</script> + +<div style="height:4000px;"></div> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_geturl.html b/dom/plugins/test/mochitest/test_pluginstream_geturl.html new file mode 100644 index 0000000000..fe69427a42 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_geturl.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPN_GetURL NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - The plugin reports that data can be sent to + - it in 1024-byte chunks, and the stream is initiated by a call to + - NPN_GetURL. + --> + <embed geturl="loremipsum.txt" streammode="normal" + streamchunksize="1024" frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + </body> + </html> +
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html b/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html new file mode 100644 index 0000000000..ee4c2b119d --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html @@ -0,0 +1,30 @@ +<body> +<head> + <title>NPAPI NPN_GetURLNotify Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - The stream is requested by + - the plugin using NPN_GetURLNotify, and the plugin does not send the + - stream back to the browser until NPP_URLNotify is called. + --> + <embed geturlnotify="loremipsum.txt" streammode="normal" + streamchunksize="1024" frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_positioning.html b/dom/plugins/test/mochitest/test_positioning.html new file mode 100644 index 0000000000..b73f4b06fd --- /dev/null +++ b/dom/plugins/test/mochitest/test_positioning.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test whether windowless plugins receive correct visible/invisible notifications.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + + <style type="text/css"> + body { + height: 10000px; + } + </style> + +<body onload="startTest()"> + <p id="display"></p> + + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var p = null; + + function startTest() { + p = document.getElementById("theplugin"); + + // Wait for the plugin to have painted once + var interval = setInterval(function() { + if (!p.getPaintCount()) + return; + + clearInterval(interval); + doScroll(); + }, 100); + } + + const kScrollAmount = 1000; + var startY; + + function doScroll() { + let y = p.getWindowPosition()[1]; + startY = y; + + scrollBy(0, kScrollAmount); + setTimeout(checkScroll, 500); + } + + function checkScroll() { + let y = p.getWindowPosition()[1]; + + is(y, startY - kScrollAmount, "Window should be informed of its new position."); + SimpleTest.finish(); + } + </script> + + <embed id="theplugin" type="application/x-test" width="200" height="200"></embed> diff --git a/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html b/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html new file mode 100644 index 0000000000..565c1494f4 --- /dev/null +++ b/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPNVcontentsScaleFactor Test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var pluginElement = document.getElementById("plugin"); + var contentsScaleFactor; + var exceptionThrown = false; + try { + contentsScaleFactor = pluginElement.queryContentsScaleFactor(); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Exception thrown getting contents scale factor."); + is(isNaN(contentsScaleFactor), false, "Invalid return getting contents scale factor"); + ok(true, "Got Scale Factor of " + contentsScaleFactor); + SimpleTest.finish(); + } + </script> + + <embed id="plugin" type="application/x-test" width="400" height="400"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html b/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html new file mode 100644 index 0000000000..8db018fd66 --- /dev/null +++ b/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPNVcontentsScaleFactor Test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="utils.js"></script> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var pluginElement = document.getElementById("plugin"); + var contentsScaleFactor; + var exceptionThrown = false; + try { + contentsScaleFactor = pluginElement.queryContentsScaleFactor(); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Exception thrown getting contents scale factor."); + is(isNaN(contentsScaleFactor), false, "Invalid return getting contents scale factor"); + ok(true, "Got Scale Factor of " + contentsScaleFactor); + SimpleTest.finish(); + } + </script> + + <embed id="plugin" type="application/x-test" width="400" height="400" wmode="window"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html b/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html new file mode 100644 index 0000000000..fc54370a0b --- /dev/null +++ b/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<!-- bug 820708 --> +<html> + <head> + <meta><charset="utf-8"/> + <title>Test Refreshing navigator.plugins (bug 820708)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css"> + </head> + <body> + <p id="display"></p> + <script class="testbody" type="application/javascript"> + "use strict"; + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + ok("Test Plug-in" in navigator.plugins, "testplugin should be present"); + ok("application/x-test" in navigator.mimeTypes, "testplugin MIME should be present"); + + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_DISABLED); + ok(!("Test Plug-in" in navigator.plugins), "testplugin should not be present"); + ok(!("application/x-test" in navigator.mimeTypes), "testplugin MIME should not be present"); + + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + ok("Test Plug-in" in navigator.plugins, "testplugin should be present again"); + ok("application/x-test" in navigator.mimeTypes, "testplugin MIME should be present again"); + SimpleTest.finish(); + </script> + </body> +</html> diff --git a/dom/plugins/test/moz.build b/dom/plugins/test/moz.build new file mode 100644 index 0000000000..7f40fb3cbe --- /dev/null +++ b/dom/plugins/test/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ["testplugin"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("gtk", "cocoa", "windows"): + MOCHITEST_MANIFESTS += ["mochitest/mochitest.ini"] + BROWSER_CHROME_MANIFESTS += ["mochitest/browser.ini"] diff --git a/dom/plugins/test/reftest/border-padding-1-ref.html b/dom/plugins/test/reftest/border-padding-1-ref.html new file mode 100644 index 0000000000..1a33644ac4 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-1-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<body style="margin:0"> +<div style="width:184px; height:192px; margin:90px 80px; outline:5px dashed blue; + border:dotted black; border-width:4px 8px 4px 8px; + background:cyan;"> + <div style="margin:3px 1px; height:186px; background:lime;"></div> +</div> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-1.html b/dom/plugins/test/reftest/border-padding-1.html new file mode 100644 index 0000000000..6fa2446f40 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-1.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('p1')"> +<object type="application/x-test" drawmode="solid" color="ff00ff00" + style="width:200px; height:200px; display:block; margin:90px 80px; + outline:5px dashed blue; + background:cyan; + border:dotted black; border-width:4px 8px 4px 8px; + padding:3px 1px;" id="p1"> +</object> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-2-ref.html b/dom/plugins/test/reftest/border-padding-2-ref.html new file mode 100644 index 0000000000..ae92da4032 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-2-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('plugin')"> +<div style="width:184px; height:192px; margin:90px 80px; outline:5px dashed blue; + border:dotted black; border-width:4px 8px 4px 8px; + background:cyan;"> + <object style="margin:3px 1px; height:186px; width:182px; display:block;" + type="application/x-test" + id="plugin"> + </object> +</div> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-2.html b/dom/plugins/test/reftest/border-padding-2.html new file mode 100644 index 0000000000..6a39d2d819 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-2.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('plugin')"> +<object type="application/x-test" + style="width:200px; height:200px; display:block; margin:90px 80px; + outline:5px dashed blue; + background:cyan; + border:dotted black; border-width:4px 8px 4px 8px; + padding:3px 1px;" + id="plugin"> +</object> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-3-ref.html b/dom/plugins/test/reftest/border-padding-3-ref.html new file mode 100644 index 0000000000..5c7bb74564 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-3-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<body style="margin:0"> +<div style="width:184px; height:192px; margin:90px 80px; outline:5px dashed blue; + border:dotted black; border-width:4px 8px 4px 8px; + background:cyan;"> + <div style="margin:3px 1px; height:186px; width:182px; background:rgb(255,128,255);"></object> +</div> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-3.html b/dom/plugins/test/reftest/border-padding-3.html new file mode 100644 index 0000000000..4d240a7eb4 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-3.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('plugin', true)"> +<object type="application/x-test" id="plugin" + drawmode="solid" color="00000000" + style="width:200px; height:200px; display:block; margin:90px 80px; + outline:5px dashed blue; + background:cyan; + border:dotted black; border-width:4px 8px 4px 8px; + padding:3px 1px;"> +</object> +<script> +var prevPaintCount = 0; +function doTestWait() { + if (document.getElementById("plugin").getPaintCount() != prevPaintCount) { + document.documentElement.removeAttribute('class'); + } else { + setTimeout(doTestWait, 0); + } +} + +function doTest() { + prevPaintCount = document.getElementById("plugin").getPaintCount(); + document.getElementById("plugin").setColor("FFFF80FF"); + setTimeout(doTestWait, 0); + +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</body> +</html> diff --git a/dom/plugins/test/reftest/div-alpha-opacity.html b/dom/plugins/test/reftest/div-alpha-opacity.html new file mode 100644 index 0000000000..fec913b640 --- /dev/null +++ b/dom/plugins/test/reftest/div-alpha-opacity.html @@ -0,0 +1,28 @@ +<!doctype html> +<html> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + width:400px; height:400px; + border:2px solid blue; + background-color: rgb(160,160,160); + opacity:0.8; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +</head> +<body> +<div id="two"></div> +<div id="one"></div> +</body> +</html> + diff --git a/dom/plugins/test/reftest/div-alpha-zindex.html b/dom/plugins/test/reftest/div-alpha-zindex.html new file mode 100644 index 0000000000..e4672b913b --- /dev/null +++ b/dom/plugins/test/reftest/div-alpha-zindex.html @@ -0,0 +1,27 @@ +<!doctype html> +<html> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + width:400px; height:400px; + background-color: rgb(0,255,0); + opacity:0.6; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +</head> +<body> +<div id="two"></div> +<div id="one"></div> +</body> +</html> + diff --git a/dom/plugins/test/reftest/div-sanity.html b/dom/plugins/test/reftest/div-sanity.html new file mode 100644 index 0000000000..9ffa539191 --- /dev/null +++ b/dom/plugins/test/reftest/div-sanity.html @@ -0,0 +1,17 @@ +<!doctype html> +<html><head> +<title>div boxes</title> +<style> +div { + width: 400px; + height: 400px; + display: inline-block; +} +</style> +</head> +<body> +<div style="background-color: #FF0000;"></div> <!-- red --> +<div style="background-color: #00FF00;"></div> <!-- green --> +<div style="background-color: #0000FF;"></div> <!-- blue --> +<div style="background-color: #999999;"></div> <!-- gray --> +</body></html> diff --git a/dom/plugins/test/reftest/plugin-alpha-opacity.html b/dom/plugins/test/reftest/plugin-alpha-opacity.html new file mode 100644 index 0000000000..2db6cc4de3 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-alpha-opacity.html @@ -0,0 +1,29 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + width:404px; height:404px; + border:2px solid blue; + opacity:.8; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('one')"> +<div id="two"></div> +<embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="FFa0a0a0"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-alpha-zindex.html b/dom/plugins/test/reftest/plugin-alpha-zindex.html new file mode 100644 index 0000000000..ead9b6f4ce --- /dev/null +++ b/dom/plugins/test/reftest/plugin-alpha-zindex.html @@ -0,0 +1,26 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('one')"> +<div id="two"></div> +<embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="9900FF00" id="p1"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-background-1-step.html b/dom/plugins/test/reftest/plugin-background-1-step.html new file mode 100644 index 0000000000..9498633b41 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-1-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 1; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-10-step.html b/dom/plugins/test/reftest/plugin-background-10-step.html new file mode 100644 index 0000000000..7a0824a565 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-10-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 10; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-2-step.html b/dom/plugins/test/reftest/plugin-background-2-step.html new file mode 100644 index 0000000000..cc186a5f29 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-2-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 2; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-5-step.html b/dom/plugins/test/reftest/plugin-background-5-step.html new file mode 100644 index 0000000000..2630719c88 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-5-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 5; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-ref.html b/dom/plugins/test/reftest/plugin-background-ref.html new file mode 100644 index 0000000000..651fdecef5 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <div id="plugin"></div> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background.css b/dom/plugins/test/reftest/plugin-background.css new file mode 100644 index 0000000000..f6b251214d --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.css @@ -0,0 +1,61 @@ +div { + position: absolute; +} +#bad { + left:220px; top:0px; + z-index: 0; +} +#good { + left:0px; top:0px; + width:220px; height:220px; + /* Core Animation alpha blending rounding differs + from the Core Graphics blending, adjust with care */ + background-color: rgba(0,255,0, 0.51); + z-index: 0; +} + +#topbar { + left:0px; top:0px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} +#topbar { + left:0px; top:0px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} +#leftbar { + left:0px; top:0px; + width:20px; height:220px; + background-color: rgb(0,0,0); + z-index: 2; +} +#rightbar { + left:200px; top:0px; + width:20px; height:220px; + background-color: rgb(0,0,0); + z-index: 2; +} +#bottombar { + left:0px; top:200px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} + +div#plugin { + position: absolute; + left:1px; top:1px; + width:199px; height:199px; + background-color: rgba(0,0,255, 0.2); + z-index: 1; +} + +embed#plugin { + position: absolute; + left:1px; top:1px; + z-index: 1; +} + diff --git a/dom/plugins/test/reftest/plugin-background.html b/dom/plugins/test/reftest/plugin-background.html new file mode 100644 index 0000000000..4cd1e3f538 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> +</head> +<script src="utils.js"> +</script> +<body onLoad="forceLoadPlugin('plugin')"> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background.js b/dom/plugins/test/reftest/plugin-background.js new file mode 100644 index 0000000000..8c6d28572d --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.js @@ -0,0 +1,73 @@ +// The including script sets this for us +//var NUM_STEPS; + +var plugin; +var left = 1, top = 1, width = 199, height = 199; +function movePluginTo(x, y, w, h) { + left = x; top = y; width = w; height = h; + plugin.width = w; + plugin.height = h; + plugin.style.left = left + "px"; + plugin.style.top = top + "px"; +} +function deltaInBounds(dx,dy, dw,dh) { + var l = dx + left; + var r = l + width + dw; + var t = dy + top; + var b = t + height + dh; + return (0 <= l && l <= 20 && + 0 <= t && t <= 20 && + 200 <= r && r <= 220 && + 200 <= b && b <= 220); +} + +function start() { + window.removeEventListener("MozReftestInvalidate", start); + + window.addEventListener("MozAfterPaint", step); + window.addEventListener("MozPaintWaitFinished", step); + + plugin = document.getElementById("plugin"); + + movePluginTo(0,0, 200,200); +} + +var steps = 0; +var which = "move"; // or "grow" +var dx = 1, dy = 1, dw = 1, dh = 1; +function step() { + if (++steps >= NUM_STEPS) { + window.removeEventListener("MozAfterPaint", step); + window.removeEventListener("MozPaintWaitFinished", step); + return finish(); + } + + var didSomething = false; + if (which == "grow") { + if (deltaInBounds(0,0, dw,dh)) { + movePluginTo(left,top, width+dw, height+dh); + didSomething = true; + } else { + dw = -dw; dh = -dh; + } + } else { + // "move" + if (deltaInBounds(dx,dy, 0,0)) { + movePluginTo(left+dx,top+dy, width, height); + didSomething = true; + } else { + dx = -dx; dy = -dy; + } + } + which = (which == "grow") ? "move" : "grow"; + + if (!didSomething) { + step(); + } +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +window.addEventListener("MozReftestInvalidate", start); diff --git a/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html b/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html new file mode 100644 index 0000000000..e339dd2669 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html @@ -0,0 +1,56 @@ +<!doctype html> +<html class="reftest-wait"> +<head> + <style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:4; +} +#two { + position:absolute; + top:100px; left:100px; + background-color:rgb(0,0,0,0); + z-index:3; +} +#three { + position:absolute; + left:100px; top:100px; + width:200px; height:200px; + background-color: rgb(255,0,0); + opacity:0.6; + z-index:2; +} +#four { + position:absolute; + top:100px; left:100px; + z-index:1; +} + </style> + <script src="utils.js" type="text/javascript"> + </script> + <script type="text/javascript"> +function paintCanvas() { + var canvas = document.getElementById("two"); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgba(255,0,0, 0.6)"; + ctx.fillRect(0,0, 200,200); +} + + function doTest() { + paintCanvas(); + forceLoadPlugin(['one', 'four']); + } + </script> +</style> +</head> +<body onload="doTest();"> + <embed id="four" type="application/x-test" width="200" height="200" + drawmode="solid" color="FFFF0000"></embed> + <div id="three"></div> + <canvas id="two" width="200" height="200"></canvas> + <embed id="one" type="application/x-test" width="400" height="400" + drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html b/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html new file mode 100644 index 0000000000..517099d1b1 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html @@ -0,0 +1,41 @@ +<!doctype html> +<html class="reftest-wait"> +<head> + <style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; +// Set these using the canvas API +// width:200px; height:200px; +// background-color: rgb(255,0,0); + z-index:0; +} + </style> + <script src="utils.js" type="text/javascript"> + </script> + <script type="text/javascript"> +function paintCanvas() { + var canvas = document.getElementById("two"); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgb(255,0,0)"; + ctx.fillRect(0,0, 200,200); +} + + function doTest() { + paintCanvas(); + forceLoadPlugin('one'); + } + </script> +</style> +</head> +<body onload="doTest()"> + <canvas width="200" height="200" id="two"></canvas> + <embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-sanity.html b/dom/plugins/test/reftest/plugin-sanity.html new file mode 100644 index 0000000000..4f9c30eee4 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-sanity.html @@ -0,0 +1,13 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<title>Plugin boxes</title> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin(['p1', 'p2', 'p3', 'p4'])"> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FFFF0000" id="p1"></embed> <!-- red --> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FF00FF00" id="p2"></embed> <!-- green --> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FF0000FF" id="p3"></embed> <!-- blue --> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FF999999" id="p4"></embed> <!-- gray --> +</body></html> diff --git a/dom/plugins/test/reftest/plugin-transform-1-ref.html b/dom/plugins/test/reftest/plugin-transform-1-ref.html new file mode 100644 index 0000000000..259a78b41b --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-1-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('p')">
+<embed type="application/x-test" id="p"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-1.html b/dom/plugins/test/reftest/plugin-transform-1.html new file mode 100644 index 0000000000..19f6e8c20f --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-1.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('p')">
+<embed type="application/x-test" style="-moz-transform:scale(1)" id="p"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-2-ref.html b/dom/plugins/test/reftest/plugin-transform-2-ref.html new file mode 100644 index 0000000000..93a3924d7e --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-2-ref.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML>
+<html>
+<body>
+<div style="width:100px; height:100px; -moz-transform-origin:top left;
+ -moz-transform:scale(2); background:rgb(0,255,0)"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-2.html b/dom/plugins/test/reftest/plugin-transform-2.html new file mode 100644 index 0000000000..7f48640c19 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-2.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('one')">
+<embed type="application/x-test" drawmode="solid" color="FF00FF00"
+ style="width:100px; height:100px; -moz-transform-origin:top left;
+ -moz-transform:scale(2); display:block"
+ id="one"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html b/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html new file mode 100644 index 0000000000..52fda4bcf9 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html @@ -0,0 +1,28 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:1; +} +#two { + position:absolute; + top:0px; left:0px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); + -moz-transform-origin: 0 0; + -moz-transform: translate(100px,100px); +} +</style> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('one')"> + <div id="two"></div> + <embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html b/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html new file mode 100644 index 0000000000..fafec34f43 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="ltr" style="text-align: left;"> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-1.html b/dom/plugins/test/reftest/pluginproblemui-direction-1.html new file mode 100644 index 0000000000..9888850dc9 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-1.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="rtl" style="text-align: left;"> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html b/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html new file mode 100644 index 0000000000..e807b86b5b --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="ltr" style="text-align: left;"> + <!-- a variant of pluginproblemui-direction-1.html that covers up + the spot where we get random variation with d2d, so that we + can still test it with d2d --> + <div style="position: absolute; width: 1px; height: 1px; background: red; z-index: 1; left: 401px; top: 19px;"></div> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-2.html b/dom/plugins/test/reftest/pluginproblemui-direction-2.html new file mode 100644 index 0000000000..95b358e372 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-2.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="rtl" style="text-align: left;"> + <!-- a variant of pluginproblemui-direction-1.html that covers up + the spot where we get random variation with d2d, so that we + can still test it with d2d --> + <div style="position: absolute; width: 1px; height: 1px; background: red; z-index: 1; left: 401px; top: 19px;"></div> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/reftest.list b/dom/plugins/test/reftest/reftest.list new file mode 100644 index 0000000000..2db57bab37 --- /dev/null +++ b/dom/plugins/test/reftest/reftest.list @@ -0,0 +1,26 @@ +# basic sanity checking +random-if(!haveTestPlugin) HTTP != plugin-sanity.html about:blank +fails-if(!haveTestPlugin) HTTP == plugin-sanity.html div-sanity.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-alpha-zindex.html div-alpha-zindex.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-164000) HTTP == plugin-alpha-opacity.html div-alpha-opacity.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) HTTP == windowless-clipping-1.html windowless-clipping-1-ref.html # bug 631832 +# fuzzy because of anti-aliasing in dashed border +fuzzy(0-16,0-256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) HTTP == border-padding-1.html border-padding-1-ref.html # bug 629430 +fuzzy(0-16,0-256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) HTTP == border-padding-2.html border-padding-2-ref.html # bug 629430 +fuzzy(0-16,0-256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin||Android) HTTP == border-padding-3.html border-padding-3-ref.html # bug 629430 # bug 773482 +# The following two "pluginproblemui-direction" tests are unreliable on all platforms. They should be re-written or replaced. +#random-if(cocoaWidget||d2d||/^Windows\x20NT\x205\.1/.test(http.oscpu)) fails-if(!haveTestPlugin&&!Android) HTTP == pluginproblemui-direction-1.html pluginproblemui-direction-1-ref.html # bug 567367 +#random-if(cocoaWidget) fails-if(!haveTestPlugin&&!Android) HTTP == pluginproblemui-direction-2.html pluginproblemui-direction-2-ref.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-canvas-alpha-zindex.html div-alpha-zindex.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-transform-alpha-zindex.html div-alpha-zindex.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-busy-alpha-zindex.html div-alpha-zindex.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-1-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-2-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-5-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-10-step.html plugin-background-ref.html +random-if(!haveTestPlugin) HTTP == plugin-transform-1.html plugin-transform-1-ref.html +fails-if(!haveTestPlugin) HTTP == plugin-transform-2.html plugin-transform-2-ref.html +skip-if(!haveTestPlugin) HTTP == shrink-1.html shrink-1-ref.html +pref(dom.mozPaintCount.enabled,true) skip-if(!haveTestPlugin) HTTP == update-1.html update-1-ref.html +skip-if(!haveTestPlugin) HTTP == windowless-layers.html windowless-layers-ref.html diff --git a/dom/plugins/test/reftest/shrink-1-ref.html b/dom/plugins/test/reftest/shrink-1-ref.html new file mode 100644 index 0000000000..0906fe5789 --- /dev/null +++ b/dom/plugins/test/reftest/shrink-1-ref.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('plugin')"> + <embed id="plugin" type="application/x-test" + width="50px" height="40px"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/shrink-1.html b/dom/plugins/test/reftest/shrink-1.html new file mode 100644 index 0000000000..a277e1afaa --- /dev/null +++ b/dom/plugins/test/reftest/shrink-1.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <script src="utils.js"> + </script> + <script> +function doShrink() +{ + var plugin = document.getElementById("plugin"); + plugin.setSlowPaint(true); + plugin.width = "50"; + plugin.height = "40"; + + document.documentElement.removeAttribute("class"); +} + +document.addEventListener("MozReftestInvalidate", doShrink); + </script> +</head> +<body onLoad="forceLoadPlugin('plugin', true)"> + <embed id="plugin" type="application/x-test" + width="300" height="500"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/update-1-ref.html b/dom/plugins/test/reftest/update-1-ref.html new file mode 100644 index 0000000000..7303d19840 --- /dev/null +++ b/dom/plugins/test/reftest/update-1-ref.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('plugin')"> + <embed id="plugin" type="application/x-test" + drawmode="solid" color="FFFF0000" width="30" height="50"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/update-1.html b/dom/plugins/test/reftest/update-1.html new file mode 100644 index 0000000000..cff85bc9f0 --- /dev/null +++ b/dom/plugins/test/reftest/update-1.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <title>Test for bugs 807728 and 810426</title> + <script src="utils.js"> + </script> + <script> +function start() +{ + document.removeEventListener("MozReftestInvalidate", start); + + var plugin = document.getElementById("plugin"); + var color = "FF000000"; + var color_count = 0; + var last_paint_count = 0; + // Enough paints to test reusing a surface after it has been + // moved from front to back buffer. + // FIXME: Stop using mozPaintCount for this test. + // Can't make it a chrome:// url because it needs http, thus no + // SpecialPowers :/ + var final_paint_count = window.mozPaintCount + 10; + var final_color = "FFFF0000"; + + // Using mozPaintCount to track when content has been updated as an + // indication that the browser has received updates, instead of + // plugin.getPaintCount() which tracks when the plugin sends updates or + // MozAfterPaint events which track OS paints. Not using + // MozPaintWaitFinished because invalidation causes no geometry changes. + function wait_for_paints() { + var paint_count = window.mozPaintCount; + if (paint_count >= final_paint_count && color == final_color) { + document.documentElement.removeAttribute("class"); + return; + } + if (paint_count != last_paint_count) { + last_paint_count = paint_count; + if (paint_count + 1 >= final_paint_count) { + color = final_color; + // Wait for the paint with the final color + final_paint_count = paint_count + 1; + } else { + ++color_count; + color = "FF00000" + color_count; + } + plugin.setColor(color); + } + setTimeout(wait_for_paints, 0); + } + + wait_for_paints(); +} + +// MozReftestInvalidate is delivered after initial painting has settled. +document.addEventListener("MozReftestInvalidate", start); + </script> +</head> +<body onLoad="forceLoadPlugin('plugin', true)"> + <embed id="plugin" type="application/x-test" + drawmode="solid" color="FF000000" width="30" height="50"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/utils.js b/dom/plugins/test/reftest/utils.js new file mode 100644 index 0000000000..f046089afc --- /dev/null +++ b/dom/plugins/test/reftest/utils.js @@ -0,0 +1,18 @@ +function forceLoadPluginElement(id) { + var e = document.getElementById(id); + var found = e.pluginFoundElement; +} + +function forceLoadPlugin(ids, skipRemoveAttribute) { + if (Array.isArray(ids)) { + ids.forEach(function(element, index, array) { + forceLoadPluginElement(element); + }); + } else { + forceLoadPluginElement(ids); + } + if (skipRemoveAttribute) { + return; + } + document.documentElement.removeAttribute("class"); +} diff --git a/dom/plugins/test/reftest/windowless-clipping-1-ref.html b/dom/plugins/test/reftest/windowless-clipping-1-ref.html new file mode 100644 index 0000000000..e59ecb79b2 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-clipping-1-ref.html @@ -0,0 +1,14 @@ +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('p1')"> +<div style="width:100px; height:100px; overflow:auto;"> + <div style="width:100px; height:100px; overflow:hidden;"> + <embed type="application/x-test" style="width:200px;" id="p1"></embed> + </div> +</div> +<div style="width:100px; height:100px; background-color:lime;"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/windowless-clipping-1.html b/dom/plugins/test/reftest/windowless-clipping-1.html new file mode 100644 index 0000000000..dc1c25ac10 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-clipping-1.html @@ -0,0 +1,15 @@ +<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('p1')">
+<div style="width:100px; height:100px; overflow:hidden;">
+ <embed type="application/x-test" style="width:200px;"></embed>
+</div>
+<div style="width:100px; height:100px; overflow:hidden;">
+ <embed type="application/x-test" style="width:200px;"
+ drawmode="solid" color="ff00ff00" id="p1"></embed>
+</div>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/windowless-layers-ref.html b/dom/plugins/test/reftest/windowless-layers-ref.html new file mode 100644 index 0000000000..765527b68f --- /dev/null +++ b/dom/plugins/test/reftest/windowless-layers-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('p1')"> + <embed type="application/x-test" style="width:200px; height:200px;" id="p1"> +</body> +</html> diff --git a/dom/plugins/test/reftest/windowless-layers.html b/dom/plugins/test/reftest/windowless-layers.html new file mode 100644 index 0000000000..9e24c13a68 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-layers.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin(['p1', 'p2'])"> + <div style="width:200px; height:200px; overflow:hidden; position:absolute; z-index:1"> + <embed type="application/x-test" style="width:200px; height:200px;" id="p1"> + </div> + <div style="width:200px; height:100px; overflow:hidden; position:absolute; z-index:2"> + <embed type="application/x-test" style="width:200px; height:200px;" id="p2"> + </div> +</body> +</html> diff --git a/dom/plugins/test/testplugin/Info.plist b/dom/plugins/test/testplugin/Info.plist new file mode 100644 index 0000000000..dc6aa5cec3 --- /dev/null +++ b/dom/plugins/test/testplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnptest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.TestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>TEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Test Plug-in</string> + <key>WebPluginDescription</key> + <string>Plug-in for testing purposes.™ (हिन्दी 中文 العربية)</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>tst</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Test ™ mimetype</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/README b/dom/plugins/test/testplugin/README new file mode 100644 index 0000000000..993de360ce --- /dev/null +++ b/dom/plugins/test/testplugin/README @@ -0,0 +1,424 @@ += Instructions for using the test plugin = + +== MIME type == + +The test plugin registers itself for the MIME type "application/x-test". + +== Event Model == + +* getEventModel() +Returns the NPAPI event model in use. On platforms without event models, +simply returns 0; + +== Rendering == + +By default, the plugin fills its rectangle with gray, with a black border, and +renders the user-agent string (obtained from NPN_UserAgent) centered in black. +This rendering method is not supported for the async drawing models. + +The test plugin supports the following parameters: + +* drawmode="solid" +The plugin will draw a solid color instead of the default rendering described +above. The default solid color is completely transparent black (i.e., nothing). +This should be specified when using one of the async models. + +* asyncmodel="bitmap" +The plugin will use the NPAPI Async Bitmap drawing model extension. On +unsupported platforms this will fallback to non-async rendering. + +* asyncmodel="dxgi" +The plugin will use the NPAPI Async DXGI drawing model extension. Only +supported on Windows Vista or higher. On unsupported platforms this will +fallback to non-async rendering. + +* color +This specifies the color to use for drawmode="solid". The value should be 8 hex +digits, 2 per channel in AARRGGBB format. + +== Generic API Tests == + +* setUndefinedValueTest +Attempts to set the value of an undefined variable (0x0) via NPN_SetValue, +returns true if it succeeds and false if it doesn't. It should never succeed. + +* .getReflector() +Hands back an object which reflects properties as values, e.g. + .getReflector().foo = 'foo' + .getReflector()['foo'] = 'foo' + .getReflector()[1] = 1 + +* .getNPNVdocumentOrigin() +Returns the origin string retrieved from the browser by a NPNVdocumentOrigin +variable request. Does not cache the value, gets it from the browser every time. + +== NPN_ConvertPoint testing == + +* convertPointX(sourceSpace, sourceX, sourceY, destSpace) +* convertPointY(sourceSpace, sourceX, sourceY, destSpace) +The plugin uses NPN_ConvertPoint to convert sourceX and sourceY from the source +to dest space and returns the X or Y result based on the call. + +== NPCocoaEventWindowFocusChanged == + +* getTopLevelWindowActivationState() +Returns the activation state for the top-level window as set by the last +NPCocoaEventWindowFocusChanged event. Returns true for active, false for +inactive, and throws an exception if the state is unknown (uninitialized). + +* getTopLevelWindowActivationEventCount() +Returns the number of NPCocoaEventWindowFocusChanged events received by +the instance. + +== Focus Tests == + +* getFocusState() +Returns the plugin's focus state. Returns true for focused, false for unfocused, +and throws an exception if the state is unknown (uninitialized). This does not +necessarily correspond to actual input focus - this corresponds to focus as +defined by the NPAPI event model in use. + +* getFocusEventCount() +Returns the number of focus events received by the instance. + +== NPRuntime testing == + +The test plugin object supports the following scriptable methods: + +* identifierToStringTest(ident) +Converts a string, int32 or double parameter 'ident' to an NPIdentifier and +then to a string, which is returned. + +* npnEvaluateTest(script) +Calls NPN_Evaluate on the 'script' argument, which is a string containing +some script to be executed. Returns the result of the evaluation. + +* npnInvokeTest(method, expected, args...) +Causes the plugin to call the specified script method using NPN_Invoke, +passing it 1 or more arguments specified in args. The return value of this +call is compared against 'expected', and if they match, npnInvokeTest will +return true. Otherwise, it will return false. + +* npnInvokeDefaultTest(object, argument) +Causes the plugin to call NPN_InvokeDefault on the specified object, +with the specified argument. Returns the result of the invocation. + +* getError() +If an error has occurred during the last stream or npruntime function, +this will return a string error message, otherwise it returns "pass". + +* throwExceptionNextInvoke() +Sets a flag which causes the next call to a scriptable method to throw +one or more exceptions. If no parameters are passed to the next +scriptable method call, it will cause a generic exception to be thrown. +Otherwise there will be one exception thrown per argument, with the argument +used as the exception message. Example: + + plugin.throwExceptionNextInvoke(); + plugin.npnInvokeTest("first exception message", "second exception message"); + +* () - default method +Returns a string consisting of the plugin name, concatenated with any +arguments passed to the method. + +* .crash() - Crashes the plugin + +* getObjectValue() - Returns a custom plugin-implemented scriptable object. +* checkObjectValue(obj) - Returns true if the object from getObjectValue() is + the same object passed into this function. It should return true when + the object is passed to the same plugin instance, and false when passed + to other plugin instances, see bug 532246 and + test_multipleinstanceobjects.html. + +* callOnDestroy(fn) - Calls `fn` when the plugin instance is being destroyed + +* getAuthInfo(protocol, host, port, scheme, realm) - a wrapper for +NPN_GetAuthenticationInfo(). Returns a string "username|password" for +the specified auth criteria, or throws an exception if no data is +available. + +* timerTest(callback) - initiates tests of NPN_ScheduleTimer & +NPN_UnscheduleTimer. When finished, calls the script callback +with a boolean value, indicating whether the tests were successful. + +* asyncCallbackTest(callback) - initiates tests of +NPN_PluginThreadAsyncCall. When finished, calls the script callback +with a boolean value, indicating whether the tests were successful. + +* paintscript="..." content attribute +If the "paintscript" attribute is set on the plugin element during plugin +initialization, then every time the plugin paints it gets the contents of that +attribute and evaluates it as a script in the context of the plugin's DOM +window. This is useful for testing evil plugin code that might, for example, +modify the DOM during painting. + +== Private browsing == + +The test plugin object supports the following scriptable methods: + +* queryPrivateModeState +Returns the value of NPN_GetValue(NPNVprivateModeBool). + +* lastReportedPrivateModeState +Returns the last value set by NPP_SetValue(NPNVprivateModeBool). + +== Windowed/windowless mode == + +The test plugin is windowless by default. + +The test plugin supports the following parameter: + +* wmode="window" +The plugin will be given a native widget on platforms where we support this +(Windows and X). + +The test plugin object supports the following scriptable method: + +* hasWidget() +Returns true if the plugin has an associated widget. This will return true if +wmode="window" was specified and the platform supports windowed plugins. + +== Plugin invalidation == + +* setColor(colorString) +Sets the color used for drawmode="solid" and invalidates the plugin area. +This calls NPN_Invalidate, even for windowed plugins, since that should work +for windowed plugins too (Silverlight depends on it). + +* getPaintCount() +Returns the number of times this plugin instance has processed a paint request. +This can be used to detect whether painting has happened in a plugin's +window. + +* getWidthAtLastPaint() +Returns the window width that was current when the plugin last painted. + +* setInvalidateDuringPaint(value) +When value is true, every time the plugin paints, it will invalidate +itself *during the paint* using NPN_Invalidate. + +* setSlowPaint(value) +When value is true, the instance will sleep briefly during paint. + +== Plugin geometry == + +The test plugin supports the following scriptable methods: + +* getEdge(edge) +Returns the integer screen pixel coordinate of an edge of the plugin's +area: +-- edge=0: returns left edge coordinate +-- edge=1: returns top edge coordinate +-- edge=2: returns right edge coordinate +-- edge=3: returns bottom edge coordinate +The coordinates are relative to the top-left corner of the top-level window +containing the plugin, including the window decorations. Therefore: +-- On Mac, they're relative to the top-left corner of the toplevel Cocoa +window. +-- On Windows, they're relative to the top-left corner of the toplevel HWND's +non-client area. +-- On GTK2, they're relative to the top-left corner of the toplevel window's +window manager frame. +This means they can be added to Gecko's window.screenX/screenY (if DPI is set +to 96) to get screen coordinates. +On the platforms that support window-mode plugins (Windows/GTK2), this only +works for window-mode plugins. It will throw an error for windowless plugins. + +* getClipRegionRectCount() +Returns the number of rectangles in the plugin's clip region. +For plugins with widgets, the clip region is computed as the intersection of the +clip region for the widget (if the platform does not support clip regions +on native widgets, this would just be the widget's rectangle) with the +clip regions of all ancestor widgets which would clip this widget. +On the platforms that support window-mode plugins (Windows/GTK2), this only +works for window-mode plugins. It will throw an error for windowless plugins. +On Mac, all plugins have a clip region containing just a single clip +rectangle only. So if you request wmode="window" but the plugin reports +!hasWidget, you can assume that complex clip regions are not supported. + +* getClipRegionRectEdge(i, edge) +Returns the integer screen pixel coordinate of an edge of a rectangle from the +plugin's clip region. If i is less than zero or greater than or equal to +getClipRegionRectCount(), this will throw an error. The coordinates are +the same as for getEdge. See getClipRegionRectCount() above for +notes on platform plugin limitations. + +== Keyboard events == + +* getLastKeyText() +Returns the text which was inputted by last keyboard events. This is cleared at +every keydown event. +NOTE: Currently, this is implemented only on Windows. + +== Mouse events == + +The test plugin supports the following scriptable methods: + +* getLastMouseX() +Returns the X coordinate of the last mouse event (move, button up, or +button down), relative to the left edge of the plugin, or -1 if no mouse +event has been received. + +* getLastMouseX() +Returns the Y coordinate of the last mouse event (move, button up, or +button down), relative to the top edge of the plugin, or -1 if no mouse +event has been received. + +== Instance lifecycle == + +The test plugin supports the following scriptable methods: + +* startWatchingInstanceCount() +Marks all currently running instances as "ignored". Throws an exception if +there is already a watch (startWatchingInstanceCount has already been +called on some instance without a corresponding stopWatchingInstanceCount). + +* getInstanceCount() +Returns the count of currently running instances that are not ignored. +Throws an exception if there is no watch. + +* stopWatchingInstanceCount() +Stops watching. Throws an exception if there is no watch. + +== NPAPI Timers == + +* unscheduleAllTimers() +Instructs the plugin instance to cancel all timers created via +NPN_ScheduleTimer. + +== Stream Functionality == + +The test plugin enables a variety of NPAPI streaming tests, which are +initiated by passing a variety of attributes to the <embed> element which +causes the plugin to be initialized. The plugin stream test code is +designed to receive a stream from the browser (by specifying a "src", +"geturl", or "geturlnotify" attribute), and then (if a "frame" attribute +is specified) send the data from that stream back to the browser in another +stream, whereupon it will be displayed in the specified frame. If some +error occurs during stream processing, an error message will appear in the +frame instead of the stream data. If no "frame" attribute is present, a +stream can still be received by the plugin, but the plugin will do nothing +with it. + +The attributes which control stream tests are: + +"streamchunksize": the number of bytes the plugin reports it can accept + in calls to NPP_WriteReady. Defaults to 1,024. + +"src": a url. If specified, the browser will call NPP_NewStream for + this url as soon as the plugin is initialized. + +"geturl": a url. If specified, the plugin will request this url + from the browser when the plugin is initialized, via a call to + NPN_GetURL. + +"geturlnotify": a url. If specified, the plugin will request this url + from the browser when the plugin is initialized, via a call to + NPN_GetURLNotify. The plugin passes some "notifyData" to + NPN_GetURLNotify, which it verifies is present in the call to + NPP_URLNotify. If the "notifyData" does not match, an error + will be displayed in the test frame (if any), instead of the stream + data. + +"frame": the name of a frame in the same HTML document as the <embed> + element which instantiated the plugin. For any of the preceding three + attributes, a stream is received by the plugin via calls to NPP_NewStream, + NPP_WriteReady, NPP_Write, and NPP_DestroyStream. When NPP_DestroyStream + is called (or NPP_UrlNotify, in the case of "geturlnotify"), and a + "frame" attribute is present, the data from the stream is converted into a + data: url, and sent back to the browser in another stream via a call to + NPN_GetURL, whereupon it should be displayed in the specified frame. + +"posturl": a url. After the plugin receives a stream, and NPP_DestroyStream + is called, if "posturl" is specified, the plugin will post the contents + of the stream to the specified url via NPN_PostURL. See "postmode" for + additional details. + +"postmode": either "frame" or "stream". If "frame", and a "frame" attribute + is present, the plugin will pass the frame name to calls to NPN_PostURL, + so that the HTTP response from that operation will be displayed in the + specified frame. If "stream", the HTTP response is delivered to the plugin + via calls to NPP_NewStream etc, and if a "frame" attribute is present, the + contents of that stream will be passed back to the browser and displayed + in the specified frame via NPN_GetURL. + +"newstream": if "true", then any stream which is sent to a frame in the browser + is sent via calls to NPN_NewStream and NPN_Write. Doing so will cause + the browser to store the stream data in a file, and set the frame's + location to the corresponding file:// url. + +"functiontofail": one of "npp_newstream", "npp_write", "npp_destroystream". + When specified, the given function will return an error code (-1 for + NPP_Write, or else the value of the "failurecode" attribute) the first time + it is called by the browser. + +"failurecode": one of the NPError constants. Used to specify the error + that will be returned by the "functiontofail". + +* streamTest(url, doPost, postData, writeCallback, notifyCallback, redirectCallback, allowRedirects, postFile = false) +This will test how NPN_GetURLNotify and NPN_PostURLNotify behave when they are +called with arbitrary (malformed) URLs. The function will return `true` if +NPN_[Get/Post]URLNotify succeeds, and `false` if it fails. +@url url to request +@param doPost whether to call NPN_PostURLNotify +@param postData null, or a string to send a postdata +@writeCallback will be called when data is received for the stream +@notifyCallback will be called when the urlnotify is received with the notify result +@redirectCallback will be called from urlredirectnotify if a redirect is attempted +@allowRedirects boolean value indicating whether or not to allow redirects +@postFile boolean optional, defaults to false, set to true if postData contains a filename + +* postFileToURLTest(url) +Calls NPN_PostURL/NPN_PostURLNotify to make a POST request to the URL with +request from postFile. +The function will return `0` if NPN_PostURL/NPN_PostURLNotify succeeds, and +the error code if it fails. +@param url string, url to request + +* setPluginWantsAllStreams(wantsAllStreams) +Set the value returned by the plugin for NPPVpluginWantsAllNetworkStreams. + +== Internal consistency == + +* doInternalConsistencyCheck() +Does internal consistency checking, returning an empty string if everything is +OK, otherwise returning some kind of error string. On Windows, in windowed +mode, this checks that the position of the plugin's internal child +window has not been disturbed relative to the plugin window. + +== Windows native widget message behaviour == + +* Mouse events are handled (saving the last mouse event coordinate) and not +passed to the overridden windowproc. + +* WM_MOUSEWHEEL events are handled and not passed to the parent window or the +overridden windowproc. + +* WM_MOUSEACTIVATE events are handled by calling SetFocus on the plugin's +widget, if the plugin is windowed. If it's not windowed they're passed to +the overriden windowproc (but hopefully never sent by the browser anyway). + +== FPU Control == + +x86-only on some OSes: + +* The .enableFPExceptions() method will enable floating-point exceptions, + as evil plugins or extensions might do. + +== HiDPI Mode == + +* queryContentsScaleFactor() +Returns the contents scale factor. On platforms without support for this query +always returns 1.0 (a double value). Likewise on hardware without HiDPI mode +support. + +== Plugin audio channel support == + +* startAudioPlayback() +Simulates the plugin starting to play back audio. + +* stopAudioPlayback() +Simulates the plugin stopping to play back audio. + +* audioMuted() +Returns the last value set by NPP_SetValue(NPNVmuteAudioBool). diff --git a/dom/plugins/test/testplugin/flashplugin/Info.plist b/dom/plugins/test/testplugin/flashplugin/Info.plist new file mode 100644 index 0000000000..0e6168e686 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnpswftest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.FlashTestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>FLASHTEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Shockwave Flash</string> + <key>WebPluginDescription</key> + <string>Flash plug-in for testing purposes.</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-shockwave-flash-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>swf</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Flash test type</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/flashplugin/moz.build b/dom/plugins/test/testplugin/flashplugin/moz.build new file mode 100644 index 0000000000..f66fb6eca4 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SharedLibrary("npswftest") + +relative_path = "flashplugin" +cocoa_name = "npswftest" +include("../testplugin.mozbuild") diff --git a/dom/plugins/test/testplugin/flashplugin/nptest.def b/dom/plugins/test/testplugin/flashplugin/nptest.def new file mode 100644 index 0000000000..3a62d05d95 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPSWFTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/flashplugin/nptest.rc b/dom/plugins/test/testplugin/flashplugin/nptest.rc new file mode 100644 index 0000000000..e970d26091 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Flash plug-in for testing purposes." + VALUE "FileExtents", "swf" + VALUE "FileOpenName", "Flash test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npswftest" + VALUE "MIMEType", "application/x-shockwave-flash-test" + VALUE "OriginalFilename", "npswftest.dll" + VALUE "ProductName", "Shockwave Flash" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp b/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp new file mode 100644 index 0000000000..31f4f6321f --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp @@ -0,0 +1,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/. */ + +const char* sPluginName = "Shockwave Flash"; +const char* sPluginDescription = "Flash plug-in for testing purposes."; +const char* sMimeDescription = + "application/x-shockwave-flash-test:swf:Flash test type"; diff --git a/dom/plugins/test/testplugin/moz.build b/dom/plugins/test/testplugin/moz.build new file mode 100644 index 0000000000..cb380e2db8 --- /dev/null +++ b/dom/plugins/test/testplugin/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ["secondplugin", "flashplugin"] + +SharedLibrary("nptest") + +relative_path = "." +cocoa_name = "Test" +include("testplugin.mozbuild") diff --git a/dom/plugins/test/testplugin/nptest.cpp b/dom/plugins/test/testplugin/nptest.cpp new file mode 100644 index 0000000000..2a9d20cdca --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.cpp @@ -0,0 +1,3282 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Dave Townsend <dtownsend@oxymoronical.com> + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest.h" +#include "nptest_utils.h" +#include "nptest_platform.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/IntentionalCrash.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <iostream> +#include <string> +#include <sstream> +#include <list> +#include <ctime> + +#ifdef XP_WIN +# include <process.h> +# include <float.h> +# include <windows.h> +# define getpid _getpid +# define strcasecmp _stricmp +#else +# include <unistd.h> +# include <pthread.h> +#endif + +using std::list; +using std::ostringstream; +using std::string; + +#define PLUGIN_VERSION "1.0.0.0" + +extern const char* sPluginName; +extern const char* sPluginDescription; +static char sPluginVersion[] = PLUGIN_VERSION; + +// +// Intentional crash +// + +int gCrashCount = 0; + +static void Crash() { + int* pi = nullptr; + *pi = 55; // Crash dereferencing null pointer + ++gCrashCount; +} + +static void IntentionalCrash() { + mozilla::NoteIntentionalCrash("plugin"); + Crash(); +} + +// +// static data +// + +static NPNetscapeFuncs* sBrowserFuncs = nullptr; +static NPClass sNPClass; + +// +// identifiers +// + +typedef bool (*ScriptableFunction)(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +static bool npnEvaluateTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool npnInvokeTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool npnInvokeDefaultTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setUndefinedValueTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool identifierToStringTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool getClipRegionRectCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastMouseX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastMouseY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool resetPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getWidthAtLastPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setInvalidateDuringPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setSlowPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool doInternalConsistencyCheck(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setColor(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool throwExceptionNextInvoke(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool convertPointX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool convertPointY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool streamTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool postFileToURLTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setPluginWantsAllStreams(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool crashPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool crashOnDestroy(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool checkObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool hangPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool stallPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getClipboardText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool callOnDestroy(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool reinitWidget(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool triggerXError(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool propertyAndMethod(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getTopLevelWindowActivationState(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); +static bool getTopLevelWindowActivationEventCount(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); +static bool getFocusState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getFocusEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getEventModel(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getReflector(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool isVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool getWindowPosition(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool constructObject(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setSitesWithData(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastKeyText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getNPNVdocumentOrigin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getMouseUpEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool queryContentsScaleFactor(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool queryCSSZoomFactorGetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool queryCSSZoomFactorSetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool echoString(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool startAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool stopAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getAudioMuted(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastCompositionText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getInvokeDefaultObject(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +static const NPUTF8* sPluginMethodIdentifierNames[] = { + "npnEvaluateTest", + "npnInvokeTest", + "npnInvokeDefaultTest", + "setUndefinedValueTest", + "identifierToStringTest", + "timerTest", + "queryPrivateModeState", + "lastReportedPrivateModeState", + "hasWidget", + "getEdge", + "getClipRegionRectCount", + "getClipRegionRectEdge", + "startWatchingInstanceCount", + "getInstanceCount", + "stopWatchingInstanceCount", + "getLastMouseX", + "getLastMouseY", + "getPaintCount", + "resetPaintCount", + "getWidthAtLastPaint", + "setInvalidateDuringPaint", + "setSlowPaint", + "getError", + "doInternalConsistencyCheck", + "setColor", + "throwExceptionNextInvoke", + "convertPointX", + "convertPointY", + "streamTest", + "postFileToURLTest", + "setPluginWantsAllStreams", + "crash", + "crashOnDestroy", + "getObjectValue", + "checkObjectValue", + "enableFPExceptions", + "hang", + "stall", + "getClipboardText", + "callOnDestroy", + "reinitWidget", + "crashInNestedLoop", + "triggerXError", + "destroySharedGfxStuff", + "propertyAndMethod", + "getTopLevelWindowActivationState", + "getTopLevelWindowActivationEventCount", + "getFocusState", + "getFocusEventCount", + "getEventModel", + "getReflector", + "isVisible", + "getWindowPosition", + "constructObject", + "setSitesWithData", + "setSitesWithDataCapabilities", + "getLastKeyText", + "getNPNVdocumentOrigin", + "getMouseUpEventCount", + "queryContentsScaleFactor", + "queryCSSZoomFactorSetValue", + "queryCSSZoomFactorGetValue", + "echoString", + "startAudioPlayback", + "stopAudioPlayback", + "audioMuted", + "nativeWidgetIsVisible", + "getLastCompositionText", + "getInvokeDefaultObject", +}; +static NPIdentifier + sPluginMethodIdentifiers[MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames)]; +static const ScriptableFunction sPluginMethodFunctions[] = { + npnEvaluateTest, + npnInvokeTest, + npnInvokeDefaultTest, + setUndefinedValueTest, + identifierToStringTest, + timerTest, + queryPrivateModeState, + lastReportedPrivateModeState, + hasWidget, + getEdge, + getClipRegionRectCount, + getClipRegionRectEdge, + startWatchingInstanceCount, + getInstanceCount, + stopWatchingInstanceCount, + getLastMouseX, + getLastMouseY, + getPaintCount, + resetPaintCount, + getWidthAtLastPaint, + setInvalidateDuringPaint, + setSlowPaint, + getError, + doInternalConsistencyCheck, + setColor, + throwExceptionNextInvoke, + convertPointX, + convertPointY, + streamTest, + postFileToURLTest, + setPluginWantsAllStreams, + crashPlugin, + crashOnDestroy, + getObjectValue, + checkObjectValue, + enableFPExceptions, + hangPlugin, + stallPlugin, + getClipboardText, + callOnDestroy, + reinitWidget, + crashPluginInNestedLoop, + triggerXError, + destroySharedGfxStuff, + propertyAndMethod, + getTopLevelWindowActivationState, + getTopLevelWindowActivationEventCount, + getFocusState, + getFocusEventCount, + getEventModel, + getReflector, + isVisible, + getWindowPosition, + constructObject, + setSitesWithData, + setSitesWithDataCapabilities, + getLastKeyText, + getNPNVdocumentOrigin, + getMouseUpEventCount, + queryContentsScaleFactor, + queryCSSZoomFactorGetValue, + queryCSSZoomFactorSetValue, + echoString, + startAudioPlayback, + stopAudioPlayback, + getAudioMuted, + nativeWidgetIsVisible, + getLastCompositionText, + getInvokeDefaultObject, +}; + +static_assert(MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames) == + MOZ_ARRAY_LENGTH(sPluginMethodFunctions), + "Arrays should have the same size"); + +static const NPUTF8* sPluginPropertyIdentifierNames[] = {"propertyAndMethod"}; +static NPIdentifier sPluginPropertyIdentifiers[MOZ_ARRAY_LENGTH( + sPluginPropertyIdentifierNames)]; +static NPVariant + sPluginPropertyValues[MOZ_ARRAY_LENGTH(sPluginPropertyIdentifierNames)]; + +struct URLNotifyData { + const char* cookie; + NPObject* writeCallback; + NPObject* notifyCallback; + NPObject* redirectCallback; + bool allowRedirects; + uint32_t size; + char* data; +}; + +static URLNotifyData kNotifyData = {"static-cookie", nullptr, nullptr, nullptr, + false, 0, nullptr}; + +static const char* SUCCESS_STRING = "pass"; + +static bool sIdentifiersInitialized = false; + +struct timerEvent { + int32_t timerIdReceive; + int32_t timerIdSchedule; + uint32_t timerInterval; + bool timerRepeat; + int32_t timerIdUnschedule; +}; +static timerEvent timerEvents[] = { + // clang-format off + {-1, 0, 200, false, -1}, + {0, 0, 400, false, -1}, + {0, 0, 200, true, -1}, + {0, 1, 400, true, -1}, + {0, -1, 0, false, 0}, + {1, -1, 0, false, -1}, + {1, -1, 0, false, 1}, + // clang-format on +}; +static uint32_t currentTimerEventCount = 0; +static uint32_t totalTimerEvents = sizeof(timerEvents) / sizeof(timerEvent); + +/** + * Incremented for every startWatchingInstanceCount. + */ +static int32_t sCurrentInstanceCountWatchGeneration = 0; +/** + * Tracks the number of instances created or destroyed since the last + * startWatchingInstanceCount. + */ +static int32_t sInstanceCount = 0; +/** + * True when we've had a startWatchingInstanceCount with no corresponding + * stopWatchingInstanceCount. + */ +static bool sWatchingInstanceCount = false; + +/** + * A list representing sites for which the plugin has stored data. See + * NPP_ClearSiteData and NPP_GetSitesWithData. + */ +struct siteData { + string site; + uint64_t flags; + uint64_t age; +}; +static list<siteData>* sSitesWithData; +static bool sClearByAgeSupported; + +static void initializeIdentifiers() { + if (!sIdentifiersInitialized) { + NPN_GetStringIdentifiers(sPluginMethodIdentifierNames, + MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames), + sPluginMethodIdentifiers); + NPN_GetStringIdentifiers(sPluginPropertyIdentifierNames, + MOZ_ARRAY_LENGTH(sPluginPropertyIdentifierNames), + sPluginPropertyIdentifiers); + + sIdentifiersInitialized = true; + + // Check whether nullptr is handled in NPN_GetStringIdentifiers + NPIdentifier IDList[2]; + static char const* const kIDNames[2] = {nullptr, "setCookie"}; + NPN_GetStringIdentifiers(const_cast<const NPUTF8**>(kIDNames), 2, IDList); + } +} + +static void clearIdentifiers() { + memset(sPluginMethodIdentifiers, 0, + MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers) * sizeof(NPIdentifier)); + memset(sPluginPropertyIdentifiers, 0, + MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers) * sizeof(NPIdentifier)); + + sIdentifiersInitialized = false; +} + +static void sendBufferToFrame(NPP instance) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + string outbuf; + if (!instanceData->npnNewStream) outbuf = "data:text/html,"; + const char* buf = reinterpret_cast<char*>(instanceData->streamBuf); + int32_t bufsize = instanceData->streamBufSize; + if (instanceData->err.str().length() > 0) { + outbuf.append(instanceData->err.str()); + } else if (bufsize > 0) { + outbuf.append(buf); + } else { + outbuf.append("Error: no data in buffer"); + } + + // Convert CRLF to LF, and escape most other non-alphanumeric chars. + for (size_t i = 0; i < outbuf.length(); i++) { + if (outbuf[i] == '\n') { + outbuf.replace(i, 1, "%0a"); + i += 2; + } else if (outbuf[i] == '\r') { + outbuf.replace(i, 1, ""); + i -= 1; + } else { + int ascii = outbuf[i]; + if (!((ascii >= ',' && ascii <= ';') || (ascii >= 'A' && ascii <= 'Z') || + (ascii >= 'a' && ascii <= 'z'))) { + char hex[10]; + sprintf(hex, "%%%x", ascii); + outbuf.replace(i, 1, hex); + i += 2; + } + } + } + + NPError err = + NPN_GetURL(instance, outbuf.c_str(), instanceData->frame.c_str()); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURL returned " << err; + } +} + +static void XPSleep(unsigned int seconds) { +#ifdef XP_WIN + Sleep(1000 * seconds); +#else + sleep(seconds); +#endif +} + +TestFunction getFuncFromString(const char* funcname) { + FunctionTable funcTable[] = { + {FUNCTION_NPP_NEWSTREAM, "npp_newstream"}, + {FUNCTION_NPP_WRITEREADY, "npp_writeready"}, + {FUNCTION_NPP_WRITE, "npp_write"}, + {FUNCTION_NPP_DESTROYSTREAM, "npp_destroystream"}, + {FUNCTION_NPP_WRITE_RPC, "npp_write_rpc"}, + {FUNCTION_NONE, nullptr}}; + int32_t i = 0; + while (funcTable[i].funcName) { + if (!strcmp(funcname, funcTable[i].funcName)) return funcTable[i].funcId; + i++; + } + return FUNCTION_NONE; +} + +static void DuplicateNPVariant(NPVariant& aDest, const NPVariant& aSrc) { + if (NPVARIANT_IS_STRING(aSrc)) { + NPString src = NPVARIANT_TO_STRING(aSrc); + char* buf = new char[src.UTF8Length]; + strncpy(buf, src.UTF8Characters, src.UTF8Length); + STRINGN_TO_NPVARIANT(buf, src.UTF8Length, aDest); + } else if (NPVARIANT_IS_OBJECT(aSrc)) { + NPObject* obj = NPN_RetainObject(NPVARIANT_TO_OBJECT(aSrc)); + OBJECT_TO_NPVARIANT(obj, aDest); + } else { + aDest = aSrc; + } +} + +static bool bug813906(NPP npp, const char* const function, + const char* const url, const char* const frame) { + NPObject* windowObj = nullptr; + NPError err = NPN_GetValue(npp, NPNVWindowNPObject, &windowObj); + if (err != NPERR_NO_ERROR) { + return false; + } + + NPVariant result; + bool res = NPN_Invoke(npp, windowObj, NPN_GetStringIdentifier(function), + nullptr, 0, &result); + NPN_ReleaseObject(windowObj); + if (!res) { + return false; + } + + NPN_ReleaseVariantValue(&result); + + err = NPN_GetURL(npp, url, frame); + if (err != NPERR_NO_ERROR) { + err = NPN_GetURL(npp, "about:blank", frame); + return false; + } + + return true; +} + +void drawAsyncBitmapColor(InstanceData* instanceData) { + NPP npp = instanceData->npp; + + uint32_t* pixelData = (uint32_t*)instanceData->backBuffer->bitmap.data; + + uint32_t rgba = instanceData->scriptableObject->drawColor; + + unsigned char subpixels[4]; + memcpy(subpixels, &rgba, sizeof(subpixels)); + + subpixels[0] = uint8_t(float(subpixels[3] * subpixels[0]) / 0xFF); + subpixels[1] = uint8_t(float(subpixels[3] * subpixels[1]) / 0xFF); + subpixels[2] = uint8_t(float(subpixels[3] * subpixels[2]) / 0xFF); + uint32_t premultiplied; + memcpy(&premultiplied, subpixels, sizeof(premultiplied)); + + for (uint32_t* lastPixel = + pixelData + instanceData->backBuffer->size.width * + instanceData->backBuffer->size.height; + pixelData < lastPixel; ++pixelData) { + *pixelData = premultiplied; + } + + NPN_SetCurrentAsyncSurface(npp, instanceData->backBuffer, NULL); + NPAsyncSurface* oldFront = instanceData->frontBuffer; + instanceData->frontBuffer = instanceData->backBuffer; + instanceData->backBuffer = oldFront; +} + +// +// function signatures +// + +NPObject* scriptableAllocate(NPP npp, NPClass* aClass); +void scriptableDeallocate(NPObject* npobj); +void scriptableInvalidate(NPObject* npobj); +bool scriptableHasMethod(NPObject* npobj, NPIdentifier name); +bool scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, + uint32_t argCount, NPVariant* result); +bool scriptableHasProperty(NPObject* npobj, NPIdentifier name); +bool scriptableGetProperty(NPObject* npobj, NPIdentifier name, + NPVariant* result); +bool scriptableSetProperty(NPObject* npobj, NPIdentifier name, + const NPVariant* value); +bool scriptableRemoveProperty(NPObject* npobj, NPIdentifier name); +bool scriptableEnumerate(NPObject* npobj, NPIdentifier** identifier, + uint32_t* count); +bool scriptableConstruct(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +// +// npapi plugin functions +// + +#ifdef XP_UNIX +NP_EXPORT(char*) +NP_GetPluginVersion() { return sPluginVersion; } +#endif + +extern const char* sMimeDescription; + +#if defined(XP_UNIX) +NP_EXPORT(const char*) +NP_GetMIMEDescription() +#elif defined(XP_WIN) +const char* NP_GetMIMEDescription() +#endif +{ + return sMimeDescription; +} + +#ifdef XP_UNIX +NP_EXPORT(NPError) +NP_GetValue(void* future, NPPVariable aVariable, void* aValue) { + switch (aVariable) { + case NPPVpluginNameString: + *((const char**)aValue) = sPluginName; + break; + case NPPVpluginDescriptionString: + *((const char**)aValue) = sPluginDescription; + break; + default: + return NPERR_INVALID_PARAM; + } + return NPERR_NO_ERROR; +} +#endif + +static bool fillPluginFunctionTable(NPPluginFuncs* pFuncs) { + // Check the size of the provided structure based on the offset of the + // last member we need. + if (pFuncs->size < + (offsetof(NPPluginFuncs, getsiteswithdata) + sizeof(void*))) + return false; + + pFuncs->newp = NPP_New; + pFuncs->destroy = NPP_Destroy; + pFuncs->setwindow = NPP_SetWindow; + pFuncs->newstream = NPP_NewStream; + pFuncs->destroystream = NPP_DestroyStream; + pFuncs->writeready = NPP_WriteReady; + pFuncs->write = NPP_Write; + pFuncs->print = NPP_Print; + pFuncs->event = NPP_HandleEvent; + pFuncs->urlnotify = NPP_URLNotify; + pFuncs->getvalue = NPP_GetValue; + pFuncs->setvalue = NPP_SetValue; + pFuncs->urlredirectnotify = NPP_URLRedirectNotify; + pFuncs->clearsitedata = NPP_ClearSiteData; + pFuncs->getsiteswithdata = NPP_GetSitesWithData; + + return true; +} + +#if defined(XP_MACOSX) +NP_EXPORT(NPError) +NP_Initialize(NPNetscapeFuncs* bFuncs) +#elif defined(XP_WIN) +NPError OSCALL NP_Initialize(NPNetscapeFuncs* bFuncs) +#elif defined(XP_UNIX) +NP_EXPORT(NPError) +NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs) +#endif +{ + sBrowserFuncs = bFuncs; + + initializeIdentifiers(); + + for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sPluginPropertyValues); i++) { + VOID_TO_NPVARIANT(sPluginPropertyValues[i]); + } + + memset(&sNPClass, 0, sizeof(NPClass)); + sNPClass.structVersion = NP_CLASS_STRUCT_VERSION; + sNPClass.allocate = (NPAllocateFunctionPtr)scriptableAllocate; + sNPClass.deallocate = (NPDeallocateFunctionPtr)scriptableDeallocate; + sNPClass.invalidate = (NPInvalidateFunctionPtr)scriptableInvalidate; + sNPClass.hasMethod = (NPHasMethodFunctionPtr)scriptableHasMethod; + sNPClass.invoke = (NPInvokeFunctionPtr)scriptableInvoke; + sNPClass.invokeDefault = nullptr; + sNPClass.hasProperty = (NPHasPropertyFunctionPtr)scriptableHasProperty; + sNPClass.getProperty = (NPGetPropertyFunctionPtr)scriptableGetProperty; + sNPClass.setProperty = (NPSetPropertyFunctionPtr)scriptableSetProperty; + sNPClass.removeProperty = + (NPRemovePropertyFunctionPtr)scriptableRemoveProperty; + sNPClass.enumerate = (NPEnumerationFunctionPtr)scriptableEnumerate; + sNPClass.construct = (NPConstructFunctionPtr)scriptableConstruct; + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + if (!fillPluginFunctionTable(pFuncs)) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } +#endif + + return NPERR_NO_ERROR; +} + +#if defined(XP_MACOSX) +NP_EXPORT(NPError) +NP_GetEntryPoints(NPPluginFuncs* pFuncs) +#elif defined(XP_WIN) +NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs) +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +{ + if (!fillPluginFunctionTable(pFuncs)) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } + + return NPERR_NO_ERROR; +} +#endif + +#if defined(XP_UNIX) +NP_EXPORT(NPError) +NP_Shutdown() +#elif defined(XP_WIN) + NPError OSCALL NP_Shutdown() +#endif +{ + clearIdentifiers(); + + for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sPluginPropertyValues); i++) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + } + + return NPERR_NO_ERROR; +} + +NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, + int16_t argc, char* argn[], char* argv[], NPSavedData* saved) { + // Make sure our pdata field is nullptr at this point. If it isn't, that + // probably means the browser gave us uninitialized memory. + if (instance->pdata) { + printf("NPP_New called with non-NULL NPP->pdata pointer!\n"); + return NPERR_GENERIC_ERROR; + } + + // Make sure we can render this plugin + NPBool browserSupportsWindowless = false; + NPN_GetValue(instance, NPNVSupportsWindowless, &browserSupportsWindowless); + if (!browserSupportsWindowless && !pluginSupportsWindowMode()) { + printf( + "Windowless mode not supported by the browser, windowed mode not " + "supported by the plugin!\n"); + return NPERR_GENERIC_ERROR; + } + + // set up our our instance data + InstanceData* instanceData = new InstanceData; + instanceData->npp = instance; + instanceData->testFunction = FUNCTION_NONE; + instanceData->functionToFail = FUNCTION_NONE; + instanceData->failureCode = 0; + instanceData->callOnDestroy = nullptr; + instanceData->streamChunkSize = 1024; + instanceData->streamBuf = nullptr; + instanceData->streamBufSize = 0; + instanceData->throwOnNextInvoke = false; + instanceData->runScriptOnPaint = false; + instanceData->dontTouchElement = false; + instanceData->hasWidget = false; + instanceData->npnNewStream = false; + instanceData->invalidateDuringPaint = false; + instanceData->slowPaint = false; + instanceData->playingAudio = false; + instanceData->audioMuted = false; + instanceData->writeCount = 0; + instanceData->writeReadyCount = 0; + memset(&instanceData->window, 0, sizeof(instanceData->window)); + instanceData->crashOnDestroy = false; + instanceData->cleanupWidget = true; // only used by nptest_gtk + instanceData->topLevelWindowActivationState = ACTIVATION_STATE_UNKNOWN; + instanceData->topLevelWindowActivationEventCount = 0; + instanceData->focusState = ACTIVATION_STATE_UNKNOWN; + instanceData->focusEventCount = 0; + instanceData->eventModel = 0; + instanceData->wantsAllStreams = false; + instanceData->mouseUpEventCount = 0; + instanceData->bugMode = -1; + instanceData->asyncDrawing = AD_NONE; + instanceData->frontBuffer = nullptr; + instanceData->backBuffer = nullptr; + instanceData->placeholderWnd = nullptr; + instanceData->cssZoomFactor = 1.0; + instance->pdata = instanceData; + + TestNPObject* scriptableObject = + (TestNPObject*)NPN_CreateObject(instance, &sNPClass); + if (!scriptableObject) { + printf( + "NPN_CreateObject failed to create an object, can't create a plugin " + "instance\n"); + delete instanceData; + return NPERR_GENERIC_ERROR; + } + scriptableObject->npp = instance; + scriptableObject->drawMode = DM_DEFAULT; + scriptableObject->drawColor = 0; + instanceData->scriptableObject = scriptableObject; + + instanceData->instanceCountWatchGeneration = + sCurrentInstanceCountWatchGeneration; + + AsyncDrawing requestAsyncDrawing = AD_NONE; + + bool requestWindow = false; + bool alreadyHasSalign = false; + // handle extra params + for (int i = 0; i < argc; i++) { + if (strcmp(argn[i], "drawmode") == 0) { + if (strcmp(argv[i], "solid") == 0) + scriptableObject->drawMode = DM_SOLID_COLOR; + } else if (strcmp(argn[i], "color") == 0) { + scriptableObject->drawColor = parseHexColor(argv[i], strlen(argv[i])); + } else if (strcmp(argn[i], "wmode") == 0) { + if (strcmp(argv[i], "window") == 0) { + requestWindow = true; + } + } else if (strcmp(argn[i], "asyncmodel") == 0) { + if (strcmp(argv[i], "bitmap") == 0) { + requestAsyncDrawing = AD_BITMAP; + } else if (strcmp(argv[i], "dxgi") == 0) { + requestAsyncDrawing = AD_DXGI; + } + } + if (strcmp(argn[i], "streamchunksize") == 0) { + instanceData->streamChunkSize = atoi(argv[i]); + } + if (strcmp(argn[i], "failurecode") == 0) { + instanceData->failureCode = atoi(argv[i]); + } + if (strcmp(argn[i], "functiontofail") == 0) { + instanceData->functionToFail = getFuncFromString(argv[i]); + } + if (strcmp(argn[i], "geturl") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_GETURL; + } + if (strcmp(argn[i], "posturl") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_POSTURL; + } + if (strcmp(argn[i], "geturlnotify") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_GETURLNOTIFY; + } + if (strcmp(argn[i], "postmode") == 0) { + if (strcmp(argv[i], "frame") == 0) { + instanceData->postMode = POSTMODE_FRAME; + } else if (strcmp(argv[i], "stream") == 0) { + instanceData->postMode = POSTMODE_STREAM; + } + } + if (strcmp(argn[i], "frame") == 0) { + instanceData->frame = argv[i]; + } + if (strcmp(argn[i], "newstream") == 0 && strcmp(argv[i], "true") == 0) { + instanceData->npnNewStream = true; + } + if (strcmp(argn[i], "newcrash") == 0) { + IntentionalCrash(); + } + if (strcmp(argn[i], "paintscript") == 0) { + instanceData->runScriptOnPaint = true; + } + + if (strcmp(argn[i], "donttouchelement") == 0) { + instanceData->dontTouchElement = true; + } + // "cleanupwidget" is only used with nptest_gtk, defaulting to true. It + // indicates whether the plugin should destroy its window in response to + // NPP_Destroy (or let the platform destroy the widget when the parent + // window gets destroyed). + if (strcmp(argn[i], "cleanupwidget") == 0 && + strcmp(argv[i], "false") == 0) { + instanceData->cleanupWidget = false; + } + if (strcmp(argn[i], "bugmode") == 0) { + instanceData->bugMode = atoi(argv[i]); + } + + // Bug 1307694 - There are two flash parameters that are order dependent for + // scaling/sizing the plugin. If they ever change from what is expected, it + // breaks flash on the web. In a test, if the scale tag ever happens + // with an salign before it, fail the plugin creation. + if (strcmp(argn[i], "scale") == 0) { + if (alreadyHasSalign) { + // If salign came before this parameter, error out now. + return NPERR_GENERIC_ERROR; + } + } + if (strcmp(argn[i], "salign") == 0) { + alreadyHasSalign = true; + } + } + + if (!browserSupportsWindowless || !pluginSupportsWindowlessMode()) { + requestWindow = true; + } else if (!pluginSupportsWindowMode()) { + requestWindow = false; + } + if (requestWindow) { + instanceData->hasWidget = true; + } else { + // NPPVpluginWindowBool should default to true, so we may as well + // test that by not setting it in the window case + NPN_SetValue(instance, NPPVpluginWindowBool, (void*)false); + } + + if (scriptableObject->drawMode == DM_SOLID_COLOR && + (scriptableObject->drawColor & 0xFF000000) != 0xFF000000) { + NPN_SetValue(instance, NPPVpluginTransparentBool, (void*)true); + } + + if (requestAsyncDrawing == AD_BITMAP) { + NPBool supportsAsyncBitmap = false; + if ((NPN_GetValue(instance, NPNVsupportsAsyncBitmapSurfaceBool, + &supportsAsyncBitmap) == NPERR_NO_ERROR) && + supportsAsyncBitmap) { + if (NPN_SetValue(instance, NPPVpluginDrawingModel, + (void*)NPDrawingModelAsyncBitmapSurface) == + NPERR_NO_ERROR) { + instanceData->asyncDrawing = AD_BITMAP; + } + } + } +#ifdef XP_WIN + else if (requestAsyncDrawing == AD_DXGI) { + NPBool supportsAsyncDXGI = false; + if ((NPN_GetValue(instance, NPNVsupportsAsyncWindowsDXGISurfaceBool, + &supportsAsyncDXGI) == NPERR_NO_ERROR) && + supportsAsyncDXGI) { + if (NPN_SetValue(instance, NPPVpluginDrawingModel, + (void*)NPDrawingModelAsyncWindowsDXGISurface) == + NPERR_NO_ERROR) { + instanceData->asyncDrawing = AD_DXGI; + } + } + } +#endif + + // If we can't get the right drawing mode, we fail, otherwise our tests might + // appear to be passing when they shouldn't. Real plugins should not do this. + if (instanceData->asyncDrawing != requestAsyncDrawing) { + return NPERR_GENERIC_ERROR; + } + + instanceData->lastReportedPrivateModeState = false; + instanceData->lastMouseX = instanceData->lastMouseY = -1; + instanceData->widthAtLastPaint = -1; + instanceData->paintCount = 0; + + // do platform-specific initialization + NPError err = pluginInstanceInit(instanceData); + if (err != NPERR_NO_ERROR) { + NPN_ReleaseObject(scriptableObject); + delete instanceData; + return err; + } + + NPVariant variantTrue; + BOOLEAN_TO_NPVARIANT(true, variantTrue); + NPObject* o = nullptr; + + // Set a property on NPNVPluginElementNPObject, unless the consumer explicitly + // opted out of this behavior. + if (!instanceData->dontTouchElement) { + err = NPN_GetValue(instance, NPNVPluginElementNPObject, &o); + if (err == NPERR_NO_ERROR) { + NPN_SetProperty(instance, o, + NPN_GetStringIdentifier("pluginFoundElement"), + &variantTrue); + NPN_ReleaseObject(o); + o = nullptr; + } + } + + // Set a property on NPNVWindowNPObject + err = NPN_GetValue(instance, NPNVWindowNPObject, &o); + if (err == NPERR_NO_ERROR) { + NPN_SetProperty(instance, o, NPN_GetStringIdentifier("pluginFoundWindow"), + &variantTrue); + NPN_ReleaseObject(o); + o = nullptr; + } + + ++sInstanceCount; + + if (instanceData->testFunction == FUNCTION_NPP_GETURL) { + NPError err = NPN_GetURL(instance, instanceData->testUrl.c_str(), nullptr); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURL returned " << err; + } + } else if (instanceData->testFunction == FUNCTION_NPP_GETURLNOTIFY) { + NPError err = NPN_GetURLNotify(instance, instanceData->testUrl.c_str(), + nullptr, static_cast<void*>(&kNotifyData)); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURLNotify returned " << err; + } + } + + if ((instanceData->bugMode == 813906) && instanceData->frame.length()) { + bug813906(instance, "f", "browser.xhtml", instanceData->frame.c_str()); + } + + return NPERR_NO_ERROR; +} + +NPError NPP_Destroy(NPP instance, NPSavedData** save) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->crashOnDestroy) IntentionalCrash(); + + if (instanceData->callOnDestroy) { + NPVariant result; + NPN_InvokeDefault(instance, instanceData->callOnDestroy, nullptr, 0, + &result); + NPN_ReleaseVariantValue(&result); + NPN_ReleaseObject(instanceData->callOnDestroy); + } + + if (instanceData->streamBuf) { + free(instanceData->streamBuf); + } + + if (instanceData->frontBuffer) { + NPN_SetCurrentAsyncSurface(instance, nullptr, nullptr); + NPN_FinalizeAsyncSurface(instance, instanceData->frontBuffer); + NPN_MemFree(instanceData->frontBuffer); + } + if (instanceData->backBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->backBuffer); + NPN_MemFree(instanceData->backBuffer); + } + + pluginInstanceShutdown(instanceData); + NPN_ReleaseObject(instanceData->scriptableObject); + + if (sCurrentInstanceCountWatchGeneration == + instanceData->instanceCountWatchGeneration) { + --sInstanceCount; + } + delete instanceData; + + return NPERR_NO_ERROR; +} + +NPError NPP_SetWindow(NPP instance, NPWindow* window) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->scriptableObject->drawMode == DM_DEFAULT && + (instanceData->window.width != window->width || + instanceData->window.height != window->height)) { + NPRect r; + r.left = r.top = 0; + r.right = window->width; + r.bottom = window->height; + NPN_InvalidateRect(instance, &r); + } + + void* oldWindow = instanceData->window.window; + pluginDoSetWindow(instanceData, window); + if (instanceData->hasWidget && oldWindow != instanceData->window.window) { + pluginWidgetInit(instanceData, oldWindow); + } + + if (instanceData->asyncDrawing != AD_NONE) { + if (instanceData->frontBuffer && + instanceData->frontBuffer->size.width >= 0 && + (uint32_t)instanceData->frontBuffer->size.width == window->width && + instanceData->frontBuffer->size.height >= 0 && + (uint32_t)instanceData->frontBuffer->size.height == window->height) { + return NPERR_NO_ERROR; + } + if (instanceData->frontBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->frontBuffer); + NPN_MemFree(instanceData->frontBuffer); + } + if (instanceData->backBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->backBuffer); + NPN_MemFree(instanceData->backBuffer); + } + instanceData->frontBuffer = + (NPAsyncSurface*)NPN_MemAlloc(sizeof(NPAsyncSurface)); + instanceData->backBuffer = + (NPAsyncSurface*)NPN_MemAlloc(sizeof(NPAsyncSurface)); + + NPSize size; + size.width = window->width; + size.height = window->height; + + memcpy(instanceData->backBuffer, instanceData->frontBuffer, + sizeof(NPAsyncSurface)); + + NPN_InitAsyncSurface(instance, &size, NPImageFormatBGRA32, nullptr, + instanceData->frontBuffer); + NPN_InitAsyncSurface(instance, &size, NPImageFormatBGRA32, nullptr, + instanceData->backBuffer); + +#if defined(XP_WIN) + if (instanceData->asyncDrawing == AD_DXGI) { + if (!setupDxgiSurfaces(instance, instanceData)) { + return NPERR_GENERIC_ERROR; + } + } +#endif + } + + if (instanceData->asyncDrawing == AD_BITMAP) { + drawAsyncBitmapColor(instanceData); + } +#if defined(XP_WIN) + else if (instanceData->asyncDrawing == AD_DXGI) { + drawDxgiBitmapColor(instanceData); + } +#endif + + return NPERR_NO_ERROR; +} + +NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM && + instanceData->failureCode) { + instanceData->err << SUCCESS_STRING; + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + return instanceData->failureCode; + } + + if (stream->notifyData && + static_cast<URLNotifyData*>(stream->notifyData) != &kNotifyData) { + // stream from streamTest + *stype = NP_NORMAL; + } else { + *stype = NP_NORMAL; + + if (instanceData->streamBufSize) { + free(instanceData->streamBuf); + instanceData->streamBufSize = 0; + if (instanceData->testFunction == FUNCTION_NPP_POSTURL && + instanceData->postMode == POSTMODE_STREAM) { + instanceData->testFunction = FUNCTION_NPP_GETURL; + } else { + // We already got a stream and didn't ask for another one. + instanceData->err << "Received unexpected multiple NPP_NewStream"; + } + } + } + return NPERR_NO_ERROR; +} + +NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_DestroyStream called"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE) { + if (instanceData->writeCount == 1) + instanceData->err << SUCCESS_STRING; + else + instanceData->err << "NPP_Write called after returning -1"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_DESTROYSTREAM && + instanceData->failureCode) { + instanceData->err << SUCCESS_STRING; + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + return instanceData->failureCode; + } + + URLNotifyData* nd = static_cast<URLNotifyData*>(stream->notifyData); + if (nd && nd != &kNotifyData) { + return NPERR_NO_ERROR; + } + + if (instanceData->frame.length() > 0 && + instanceData->testFunction != FUNCTION_NPP_GETURLNOTIFY && + instanceData->testFunction != FUNCTION_NPP_POSTURL) { + sendBufferToFrame(instance); + } + if (instanceData->testFunction == FUNCTION_NPP_POSTURL) { + NPError err = NPN_PostURL( + instance, instanceData->testUrl.c_str(), + instanceData->postMode == POSTMODE_FRAME ? instanceData->frame.c_str() + : nullptr, + instanceData->streamBufSize, + reinterpret_cast<char*>(instanceData->streamBuf), false); + if (err != NPERR_NO_ERROR) + instanceData->err << "Error: NPN_PostURL returned error value " << err; + } + return NPERR_NO_ERROR; +} + +int32_t NPP_WriteReady(NPP instance, NPStream* stream) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->writeReadyCount++; + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_WriteReady called"; + } + + // temporarily disabled per bug 519870 + // if (instanceData->writeReadyCount == 1) { + // return 0; + //} + + return instanceData->streamChunkSize; +} + +int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, + void* buffer) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->writeCount++; + + // temporarily disabled per bug 519870 + // if (instanceData->writeReadyCount == 1) { + // instanceData->err << "NPP_Write called even though NPP_WriteReady " << + // "returned 0"; + //} + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE_RPC) { + // Make an RPC call and pretend to consume the data + NPObject* windowObject = nullptr; + NPN_GetValue(instance, NPNVWindowNPObject, &windowObject); + if (windowObject) NPN_ReleaseObject(windowObject); + + return len; + } + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_Write called"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE) { + return -1; + } + + URLNotifyData* nd = static_cast<URLNotifyData*>(stream->notifyData); + + if (nd && nd->writeCallback) { + NPVariant args[1]; + STRINGN_TO_NPVARIANT(stream->url, strlen(stream->url), args[0]); + + NPVariant result; + NPN_InvokeDefault(instance, nd->writeCallback, args, 1, &result); + NPN_ReleaseVariantValue(&result); + } + + if (nd && nd != &kNotifyData) { + uint32_t newsize = nd->size + len; + nd->data = (char*)realloc(nd->data, newsize); + memcpy(nd->data + nd->size, buffer, len); + nd->size = newsize; + return len; + } + + char* streamBuf = reinterpret_cast<char*>(instanceData->streamBuf); + if (offset + len <= instanceData->streamBufSize) { + if (memcmp(buffer, streamBuf + offset, len)) { + instanceData->err << "Error: data written doesn't match"; + } else { + printf("data matches!\n"); + } + } else { + if (instanceData->streamBufSize == 0) { + instanceData->streamBuf = malloc(len + 1); + streamBuf = reinterpret_cast<char*>(instanceData->streamBuf); + } else { + instanceData->streamBuf = + realloc(reinterpret_cast<char*>(instanceData->streamBuf), + instanceData->streamBufSize + len + 1); + streamBuf = reinterpret_cast<char*>(instanceData->streamBuf); + } + memcpy(streamBuf + instanceData->streamBufSize, buffer, len); + instanceData->streamBufSize = instanceData->streamBufSize + len; + streamBuf[instanceData->streamBufSize] = '\0'; + } + return len; +} + +void NPP_Print(NPP instance, NPPrint* platformPrint) {} + +int16_t NPP_HandleEvent(NPP instance, void* event) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + return pluginHandleEvent(instanceData, event); +} + +void NPP_URLNotify(NPP instance, const char* url, NPReason reason, + void* notifyData) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + URLNotifyData* ndata = static_cast<URLNotifyData*>(notifyData); + + if (&kNotifyData == ndata) { + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + } else if (!strcmp(ndata->cookie, "dynamic-cookie")) { + if (ndata->notifyCallback) { + NPVariant args[2]; + INT32_TO_NPVARIANT(reason, args[0]); + if (ndata->data) { + STRINGN_TO_NPVARIANT(ndata->data, ndata->size, args[1]); + } else { + STRINGN_TO_NPVARIANT("", 0, args[1]); + } + + NPVariant result; + NPN_InvokeDefault(instance, ndata->notifyCallback, args, 2, &result); + NPN_ReleaseVariantValue(&result); + } + + // clean up the URLNotifyData + if (ndata->writeCallback) { + NPN_ReleaseObject(ndata->writeCallback); + } + if (ndata->notifyCallback) { + NPN_ReleaseObject(ndata->notifyCallback); + } + if (ndata->redirectCallback) { + NPN_ReleaseObject(ndata->redirectCallback); + } + free(ndata->data); + delete ndata; + } else { + printf("ERROR! NPP_URLNotify called with wrong cookie\n"); + instanceData->err << "Error: NPP_URLNotify called with wrong cookie"; + } +} + +NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value) { + InstanceData* instanceData = (InstanceData*)instance->pdata; + if (variable == NPPVpluginScriptableNPObject) { + NPObject* object = instanceData->scriptableObject; + NPN_RetainObject(object); + *((NPObject**)value) = object; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginNeedsXEmbed) { + // Only relevant for X plugins + // use 4-byte writes like some plugins may do + *(uint32_t*)value = instanceData->hasWidget; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginWantsAllNetworkStreams) { + // use 4-byte writes like some plugins may do + *(uint32_t*)value = instanceData->wantsAllStreams; + return NPERR_NO_ERROR; + } + + return NPERR_GENERIC_ERROR; +} + +NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value) { + if (variable == NPNVprivateModeBool) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->lastReportedPrivateModeState = + bool(*static_cast<NPBool*>(value)); + return NPERR_NO_ERROR; + } + if (variable == NPNVmuteAudioBool) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->audioMuted = bool(*static_cast<NPBool*>(value)); + return NPERR_NO_ERROR; + } + if (variable == NPNVCSSZoomFactor) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->cssZoomFactor = *static_cast<double*>(value); + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; +} + +void NPP_URLRedirectNotify(NPP instance, const char* url, int32_t status, + void* notifyData) { + if (notifyData) { + URLNotifyData* nd = static_cast<URLNotifyData*>(notifyData); + if (nd->redirectCallback) { + NPVariant args[2]; + STRINGN_TO_NPVARIANT(url, strlen(url), args[0]); + INT32_TO_NPVARIANT(status, args[1]); + + NPVariant result; + NPN_InvokeDefault(instance, nd->redirectCallback, args, 2, &result); + NPN_ReleaseVariantValue(&result); + } + NPN_URLRedirectResponse(instance, notifyData, nd->allowRedirects); + return; + } + NPN_URLRedirectResponse(instance, notifyData, true); +} + +NPError NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge) { + if (!sSitesWithData) return NPERR_NO_ERROR; + + // Error condition: no support for clear-by-age + if (!sClearByAgeSupported && maxAge != uint64_t(int64_t(-1))) + return NPERR_TIME_RANGE_NOT_SUPPORTED; + + // Iterate over list and remove matches + list<siteData>::iterator iter = sSitesWithData->begin(); + list<siteData>::iterator end = sSitesWithData->end(); + while (iter != end) { + const siteData& data = *iter; + list<siteData>::iterator next = iter; + ++next; + if ((!site || data.site.compare(site) == 0) && + (flags == NP_CLEAR_ALL || data.flags & flags) && data.age <= maxAge) { + sSitesWithData->erase(iter); + } + iter = next; + } + + return NPERR_NO_ERROR; +} + +char** NPP_GetSitesWithData() { + int length = 0; + char** result; + + if (sSitesWithData) length = sSitesWithData->size(); + + // Allocate the maximum possible size the list could be. + result = static_cast<char**>(NPN_MemAlloc((length + 1) * sizeof(char*))); + result[length] = nullptr; + + if (length == 0) { + // Represent the no site data case as an array of length 1 with a nullptr + // entry. + return result; + } + + // Iterate the list of stored data, and build a list of strings. + list<string> sites; + { + list<siteData>::iterator iter = sSitesWithData->begin(); + list<siteData>::iterator end = sSitesWithData->end(); + for (; iter != end; ++iter) { + const siteData& data = *iter; + sites.push_back(data.site); + } + } + + // Remove duplicate strings. + sites.sort(); + sites.unique(); + + // Add strings to the result array, and null terminate. + { + int i = 0; + list<string>::iterator iter = sites.begin(); + list<string>::iterator end = sites.end(); + for (; iter != end; ++iter, ++i) { + const string& site = *iter; + result[i] = static_cast<char*>(NPN_MemAlloc(site.length() + 1)); + memcpy(result[i], site.c_str(), site.length() + 1); + } + } + result[sites.size()] = nullptr; + + return result; +} + +// +// npapi browser functions +// + +bool NPN_SetProperty(NPP instance, NPObject* obj, NPIdentifier propertyName, + const NPVariant* value) { + return sBrowserFuncs->setproperty(instance, obj, propertyName, value); +} + +NPIdentifier NPN_GetIntIdentifier(int32_t intid) { + return sBrowserFuncs->getintidentifier(intid); +} + +NPIdentifier NPN_GetStringIdentifier(const NPUTF8* name) { + return sBrowserFuncs->getstringidentifier(name); +} + +void NPN_GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers) { + return sBrowserFuncs->getstringidentifiers(names, nameCount, identifiers); +} + +bool NPN_IdentifierIsString(NPIdentifier identifier) { + return sBrowserFuncs->identifierisstring(identifier); +} + +NPUTF8* NPN_UTF8FromIdentifier(NPIdentifier identifier) { + return sBrowserFuncs->utf8fromidentifier(identifier); +} + +int32_t NPN_IntFromIdentifier(NPIdentifier identifier) { + return sBrowserFuncs->intfromidentifier(identifier); +} + +NPError NPN_GetValue(NPP instance, NPNVariable variable, void* value) { + return sBrowserFuncs->getvalue(instance, variable, value); +} + +NPError NPN_SetValue(NPP instance, NPPVariable variable, void* value) { + return sBrowserFuncs->setvalue(instance, variable, value); +} + +void NPN_InvalidateRect(NPP instance, NPRect* rect) { + sBrowserFuncs->invalidaterect(instance, rect); +} + +bool NPN_HasProperty(NPP instance, NPObject* obj, NPIdentifier propertyName) { + return sBrowserFuncs->hasproperty(instance, obj, propertyName); +} + +NPObject* NPN_CreateObject(NPP instance, NPClass* aClass) { + return sBrowserFuncs->createobject(instance, aClass); +} + +bool NPN_Invoke(NPP npp, NPObject* obj, NPIdentifier methodName, + const NPVariant* args, uint32_t argCount, NPVariant* result) { + return sBrowserFuncs->invoke(npp, obj, methodName, args, argCount, result); +} + +bool NPN_InvokeDefault(NPP npp, NPObject* obj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return sBrowserFuncs->invokeDefault(npp, obj, args, argCount, result); +} + +bool NPN_Construct(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return sBrowserFuncs->construct(npp, npobj, args, argCount, result); +} + +const char* NPN_UserAgent(NPP instance) { + return sBrowserFuncs->uagent(instance); +} + +NPObject* NPN_RetainObject(NPObject* obj) { + return sBrowserFuncs->retainobject(obj); +} + +void NPN_ReleaseObject(NPObject* obj) { + return sBrowserFuncs->releaseobject(obj); +} + +void* NPN_MemAlloc(uint32_t size) { return sBrowserFuncs->memalloc(size); } + +char* NPN_StrDup(const char* str) { + return strcpy((char*)sBrowserFuncs->memalloc(strlen(str) + 1), str); +} + +void NPN_MemFree(void* ptr) { return sBrowserFuncs->memfree(ptr); } + +uint32_t NPN_ScheduleTimer(NPP instance, uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)) { + return sBrowserFuncs->scheduletimer(instance, interval, repeat, timerFunc); +} + +void NPN_UnscheduleTimer(NPP instance, uint32_t timerID) { + return sBrowserFuncs->unscheduletimer(instance, timerID); +} + +void NPN_ReleaseVariantValue(NPVariant* variant) { + return sBrowserFuncs->releasevariantvalue(variant); +} + +NPError NPN_GetURLNotify(NPP instance, const char* url, const char* target, + void* notifyData) { + return sBrowserFuncs->geturlnotify(instance, url, target, notifyData); +} + +NPError NPN_GetURL(NPP instance, const char* url, const char* target) { + return sBrowserFuncs->geturl(instance, url, target); +} + +NPError NPN_PostURLNotify(NPP instance, const char* url, const char* target, + uint32_t len, const char* buf, NPBool file, + void* notifyData) { + return sBrowserFuncs->posturlnotify(instance, url, target, len, buf, file, + notifyData); +} + +NPError NPN_PostURL(NPP instance, const char* url, const char* target, + uint32_t len, const char* buf, NPBool file) { + return sBrowserFuncs->posturl(instance, url, target, len, buf, file); +} + +bool NPN_Enumerate(NPP instance, NPObject* npobj, NPIdentifier** identifiers, + uint32_t* identifierCount) { + return sBrowserFuncs->enumerate(instance, npobj, identifiers, + identifierCount); +} + +bool NPN_GetProperty(NPP instance, NPObject* npobj, NPIdentifier propertyName, + NPVariant* result) { + return sBrowserFuncs->getproperty(instance, npobj, propertyName, result); +} + +bool NPN_Evaluate(NPP instance, NPObject* npobj, NPString* script, + NPVariant* result) { + return sBrowserFuncs->evaluate(instance, npobj, script, result); +} + +void NPN_SetException(NPObject* npobj, const NPUTF8* message) { + return sBrowserFuncs->setexception(npobj, message); +} + +NPBool NPN_ConvertPoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace) { + return sBrowserFuncs->convertpoint(instance, sourceX, sourceY, sourceSpace, + destX, destY, destSpace); +} + +NPError NPN_SetValueForURL(NPP instance, NPNURLVariable variable, + const char* url, const char* value, uint32_t len) { + return sBrowserFuncs->setvalueforurl(instance, variable, url, value, len); +} + +NPError NPN_GetValueForURL(NPP instance, NPNURLVariable variable, + const char* url, char** value, uint32_t* len) { + return sBrowserFuncs->getvalueforurl(instance, variable, url, value, len); +} + +void NPN_URLRedirectResponse(NPP instance, void* notifyData, NPBool allow) { + return sBrowserFuncs->urlredirectresponse(instance, notifyData, allow); +} + +NPError NPN_InitAsyncSurface(NPP instance, NPSize* size, NPImageFormat format, + void* initData, NPAsyncSurface* surface) { + return sBrowserFuncs->initasyncsurface(instance, size, format, initData, + surface); +} + +NPError NPN_FinalizeAsyncSurface(NPP instance, NPAsyncSurface* surface) { + return sBrowserFuncs->finalizeasyncsurface(instance, surface); +} + +void NPN_SetCurrentAsyncSurface(NPP instance, NPAsyncSurface* surface, + NPRect* changed) { + sBrowserFuncs->setcurrentasyncsurface(instance, surface, changed); +} + +// +// npruntime object functions +// + +NPObject* scriptableAllocate(NPP npp, NPClass* aClass) { + TestNPObject* object = (TestNPObject*)NPN_MemAlloc(sizeof(TestNPObject)); + if (!object) return nullptr; + memset(object, 0, sizeof(TestNPObject)); + return object; +} + +void scriptableDeallocate(NPObject* npobj) { NPN_MemFree(npobj); } + +void scriptableInvalidate(NPObject* npobj) {} + +bool scriptableHasMethod(NPObject* npobj, NPIdentifier name) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers)); i++) { + if (name == sPluginMethodIdentifiers[i]) return true; + } + return false; +} + +bool scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (id->throwOnNextInvoke) { + id->throwOnNextInvoke = false; + if (argCount == 0) { + NPN_SetException(npobj, nullptr); + } else { + for (uint32_t i = 0; i < argCount; i++) { + const NPString* argstr = &NPVARIANT_TO_STRING(args[i]); + NPN_SetException(npobj, argstr->UTF8Characters); + } + } + return false; + } + + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers)); i++) { + if (name == sPluginMethodIdentifiers[i]) + return sPluginMethodFunctions[i](npobj, args, argCount, result); + } + return false; +} + +bool scriptableHasProperty(NPObject* npobj, NPIdentifier name) { + if (NPN_IdentifierIsString(name)) { + NPUTF8* asUTF8 = NPN_UTF8FromIdentifier(name); + if (NPN_GetStringIdentifier(asUTF8) != name) { + Crash(); + } + NPN_MemFree(asUTF8); + } else { + if (NPN_GetIntIdentifier(NPN_IntFromIdentifier(name)) != name) { + Crash(); + } + } + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + return true; + } + } + return false; +} + +bool scriptableGetProperty(NPObject* npobj, NPIdentifier name, + NPVariant* result) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + DuplicateNPVariant(*result, sPluginPropertyValues[i]); + return true; + } + } + return false; +} + +bool scriptableSetProperty(NPObject* npobj, NPIdentifier name, + const NPVariant* value) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + DuplicateNPVariant(sPluginPropertyValues[i], *value); + return true; + } + } + return false; +} + +bool scriptableRemoveProperty(NPObject* npobj, NPIdentifier name) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + + // Avoid double frees (see test_propertyAndMethod.html, which deletes a + // property that doesn't exist). + VOID_TO_NPVARIANT(sPluginPropertyValues[i]); + return true; + } + } + return false; +} + +bool scriptableEnumerate(NPObject* npobj, NPIdentifier** identifier, + uint32_t* count) { + const int bufsize = + sizeof(NPIdentifier) * MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames); + NPIdentifier* ids = (NPIdentifier*)NPN_MemAlloc(bufsize); + if (!ids) return false; + + memcpy(ids, sPluginMethodIdentifiers, bufsize); + *identifier = ids; + *count = MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames); + return true; +} + +bool scriptableConstruct(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return false; +} + +// +// test functions +// + +static bool compareVariants(NPP instance, const NPVariant* var1, + const NPVariant* var2) { + bool success = true; + InstanceData* id = static_cast<InstanceData*>(instance->pdata); + if (var1->type != var2->type) { + id->err << "Variant types don't match; got " << var1->type << " expected " + << var2->type; + return false; + } + + // Cast var1->type from NPVariantType to int to avoid compiler warnings about + // not needing a default case when we have cases for every enum value. + switch (static_cast<int>(var1->type)) { + case NPVariantType_Int32: { + int32_t result = NPVARIANT_TO_INT32(*var1); + int32_t expected = NPVARIANT_TO_INT32(*var2); + if (result != expected) { + id->err << "Variant values don't match; got " << result << " expected " + << expected; + success = false; + } + break; + } + case NPVariantType_Double: { + double result = NPVARIANT_TO_DOUBLE(*var1); + double expected = NPVARIANT_TO_DOUBLE(*var2); + if (result != expected) { + id->err << "Variant values don't match (double)"; + success = false; + } + break; + } + case NPVariantType_Void: { + // void values are always equivalent + break; + } + case NPVariantType_Null: { + // null values are always equivalent + break; + } + case NPVariantType_Bool: { + bool result = NPVARIANT_TO_BOOLEAN(*var1); + bool expected = NPVARIANT_TO_BOOLEAN(*var2); + if (result != expected) { + id->err << "Variant values don't match (bool)"; + success = false; + } + break; + } + case NPVariantType_String: { + const NPString* result = &NPVARIANT_TO_STRING(*var1); + const NPString* expected = &NPVARIANT_TO_STRING(*var2); + if (strcmp(result->UTF8Characters, expected->UTF8Characters) || + strlen(result->UTF8Characters) != strlen(expected->UTF8Characters)) { + id->err << "Variant values don't match; got " << result->UTF8Characters + << " expected " << expected->UTF8Characters; + success = false; + } + break; + } + case NPVariantType_Object: { + uint32_t i, identifierCount = 0; + NPIdentifier* identifiers; + NPObject* result = NPVARIANT_TO_OBJECT(*var1); + NPObject* expected = NPVARIANT_TO_OBJECT(*var2); + bool enumerate_result = + NPN_Enumerate(instance, expected, &identifiers, &identifierCount); + if (!enumerate_result) { + id->err << "NPN_Enumerate failed"; + success = false; + } + for (i = 0; i < identifierCount; i++) { + NPVariant resultVariant, expectedVariant; + if (!NPN_GetProperty(instance, expected, identifiers[i], + &expectedVariant)) { + id->err << "NPN_GetProperty returned false"; + success = false; + } else { + if (!NPN_HasProperty(instance, result, identifiers[i])) { + id->err << "NPN_HasProperty returned false"; + success = false; + } else { + if (!NPN_GetProperty(instance, result, identifiers[i], + &resultVariant)) { + id->err << "NPN_GetProperty 2 returned false"; + success = false; + } else { + success = + compareVariants(instance, &resultVariant, &expectedVariant); + NPN_ReleaseVariantValue(&expectedVariant); + } + } + NPN_ReleaseVariantValue(&resultVariant); + } + } + NPN_MemFree(identifiers); + break; + } + default: + id->err << "Unknown variant type"; + success = false; + MOZ_ASSERT_UNREACHABLE("Unknown variant type?!"); + } + + return success; +} + +static bool throwExceptionNextInvoke(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->throwOnNextInvoke = true; + BOOLEAN_TO_NPVARIANT(true, *result); + return true; +} + +static bool npnInvokeDefaultTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + bool success = false; + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return false; + + NPIdentifier objectIdentifier = variantToIdentifier(args[0]); + if (!objectIdentifier) return false; + + NPVariant objectVariant; + if (NPN_GetProperty(npp, windowObject, objectIdentifier, &objectVariant)) { + if (NPVARIANT_IS_OBJECT(objectVariant)) { + NPObject* selfObject = NPVARIANT_TO_OBJECT(objectVariant); + if (selfObject != nullptr) { + NPVariant resultVariant; + if (NPN_InvokeDefault(npp, selfObject, + argCount > 1 ? &args[1] : nullptr, argCount - 1, + &resultVariant)) { + *result = resultVariant; + success = true; + } + } + } + NPN_ReleaseVariantValue(&objectVariant); + } + + NPN_ReleaseObject(windowObject); + return success; +} + +static bool npnInvokeTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->err.str(""); + if (argCount < 2) return false; + + NPIdentifier function = variantToIdentifier(args[0]); + if (!function) return false; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return false; + + NPVariant invokeResult; + bool invokeReturn = + NPN_Invoke(npp, windowObject, function, argCount > 2 ? &args[2] : nullptr, + argCount - 2, &invokeResult); + + bool compareResult = compareVariants(npp, &invokeResult, &args[1]); + + NPN_ReleaseObject(windowObject); + NPN_ReleaseVariantValue(&invokeResult); + BOOLEAN_TO_NPVARIANT(invokeReturn && compareResult, *result); + return true; +} + +static bool npnEvaluateTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + bool success = false; + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (argCount != 1) return false; + + if (!NPVARIANT_IS_STRING(args[0])) return false; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return false; + + success = NPN_Evaluate(npp, windowObject, + (NPString*)&NPVARIANT_TO_STRING(args[0]), result); + + NPN_ReleaseObject(windowObject); + return success; +} + +static bool setUndefinedValueTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + NPError err = NPN_SetValue(npp, (NPPVariable)0x0, 0x0); + BOOLEAN_TO_NPVARIANT((err == NPERR_NO_ERROR), *result); + return true; +} + +static bool identifierToStringTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1) return false; + NPIdentifier identifier = variantToIdentifier(args[0]); + if (!identifier) return false; + + NPUTF8* utf8String = NPN_UTF8FromIdentifier(identifier); + if (!utf8String) return false; + STRINGZ_TO_NPVARIANT(utf8String, *result); + return true; +} + +static bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPBool pms = false; + NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, NPNVprivateModeBool, + &pms); + BOOLEAN_TO_NPVARIANT(pms, *result); + return true; +} + +static bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + BOOLEAN_TO_NPVARIANT(id->lastReportedPrivateModeState, *result); + return true; +} + +static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + BOOLEAN_TO_NPVARIANT(id->hasWidget, *result); + return true; +} + +static bool getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1) return false; + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t edge = NPVARIANT_TO_INT32(args[0]); + if (edge < EDGE_LEFT || edge > EDGE_BOTTOM) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + int32_t r = pluginGetEdge(id, RectEdge(edge)); + if (r == NPTEST_INT32_ERROR) return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool getClipRegionRectCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + int32_t r = pluginGetClipRegionRectCount(id); + if (r == NPTEST_INT32_ERROR) return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 2) return false; + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t rectIndex = NPVARIANT_TO_INT32(args[0]); + if (rectIndex < 0) return false; + if (!NPVARIANT_IS_INT32(args[1])) return false; + int32_t edge = NPVARIANT_TO_INT32(args[1]); + if (edge < EDGE_LEFT || edge > EDGE_BOTTOM) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + int32_t r = pluginGetClipRegionRectEdge(id, rectIndex, RectEdge(edge)); + if (r == NPTEST_INT32_ERROR) return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + if (sWatchingInstanceCount) return false; + + sWatchingInstanceCount = true; + sInstanceCount = 0; + ++sCurrentInstanceCountWatchGeneration; + return true; +} + +static bool getInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + if (!sWatchingInstanceCount) return false; + + INT32_TO_NPVARIANT(sInstanceCount, *result); + return true; +} + +static bool stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + if (!sWatchingInstanceCount) return false; + + sWatchingInstanceCount = false; + return true; +} + +static bool getLastMouseX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->lastMouseX, *result); + return true; +} + +static bool getLastMouseY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->lastMouseY, *result); + return true; +} + +static bool getPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->paintCount, *result); + return true; +} + +static bool resetPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->paintCount = 0; + return true; +} + +static bool getWidthAtLastPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->widthAtLastPaint, *result); + return true; +} + +static bool setInvalidateDuringPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1) return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) return false; + bool doInvalidate = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->invalidateDuringPaint = doInvalidate; + return true; +} + +static bool setSlowPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1) return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) return false; + bool slow = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->slowPaint = slow; + return true; +} + +static bool getError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (id->err.str().length() == 0) { + char* outval = NPN_StrDup(SUCCESS_STRING); + STRINGZ_TO_NPVARIANT(outval, *result); + } else { + char* outval = NPN_StrDup(id->err.str().c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + } + return true; +} + +static bool doInternalConsistencyCheck(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + string error; + pluginDoInternalConsistencyCheck(id, error); + NPUTF8* utf8String = (NPUTF8*)NPN_MemAlloc(error.length() + 1); + if (!utf8String) { + return false; + } + memcpy(utf8String, error.c_str(), error.length() + 1); + STRINGZ_TO_NPVARIANT(utf8String, *result); + return true; +} + +static bool convertPointX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 4) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t sourceSpace = NPVARIANT_TO_INT32(args[0]); + + if (!NPVARIANT_IS_INT32(args[1])) return false; + double sourceX = static_cast<double>(NPVARIANT_TO_INT32(args[1])); + + if (!NPVARIANT_IS_INT32(args[2])) return false; + double sourceY = static_cast<double>(NPVARIANT_TO_INT32(args[2])); + + if (!NPVARIANT_IS_INT32(args[3])) return false; + int32_t destSpace = NPVARIANT_TO_INT32(args[3]); + + double resultX, resultY; + NPN_ConvertPoint(npp, sourceX, sourceY, (NPCoordinateSpace)sourceSpace, + &resultX, &resultY, (NPCoordinateSpace)destSpace); + + DOUBLE_TO_NPVARIANT(resultX, *result); + return true; +} + +static bool convertPointY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 4) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t sourceSpace = NPVARIANT_TO_INT32(args[0]); + + if (!NPVARIANT_IS_INT32(args[1])) return false; + double sourceX = static_cast<double>(NPVARIANT_TO_INT32(args[1])); + + if (!NPVARIANT_IS_INT32(args[2])) return false; + double sourceY = static_cast<double>(NPVARIANT_TO_INT32(args[2])); + + if (!NPVARIANT_IS_INT32(args[3])) return false; + int32_t destSpace = NPVARIANT_TO_INT32(args[3]); + + double resultX, resultY; + NPN_ConvertPoint(npp, sourceX, sourceY, (NPCoordinateSpace)sourceSpace, + &resultX, &resultY, (NPCoordinateSpace)destSpace); + + DOUBLE_TO_NPVARIANT(resultY, *result); + return true; +} + +static bool streamTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // .streamTest(url, doPost, postData, writeCallback, notifyCallback, + // redirectCallback, allowRedirects, postFile = false) + if (!(7 <= argCount && argCount <= 8)) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_STRING(args[0])) return false; + NPString url = NPVARIANT_TO_STRING(args[0]); + + if (!NPVARIANT_IS_BOOLEAN(args[1])) return false; + bool doPost = NPVARIANT_TO_BOOLEAN(args[1]); + + NPString postData = {nullptr, 0}; + if (NPVARIANT_IS_STRING(args[2])) { + postData = NPVARIANT_TO_STRING(args[2]); + } else { + if (!NPVARIANT_IS_NULL(args[2])) { + return false; + } + } + + NPObject* writeCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[3])) { + writeCallback = NPVARIANT_TO_OBJECT(args[3]); + } else { + if (!NPVARIANT_IS_NULL(args[3])) { + return false; + } + } + + NPObject* notifyCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[4])) { + notifyCallback = NPVARIANT_TO_OBJECT(args[4]); + } else { + if (!NPVARIANT_IS_NULL(args[4])) { + return false; + } + } + + NPObject* redirectCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[5])) { + redirectCallback = NPVARIANT_TO_OBJECT(args[5]); + } else { + if (!NPVARIANT_IS_NULL(args[5])) { + return false; + } + } + + if (!NPVARIANT_IS_BOOLEAN(args[6])) return false; + bool allowRedirects = NPVARIANT_TO_BOOLEAN(args[6]); + + bool postFile = false; + if (argCount >= 8) { + if (!NPVARIANT_IS_BOOLEAN(args[7])) { + return false; + } + postFile = NPVARIANT_TO_BOOLEAN(args[7]); + } + + URLNotifyData* ndata = new URLNotifyData; + ndata->cookie = "dynamic-cookie"; + ndata->writeCallback = writeCallback; + ndata->notifyCallback = notifyCallback; + ndata->redirectCallback = redirectCallback; + ndata->size = 0; + ndata->data = nullptr; + ndata->allowRedirects = allowRedirects; + + /* null-terminate "url" */ + char* urlstr = (char*)malloc(url.UTF8Length + 1); + strncpy(urlstr, url.UTF8Characters, url.UTF8Length); + urlstr[url.UTF8Length] = '\0'; + + NPError err; + if (doPost) { + err = NPN_PostURLNotify(npp, urlstr, nullptr, postData.UTF8Length, + postData.UTF8Characters, postFile, ndata); + } else { + err = NPN_GetURLNotify(npp, urlstr, nullptr, ndata); + } + + free(urlstr); + + if (NPERR_NO_ERROR == err) { + if (ndata->writeCallback) { + NPN_RetainObject(ndata->writeCallback); + } + if (ndata->notifyCallback) { + NPN_RetainObject(ndata->notifyCallback); + } + if (ndata->redirectCallback) { + NPN_RetainObject(ndata->redirectCallback); + } + BOOLEAN_TO_NPVARIANT(true, *result); + } else { + delete ndata; + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +static bool postFileToURLTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (1 != argCount) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + string url; + { + if (!NPVARIANT_IS_STRING(args[0])) return false; + NPString npurl = NPVARIANT_TO_STRING(args[0]); + // make a copy to ensure that the url string is null-terminated + url = string(npurl.UTF8Characters, npurl.UTF8Length); + } + + NPError err; + { + string buf("/path/to/file"); + err = NPN_PostURL(npp, url.c_str(), nullptr /* target */, buf.length(), + buf.c_str(), true /* file */); + } + + BOOLEAN_TO_NPVARIANT(NPERR_NO_ERROR == err, *result); + return true; +} + +static bool setPluginWantsAllStreams(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (1 != argCount) return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) return false; + bool wantsAllStreams = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + id->wantsAllStreams = wantsAllStreams; + + return true; +} + +static bool crashPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + IntentionalCrash(); + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool crashOnDestroy(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + id->crashOnDestroy = true; + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool setColor(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1) return false; + if (!NPVARIANT_IS_STRING(args[0])) return false; + const NPString* str = &NPVARIANT_TO_STRING(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + id->scriptableObject->drawColor = + parseHexColor(str->UTF8Characters, str->UTF8Length); + + NPRect r; + r.left = 0; + r.top = 0; + r.right = id->window.width; + r.bottom = id->window.height; + if (id->asyncDrawing == AD_NONE) { + NPN_InvalidateRect(npp, &r); + } else if (id->asyncDrawing == AD_BITMAP) { + drawAsyncBitmapColor(id); + } + + VOID_TO_NPVARIANT(*result); + return true; +} + +void notifyDidPaint(InstanceData* instanceData) { + ++instanceData->paintCount; + instanceData->widthAtLastPaint = instanceData->window.width; + + if (instanceData->invalidateDuringPaint) { + NPRect r; + r.left = 0; + r.top = 0; + r.right = instanceData->window.width; + r.bottom = instanceData->window.height; + NPN_InvalidateRect(instanceData->npp, &r); + } + + if (instanceData->slowPaint) { + XPSleep(1); + } + + if (instanceData->runScriptOnPaint) { + NPObject* o = nullptr; + NPN_GetValue(instanceData->npp, NPNVPluginElementNPObject, &o); + if (o) { + NPVariant param; + STRINGZ_TO_NPVARIANT("paintscript", param); + NPVariant result; + NPN_Invoke(instanceData->npp, o, NPN_GetStringIdentifier("getAttribute"), + ¶m, 1, &result); + + if (NPVARIANT_IS_STRING(result)) { + NPObject* windowObject; + NPN_GetValue(instanceData->npp, NPNVWindowNPObject, &windowObject); + if (windowObject) { + NPVariant evalResult; + NPN_Evaluate(instanceData->npp, windowObject, + (NPString*)&NPVARIANT_TO_STRING(result), &evalResult); + NPN_ReleaseVariantValue(&evalResult); + NPN_ReleaseObject(windowObject); + } + } + + NPN_ReleaseVariantValue(&result); + NPN_ReleaseObject(o); + } + } +} + +static const NPClass kTestSharedNPClass = { + NP_CLASS_STRUCT_VERSION, + // Everything else is nullptr +}; + +static bool getObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* o = + NPN_CreateObject(npp, const_cast<NPClass*>(&kTestSharedNPClass)); + if (!o) return false; + + OBJECT_TO_NPVARIANT(o, *result); + return true; +} + +static bool checkObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + VOID_TO_NPVARIANT(*result); + + if (1 != argCount) return false; + + if (!NPVARIANT_IS_OBJECT(args[0])) return false; + + NPObject* o = NPVARIANT_TO_OBJECT(args[0]); + + BOOLEAN_TO_NPVARIANT(o->_class == &kTestSharedNPClass, *result); + return true; +} + +static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + VOID_TO_NPVARIANT(*result); + +#if defined(XP_WIN) && defined(_M_IX86) + _control87(0, _MCW_EM); + return true; +#else + return false; +#endif +} + +static void timerCallback(NPP npp, uint32_t timerID) { + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + currentTimerEventCount++; + timerEvent event = timerEvents[currentTimerEventCount]; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return; + + NPVariant rval; + if (timerID != id->timerID[event.timerIdReceive]) { + id->timerTestResult = false; + } + + if (currentTimerEventCount == totalTimerEvents - 1) { + NPVariant arg; + BOOLEAN_TO_NPVARIANT(id->timerTestResult, arg); + NPN_Invoke(npp, windowObject, + NPN_GetStringIdentifier(id->timerTestScriptCallback.c_str()), + &arg, 1, &rval); + NPN_ReleaseVariantValue(&arg); + } + + NPN_ReleaseObject(windowObject); + + if (event.timerIdSchedule > -1) { + id->timerID[event.timerIdSchedule] = NPN_ScheduleTimer( + npp, event.timerInterval, event.timerRepeat, timerCallback); + } + if (event.timerIdUnschedule > -1) { + NPN_UnscheduleTimer(npp, id->timerID[event.timerIdUnschedule]); + } +} + +static bool timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + currentTimerEventCount = 0; + + if (argCount < 1 || !NPVARIANT_IS_STRING(args[0])) return false; + const NPString* argstr = &NPVARIANT_TO_STRING(args[0]); + id->timerTestScriptCallback = argstr->UTF8Characters; + + id->timerTestResult = true; + timerEvent event = timerEvents[currentTimerEventCount]; + + id->timerID[event.timerIdSchedule] = NPN_ScheduleTimer( + npp, event.timerInterval, event.timerRepeat, timerCallback); + + return id->timerID[event.timerIdSchedule] != 0; +} + +bool hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + mozilla::NoteIntentionalCrash("plugin"); + + bool busyHang = false; + if ((argCount == 1) && NPVARIANT_IS_BOOLEAN(args[0])) { + busyHang = NPVARIANT_TO_BOOLEAN(args[0]); + } + + if (busyHang) { + const time_t start = std::time(nullptr); + while ((std::time(nullptr) - start) < 100000) { + volatile int dummy = 0; + for (int i = 0; i < 1000; ++i) { + dummy++; + } + } + } else { +#ifdef XP_WIN + Sleep(100000000); + Sleep(100000000); +#else + pause(); + pause(); +#endif + } + + // NB: returning true here means that we weren't terminated, and + // thus the hang detection/handling didn't work correctly. The + // test harness will succeed in calling this function, and the + // test will fail. + return true; +} + +bool stallPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + uint32_t stallTimeSeconds = 0; + if ((argCount == 1) && NPVARIANT_IS_INT32(args[0])) { + stallTimeSeconds = (uint32_t)NPVARIANT_TO_INT32(args[0]); + } + +#ifdef XP_WIN + Sleep(stallTimeSeconds * 1000U); +#else + sleep(stallTimeSeconds); +#endif + + return true; +} + +#if defined(MOZ_WIDGET_GTK) +bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + string sel = pluginGetClipboardText(id); + + uint32_t len = sel.size(); + char* selCopy = static_cast<char*>(NPN_MemAlloc(1 + len)); + if (!selCopy) return false; + + memcpy(selCopy, sel.c_str(), len); + selCopy[len] = '\0'; + + STRINGN_TO_NPVARIANT(selCopy, len, *result); + // *result owns str now + + return true; +} + +bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + return pluginCrashInNestedLoop(id); +} + +bool triggerXError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + return pluginTriggerXError(id); +} + +bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + return pluginDestroySharedGfxStuff(id); +} + +#else +bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + // XXX Not implemented! + return false; +} + +bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // XXX Not implemented! + return false; +} + +bool triggerXError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + // XXX Not implemented! + return false; +} + +bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // XXX Not implemented! + return false; +} +#endif + +#if defined(XP_WIN) +bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + bool visible = pluginNativeWidgetIsVisible(id); + BOOLEAN_TO_NPVARIANT(visible, *result); + return true; +} +#else +bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // XXX Not implemented! + return false; +} +#endif + +bool getLastCompositionText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { +#ifdef XP_WIN + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + char* outval = NPN_StrDup(id->lastComposition.c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +#else + // XXX not implemented + return false; +#endif +} + +bool scriptableInvokeDefault(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + ostringstream value; + value << sPluginName; + for (uint32_t i = 0; i < argCount; i++) { + switch (args[i].type) { + case NPVariantType_Int32: + value << ";" << NPVARIANT_TO_INT32(args[i]); + break; + case NPVariantType_String: { + const NPString* argstr = &NPVARIANT_TO_STRING(args[i]); + value << ";" << argstr->UTF8Characters; + break; + } + case NPVariantType_Void: + value << ";undefined"; + break; + case NPVariantType_Null: + value << ";null"; + break; + default: + value << ";other"; + } + } + + char* outval = NPN_StrDup(value.str().c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +} + +static const NPClass kInvokeDefaultClass = {NP_CLASS_STRUCT_VERSION, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + scriptableInvokeDefault, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}; + +bool getInvokeDefaultObject(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (0 != argCount) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + NPObject* testObject = + NPN_CreateObject(npp, const_cast<NPClass*>(&kInvokeDefaultClass)); + OBJECT_TO_NPVARIANT(testObject, *result); + return true; +} + +bool callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + if (id->callOnDestroy) return false; + + if (1 != argCount || !NPVARIANT_IS_OBJECT(args[0])) return false; + + id->callOnDestroy = NPVARIANT_TO_OBJECT(args[0]); + NPN_RetainObject(id->callOnDestroy); + + return true; +} + +// On Linux at least, a windowed plugin resize causes Flash Player to +// reconnect to the browser window. This method simulates that. +bool reinitWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + if (!id->hasWidget) return false; + + pluginWidgetInit(id, id->window.window); + return true; +} + +bool propertyAndMethod(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + INT32_TO_NPVARIANT(5, *result); + return true; +} + +// Returns top-level window activation state as indicated by Cocoa NPAPI's +// NPCocoaEventWindowFocusChanged events - 'true' if active, 'false' if not. +// Throws an exception if no events have been received and thus this state +// is unknown. +bool getTopLevelWindowActivationState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + // Throw an exception for unknown state. + if (id->topLevelWindowActivationState == ACTIVATION_STATE_UNKNOWN) { + return false; + } + + if (id->topLevelWindowActivationState == ACTIVATION_STATE_ACTIVATED) { + BOOLEAN_TO_NPVARIANT(true, *result); + } else if (id->topLevelWindowActivationState == + ACTIVATION_STATE_DEACTIVATED) { + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +bool getTopLevelWindowActivationEventCount(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + INT32_TO_NPVARIANT(id->topLevelWindowActivationEventCount, *result); + + return true; +} + +// Returns top-level window activation state as indicated by Cocoa NPAPI's +// NPCocoaEventFocusChanged events - 'true' if active, 'false' if not. +// Throws an exception if no events have been received and thus this state +// is unknown. +bool getFocusState(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + // Throw an exception for unknown state. + if (id->focusState == ACTIVATION_STATE_UNKNOWN) { + return false; + } + + if (id->focusState == ACTIVATION_STATE_ACTIVATED) { + BOOLEAN_TO_NPVARIANT(true, *result); + } else if (id->focusState == ACTIVATION_STATE_DEACTIVATED) { + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +bool getFocusEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + INT32_TO_NPVARIANT(id->focusEventCount, *result); + + return true; +} + +bool getEventModel(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + INT32_TO_NPVARIANT(id->eventModel, *result); + + return true; +} + +static bool ReflectorHasMethod(NPObject* npobj, NPIdentifier name) { + return false; +} + +static bool ReflectorHasProperty(NPObject* npobj, NPIdentifier name) { + return true; +} + +static bool ReflectorGetProperty(NPObject* npobj, NPIdentifier name, + NPVariant* result) { + if (NPN_IdentifierIsString(name)) { + char* s = NPN_UTF8FromIdentifier(name); + STRINGZ_TO_NPVARIANT(s, *result); + return true; + } + + INT32_TO_NPVARIANT(NPN_IntFromIdentifier(name), *result); + return true; +} + +static const NPClass kReflectorNPClass = {NP_CLASS_STRUCT_VERSION, + nullptr, + nullptr, + nullptr, + ReflectorHasMethod, + nullptr, + nullptr, + ReflectorHasProperty, + ReflectorGetProperty, + nullptr, + nullptr, + nullptr, + nullptr}; + +bool getReflector(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (0 != argCount) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* reflector = + NPN_CreateObject(npp, + const_cast<NPClass*>(&kReflectorNPClass)); // retains + OBJECT_TO_NPVARIANT(reflector, *result); + return true; +} + +bool isVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + BOOLEAN_TO_NPVARIANT( + id->window.clipRect.top != 0 || id->window.clipRect.left != 0 || + id->window.clipRect.bottom != 0 || id->window.clipRect.right != 0, + *result); + return true; +} + +bool getWindowPosition(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + NPObject* window = nullptr; + NPError err = NPN_GetValue(npp, NPNVWindowNPObject, &window); + if (NPERR_NO_ERROR != err || !window) return false; + + NPIdentifier arrayID = NPN_GetStringIdentifier("Array"); + NPVariant arrayFunctionV; + bool ok = NPN_GetProperty(npp, window, arrayID, &arrayFunctionV); + + NPN_ReleaseObject(window); + + if (!ok) return false; + + if (!NPVARIANT_IS_OBJECT(arrayFunctionV)) { + NPN_ReleaseVariantValue(&arrayFunctionV); + return false; + } + NPObject* arrayFunction = NPVARIANT_TO_OBJECT(arrayFunctionV); + + NPVariant elements[4]; + INT32_TO_NPVARIANT(id->window.x, elements[0]); + INT32_TO_NPVARIANT(id->window.y, elements[1]); + INT32_TO_NPVARIANT(id->window.width, elements[2]); + INT32_TO_NPVARIANT(id->window.height, elements[3]); + + ok = NPN_InvokeDefault(npp, arrayFunction, elements, 4, result); + + NPN_ReleaseObject(arrayFunction); + + return ok; +} + +bool constructObject(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount == 0 || !NPVARIANT_IS_OBJECT(args[0])) return false; + + NPObject* ctor = NPVARIANT_TO_OBJECT(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + return NPN_Construct(npp, ctor, args + 1, argCount - 1, result); +} + +bool setSitesWithData(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1 || !NPVARIANT_IS_STRING(args[0])) return false; + + // Clear existing data. + delete sSitesWithData; + + const NPString* str = &NPVARIANT_TO_STRING(args[0]); + if (str->UTF8Length == 0) return true; + + // Parse the comma-delimited string into a vector. + sSitesWithData = new list<siteData>; + const char* iterator = str->UTF8Characters; + const char* end = iterator + str->UTF8Length; + while (1) { + const char* next = strchr(iterator, ','); + if (!next) next = end; + + // Parse out the three tokens into a siteData struct. + const char* siteEnd = strchr(iterator, ':'); + *((char*)siteEnd) = '\0'; + const char* flagsEnd = strchr(siteEnd + 1, ':'); + *((char*)flagsEnd) = '\0'; + *((char*)next) = '\0'; + + siteData data; + data.site = string(iterator); + data.flags = atoi(siteEnd + 1); + data.age = atoi(flagsEnd + 1); + + sSitesWithData->push_back(data); + + if (next == end) break; + + iterator = next + 1; + } + + return true; +} + +bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1 || !NPVARIANT_IS_BOOLEAN(args[0])) return false; + + sClearByAgeSupported = NPVARIANT_TO_BOOLEAN(args[0]); + return true; +} + +bool getLastKeyText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + char* outval = NPN_StrDup(id->lastKeyText.c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +} + +bool getNPNVdocumentOrigin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + char* origin = nullptr; + NPError err = NPN_GetValue(npp, NPNVdocumentOrigin, &origin); + if (err != NPERR_NO_ERROR) { + return false; + } + + STRINGZ_TO_NPVARIANT(origin, *result); + return true; +} + +bool getMouseUpEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->mouseUpEventCount, *result); + return true; +} + +bool queryContentsScaleFactor(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + double scaleFactor = 1.0; +#if defined(XP_MACOSX) || defined(XP_WIN) + NPError err = NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, + NPNVcontentsScaleFactor, &scaleFactor); + if (err != NPERR_NO_ERROR) { + return false; + } +#endif + DOUBLE_TO_NPVARIANT(scaleFactor, *result); + return true; +} + +bool queryCSSZoomFactorSetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + if (!npp) { + return false; + } + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (!id) { + return false; + } + DOUBLE_TO_NPVARIANT(id->cssZoomFactor, *result); + return true; +} + +bool queryCSSZoomFactorGetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + double zoomFactor = 1.0; + NPError err = NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, + NPNVCSSZoomFactor, &zoomFactor); + if (err != NPERR_NO_ERROR) { + return false; + } + DOUBLE_TO_NPVARIANT(zoomFactor, *result); + return true; +} + +bool echoString(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1) { + return false; + } + + if (!NPVARIANT_IS_STRING(args[0])) { + return false; + } + + const NPString& arg = NPVARIANT_TO_STRING(args[0]); + NPUTF8* buffer = + static_cast<NPUTF8*>(NPN_MemAlloc(sizeof(NPUTF8) * arg.UTF8Length)); + if (!buffer) { + return false; + } + + std::copy(arg.UTF8Characters, arg.UTF8Characters + arg.UTF8Length, buffer); + STRINGN_TO_NPVARIANT(buffer, arg.UTF8Length, *result); + + return true; +} + +static bool toggleAudioPlayback(NPObject* npobj, uint32_t argCount, + bool playingAudio, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->playingAudio = playingAudio; + + NPN_SetValue(npp, NPPVpluginIsPlayingAudio, (void*)playingAudio); + + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool startAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return toggleAudioPlayback(npobj, argCount, true, result); +} + +static bool stopAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return toggleAudioPlayback(npobj, argCount, false, result); +} + +static bool getAudioMuted(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + BOOLEAN_TO_NPVARIANT(id->audioMuted, *result); + return true; +} diff --git a/dom/plugins/test/testplugin/nptest.def b/dom/plugins/test/testplugin/nptest.def new file mode 100644 index 0000000000..4c543d5b9f --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/nptest.h b/dom/plugins/test/testplugin/nptest.h new file mode 100644 index 0000000000..3bac9cb65b --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.h @@ -0,0 +1,150 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_h_ +#define nptest_h_ + +#include "mozilla-config.h" + +#include "npapi.h" +#include "npfunctions.h" +#include "npruntime.h" +#include <stdint.h> +#include <string> +#include <sstream> + +typedef enum { DM_DEFAULT, DM_SOLID_COLOR } DrawMode; + +typedef enum { + FUNCTION_NONE, + FUNCTION_NPP_GETURL, + FUNCTION_NPP_GETURLNOTIFY, + FUNCTION_NPP_POSTURL, + FUNCTION_NPP_POSTURLNOTIFY, + FUNCTION_NPP_NEWSTREAM, + FUNCTION_NPP_WRITEREADY, + FUNCTION_NPP_WRITE, + FUNCTION_NPP_DESTROYSTREAM, + FUNCTION_NPP_WRITE_RPC +} TestFunction; + +typedef enum { AD_NONE, AD_BITMAP, AD_DXGI } AsyncDrawing; + +typedef enum { + ACTIVATION_STATE_UNKNOWN, + ACTIVATION_STATE_ACTIVATED, + ACTIVATION_STATE_DEACTIVATED +} ActivationState; + +typedef struct FunctionTable { + TestFunction funcId; + const char* funcName; +} FunctionTable; + +typedef enum { POSTMODE_FRAME, POSTMODE_STREAM } PostMode; + +typedef struct TestNPObject : NPObject { + NPP npp; + DrawMode drawMode; + uint32_t drawColor; // 0xAARRGGBB +} TestNPObject; + +typedef struct _PlatformData PlatformData; + +typedef struct InstanceData { + NPP npp; + NPWindow window; + TestNPObject* scriptableObject; + PlatformData* platformData; + int32_t instanceCountWatchGeneration; + bool lastReportedPrivateModeState; + bool hasWidget; + bool npnNewStream; + bool throwOnNextInvoke; + bool runScriptOnPaint; + bool dontTouchElement; + uint32_t timerID[2]; + bool timerTestResult; + bool invalidateDuringPaint; + bool slowPaint; + bool playingAudio; + bool audioMuted; + int32_t winX; + int32_t winY; + int32_t lastMouseX; + int32_t lastMouseY; + int32_t widthAtLastPaint; + int32_t paintCount; + int32_t writeCount; + int32_t writeReadyCount; + TestFunction testFunction; + TestFunction functionToFail; + NPError failureCode; + NPObject* callOnDestroy; + PostMode postMode; + std::string testUrl; + std::string frame; + std::string timerTestScriptCallback; + std::ostringstream err; + int32_t streamChunkSize; + int32_t streamBufSize; + void* streamBuf; + bool crashOnDestroy; + bool cleanupWidget; + ActivationState topLevelWindowActivationState; + int32_t topLevelWindowActivationEventCount; + ActivationState focusState; + int32_t focusEventCount; + int32_t eventModel; + bool closeStream; + std::string lastKeyText; + bool wantsAllStreams; + int32_t mouseUpEventCount; + int32_t bugMode; + AsyncDrawing asyncDrawing; + NPAsyncSurface* frontBuffer; + NPAsyncSurface* backBuffer; + std::string lastComposition; + void* placeholderWnd; + double cssZoomFactor; +} InstanceData; + +void notifyDidPaint(InstanceData* instanceData); + +#if defined(XP_WIN) +bool setupDxgiSurfaces(NPP npp, InstanceData* instanceData); +void drawDxgiBitmapColor(InstanceData* instanceData); +#endif + +#endif // nptest_h_ diff --git a/dom/plugins/test/testplugin/nptest.rc b/dom/plugins/test/testplugin/nptest.rc new file mode 100644 index 0000000000..948fb846ef --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Plug-in for testing purposes.\x2122 (\x0939\x093f\x0928\x094d\x0926\x0940 \x4e2d\x6587 \x0627\x0644\x0639\x0631\x0628\x064a\x0629)" + VALUE "FileExtents", "tst" + VALUE "FileOpenName", L"Test \x2122 mimetype" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "nptest" + VALUE "MIMEType", "application/x-test" + VALUE "OriginalFilename", "nptest.dll" + VALUE "ProductName", "Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/nptest_droid.cpp b/dom/plugins/test/testplugin/nptest_droid.cpp new file mode 100644 index 0000000000..733f18befe --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_droid.cpp @@ -0,0 +1,80 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2010, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Brad Lassey <blassey@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ +#include "nptest_platform.h" +#include "npapi.h" + +struct _PlatformData {}; + +bool pluginSupportsWindowMode() { return false; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { + printf("NPERR_INCOMPATIBLE_VERSION_ERROR\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; +} + +void pluginInstanceShutdown(InstanceData* instanceData) { + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + instanceData->window = *newWindow; +} + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { + // XXX nothing here yet since we don't support windowed plugins +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { return 0; } + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) { + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error) {} diff --git a/dom/plugins/test/testplugin/nptest_gtk2.cpp b/dom/plugins/test/testplugin/nptest_gtk2.cpp new file mode 100644 index 0000000000..c7779b1d29 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_gtk2.cpp @@ -0,0 +1,707 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * Michael Ventnor <mventnor@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" +#include "npapi.h" +#include <pthread.h> +#include <gdk/gdk.h> +#ifdef MOZ_X11 +# include <gdk/gdkx.h> +# include <X11/extensions/shape.h> +#endif +#include <glib.h> +#include <gtk/gtk.h> +#include <unistd.h> + +#include "mozilla/Assertions.h" +#include "mozilla/IntentionalCrash.h" + +struct _PlatformData { +#ifdef MOZ_X11 + Display* display; + Visual* visual; + Colormap colormap; +#endif + GtkWidget* plug; +}; + +bool pluginSupportsWindowMode() { return false; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { +#ifdef MOZ_X11 + instanceData->platformData = + static_cast<PlatformData*>(NPN_MemAlloc(sizeof(PlatformData))); + if (!instanceData->platformData) return NPERR_OUT_OF_MEMORY_ERROR; + + instanceData->platformData->display = nullptr; + instanceData->platformData->visual = nullptr; + instanceData->platformData->colormap = X11None; + instanceData->platformData->plug = nullptr; + + return NPERR_NO_ERROR; +#else + // we only support X11 here, since thats what the plugin system uses + return NPERR_INCOMPATIBLE_VERSION_ERROR; +#endif +} + +void pluginInstanceShutdown(InstanceData* instanceData) { + if (instanceData->hasWidget) { + Window window = reinterpret_cast<XID>(instanceData->window.window); + + if (window != X11None) { + // This window XID should still be valid. + // See bug 429604 and bug 454756. + XWindowAttributes attributes; + if (!XGetWindowAttributes(instanceData->platformData->display, window, + &attributes)) + g_error("XGetWindowAttributes failed at plugin instance shutdown"); + } + } + + GtkWidget* plug = instanceData->platformData->plug; + if (plug) { + instanceData->platformData->plug = 0; + if (instanceData->cleanupWidget) { + // Default/tidy behavior + gtk_widget_destroy(plug); + } else { + // Flash Player style: let the GtkPlug destroy itself on disconnect. + g_signal_handlers_disconnect_matched(plug, G_SIGNAL_MATCH_DATA, 0, 0, + nullptr, nullptr, instanceData); + } + } + + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; +} + +static void SetCairoRGBA(cairo_t* cairoWindow, uint32_t rgba) { + float b = (rgba & 0xFF) / 255.0; + float g = ((rgba & 0xFF00) >> 8) / 255.0; + float r = ((rgba & 0xFF0000) >> 16) / 255.0; + float a = ((rgba & 0xFF000000) >> 24) / 255.0; + + cairo_set_source_rgba(cairoWindow, r, g, b, a); +} + +static void pluginDrawSolid(InstanceData* instanceData, GdkDrawable* gdkWindow, + int x, int y, int width, int height) { + cairo_t* cairoWindow = gdk_cairo_create(gdkWindow); + + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + + NPRect* clip = &instanceData->window.clipRect; + cairo_rectangle(cairoWindow, clip->left, clip->top, clip->right - clip->left, + clip->bottom - clip->top); + cairo_clip(cairoWindow); + + GdkRectangle windowRect = {x, y, width, height}; + gdk_cairo_rectangle(cairoWindow, &windowRect); + SetCairoRGBA(cairoWindow, instanceData->scriptableObject->drawColor); + + cairo_fill(cairoWindow); + cairo_destroy(cairoWindow); +} + +static void pluginDrawWindow(InstanceData* instanceData, GdkDrawable* gdkWindow, + const GdkRectangle& invalidRect) { + NPWindow& window = instanceData->window; + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + + int x = window.x; + int y = window.y; + int width = window.width; + int height = window.height; + + notifyDidPaint(instanceData); + + if (instanceData->scriptableObject->drawMode == DM_SOLID_COLOR) { + // drawing a solid color for reftests + pluginDrawSolid(instanceData, gdkWindow, invalidRect.x, invalidRect.y, + invalidRect.width, invalidRect.height); + return; + } + + NPP npp = instanceData->npp; + if (!npp) return; + + const char* uaString = NPN_UserAgent(npp); + if (!uaString) return; + + GdkGC* gdkContext = gdk_gc_new(gdkWindow); + if (!gdkContext) return; + + NPRect* clip = &window.clipRect; + GdkRectangle gdkClip = {clip->left, clip->top, clip->right - clip->left, + clip->bottom - clip->top}; + gdk_gc_set_clip_rectangle(gdkContext, &gdkClip); + + // draw a grey background for the plugin frame + GdkColor grey; + grey.red = grey.blue = grey.green = 32767; + gdk_gc_set_rgb_fg_color(gdkContext, &grey); + gdk_draw_rectangle(gdkWindow, gdkContext, TRUE, x, y, width, height); + + // draw a 3-pixel-thick black frame around the plugin + GdkColor black; + black.red = black.green = black.blue = 0; + gdk_gc_set_rgb_fg_color(gdkContext, &black); + gdk_gc_set_line_attributes(gdkContext, 3, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, + GDK_JOIN_MITER); + gdk_draw_rectangle(gdkWindow, gdkContext, FALSE, x + 1, y + 1, width - 3, + height - 3); + + // paint the UA string + PangoContext* pangoContext = gdk_pango_context_get(); + PangoLayout* pangoTextLayout = pango_layout_new(pangoContext); + pango_layout_set_width(pangoTextLayout, (width - 10) * PANGO_SCALE); + pango_layout_set_text(pangoTextLayout, uaString, -1); + gdk_draw_layout(gdkWindow, gdkContext, x + 5, y + 5, pangoTextLayout); + g_object_unref(pangoTextLayout); + + g_object_unref(gdkContext); +} + +static gboolean ExposeWidget(GtkWidget* widget, GdkEventExpose* event, + gpointer user_data) { + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + pluginDrawWindow(instanceData, event->window, event->area); + return TRUE; +} + +static gboolean MotionEvent(GtkWidget* widget, GdkEventMotion* event, + gpointer user_data) { + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + instanceData->lastMouseX = event->x; + instanceData->lastMouseY = event->y; + return TRUE; +} + +static gboolean ButtonEvent(GtkWidget* widget, GdkEventButton* event, + gpointer user_data) { + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + instanceData->lastMouseX = event->x; + instanceData->lastMouseY = event->y; + if (event->type == GDK_BUTTON_RELEASE) { + instanceData->mouseUpEventCount++; + } + return TRUE; +} + +static gboolean DeleteWidget(GtkWidget* widget, GdkEvent* event, + gpointer user_data) { + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + // Some plugins do not expect the plug to be removed from the socket before + // the plugin instance is destroyed. e.g. bug 485125 + if (instanceData->platformData->plug) g_error("plug removed"); // this aborts + + return FALSE; +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + instanceData->window = *newWindow; + +#ifdef MOZ_X11 + NPSetWindowCallbackStruct* ws_info = + static_cast<NPSetWindowCallbackStruct*>(newWindow->ws_info); + instanceData->platformData->display = ws_info->display; + instanceData->platformData->visual = ws_info->visual; + instanceData->platformData->colormap = ws_info->colormap; +#endif +} + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { +#ifdef MOZ_X11 + GtkWidget* oldPlug = instanceData->platformData->plug; + if (oldPlug) { + instanceData->platformData->plug = 0; + gtk_widget_destroy(oldPlug); + } + + GdkNativeWindow nativeWinId = + reinterpret_cast<XID>(instanceData->window.window); + + /* create a GtkPlug container */ + GtkWidget* plug = gtk_plug_new(nativeWinId); + + // Test for bugs 539138 and 561308 + if (!plug->window) g_error("Plug has no window"); // aborts + + /* make sure the widget is capable of receiving focus */ + GTK_WIDGET_SET_FLAGS(GTK_WIDGET(plug), GTK_CAN_FOCUS); + + /* all the events that our widget wants to receive */ + gtk_widget_add_events(plug, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK); + g_signal_connect(plug, "expose-event", G_CALLBACK(ExposeWidget), + instanceData); + g_signal_connect(plug, "motion_notify_event", G_CALLBACK(MotionEvent), + instanceData); + g_signal_connect(plug, "button_press_event", G_CALLBACK(ButtonEvent), + instanceData); + g_signal_connect(plug, "button_release_event", G_CALLBACK(ButtonEvent), + instanceData); + g_signal_connect(plug, "delete-event", G_CALLBACK(DeleteWidget), + instanceData); + gtk_widget_show(plug); + + instanceData->platformData->plug = plug; +#endif +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { +#ifdef MOZ_X11 + XEvent* nsEvent = (XEvent*)event; + + switch (nsEvent->type) { + case GraphicsExpose: { + const XGraphicsExposeEvent& expose = nsEvent->xgraphicsexpose; + NPWindow& window = instanceData->window; + window.window = (void*)(expose.drawable); + + GdkNativeWindow nativeWinId = reinterpret_cast<XID>(window.window); + + GdkDisplay* gdkDisplay = gdk_x11_lookup_xdisplay(expose.display); + if (!gdkDisplay) { + g_warning("Display not opened by GDK"); + return 0; + } + // gdk_pixmap_foreign_new() doesn't check whether a GdkPixmap already + // exists, so check first. + // https://bugzilla.gnome.org/show_bug.cgi?id=590690 + GdkPixmap* gdkDrawable = + GDK_DRAWABLE(gdk_pixmap_lookup_for_display(gdkDisplay, nativeWinId)); + // If there is no existing GdkPixmap or it doesn't have a colormap then + // create our own. + if (gdkDrawable) { + GdkColormap* gdkColormap = gdk_drawable_get_colormap(gdkDrawable); + if (!gdkColormap) { + g_warning("No GdkColormap on GdkPixmap"); + return 0; + } + if (gdk_x11_colormap_get_xcolormap(gdkColormap) != + instanceData->platformData->colormap) { + g_warning("wrong Colormap"); + return 0; + } + if (gdk_x11_visual_get_xvisual(gdk_colormap_get_visual(gdkColormap)) != + instanceData->platformData->visual) { + g_warning("wrong Visual"); + return 0; + } + g_object_ref(gdkDrawable); + } else { + gdkDrawable = GDK_DRAWABLE( + gdk_pixmap_foreign_new_for_display(gdkDisplay, nativeWinId)); + VisualID visualID = instanceData->platformData->visual->visualid; + GdkVisual* gdkVisual = gdk_x11_screen_lookup_visual( + gdk_drawable_get_screen(gdkDrawable), visualID); + GdkColormap* gdkColormap = gdk_x11_colormap_foreign_new( + gdkVisual, instanceData->platformData->colormap); + gdk_drawable_set_colormap(gdkDrawable, gdkColormap); + g_object_unref(gdkColormap); + } + + const NPRect& clip = window.clipRect; + if (expose.x < clip.left || expose.y < clip.top || + expose.x + expose.width > clip.right || + expose.y + expose.height > clip.bottom) { + g_warning( + "expose rectangle (x=%d,y=%d,w=%d,h=%d) not in clip rectangle " + "(l=%d,t=%d,r=%d,b=%d)", + expose.x, expose.y, expose.width, expose.height, clip.left, + clip.top, clip.right, clip.bottom); + return 0; + } + if (expose.x < window.x || expose.y < window.y || + expose.x + expose.width > window.x + int32_t(window.width) || + expose.y + expose.height > window.y + int32_t(window.height)) { + g_warning( + "expose rectangle (x=%d,y=%d,w=%d,h=%d) not in plugin rectangle " + "(x=%d,y=%d,w=%d,h=%d)", + expose.x, expose.y, expose.width, expose.height, window.x, window.y, + window.width, window.height); + return 0; + } + + GdkRectangle invalidRect = {expose.x, expose.y, expose.width, + expose.height}; + pluginDrawWindow(instanceData, gdkDrawable, invalidRect); + g_object_unref(gdkDrawable); + break; + } + case MotionNotify: { + XMotionEvent* motion = &nsEvent->xmotion; + instanceData->lastMouseX = motion->x; + instanceData->lastMouseY = motion->y; + break; + } + case ButtonPress: + case ButtonRelease: { + XButtonEvent* button = &nsEvent->xbutton; + instanceData->lastMouseX = button->x; + instanceData->lastMouseY = button->y; + if (nsEvent->type == ButtonRelease) { + instanceData->mouseUpEventCount++; + } + break; + } + default: + break; + } +#endif + + return 0; +} + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + if (!instanceData->hasWidget) return NPTEST_INT32_ERROR; + + GtkWidget* plug = instanceData->platformData->plug; + if (!plug) return NPTEST_INT32_ERROR; + GdkWindow* plugWnd = plug->window; + if (!plugWnd) return NPTEST_INT32_ERROR; + + GdkWindow* toplevelGdk = 0; +#ifdef MOZ_X11 + Window toplevel = 0; + NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel); + if (!toplevel) return NPTEST_INT32_ERROR; + toplevelGdk = gdk_window_foreign_new(toplevel); +#endif + if (!toplevelGdk) return NPTEST_INT32_ERROR; + + GdkRectangle toplevelFrameExtents; + gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents); + g_object_unref(toplevelGdk); + + gint pluginWidth, pluginHeight; + gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &pluginWidth, &pluginHeight); + gint pluginOriginX, pluginOriginY; + gdk_window_get_origin(plugWnd, &pluginOriginX, &pluginOriginY); + gint pluginX = pluginOriginX - toplevelFrameExtents.x; + gint pluginY = pluginOriginY - toplevelFrameExtents.y; + + switch (edge) { + case EDGE_LEFT: + return pluginX; + case EDGE_TOP: + return pluginY; + case EDGE_RIGHT: + return pluginX + pluginWidth; + case EDGE_BOTTOM: + return pluginY + pluginHeight; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +#ifdef MOZ_X11 +static void intersectWithShapeRects(Display* display, Window window, int kind, + GdkRegion* region) { + int count = -1, order; + XRectangle* shapeRects = + XShapeGetRectangles(display, window, kind, &count, &order); + // The documentation says that shapeRects will be nullptr when the + // extension is not supported. Unfortunately XShapeGetRectangles + // also returns nullptr when the region is empty, so we can't treat + // nullptr as failure. I hope this way is OK. + if (count < 0) return; + + GdkRegion* shapeRegion = gdk_region_new(); + if (!shapeRegion) { + XFree(shapeRects); + return; + } + + for (int i = 0; i < count; ++i) { + XRectangle* r = &shapeRects[i]; + GdkRectangle rect = {r->x, r->y, r->width, r->height}; + gdk_region_union_with_rect(shapeRegion, &rect); + } + XFree(shapeRects); + + gdk_region_intersect(region, shapeRegion); + gdk_region_destroy(shapeRegion); +} +#endif + +static GdkRegion* computeClipRegion(InstanceData* instanceData) { + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + if (!instanceData->hasWidget) return 0; + + GtkWidget* plug = instanceData->platformData->plug; + if (!plug) return 0; + GdkWindow* plugWnd = plug->window; + if (!plugWnd) return 0; + + gint plugWidth, plugHeight; + gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &plugWidth, &plugHeight); + GdkRectangle pluginRect = {0, 0, plugWidth, plugHeight}; + GdkRegion* region = gdk_region_rectangle(&pluginRect); + if (!region) return 0; + + int pluginX = 0, pluginY = 0; + +#ifdef MOZ_X11 + Display* display = GDK_WINDOW_XDISPLAY(plugWnd); + Window window = GDK_WINDOW_XWINDOW(plugWnd); + + Window toplevel = 0; + NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel); + if (!toplevel) return 0; + + for (;;) { + Window root; + int x, y; + unsigned int width, height, border_width, depth; + if (!XGetGeometry(display, window, &root, &x, &y, &width, &height, + &border_width, &depth)) { + gdk_region_destroy(region); + return 0; + } + + GdkRectangle windowRect = {0, 0, static_cast<gint>(width), + static_cast<gint>(height)}; + GdkRegion* windowRgn = gdk_region_rectangle(&windowRect); + if (!windowRgn) { + gdk_region_destroy(region); + return 0; + } + intersectWithShapeRects(display, window, ShapeBounding, windowRgn); + intersectWithShapeRects(display, window, ShapeClip, windowRgn); + gdk_region_offset(windowRgn, -pluginX, -pluginY); + gdk_region_intersect(region, windowRgn); + gdk_region_destroy(windowRgn); + + // Stop now if we've reached the toplevel. Stopping here means + // clipping performed by the toplevel window is taken into account. + if (window == toplevel) break; + + Window parent; + Window* children; + unsigned int nchildren; + if (!XQueryTree(display, window, &root, &parent, &children, &nchildren)) { + gdk_region_destroy(region); + return 0; + } + XFree(children); + + pluginX += x; + pluginY += y; + + window = parent; + } +#endif + // pluginX and pluginY are now relative to the toplevel. Make them + // relative to the window frame top-left. + GdkWindow* toplevelGdk = gdk_window_foreign_new(window); + if (!toplevelGdk) return 0; + GdkRectangle toplevelFrameExtents; + gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents); + gint toplevelOriginX, toplevelOriginY; + gdk_window_get_origin(toplevelGdk, &toplevelOriginX, &toplevelOriginY); + g_object_unref(toplevelGdk); + + pluginX += toplevelOriginX - toplevelFrameExtents.x; + pluginY += toplevelOriginY - toplevelFrameExtents.y; + + gdk_region_offset(region, pluginX, pluginY); + return region; +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { + GdkRegion* region = computeClipRegion(instanceData); + if (!region) return NPTEST_INT32_ERROR; + + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(region, &rects, &nrects); + gdk_region_destroy(region); + g_free(rects); + return nrects; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) { + GdkRegion* region = computeClipRegion(instanceData); + if (!region) return NPTEST_INT32_ERROR; + + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(region, &rects, &nrects); + gdk_region_destroy(region); + if (rectIndex >= nrects) { + g_free(rects); + return NPTEST_INT32_ERROR; + } + + GdkRectangle rect = rects[rectIndex]; + g_free(rects); + + switch (edge) { + case EDGE_LEFT: + return rect.x; + case EDGE_TOP: + return rect.y; + case EDGE_RIGHT: + return rect.x + rect.width; + case EDGE_BOTTOM: + return rect.y + rect.height; + } + return NPTEST_INT32_ERROR; +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error) {} + +std::string pluginGetClipboardText(InstanceData* instanceData) { + GtkClipboard* cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + // XXX this is a BAD WAY to interact with GtkClipboard. We use this + // deprecated interface only to test nested event loop handling. + gchar* text = gtk_clipboard_wait_for_text(cb); + std::string retText = text ? text : ""; + + g_free(text); + + return retText; +} + +//----------------------------------------------------------------------------- +// NB: this test is quite gross in that it's not only +// nondeterministic, but dependent on the guts of the nested glib +// event loop handling code in PluginModule. We first sleep long +// enough to make sure that the "detection timer" will be pending when +// we enter the nested glib loop, then similarly for the "process browser +// events" timer. Then we "schedule" the crasher thread to run at about the +// same time we expect that the PluginModule "process browser events" task +// will run. If all goes well, the plugin process will crash and generate the +// XPCOM "plugin crashed" task, and the browser will run that task while still +// in the "process some events" loop. + +static void* CrasherThread(void* data) { + // Give the parent thread a chance to send the message. + usleep(200); + + // Exit (without running atexit hooks) rather than crashing with a signal + // so as to make timing more reliable. The process terminates immediately + // rather than waiting for a thread in the parent process to attach and + // generate a minidump. + _exit(1); + + // not reached + return (nullptr); +} + +bool pluginCrashInNestedLoop(InstanceData* instanceData) { + // wait at least long enough for nested loop detector task to be pending ... + sleep(1); + + // Run the nested loop detector by processing all events that are waiting. + bool found_event = false; + while (g_main_context_iteration(nullptr, FALSE)) { + found_event = true; + } + if (!found_event) { + g_warning("DetectNestedEventLoop did not fire"); + return true; // trigger a test failure + } + + // wait at least long enough for the "process browser events" task to be + // pending ... + sleep(1); + + // we'll be crashing soon, note that fact now to avoid messing with + // timing too much + mozilla::NoteIntentionalCrash("plugin"); + + // schedule the crasher thread ... + pthread_t crasherThread; + if (0 != pthread_create(&crasherThread, nullptr, CrasherThread, nullptr)) { + g_warning("Failed to create thread"); + return true; // trigger a test failure + } + + // .. and hope it crashes at about the same time as the "process browser + // events" task (that should run in this loop) is being processed in the + // parent. + found_event = false; + while (g_main_context_iteration(nullptr, FALSE)) { + found_event = true; + } + if (found_event) { + g_warning("Should have crashed in ProcessBrowserEvents"); + } else { + g_warning("ProcessBrowserEvents did not fire"); + } + + // if we get here without crashing, then we'll trigger a test failure + return true; +} + +bool pluginTriggerXError(InstanceData* instanceData) { + mozilla::NoteIntentionalCrash("plugin"); + int num_prop_return; + // Window parameter is None to generate a fatal error, and this function + // should not return. + XListProperties(GDK_DISPLAY(), X11None, &num_prop_return); + + // if we get here without crashing, then we'll trigger a test failure + return true; +} + +static int SleepThenDie(Display* display) { + mozilla::NoteIntentionalCrash("plugin"); + fprintf(stderr, "[testplugin:%d] SleepThenDie: sleeping\n", getpid()); + sleep(1); + + fprintf(stderr, "[testplugin:%d] SleepThenDie: dying\n", getpid()); + _exit(1); +} + +bool pluginDestroySharedGfxStuff(InstanceData* instanceData) { + // Closing the X socket results in the gdk error handler being + // invoked, which exit()s us. We want to give the parent process a + // little while to do whatever it wanted to do, so steal the IO + // handler from gdk and set up our own that delays seppuku. + XSetIOErrorHandler(SleepThenDie); + close(ConnectionNumber(GDK_DISPLAY())); + return true; +} diff --git a/dom/plugins/test/testplugin/nptest_macosx.mm b/dom/plugins/test/testplugin/nptest_macosx.mm new file mode 100644 index 0000000000..191b8f5c6f --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_macosx.mm @@ -0,0 +1,275 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" +#include "nsAlgorithm.h" +#include <CoreServices/CoreServices.h> +#include <algorithm> + +bool pluginSupportsWindowMode() { return false; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { + NPP npp = instanceData->npp; + + NPBool supportsCoreGraphics = false; + if ((NPN_GetValue(npp, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) == NPERR_NO_ERROR) && + supportsCoreGraphics) { + NPN_SetValue(npp, NPPVpluginDrawingModel, (void*)NPDrawingModelCoreGraphics); + } else { + printf("CoreGraphics drawing model not supported, can't create a plugin instance.\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + NPBool supportsCocoaEvents = false; + if ((NPN_GetValue(npp, NPNVsupportsCocoaBool, &supportsCocoaEvents) == NPERR_NO_ERROR) && + supportsCocoaEvents) { + NPN_SetValue(npp, NPPVpluginEventModel, (void*)NPEventModelCocoa); + instanceData->eventModel = NPEventModelCocoa; + } else { + printf("Cocoa event model not supported, can't create a plugin instance.\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + return NPERR_NO_ERROR; +} + +void pluginInstanceShutdown(InstanceData* instanceData) {} + +static bool RectEquals(const NPRect& r1, const NPRect& r2) { + return r1.left == r2.left && r1.top == r2.top && r1.right == r2.right && r1.bottom == r2.bottom; +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + // Ugh. Due to a terrible Gecko bug, we have to ignore position changes + // when the clip rect doesn't change; the position can be wrong + // when set by a path other than nsPluginFrame::FixUpPluginWindow. + int32_t oldX = instanceData->window.x; + int32_t oldY = instanceData->window.y; + bool clipChanged = !RectEquals(instanceData->window.clipRect, newWindow->clipRect); + instanceData->window = *newWindow; + if (!clipChanged) { + instanceData->window.x = oldX; + instanceData->window.y = oldY; + } +} + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { + // Should never be called since we don't support window mode +} + +static void GetColorsFromRGBA(uint32_t rgba, float* r, float* g, float* b, float* a) { + *b = (rgba & 0xFF) / 255.0; + *g = ((rgba & 0xFF00) >> 8) / 255.0; + *r = ((rgba & 0xFF0000) >> 16) / 255.0; + *a = ((rgba & 0xFF000000) >> 24) / 255.0; +} + +static void pluginDraw(InstanceData* instanceData, NPCocoaEvent* event) { + if (!instanceData) return; + + notifyDidPaint(instanceData); + + NPP npp = instanceData->npp; + if (!npp) return; + + const char* uaString = NPN_UserAgent(npp); + if (!uaString) return; + + NPWindow window = instanceData->window; + + CGContextRef cgContext = event->data.draw.context; + + float windowWidth = window.width; + float windowHeight = window.height; + + switch (instanceData->scriptableObject->drawMode) { + case DM_DEFAULT: { + CFStringRef uaCFString = + CFStringCreateWithCString(kCFAllocatorDefault, uaString, kCFStringEncodingASCII); + // save the cgcontext gstate + CGContextSaveGState(cgContext); + + // we get a flipped context + CGContextTranslateCTM(cgContext, 0.0, windowHeight); + CGContextScaleCTM(cgContext, 1.0, -1.0); + + // draw a gray background for the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + CGContextSetGrayFillColor(cgContext, 0.5, 1.0); + CGContextDrawPath(cgContext, kCGPathFill); + + // draw a black frame around the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + CGContextSetGrayStrokeColor(cgContext, 0.0, 1.0); + CGContextSetLineWidth(cgContext, 6.0); + CGContextStrokePath(cgContext); + + // draw the UA string using Core Text + CGContextSetTextMatrix(cgContext, CGAffineTransformIdentity); + + // Initialize a rectangular path. + CGMutablePathRef path = CGPathCreateMutable(); + CGRect bounds = CGRectMake(10.0, 10.0, std::max(0.0, windowWidth - 20.0), + std::max(0.0, windowHeight - 20.0)); + CGPathAddRect(path, NULL, bounds); + + // Initialize an attributed string. + CFMutableAttributedStringRef attrString = + CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), uaCFString); + + // Create a color and add it as an attribute to the string. + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + CGFloat components[] = {0.0, 0.0, 0.0, 1.0}; + CGColorRef red = CGColorCreate(rgbColorSpace, components); + CGColorSpaceRelease(rgbColorSpace); + CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 50), + kCTForegroundColorAttributeName, red); + + // Create the framesetter with the attributed string. + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString); + CFRelease(attrString); + + // Create the frame and draw it into the graphics context + CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); + CFRelease(framesetter); + if (frame) { + CTFrameDraw(frame, cgContext); + CFRelease(frame); + } + + // restore the cgcontext gstate + CGContextRestoreGState(cgContext); + break; + } + case DM_SOLID_COLOR: { + // save the cgcontext gstate + CGContextSaveGState(cgContext); + + // we get a flipped context + CGContextTranslateCTM(cgContext, 0.0, windowHeight); + CGContextScaleCTM(cgContext, 1.0, -1.0); + + // draw a solid background for the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + + float r, g, b, a; + GetColorsFromRGBA(instanceData->scriptableObject->drawColor, &r, &g, &b, &a); + CGContextSetRGBFillColor(cgContext, r, g, b, a); + CGContextDrawPath(cgContext, kCGPathFill); + + // restore the cgcontext gstate + CGContextRestoreGState(cgContext); + break; + } + } +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { + NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event; + if (!cocoaEvent) return kNPEventNotHandled; + + switch (cocoaEvent->type) { + case NPCocoaEventDrawRect: + pluginDraw(instanceData, cocoaEvent); + break; + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseDragged: + instanceData->lastMouseX = (int32_t)cocoaEvent->data.mouse.pluginX; + instanceData->lastMouseY = (int32_t)cocoaEvent->data.mouse.pluginY; + if (cocoaEvent->type == NPCocoaEventMouseUp) { + instanceData->mouseUpEventCount++; + } + break; + case NPCocoaEventWindowFocusChanged: + instanceData->topLevelWindowActivationState = cocoaEvent->data.focus.hasFocus + ? ACTIVATION_STATE_ACTIVATED + : ACTIVATION_STATE_DEACTIVATED; + instanceData->topLevelWindowActivationEventCount = + instanceData->topLevelWindowActivationEventCount + 1; + break; + case NPCocoaEventFocusChanged: + instanceData->focusState = cocoaEvent->data.focus.hasFocus ? ACTIVATION_STATE_ACTIVATED + : ACTIVATION_STATE_DEACTIVATED; + instanceData->focusEventCount = instanceData->focusEventCount + 1; + break; + default: + return kNPEventNotHandled; + } + + return kNPEventHandled; +} + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + NPWindow* w = &instanceData->window; + switch (edge) { + case EDGE_LEFT: + return w->x; + case EDGE_TOP: + return w->y; + case EDGE_RIGHT: + return w->x + w->width; + case EDGE_BOTTOM: + return w->y + w->height; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { return 1; } + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, int32_t rectIndex, RectEdge edge) { + if (rectIndex != 0) return NPTEST_INT32_ERROR; + + // We have to add the Cocoa titlebar height here since the clip rect + // is being returned relative to that + static const int COCOA_TITLEBAR_HEIGHT = 22; + + NPWindow* w = &instanceData->window; + switch (edge) { + case EDGE_LEFT: + return w->clipRect.left; + case EDGE_TOP: + return w->clipRect.top + COCOA_TITLEBAR_HEIGHT; + case EDGE_RIGHT: + return w->clipRect.right; + case EDGE_BOTTOM: + return w->clipRect.bottom + COCOA_TITLEBAR_HEIGHT; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, std::string& error) {} diff --git a/dom/plugins/test/testplugin/nptest_name.cpp b/dom/plugins/test/testplugin/nptest_name.cpp new file mode 100644 index 0000000000..3fc6f1fb3c --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_name.cpp @@ -0,0 +1,8 @@ +const char* sPluginName = "Test Plug-in"; +const char* sPluginDescription = + "Plug-in for testing purposes.\xE2\x84\xA2 " + "(\xe0\xa4\xb9\xe0\xa4\xbf\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xa6\xe0\xa5\x80 " + "\xe4\xb8\xad\xe6\x96\x87 " + "\xd8\xa7\xd9\x84\xd8\xb9\xd8\xb1\xd8\xa8\xd9\x8a\xd8\xa9)"; +const char* sMimeDescription = + "application/x-test:tst:Test \xE2\x84\xA2 mimetype"; diff --git a/dom/plugins/test/testplugin/nptest_platform.h b/dom/plugins/test/testplugin/nptest_platform.h new file mode 100644 index 0000000000..4b9584932d --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_platform.h @@ -0,0 +1,155 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_platform_h_ +#define nptest_platform_h_ + +#include "nptest.h" + +/** + * Returns true if the plugin supports windowed mode + */ +bool pluginSupportsWindowMode(); + +/** + * Returns true if the plugin supports windowless mode. At least one of + * "pluginSupportsWindowMode" and "pluginSupportsWindowlessMode" must + * return true. + */ +bool pluginSupportsWindowlessMode(); + +/** + * Initialize the plugin instance. Returning an error here will cause the + * plugin instantiation to fail. + */ +NPError pluginInstanceInit(InstanceData* instanceData); + +/** + * Shutdown the plugin instance. + */ +void pluginInstanceShutdown(InstanceData* instanceData); + +/** + * Set the instanceData's window to newWindow. + */ +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow); + +/** + * Initialize the window for a windowed plugin. oldWindow is the old + * native window value. This will never be called for windowless plugins. + */ +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow); + +/** + * Handle an event for a windowless plugin. (Windowed plugins are + * responsible for listening for their own events.) + */ +int16_t pluginHandleEvent(InstanceData* instanceData, void* event); + +enum RectEdge { EDGE_LEFT = 0, EDGE_TOP = 1, EDGE_RIGHT = 2, EDGE_BOTTOM = 3 }; + +enum { NPTEST_INT32_ERROR = 0x7FFFFFFF }; + +/** + * Return the coordinate of the given edge of the plugin's area, relative + * to the top-left corner of the toplevel window containing the plugin, + * including window decorations. Only works for window-mode plugins + * and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge); + +/** + * Return the number of rectangles in the plugin's clip region. Only + * works for window-mode plugins and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData); + +/** + * Return the coordinate of the given edge of a rectangle in the plugin's + * clip region, relative to the top-left corner of the toplevel window + * containing the plugin, including window decorations. Only works for + * window-mode plugins and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge); + +/** + * Check that the platform-specific plugin state is internally consistent. + * Just return if everything is OK, otherwise append error messages + * to 'error' separated by \n. + */ +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error); + +/** + * Get the current clipboard item as text. If the clipboard item + * isn't text, the returned value is undefined. + */ +std::string pluginGetClipboardText(InstanceData* instanceData); + +/** + * Crash while in a nested event loop. The goal is to catch the + * browser processing the XPCOM event generated from the plugin's + * crash while other plugin code is still on the stack. + * See https://bugzilla.mozilla.org/show_bug.cgi?id=550026. + */ +bool pluginCrashInNestedLoop(InstanceData* instanceData); + +/** + * Generate an X11 protocol error to terminate the plugin process. + */ +bool pluginTriggerXError(InstanceData* instanceData); + +/** + * Destroy gfx things that might be shared with the parent process + * when we're run out-of-process. It's not expected that this + * function will be called when the test plugin is loaded in-process, + * and bad things will happen if it is called. + * + * This call leaves the plugin subprocess in an undefined state. It + * must not be used after this call or weird things will happen. + */ +bool pluginDestroySharedGfxStuff(InstanceData* instanceData); + +/** + * Checks to see if the native widget is marked as visible. Works + * in e10s and non-e10s. Useful in testing e10s related compositor + * plugin window functionality. Supported on Windows. + */ +bool pluginNativeWidgetIsVisible(InstanceData* instanceData); + +#endif // nptest_platform_h_ diff --git a/dom/plugins/test/testplugin/nptest_utils.cpp b/dom/plugins/test/testplugin/nptest_utils.cpp new file mode 100644 index 0000000000..39a3f4b7b7 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_utils.cpp @@ -0,0 +1,100 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_utils.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +NPUTF8* createCStringFromNPVariant(const NPVariant* variant) { + size_t length = NPVARIANT_TO_STRING(*variant).UTF8Length; + NPUTF8* result = (NPUTF8*)malloc(length + 1); + memcpy(result, NPVARIANT_TO_STRING(*variant).UTF8Characters, length); + result[length] = '\0'; + return result; +} + +NPIdentifier variantToIdentifier(NPVariant variant) { + if (NPVARIANT_IS_STRING(variant)) + return stringVariantToIdentifier(variant); + else if (NPVARIANT_IS_INT32(variant)) + return int32VariantToIdentifier(variant); + else if (NPVARIANT_IS_DOUBLE(variant)) + return doubleVariantToIdentifier(variant); + return 0; +} + +NPIdentifier stringVariantToIdentifier(NPVariant variant) { + assert(NPVARIANT_IS_STRING(variant)); + NPUTF8* utf8String = createCStringFromNPVariant(&variant); + NPIdentifier identifier = NPN_GetStringIdentifier(utf8String); + free(utf8String); + return identifier; +} + +NPIdentifier int32VariantToIdentifier(NPVariant variant) { + assert(NPVARIANT_IS_INT32(variant)); + int32_t integer = NPVARIANT_TO_INT32(variant); + return NPN_GetIntIdentifier(integer); +} + +NPIdentifier doubleVariantToIdentifier(NPVariant variant) { + assert(NPVARIANT_IS_DOUBLE(variant)); + double value = NPVARIANT_TO_DOUBLE(variant); + // sadly there is no "getdoubleidentifier" + int32_t integer = static_cast<int32_t>(value); + return NPN_GetIntIdentifier(integer); +} + +/* + * Parse a color in hex format, #AARRGGBB or AARRGGBB. + */ +uint32_t parseHexColor(const char* color, int len) { + uint8_t bgra[4] = {0, 0, 0, 0xFF}; + int i = 0; + + // Ignore unsupported formats. + if (len != 9 && len != 8) return 0; + + // start from the right and work to the left + while (len >= 2) { // we have at least #AA or AA left. + char byte[3]; + // parse two hex digits + byte[0] = color[len - 2]; + byte[1] = color[len - 1]; + byte[2] = '\0'; + + bgra[i] = (uint8_t)(strtoul(byte, nullptr, 16) & 0xFF); + i++; + len -= 2; + } + return (bgra[3] << 24) | (bgra[2] << 16) | (bgra[1] << 8) | bgra[0]; +} diff --git a/dom/plugins/test/testplugin/nptest_utils.h b/dom/plugins/test/testplugin/nptest_utils.h new file mode 100644 index 0000000000..cb2ca5a803 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_utils.h @@ -0,0 +1,45 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_utils_h_ +#define nptest_utils_h_ + +#include "nptest.h" + +NPUTF8* createCStringFromNPVariant(const NPVariant* variant); + +NPIdentifier variantToIdentifier(NPVariant variant); +NPIdentifier stringVariantToIdentifier(NPVariant variant); +NPIdentifier int32VariantToIdentifier(NPVariant variant); +NPIdentifier doubleVariantToIdentifier(NPVariant variant); + +uint32_t parseHexColor(const char* color, int len); + +#endif // nptest_utils_h_ diff --git a/dom/plugins/test/testplugin/nptest_windows.cpp b/dom/plugins/test/testplugin/nptest_windows.cpp new file mode 100644 index 0000000000..0490d23367 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_windows.cpp @@ -0,0 +1,797 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * Jim Mathies <jmathies@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" + +#include <windows.h> +#include <windowsx.h> +#include <stdio.h> + +#include <d3d10_1.h> +#include <d2d1.h> + +void SetSubclass(HWND hWnd, InstanceData* instanceData); +void ClearSubclass(HWND hWnd); +LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam); + +struct _PlatformData { + HWND childWindow; + IDXGIAdapter1* adapter; + ID3D10Device1* device; + ID3D10Texture2D* frontBuffer; + ID3D10Texture2D* backBuffer; + ID2D1Factory* d2d1Factory; +}; + +bool pluginSupportsWindowMode() { return true; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { + instanceData->platformData = + static_cast<PlatformData*>(NPN_MemAlloc(sizeof(PlatformData))); + if (!instanceData->platformData) return NPERR_OUT_OF_MEMORY_ERROR; + + instanceData->platformData->childWindow = nullptr; + instanceData->platformData->device = nullptr; + instanceData->platformData->frontBuffer = nullptr; + instanceData->platformData->backBuffer = nullptr; + instanceData->platformData->adapter = nullptr; + instanceData->platformData->d2d1Factory = nullptr; + return NPERR_NO_ERROR; +} + +static inline bool openSharedTex2D(ID3D10Device* device, HANDLE handle, + ID3D10Texture2D** out) { + HRESULT hr = device->OpenSharedResource(handle, __uuidof(ID3D10Texture2D), + (void**)out); + if (FAILED(hr) || !*out) { + return false; + } + return true; +} + +// This is overloaded in d2d1.h so we can't use decltype(). +typedef HRESULT(WINAPI* D2D1CreateFactoryFunc)( + D2D1_FACTORY_TYPE factoryType, REFIID iid, + CONST D2D1_FACTORY_OPTIONS* pFactoryOptions, void** factory); + +static IDXGIAdapter1* FindDXGIAdapter(NPP npp, IDXGIFactory1* factory) { + DXGI_ADAPTER_DESC preferred; + if (NPN_GetValue(npp, NPNVpreferredDXGIAdapter, &preferred) != + NPERR_NO_ERROR) { + return nullptr; + } + + UINT index = 0; + for (;;) { + IDXGIAdapter1* adapter = nullptr; + if (FAILED(factory->EnumAdapters1(index, &adapter)) || !adapter) { + return nullptr; + } + + DXGI_ADAPTER_DESC desc; + if (SUCCEEDED(adapter->GetDesc(&desc)) && + desc.AdapterLuid.LowPart == preferred.AdapterLuid.LowPart && + desc.AdapterLuid.HighPart == preferred.AdapterLuid.HighPart && + desc.VendorId == preferred.VendorId && + desc.DeviceId == preferred.DeviceId) { + return adapter; + } + + adapter->Release(); + index++; + } +} + +// Note: we leak modules since we need them anyway. +bool setupDxgiSurfaces(NPP npp, InstanceData* instanceData) { + HMODULE dxgi = LoadLibraryA("dxgi.dll"); + if (!dxgi) { + return false; + } + decltype(CreateDXGIFactory1)* createDXGIFactory1 = + (decltype(CreateDXGIFactory1)*)GetProcAddress(dxgi, "CreateDXGIFactory1"); + if (!createDXGIFactory1) { + return false; + } + + IDXGIFactory1* factory1 = nullptr; + HRESULT hr = createDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory1); + if (FAILED(hr) || !factory1) { + return false; + } + + instanceData->platformData->adapter = FindDXGIAdapter(npp, factory1); + if (!instanceData->platformData->adapter) { + return false; + } + + HMODULE d3d10 = LoadLibraryA("d3d10_1.dll"); + if (!d3d10) { + return false; + } + + decltype(D3D10CreateDevice1)* createDevice = + (decltype(D3D10CreateDevice1)*)GetProcAddress(d3d10, + "D3D10CreateDevice1"); + if (!createDevice) { + return false; + } + + hr = createDevice( + instanceData->platformData->adapter, D3D10_DRIVER_TYPE_HARDWARE, nullptr, + D3D10_CREATE_DEVICE_BGRA_SUPPORT | + D3D10_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS, + D3D10_FEATURE_LEVEL_10_1, D3D10_1_SDK_VERSION, + &instanceData->platformData->device); + if (FAILED(hr) || !instanceData->platformData->device) { + return false; + } + + if (!openSharedTex2D(instanceData->platformData->device, + instanceData->frontBuffer->sharedHandle, + &instanceData->platformData->frontBuffer)) { + return false; + } + if (!openSharedTex2D(instanceData->platformData->device, + instanceData->backBuffer->sharedHandle, + &instanceData->platformData->backBuffer)) { + return false; + } + + HMODULE d2d1 = LoadLibraryA("D2d1.dll"); + if (!d2d1) { + return false; + } + auto d2d1CreateFactory = + (D2D1CreateFactoryFunc)GetProcAddress(d2d1, "D2D1CreateFactory"); + if (!d2d1CreateFactory) { + return false; + } + + D2D1_FACTORY_OPTIONS options; + options.debugLevel = D2D1_DEBUG_LEVEL_NONE; + + hr = d2d1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, + __uuidof(ID2D1Factory), &options, + (void**)&instanceData->platformData->d2d1Factory); + if (FAILED(hr) || !instanceData->platformData->d2d1Factory) { + return false; + } + + return true; +} + +void drawDxgiBitmapColor(InstanceData* instanceData) { + NPP npp = instanceData->npp; + + HRESULT hr; + + IDXGISurface* surface = nullptr; + hr = instanceData->platformData->backBuffer->QueryInterface( + __uuidof(IDXGISurface), (void**)&surface); + if (FAILED(hr) || !surface) { + return; + } + + D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED)); + + ID2D1RenderTarget* target = nullptr; + hr = instanceData->platformData->d2d1Factory->CreateDxgiSurfaceRenderTarget( + surface, &props, &target); + if (FAILED(hr) || !target) { + surface->Release(); + return; + } + + IDXGIKeyedMutex* mutex = nullptr; + hr = instanceData->platformData->backBuffer->QueryInterface( + __uuidof(IDXGIKeyedMutex), (void**)&mutex); + if (mutex) { + mutex->AcquireSync(0, 0); + } + + target->BeginDraw(); + + unsigned char subpixels[4]; + memcpy(subpixels, &instanceData->scriptableObject->drawColor, + sizeof(subpixels)); + + auto rect = D2D1::RectF(0, 0, instanceData->backBuffer->size.width, + instanceData->backBuffer->size.height); + auto color = D2D1::ColorF(float(subpixels[3] * subpixels[2]) / 0xFF, + float(subpixels[3] * subpixels[1]) / 0xFF, + float(subpixels[3] * subpixels[0]) / 0xFF, + float(subpixels[3]) / 0xff); + + ID2D1SolidColorBrush* brush = nullptr; + hr = target->CreateSolidColorBrush(color, &brush); + if (SUCCEEDED(hr) && brush) { + target->FillRectangle(rect, brush); + brush->Release(); + brush = nullptr; + } + hr = target->EndDraw(); + + if (mutex) { + mutex->ReleaseSync(0); + mutex->Release(); + mutex = nullptr; + } + + target->Release(); + surface->Release(); + target = nullptr; + surface = nullptr; + + NPN_SetCurrentAsyncSurface(npp, instanceData->backBuffer, NULL); + std::swap(instanceData->backBuffer, instanceData->frontBuffer); + std::swap(instanceData->platformData->backBuffer, + instanceData->platformData->frontBuffer); +} + +void pluginInstanceShutdown(InstanceData* instanceData) { + PlatformData* pd = instanceData->platformData; + if (pd->frontBuffer) { + pd->frontBuffer->Release(); + } + if (pd->backBuffer) { + pd->backBuffer->Release(); + } + if (pd->d2d1Factory) { + pd->d2d1Factory->Release(); + } + if (pd->device) { + pd->device->Release(); + } + if (pd->adapter) { + pd->adapter->Release(); + } + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; + ClearSubclass((HWND)instanceData->window.window); +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + instanceData->window = *newWindow; +} + +#define CHILD_WIDGET_SIZE 10 + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { + HWND hWnd = (HWND)instanceData->window.window; + if (oldWindow) { + // chrashtests/539897-1.html excercises this code + HWND hWndOld = (HWND)oldWindow; + ClearSubclass(hWndOld); + if (instanceData->platformData->childWindow) { + ::DestroyWindow(instanceData->platformData->childWindow); + } + } + + SetSubclass(hWnd, instanceData); + + instanceData->platformData->childWindow = ::CreateWindowW( + L"SCROLLBAR", L"Dummy child window", WS_CHILD, 0, 0, CHILD_WIDGET_SIZE, + CHILD_WIDGET_SIZE, hWnd, nullptr, nullptr, nullptr); +} + +static void drawToDC(InstanceData* instanceData, HDC dc, int x, int y, + int width, int height) { + switch (instanceData->scriptableObject->drawMode) { + case DM_DEFAULT: { + const RECT fill = {x, y, x + width, y + height}; + + int oldBkMode = ::SetBkMode(dc, TRANSPARENT); + HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); + if (brush) { + ::FillRect(dc, &fill, brush); + ::DeleteObject(brush); + } + if (width > 6 && height > 6) { + brush = ::CreateSolidBrush(RGB(192, 192, 192)); + if (brush) { + RECT inset = {x + 3, y + 3, x + width - 3, y + height - 3}; + ::FillRect(dc, &inset, brush); + ::DeleteObject(brush); + } + } + + const char* uaString = NPN_UserAgent(instanceData->npp); + if (uaString && width > 10 && height > 10) { + HFONT font = ::CreateFontA(20, 0, 0, 0, 400, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, 5, // CLEARTYPE_QUALITY + DEFAULT_PITCH, "Arial"); + if (font) { + HFONT oldFont = (HFONT)::SelectObject(dc, font); + RECT inset = {x + 5, y + 5, x + width - 5, y + height - 5}; + ::DrawTextA(dc, uaString, -1, &inset, + DT_LEFT | DT_TOP | DT_NOPREFIX | DT_WORDBREAK); + ::SelectObject(dc, oldFont); + ::DeleteObject(font); + } + } + ::SetBkMode(dc, oldBkMode); + } break; + + case DM_SOLID_COLOR: { + HDC offscreenDC = ::CreateCompatibleDC(dc); + if (!offscreenDC) return; + + const BITMAPV4HEADER bitmapheader = { + sizeof(BITMAPV4HEADER), + width, + height, + 1, // planes + 32, // bits + BI_BITFIELDS, + 0, // unused size + 0, + 0, // unused metrics + 0, + 0, // unused colors used/important + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000, // ARGB masks + }; + uint32_t* pixelData; + HBITMAP offscreenBitmap = ::CreateDIBSection( + dc, reinterpret_cast<const BITMAPINFO*>(&bitmapheader), 0, + reinterpret_cast<void**>(&pixelData), 0, 0); + if (!offscreenBitmap) return; + + uint32_t rgba = instanceData->scriptableObject->drawColor; + unsigned int alpha = ((rgba & 0xFF000000) >> 24); + BYTE r = ((rgba & 0xFF0000) >> 16); + BYTE g = ((rgba & 0xFF00) >> 8); + BYTE b = (rgba & 0xFF); + + // Windows expects premultiplied + r = BYTE(float(alpha * r) / 0xFF); + g = BYTE(float(alpha * g) / 0xFF); + b = BYTE(float(alpha * b) / 0xFF); + uint32_t premultiplied = (alpha << 24) + (r << 16) + (g << 8) + b; + + for (uint32_t* lastPixel = pixelData + width * height; + pixelData < lastPixel; ++pixelData) + *pixelData = premultiplied; + + ::SelectObject(offscreenDC, offscreenBitmap); + BLENDFUNCTION blendFunc; + blendFunc.BlendOp = AC_SRC_OVER; + blendFunc.BlendFlags = 0; + blendFunc.SourceConstantAlpha = 255; + blendFunc.AlphaFormat = AC_SRC_ALPHA; + ::AlphaBlend(dc, x, y, width, height, offscreenDC, 0, 0, width, height, + blendFunc); + + ::DeleteObject(offscreenDC); + ::DeleteObject(offscreenBitmap); + } break; + } +} + +void pluginDraw(InstanceData* instanceData) { + NPP npp = instanceData->npp; + if (!npp) return; + + HDC hdc = nullptr; + PAINTSTRUCT ps; + + notifyDidPaint(instanceData); + + if (instanceData->hasWidget) + hdc = ::BeginPaint((HWND)instanceData->window.window, &ps); + else + hdc = (HDC)instanceData->window.window; + + if (hdc == nullptr) return; + + // Push the browser's hdc on the resource stack. If this test plugin is + // windowless, we share the drawing surface with the rest of the browser. + int savedDCID = SaveDC(hdc); + + // When we have a widget, window.x/y are meaningless since our widget + // is always positioned correctly and we just draw into it at 0,0. + int x = instanceData->hasWidget ? 0 : instanceData->window.x; + int y = instanceData->hasWidget ? 0 : instanceData->window.y; + int width = instanceData->window.width; + int height = instanceData->window.height; + drawToDC(instanceData, hdc, x, y, width, height); + + // Pop our hdc changes off the resource stack + RestoreDC(hdc, savedDCID); + + if (instanceData->hasWidget) + ::EndPaint((HWND)instanceData->window.window, &ps); +} + +/* script interface */ + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + if (!instanceData || !instanceData->hasWidget) return NPTEST_INT32_ERROR; + + // Get the plugin client rect in screen coordinates + RECT rect = {0}; + if (!::GetClientRect((HWND)instanceData->window.window, &rect)) + return NPTEST_INT32_ERROR; + ::MapWindowPoints((HWND)instanceData->window.window, nullptr, (LPPOINT)&rect, + 2); + + // Get the toplevel window frame rect in screen coordinates + HWND rootWnd = ::GetAncestor((HWND)instanceData->window.window, GA_ROOT); + if (!rootWnd) return NPTEST_INT32_ERROR; + RECT rootRect; + if (!::GetWindowRect(rootWnd, &rootRect)) return NPTEST_INT32_ERROR; + + switch (edge) { + case EDGE_LEFT: + return rect.left - rootRect.left; + case EDGE_TOP: + return rect.top - rootRect.top; + case EDGE_RIGHT: + return rect.right - rootRect.left; + case EDGE_BOTTOM: + return rect.bottom - rootRect.top; + } + + return NPTEST_INT32_ERROR; +} + +static BOOL getWindowRegion(HWND wnd, HRGN rgn) { + if (::GetWindowRgn(wnd, rgn) != ERROR) return TRUE; + + RECT clientRect; + if (!::GetClientRect(wnd, &clientRect)) return FALSE; + return ::SetRectRgn(rgn, 0, 0, clientRect.right, clientRect.bottom); +} + +static RGNDATA* computeClipRegion(InstanceData* instanceData) { + HWND wnd = (HWND)instanceData->window.window; + HRGN rgn = ::CreateRectRgn(0, 0, 0, 0); + if (!rgn) return nullptr; + HRGN ancestorRgn = ::CreateRectRgn(0, 0, 0, 0); + if (!ancestorRgn) { + ::DeleteObject(rgn); + return nullptr; + } + if (!getWindowRegion(wnd, rgn)) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return nullptr; + } + + HWND ancestor = wnd; + for (;;) { + ancestor = ::GetAncestor(ancestor, GA_PARENT); + if (!ancestor || ancestor == ::GetDesktopWindow()) { + ::DeleteObject(ancestorRgn); + + DWORD size = ::GetRegionData(rgn, 0, nullptr); + if (!size) { + ::DeleteObject(rgn); + return nullptr; + } + + HANDLE heap = ::GetProcessHeap(); + RGNDATA* data = static_cast<RGNDATA*>(::HeapAlloc(heap, 0, size)); + if (!data) { + ::DeleteObject(rgn); + return nullptr; + } + DWORD result = ::GetRegionData(rgn, size, data); + ::DeleteObject(rgn); + if (!result) { + ::HeapFree(heap, 0, data); + return nullptr; + } + + return data; + } + + if (!getWindowRegion(ancestor, ancestorRgn)) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return 0; + } + + POINT pt = {0, 0}; + ::MapWindowPoints(ancestor, wnd, &pt, 1); + if (::OffsetRgn(ancestorRgn, pt.x, pt.y) == ERROR || + ::CombineRgn(rgn, rgn, ancestorRgn, RGN_AND) == ERROR) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return 0; + } + } +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { + RGNDATA* data = computeClipRegion(instanceData); + if (!data) return NPTEST_INT32_ERROR; + + int32_t result = data->rdh.nCount; + ::HeapFree(::GetProcessHeap(), 0, data); + return result; +} + +static int32_t addOffset(LONG coord, int32_t offset) { + if (offset == NPTEST_INT32_ERROR) return NPTEST_INT32_ERROR; + return coord + offset; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) { + RGNDATA* data = computeClipRegion(instanceData); + if (!data) return NPTEST_INT32_ERROR; + + HANDLE heap = ::GetProcessHeap(); + if (rectIndex >= int32_t(data->rdh.nCount)) { + ::HeapFree(heap, 0, data); + return NPTEST_INT32_ERROR; + } + + RECT rect = reinterpret_cast<RECT*>(data->Buffer)[rectIndex]; + ::HeapFree(heap, 0, data); + + switch (edge) { + case EDGE_LEFT: + return addOffset(rect.left, pluginGetEdge(instanceData, EDGE_LEFT)); + case EDGE_TOP: + return addOffset(rect.top, pluginGetEdge(instanceData, EDGE_TOP)); + case EDGE_RIGHT: + return addOffset(rect.right, pluginGetEdge(instanceData, EDGE_LEFT)); + case EDGE_BOTTOM: + return addOffset(rect.bottom, pluginGetEdge(instanceData, EDGE_TOP)); + } + + return NPTEST_INT32_ERROR; +} + +static void createDummyWindowForIME(InstanceData* instanceData) { + WNDCLASSW wndClass; + wndClass.style = 0; + wndClass.lpfnWndProc = DefWindowProcW; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hInstance = GetModuleHandleW(NULL); + wndClass.hIcon = nullptr; + wndClass.hCursor = nullptr; + wndClass.hbrBackground = (HBRUSH)COLOR_WINDOW; + wndClass.lpszMenuName = NULL; + wndClass.lpszClassName = L"SWFlash_PlaceholderX"; + RegisterClassW(&wndClass); + + instanceData->placeholderWnd = static_cast<void*>( + CreateWindowW(L"SWFlash_PlaceholderX", L"", WS_CHILD, 0, 0, 0, 0, + HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL)); +} + +/* windowless plugin events */ + +static bool handleEventInternal(InstanceData* instanceData, NPEvent* pe, + LRESULT* result) { + switch ((UINT)pe->event) { + case WM_PAINT: + pluginDraw(instanceData); + return true; + + case WM_MOUSEACTIVATE: + if (instanceData->hasWidget) { + ::SetFocus((HWND)instanceData->window.window); + *result = MA_ACTIVATEANDEAT; + return true; + } + return false; + + case WM_MOUSEWHEEL: + return true; + + case WM_WINDOWPOSCHANGED: { + WINDOWPOS* pPos = (WINDOWPOS*)pe->lParam; + instanceData->winX = instanceData->winY = 0; + if (pPos) { + instanceData->winX = pPos->x; + instanceData->winY = pPos->y; + return true; + } + return false; + } + + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: { + int x = instanceData->hasWidget ? 0 : instanceData->winX; + int y = instanceData->hasWidget ? 0 : instanceData->winY; + instanceData->lastMouseX = GET_X_LPARAM(pe->lParam) - x; + instanceData->lastMouseY = GET_Y_LPARAM(pe->lParam) - y; + if ((UINT)pe->event == WM_LBUTTONUP) { + instanceData->mouseUpEventCount++; + } + return true; + } + + case WM_KEYDOWN: + instanceData->lastKeyText.erase(); + *result = 0; + return true; + + case WM_CHAR: { + *result = 0; + wchar_t uniChar = static_cast<wchar_t>(pe->wParam); + if (!uniChar) { + return true; + } + char utf8Char[6]; + int len = ::WideCharToMultiByte(CP_UTF8, 0, &uniChar, 1, utf8Char, 6, + nullptr, nullptr); + if (len == 0 || len > 6) { + return true; + } + instanceData->lastKeyText.append(utf8Char, len); + return true; + } + + case WM_IME_STARTCOMPOSITION: + instanceData->lastComposition.erase(); + if (!instanceData->placeholderWnd) { + createDummyWindowForIME(instanceData); + } + return true; + + case WM_IME_ENDCOMPOSITION: + instanceData->lastComposition.erase(); + return true; + + case WM_IME_COMPOSITION: { + if (pe->lParam & GCS_COMPSTR) { + HIMC hIMC = ImmGetContext((HWND)instanceData->placeholderWnd); + if (!hIMC) { + return false; + } + WCHAR compStr[256]; + LONG len = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, compStr, + 256 * sizeof(WCHAR)); + CHAR buffer[256]; + len = ::WideCharToMultiByte(CP_UTF8, 0, compStr, len / sizeof(WCHAR), + buffer, 256, nullptr, nullptr); + instanceData->lastComposition.append(buffer, len); + ::ImmReleaseContext((HWND)instanceData->placeholderWnd, hIMC); + } + return true; + } + + default: + return false; + } +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { + NPEvent* pe = (NPEvent*)event; + + if (pe == nullptr || instanceData == nullptr || + instanceData->window.type != NPWindowTypeDrawable) + return 0; + + LRESULT result = 0; + return handleEventInternal(instanceData, pe, &result); +} + +/* windowed plugin events */ + +LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + WNDPROC wndProc = (WNDPROC)GetProp(hWnd, "MozillaWndProc"); + if (!wndProc) return 0; + InstanceData* pInstance = (InstanceData*)GetProp(hWnd, "InstanceData"); + if (!pInstance) return 0; + + NPEvent event = {static_cast<uint16_t>(uMsg), wParam, lParam}; + + LRESULT result = 0; + if (handleEventInternal(pInstance, &event, &result)) return result; + + if (uMsg == WM_CLOSE) { + ClearSubclass((HWND)pInstance->window.window); + } + + return CallWindowProc(wndProc, hWnd, uMsg, wParam, lParam); +} + +void ClearSubclass(HWND hWnd) { + if (GetProp(hWnd, "MozillaWndProc")) { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, + (LONG_PTR)GetProp(hWnd, "MozillaWndProc")); + RemoveProp(hWnd, "MozillaWndProc"); + RemoveProp(hWnd, "InstanceData"); + } +} + +void SetSubclass(HWND hWnd, InstanceData* instanceData) { + // Subclass the plugin window so we can handle our own windows events. + SetProp(hWnd, "InstanceData", (HANDLE)instanceData); + WNDPROC origProc = + (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PluginWndProc); + SetProp(hWnd, "MozillaWndProc", (HANDLE)origProc); +} + +static void checkEquals(int a, int b, const char* msg, std::string& error) { + if (a == b) { + return; + } + + error.append(msg); + char buf[100]; + sprintf(buf, " (got %d, expected %d)\n", a, b); + error.append(buf); +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error) { + if (instanceData->platformData->childWindow) { + RECT childRect; + ::GetWindowRect(instanceData->platformData->childWindow, &childRect); + RECT ourRect; + HWND hWnd = (HWND)instanceData->window.window; + ::GetWindowRect(hWnd, &ourRect); + checkEquals(childRect.left, ourRect.left, "Child widget left", error); + checkEquals(childRect.top, ourRect.top, "Child widget top", error); + checkEquals(childRect.right, childRect.left + CHILD_WIDGET_SIZE, + "Child widget width", error); + checkEquals(childRect.bottom, childRect.top + CHILD_WIDGET_SIZE, + "Child widget height", error); + } +} + +bool pluginNativeWidgetIsVisible(InstanceData* instanceData) { + HWND hWnd = (HWND)instanceData->window.window; + wchar_t className[60]; + if (::GetClassNameW(hWnd, className, sizeof(className) / sizeof(char16_t)) && + !wcsicmp(className, L"GeckoPluginWindow")) { + return ::IsWindowVisible(hWnd); + } + // something isn't right, fail the check + return false; +} diff --git a/dom/plugins/test/testplugin/secondplugin/Info.plist b/dom/plugins/test/testplugin/secondplugin/Info.plist new file mode 100644 index 0000000000..afa83a63ce --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnpsecondtest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.SecondTestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>SECONDTEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Second Test Plug-in</string> + <key>WebPluginDescription</key> + <string>Second plug-in for testing purposes.</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-Second-Test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>ts2</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Second test type</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/secondplugin/moz.build b/dom/plugins/test/testplugin/secondplugin/moz.build new file mode 100644 index 0000000000..29c12260b4 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SharedLibrary("npsecondtest") + +relative_path = "secondplugin" +cocoa_name = "SecondTest" +include("../testplugin.mozbuild") diff --git a/dom/plugins/test/testplugin/secondplugin/nptest.def b/dom/plugins/test/testplugin/secondplugin/nptest.def new file mode 100644 index 0000000000..c6584387d2 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPSECONDTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/secondplugin/nptest.rc b/dom/plugins/test/testplugin/secondplugin/nptest.rc new file mode 100644 index 0000000000..835906d0cb --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Second plug-in for testing purposes." + VALUE "FileExtents", "ts2" + VALUE "FileOpenName", "Second test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npsecondtest" + VALUE "MIMEType", "application/x-Second-Test" + VALUE "OriginalFilename", "npsecondtest.dll" + VALUE "ProductName", "Second Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp b/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp new file mode 100644 index 0000000000..23b821ae61 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const char* sPluginName = "Second Test Plug-in"; +const char* sPluginDescription = "Second plug-in for testing purposes."; +const char* sMimeDescription = "application/x-Second-Test:ts2:Second test type"; diff --git a/dom/plugins/test/testplugin/testplugin.mozbuild b/dom/plugins/test/testplugin/testplugin.mozbuild new file mode 100644 index 0000000000..2c466409ea --- /dev/null +++ b/dom/plugins/test/testplugin/testplugin.mozbuild @@ -0,0 +1,64 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'nptest.cpp', + 'nptest_utils.cpp', +] + +UNIFIED_SOURCES += [ + '%s/nptest_name.cpp' % relative_path, +] + +toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] +if toolkit == 'cocoa': + UNIFIED_SOURCES += [ + 'nptest_macosx.mm' + ] +elif toolkit == 'gtk': + UNIFIED_SOURCES += [ + 'nptest_gtk2.cpp', + ] +elif toolkit == 'windows': + UNIFIED_SOURCES += [ + 'nptest_windows.cpp', + ] + OS_LIBS += [ + 'msimg32', + 'imm32' + ] + +# must link statically with the CRT; nptest isn't Gecko code +USE_STATIC_LIBS = True + +# Don't use STL wrappers; nptest isn't Gecko code +DisableStlWrapping() + +NO_PGO = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + RCFILE = 'nptest.rc' + DEFFILE = 'nptest.def' + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' and CONFIG['TARGET_CPU'] == 'x86_64': + OS_LIBS += ['-framework Carbon'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk': + CXXFLAGS += CONFIG['MOZ_GTK2_CFLAGS'] + CFLAGS += CONFIG['MOZ_GTK2_CFLAGS'] + OS_LIBS += CONFIG['MOZ_GTK2_LIBS'] + OS_LIBS += CONFIG['XLDFLAGS'] + OS_LIBS += CONFIG['XLIBS'] + OS_LIBS += CONFIG['XEXT_LIBS'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + FINAL_TARGET = 'dist/plugins/%s.plugin/Contents/MacOS' % cocoa_name + OBJDIR_FILES.dist.plugins['%s.plugin' % cocoa_name].Contents += ['%s/Info.plist' % relative_path] +else: + FINAL_TARGET = 'dist/plugins' + +if CONFIG['CC_TYPE'] in ('clang', 'gcc'): + CXXFLAGS += ['-Wno-error=shadow'] |