diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/plugins/base | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/plugins/base')
40 files changed, 20098 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); |