From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- dom/plugins/base/components.conf | 16 + dom/plugins/base/moz.build | 95 + dom/plugins/base/npapi.h | 922 +++++ dom/plugins/base/npfunctions.h | 356 ++ dom/plugins/base/npruntime.h | 382 ++ dom/plugins/base/nptypes.h | 89 + dom/plugins/base/nsIHTTPHeaderListener.idl | 29 + dom/plugins/base/nsIPluginDocument.idl | 16 + dom/plugins/base/nsIPluginHost.idl | 179 + dom/plugins/base/nsIPluginInputStream.idl | 20 + dom/plugins/base/nsIPluginInstanceOwner.idl | 121 + dom/plugins/base/nsIPluginTag.idl | 97 + dom/plugins/base/nsJSNPRuntime.cpp | 2183 +++++++++++ dom/plugins/base/nsJSNPRuntime.h | 99 + dom/plugins/base/nsNPAPIPlugin.cpp | 1884 +++++++++ dom/plugins/base/nsNPAPIPlugin.h | 288 ++ dom/plugins/base/nsNPAPIPluginInstance.cpp | 1203 ++++++ dom/plugins/base/nsNPAPIPluginInstance.h | 327 ++ dom/plugins/base/nsNPAPIPluginStreamListener.cpp | 775 ++++ dom/plugins/base/nsNPAPIPluginStreamListener.h | 126 + dom/plugins/base/nsPluginHost.cpp | 2792 ++++++++++++++ dom/plugins/base/nsPluginHost.h | 391 ++ dom/plugins/base/nsPluginInstanceOwner.cpp | 3164 +++++++++++++++ dom/plugins/base/nsPluginInstanceOwner.h | 396 ++ dom/plugins/base/nsPluginLogging.h | 54 + dom/plugins/base/nsPluginManifestLineReader.h | 101 + dom/plugins/base/nsPluginNativeWindow.cpp | 61 + dom/plugins/base/nsPluginNativeWindow.h | 76 + dom/plugins/base/nsPluginNativeWindowWin.cpp | 658 ++++ dom/plugins/base/nsPluginStreamListenerPeer.cpp | 606 +++ dom/plugins/base/nsPluginStreamListenerPeer.h | 150 + dom/plugins/base/nsPluginTags.cpp | 926 +++++ dom/plugins/base/nsPluginTags.h | 242 ++ dom/plugins/base/nsPluginsCID.h | 16 + dom/plugins/base/nsPluginsDir.h | 80 + dom/plugins/base/nsPluginsDirDarwin.cpp | 522 +++ dom/plugins/base/nsPluginsDirUnix.cpp | 188 + dom/plugins/base/nsPluginsDirUtils.h | 88 + dom/plugins/base/nsPluginsDirWin.cpp | 349 ++ dom/plugins/base/nspluginroot.idl | 31 + dom/plugins/ipc/AStream.h | 26 + dom/plugins/ipc/BrowserStreamChild.cpp | 217 ++ dom/plugins/ipc/BrowserStreamChild.h | 150 + dom/plugins/ipc/BrowserStreamParent.cpp | 85 + dom/plugins/ipc/BrowserStreamParent.h | 58 + dom/plugins/ipc/ChildTimer.cpp | 31 + dom/plugins/ipc/ChildTimer.h | 55 + dom/plugins/ipc/D3D11SurfaceHolder.cpp | 81 + dom/plugins/ipc/D3D11SurfaceHolder.h | 46 + dom/plugins/ipc/FunctionBroker.cpp | 1429 +++++++ dom/plugins/ipc/FunctionBroker.h | 1452 +++++++ dom/plugins/ipc/FunctionBrokerChild.cpp | 111 + dom/plugins/ipc/FunctionBrokerChild.h | 51 + dom/plugins/ipc/FunctionBrokerIPCUtils.cpp | 334 ++ dom/plugins/ipc/FunctionBrokerIPCUtils.h | 436 +++ dom/plugins/ipc/FunctionBrokerParent.cpp | 139 + dom/plugins/ipc/FunctionBrokerParent.h | 67 + dom/plugins/ipc/FunctionBrokerThread.h | 52 + dom/plugins/ipc/FunctionHook.cpp | 359 ++ dom/plugins/ipc/FunctionHook.h | 206 + dom/plugins/ipc/IpdlTuple.h | 186 + dom/plugins/ipc/MiniShmParent.cpp | 178 + dom/plugins/ipc/MiniShmParent.h | 87 + dom/plugins/ipc/NPEventAndroid.h | 50 + dom/plugins/ipc/NPEventOSX.h | 198 + dom/plugins/ipc/NPEventUnix.h | 90 + dom/plugins/ipc/NPEventWindows.h | 158 + dom/plugins/ipc/PBrowserStream.ipdl | 42 + dom/plugins/ipc/PFunctionBroker.ipdl | 23 + dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl | 34 + dom/plugins/ipc/PPluginInstance.ipdl | 294 ++ dom/plugins/ipc/PPluginModule.ipdl | 155 + dom/plugins/ipc/PPluginScriptableObject.ipdl | 102 + dom/plugins/ipc/PPluginSurface.ipdl | 18 + dom/plugins/ipc/PStreamNotify.ipdl | 39 + dom/plugins/ipc/PluginBackgroundDestroyer.cpp | 35 + dom/plugins/ipc/PluginBackgroundDestroyer.h | 56 + dom/plugins/ipc/PluginBridge.h | 41 + dom/plugins/ipc/PluginHangUIParent.cpp | 400 ++ dom/plugins/ipc/PluginHangUIParent.h | 142 + dom/plugins/ipc/PluginInstanceChild.cpp | 4045 ++++++++++++++++++++ dom/plugins/ipc/PluginInstanceChild.h | 613 +++ dom/plugins/ipc/PluginInstanceParent.cpp | 2326 +++++++++++ dom/plugins/ipc/PluginInstanceParent.h | 403 ++ dom/plugins/ipc/PluginInterposeOSX.h | 137 + dom/plugins/ipc/PluginInterposeOSX.mm | 1040 +++++ dom/plugins/ipc/PluginLibrary.h | 125 + dom/plugins/ipc/PluginMessageUtils.cpp | 141 + dom/plugins/ipc/PluginMessageUtils.h | 662 ++++ dom/plugins/ipc/PluginModuleChild.cpp | 1984 ++++++++++ dom/plugins/ipc/PluginModuleChild.h | 345 ++ dom/plugins/ipc/PluginModuleParent.cpp | 2541 ++++++++++++ dom/plugins/ipc/PluginModuleParent.h | 545 +++ dom/plugins/ipc/PluginProcessChild.cpp | 186 + dom/plugins/ipc/PluginProcessChild.h | 53 + dom/plugins/ipc/PluginProcessParent.cpp | 191 + dom/plugins/ipc/PluginProcessParent.h | 95 + dom/plugins/ipc/PluginQuirks.cpp | 54 + dom/plugins/ipc/PluginQuirks.h | 64 + dom/plugins/ipc/PluginScriptableObjectChild.cpp | 1205 ++++++ dom/plugins/ipc/PluginScriptableObjectChild.h | 276 ++ dom/plugins/ipc/PluginScriptableObjectParent.cpp | 1289 +++++++ dom/plugins/ipc/PluginScriptableObjectParent.h | 169 + dom/plugins/ipc/PluginScriptableObjectUtils-inl.h | 154 + dom/plugins/ipc/PluginScriptableObjectUtils.h | 253 ++ dom/plugins/ipc/PluginSurfaceParent.cpp | 29 + dom/plugins/ipc/PluginSurfaceParent.h | 38 + dom/plugins/ipc/PluginTypes.ipdlh | 48 + dom/plugins/ipc/PluginUtilsOSX.h | 94 + dom/plugins/ipc/PluginUtilsOSX.mm | 429 +++ dom/plugins/ipc/PluginUtilsWin.cpp | 272 ++ dom/plugins/ipc/PluginUtilsWin.h | 27 + dom/plugins/ipc/PluginWidgetChild.cpp | 60 + dom/plugins/ipc/PluginWidgetChild.h | 41 + dom/plugins/ipc/PluginWidgetParent.cpp | 168 + dom/plugins/ipc/PluginWidgetParent.h | 64 + dom/plugins/ipc/StreamNotifyChild.h | 60 + dom/plugins/ipc/StreamNotifyParent.h | 41 + dom/plugins/ipc/hangui/HangUIDlg.h | 17 + dom/plugins/ipc/hangui/HangUIDlg.rc | 26 + dom/plugins/ipc/hangui/MiniShmBase.h | 280 ++ dom/plugins/ipc/hangui/MiniShmChild.cpp | 160 + dom/plugins/ipc/hangui/MiniShmChild.h | 63 + dom/plugins/ipc/hangui/PluginHangUI.h | 39 + dom/plugins/ipc/hangui/PluginHangUIChild.cpp | 386 ++ dom/plugins/ipc/hangui/PluginHangUIChild.h | 100 + dom/plugins/ipc/hangui/module.ver | 6 + dom/plugins/ipc/hangui/moz.build | 27 + dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest | 37 + dom/plugins/ipc/interpose/moz.build | 13 + .../ipc/interpose/plugin_child_interpose.mm | 129 + dom/plugins/ipc/moz.build | 149 + dom/plugins/test/crashtests/110650-1.html | 11 + dom/plugins/test/crashtests/41276-1.html | 28 + dom/plugins/test/crashtests/48856-1.html | 10 + dom/plugins/test/crashtests/539897-1.html | 35 + dom/plugins/test/crashtests/540114-1.html | 40 + dom/plugins/test/crashtests/570884.html | 8 + dom/plugins/test/crashtests/598862.html | 77 + dom/plugins/test/crashtests/626602-1.html | 109 + dom/plugins/test/crashtests/752340.html | 19 + dom/plugins/test/crashtests/843086.xhtml | 1 + dom/plugins/test/crashtests/crashtests.list | 14 + dom/plugins/test/mochitest/.eslintrc.js | 9 + dom/plugins/test/mochitest/307-xo-redirect.sjs | 6 + dom/plugins/test/mochitest/block_all_plugins.html | 10 + dom/plugins/test/mochitest/browser.ini | 17 + .../test/mochitest/browser_blockallplugins.js | 66 + dom/plugins/test/mochitest/browser_bug1163570.js | 118 + dom/plugins/test/mochitest/browser_pluginscroll.js | 417 ++ .../mochitest/browser_tabswitchbetweenplugins.js | 141 + dom/plugins/test/mochitest/file_authident.js | 14 + dom/plugins/test/mochitest/file_bug771202.html | 8 + dom/plugins/test/mochitest/file_bug863792.html | 43 + dom/plugins/test/mochitest/head.js | 147 + dom/plugins/test/mochitest/large-pic.jpg | Bin 0 -> 98570 bytes dom/plugins/test/mochitest/loremipsum.txt | 11 + dom/plugins/test/mochitest/loremipsum.xtest | 11 + .../test/mochitest/loremipsum.xtest^headers^ | 1 + dom/plugins/test/mochitest/loremipsum_file.txt | 11 + dom/plugins/test/mochitest/loremipsum_nocache.txt | 11 + .../test/mochitest/loremipsum_nocache.txt^headers^ | 1 + dom/plugins/test/mochitest/mixed_case_mime.sjs | 8 + dom/plugins/test/mochitest/mochitest.ini | 45 + dom/plugins/test/mochitest/neverending.sjs | 16 + .../mochitest/npruntime_identifiers_subpage.html | 4 + .../test/mochitest/plugin-stream-referer.sjs | 12 + dom/plugins/test/mochitest/plugin-utils.js | 147 + .../test/mochitest/plugin_no_scroll_div.html | 10 + .../test/mochitest/plugin_subframe_test.html | 10 + dom/plugins/test/mochitest/plugin_test.html | 16 + dom/plugins/test/mochitest/plugin_window.html | 23 + dom/plugins/test/mochitest/pluginstream.js | 46 + dom/plugins/test/mochitest/post.sjs | 17 + dom/plugins/test/mochitest/test_hanging.html | 59 + .../test/mochitest/test_mixed_case_mime.html | 25 + .../test/mochitest/test_plugin_fallback_focus.html | 80 + .../mochitest/test_plugin_scroll_invalidation.html | 109 + .../mochitest/test_plugin_scroll_painting.html | 64 + .../test/mochitest/test_pluginstream_geturl.html | 31 + .../mochitest/test_pluginstream_geturlnotify.html | 30 + dom/plugins/test/mochitest/test_positioning.html | 56 + .../mochitest/test_queryContentsScaleFactor.html | 31 + .../test_queryContentsScaleFactorWindowed.html | 31 + .../mochitest/test_refresh_navigator_plugins.html | 33 + dom/plugins/test/moz.build | 11 + dom/plugins/test/reftest/border-padding-1-ref.html | 10 + dom/plugins/test/reftest/border-padding-1.html | 16 + dom/plugins/test/reftest/border-padding-2-ref.html | 17 + dom/plugins/test/reftest/border-padding-2.html | 17 + dom/plugins/test/reftest/border-padding-3-ref.html | 10 + dom/plugins/test/reftest/border-padding-3.html | 35 + dom/plugins/test/reftest/div-alpha-opacity.html | 28 + dom/plugins/test/reftest/div-alpha-zindex.html | 27 + dom/plugins/test/reftest/div-sanity.html | 17 + dom/plugins/test/reftest/plugin-alpha-opacity.html | 29 + dom/plugins/test/reftest/plugin-alpha-zindex.html | 26 + .../test/reftest/plugin-background-1-step.html | 22 + .../test/reftest/plugin-background-10-step.html | 22 + .../test/reftest/plugin-background-2-step.html | 22 + .../test/reftest/plugin-background-5-step.html | 22 + .../test/reftest/plugin-background-ref.html | 17 + dom/plugins/test/reftest/plugin-background.css | 61 + dom/plugins/test/reftest/plugin-background.html | 20 + dom/plugins/test/reftest/plugin-background.js | 73 + .../test/reftest/plugin-busy-alpha-zindex.html | 56 + .../test/reftest/plugin-canvas-alpha-zindex.html | 41 + dom/plugins/test/reftest/plugin-sanity.html | 13 + .../test/reftest/plugin-transform-1-ref.html | 10 + dom/plugins/test/reftest/plugin-transform-1.html | 10 + .../test/reftest/plugin-transform-2-ref.html | 7 + dom/plugins/test/reftest/plugin-transform-2.html | 13 + .../reftest/plugin-transform-alpha-zindex.html | 28 + .../reftest/pluginproblemui-direction-1-ref.html | 21 + .../test/reftest/pluginproblemui-direction-1.html | 21 + .../reftest/pluginproblemui-direction-2-ref.html | 25 + .../test/reftest/pluginproblemui-direction-2.html | 25 + dom/plugins/test/reftest/reftest.list | 26 + dom/plugins/test/reftest/shrink-1-ref.html | 12 + dom/plugins/test/reftest/shrink-1.html | 25 + dom/plugins/test/reftest/update-1-ref.html | 12 + dom/plugins/test/reftest/update-1.html | 62 + dom/plugins/test/reftest/utils.js | 18 + .../test/reftest/windowless-clipping-1-ref.html | 14 + .../test/reftest/windowless-clipping-1.html | 15 + .../test/reftest/windowless-layers-ref.html | 10 + dom/plugins/test/reftest/windowless-layers.html | 15 + dom/plugins/test/testplugin/Info.plist | 38 + dom/plugins/test/testplugin/README | 424 ++ dom/plugins/test/testplugin/flashplugin/Info.plist | 38 + dom/plugins/test/testplugin/flashplugin/moz.build | 11 + dom/plugins/test/testplugin/flashplugin/nptest.def | 7 + dom/plugins/test/testplugin/flashplugin/nptest.rc | 42 + .../test/testplugin/flashplugin/nptest_name.cpp | 8 + dom/plugins/test/testplugin/moz.build | 13 + dom/plugins/test/testplugin/nptest.cpp | 3282 ++++++++++++++++ dom/plugins/test/testplugin/nptest.def | 7 + dom/plugins/test/testplugin/nptest.h | 150 + dom/plugins/test/testplugin/nptest.rc | 42 + dom/plugins/test/testplugin/nptest_droid.cpp | 80 + dom/plugins/test/testplugin/nptest_gtk2.cpp | 707 ++++ dom/plugins/test/testplugin/nptest_macosx.mm | 275 ++ dom/plugins/test/testplugin/nptest_name.cpp | 8 + dom/plugins/test/testplugin/nptest_platform.h | 155 + dom/plugins/test/testplugin/nptest_utils.cpp | 100 + dom/plugins/test/testplugin/nptest_utils.h | 45 + dom/plugins/test/testplugin/nptest_windows.cpp | 797 ++++ .../test/testplugin/secondplugin/Info.plist | 38 + dom/plugins/test/testplugin/secondplugin/moz.build | 11 + .../test/testplugin/secondplugin/nptest.def | 7 + dom/plugins/test/testplugin/secondplugin/nptest.rc | 42 + .../test/testplugin/secondplugin/nptest_name.cpp | 7 + dom/plugins/test/testplugin/testplugin.mozbuild | 64 + 253 files changed, 59186 insertions(+) create mode 100644 dom/plugins/base/components.conf create mode 100644 dom/plugins/base/moz.build create mode 100644 dom/plugins/base/npapi.h create mode 100644 dom/plugins/base/npfunctions.h create mode 100644 dom/plugins/base/npruntime.h create mode 100644 dom/plugins/base/nptypes.h create mode 100644 dom/plugins/base/nsIHTTPHeaderListener.idl create mode 100644 dom/plugins/base/nsIPluginDocument.idl create mode 100644 dom/plugins/base/nsIPluginHost.idl create mode 100644 dom/plugins/base/nsIPluginInputStream.idl create mode 100644 dom/plugins/base/nsIPluginInstanceOwner.idl create mode 100644 dom/plugins/base/nsIPluginTag.idl create mode 100644 dom/plugins/base/nsJSNPRuntime.cpp create mode 100644 dom/plugins/base/nsJSNPRuntime.h create mode 100644 dom/plugins/base/nsNPAPIPlugin.cpp create mode 100644 dom/plugins/base/nsNPAPIPlugin.h create mode 100644 dom/plugins/base/nsNPAPIPluginInstance.cpp create mode 100644 dom/plugins/base/nsNPAPIPluginInstance.h create mode 100644 dom/plugins/base/nsNPAPIPluginStreamListener.cpp create mode 100644 dom/plugins/base/nsNPAPIPluginStreamListener.h create mode 100644 dom/plugins/base/nsPluginHost.cpp create mode 100644 dom/plugins/base/nsPluginHost.h create mode 100644 dom/plugins/base/nsPluginInstanceOwner.cpp create mode 100644 dom/plugins/base/nsPluginInstanceOwner.h create mode 100644 dom/plugins/base/nsPluginLogging.h create mode 100644 dom/plugins/base/nsPluginManifestLineReader.h create mode 100644 dom/plugins/base/nsPluginNativeWindow.cpp create mode 100644 dom/plugins/base/nsPluginNativeWindow.h create mode 100644 dom/plugins/base/nsPluginNativeWindowWin.cpp create mode 100644 dom/plugins/base/nsPluginStreamListenerPeer.cpp create mode 100644 dom/plugins/base/nsPluginStreamListenerPeer.h create mode 100644 dom/plugins/base/nsPluginTags.cpp create mode 100644 dom/plugins/base/nsPluginTags.h create mode 100644 dom/plugins/base/nsPluginsCID.h create mode 100644 dom/plugins/base/nsPluginsDir.h create mode 100644 dom/plugins/base/nsPluginsDirDarwin.cpp create mode 100644 dom/plugins/base/nsPluginsDirUnix.cpp create mode 100644 dom/plugins/base/nsPluginsDirUtils.h create mode 100644 dom/plugins/base/nsPluginsDirWin.cpp create mode 100644 dom/plugins/base/nspluginroot.idl create mode 100644 dom/plugins/ipc/AStream.h create mode 100644 dom/plugins/ipc/BrowserStreamChild.cpp create mode 100644 dom/plugins/ipc/BrowserStreamChild.h create mode 100644 dom/plugins/ipc/BrowserStreamParent.cpp create mode 100644 dom/plugins/ipc/BrowserStreamParent.h create mode 100644 dom/plugins/ipc/ChildTimer.cpp create mode 100644 dom/plugins/ipc/ChildTimer.h create mode 100644 dom/plugins/ipc/D3D11SurfaceHolder.cpp create mode 100644 dom/plugins/ipc/D3D11SurfaceHolder.h create mode 100644 dom/plugins/ipc/FunctionBroker.cpp create mode 100644 dom/plugins/ipc/FunctionBroker.h create mode 100644 dom/plugins/ipc/FunctionBrokerChild.cpp create mode 100644 dom/plugins/ipc/FunctionBrokerChild.h create mode 100644 dom/plugins/ipc/FunctionBrokerIPCUtils.cpp create mode 100644 dom/plugins/ipc/FunctionBrokerIPCUtils.h create mode 100644 dom/plugins/ipc/FunctionBrokerParent.cpp create mode 100644 dom/plugins/ipc/FunctionBrokerParent.h create mode 100644 dom/plugins/ipc/FunctionBrokerThread.h create mode 100644 dom/plugins/ipc/FunctionHook.cpp create mode 100644 dom/plugins/ipc/FunctionHook.h create mode 100644 dom/plugins/ipc/IpdlTuple.h create mode 100644 dom/plugins/ipc/MiniShmParent.cpp create mode 100644 dom/plugins/ipc/MiniShmParent.h create mode 100644 dom/plugins/ipc/NPEventAndroid.h create mode 100644 dom/plugins/ipc/NPEventOSX.h create mode 100644 dom/plugins/ipc/NPEventUnix.h create mode 100644 dom/plugins/ipc/NPEventWindows.h create mode 100644 dom/plugins/ipc/PBrowserStream.ipdl create mode 100644 dom/plugins/ipc/PFunctionBroker.ipdl create mode 100644 dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl create mode 100644 dom/plugins/ipc/PPluginInstance.ipdl create mode 100644 dom/plugins/ipc/PPluginModule.ipdl create mode 100644 dom/plugins/ipc/PPluginScriptableObject.ipdl create mode 100644 dom/plugins/ipc/PPluginSurface.ipdl create mode 100644 dom/plugins/ipc/PStreamNotify.ipdl create mode 100644 dom/plugins/ipc/PluginBackgroundDestroyer.cpp create mode 100644 dom/plugins/ipc/PluginBackgroundDestroyer.h create mode 100644 dom/plugins/ipc/PluginBridge.h create mode 100644 dom/plugins/ipc/PluginHangUIParent.cpp create mode 100644 dom/plugins/ipc/PluginHangUIParent.h create mode 100644 dom/plugins/ipc/PluginInstanceChild.cpp create mode 100644 dom/plugins/ipc/PluginInstanceChild.h create mode 100644 dom/plugins/ipc/PluginInstanceParent.cpp create mode 100644 dom/plugins/ipc/PluginInstanceParent.h create mode 100644 dom/plugins/ipc/PluginInterposeOSX.h create mode 100644 dom/plugins/ipc/PluginInterposeOSX.mm create mode 100644 dom/plugins/ipc/PluginLibrary.h create mode 100644 dom/plugins/ipc/PluginMessageUtils.cpp create mode 100644 dom/plugins/ipc/PluginMessageUtils.h create mode 100644 dom/plugins/ipc/PluginModuleChild.cpp create mode 100644 dom/plugins/ipc/PluginModuleChild.h create mode 100644 dom/plugins/ipc/PluginModuleParent.cpp create mode 100644 dom/plugins/ipc/PluginModuleParent.h create mode 100644 dom/plugins/ipc/PluginProcessChild.cpp create mode 100644 dom/plugins/ipc/PluginProcessChild.h create mode 100644 dom/plugins/ipc/PluginProcessParent.cpp create mode 100644 dom/plugins/ipc/PluginProcessParent.h create mode 100644 dom/plugins/ipc/PluginQuirks.cpp create mode 100644 dom/plugins/ipc/PluginQuirks.h create mode 100644 dom/plugins/ipc/PluginScriptableObjectChild.cpp create mode 100644 dom/plugins/ipc/PluginScriptableObjectChild.h create mode 100644 dom/plugins/ipc/PluginScriptableObjectParent.cpp create mode 100644 dom/plugins/ipc/PluginScriptableObjectParent.h create mode 100644 dom/plugins/ipc/PluginScriptableObjectUtils-inl.h create mode 100644 dom/plugins/ipc/PluginScriptableObjectUtils.h create mode 100644 dom/plugins/ipc/PluginSurfaceParent.cpp create mode 100644 dom/plugins/ipc/PluginSurfaceParent.h create mode 100644 dom/plugins/ipc/PluginTypes.ipdlh create mode 100644 dom/plugins/ipc/PluginUtilsOSX.h create mode 100644 dom/plugins/ipc/PluginUtilsOSX.mm create mode 100644 dom/plugins/ipc/PluginUtilsWin.cpp create mode 100644 dom/plugins/ipc/PluginUtilsWin.h create mode 100644 dom/plugins/ipc/PluginWidgetChild.cpp create mode 100644 dom/plugins/ipc/PluginWidgetChild.h create mode 100644 dom/plugins/ipc/PluginWidgetParent.cpp create mode 100644 dom/plugins/ipc/PluginWidgetParent.h create mode 100644 dom/plugins/ipc/StreamNotifyChild.h create mode 100644 dom/plugins/ipc/StreamNotifyParent.h create mode 100644 dom/plugins/ipc/hangui/HangUIDlg.h create mode 100644 dom/plugins/ipc/hangui/HangUIDlg.rc create mode 100644 dom/plugins/ipc/hangui/MiniShmBase.h create mode 100644 dom/plugins/ipc/hangui/MiniShmChild.cpp create mode 100644 dom/plugins/ipc/hangui/MiniShmChild.h create mode 100644 dom/plugins/ipc/hangui/PluginHangUI.h create mode 100644 dom/plugins/ipc/hangui/PluginHangUIChild.cpp create mode 100644 dom/plugins/ipc/hangui/PluginHangUIChild.h create mode 100644 dom/plugins/ipc/hangui/module.ver create mode 100644 dom/plugins/ipc/hangui/moz.build create mode 100644 dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest create mode 100644 dom/plugins/ipc/interpose/moz.build create mode 100644 dom/plugins/ipc/interpose/plugin_child_interpose.mm create mode 100644 dom/plugins/ipc/moz.build create mode 100644 dom/plugins/test/crashtests/110650-1.html create mode 100644 dom/plugins/test/crashtests/41276-1.html create mode 100644 dom/plugins/test/crashtests/48856-1.html create mode 100644 dom/plugins/test/crashtests/539897-1.html create mode 100644 dom/plugins/test/crashtests/540114-1.html create mode 100644 dom/plugins/test/crashtests/570884.html create mode 100644 dom/plugins/test/crashtests/598862.html create mode 100644 dom/plugins/test/crashtests/626602-1.html create mode 100644 dom/plugins/test/crashtests/752340.html create mode 100644 dom/plugins/test/crashtests/843086.xhtml create mode 100644 dom/plugins/test/crashtests/crashtests.list create mode 100644 dom/plugins/test/mochitest/.eslintrc.js create mode 100644 dom/plugins/test/mochitest/307-xo-redirect.sjs create mode 100644 dom/plugins/test/mochitest/block_all_plugins.html create mode 100644 dom/plugins/test/mochitest/browser.ini create mode 100644 dom/plugins/test/mochitest/browser_blockallplugins.js create mode 100644 dom/plugins/test/mochitest/browser_bug1163570.js create mode 100644 dom/plugins/test/mochitest/browser_pluginscroll.js create mode 100644 dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js create mode 100644 dom/plugins/test/mochitest/file_authident.js create mode 100644 dom/plugins/test/mochitest/file_bug771202.html create mode 100644 dom/plugins/test/mochitest/file_bug863792.html create mode 100644 dom/plugins/test/mochitest/head.js create mode 100644 dom/plugins/test/mochitest/large-pic.jpg create mode 100644 dom/plugins/test/mochitest/loremipsum.txt create mode 100644 dom/plugins/test/mochitest/loremipsum.xtest create mode 100644 dom/plugins/test/mochitest/loremipsum.xtest^headers^ create mode 100644 dom/plugins/test/mochitest/loremipsum_file.txt create mode 100644 dom/plugins/test/mochitest/loremipsum_nocache.txt create mode 100644 dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ create mode 100644 dom/plugins/test/mochitest/mixed_case_mime.sjs create mode 100644 dom/plugins/test/mochitest/mochitest.ini create mode 100644 dom/plugins/test/mochitest/neverending.sjs create mode 100644 dom/plugins/test/mochitest/npruntime_identifiers_subpage.html create mode 100644 dom/plugins/test/mochitest/plugin-stream-referer.sjs create mode 100644 dom/plugins/test/mochitest/plugin-utils.js create mode 100644 dom/plugins/test/mochitest/plugin_no_scroll_div.html create mode 100644 dom/plugins/test/mochitest/plugin_subframe_test.html create mode 100644 dom/plugins/test/mochitest/plugin_test.html create mode 100644 dom/plugins/test/mochitest/plugin_window.html create mode 100644 dom/plugins/test/mochitest/pluginstream.js create mode 100644 dom/plugins/test/mochitest/post.sjs create mode 100644 dom/plugins/test/mochitest/test_hanging.html create mode 100644 dom/plugins/test/mochitest/test_mixed_case_mime.html create mode 100644 dom/plugins/test/mochitest/test_plugin_fallback_focus.html create mode 100644 dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html create mode 100644 dom/plugins/test/mochitest/test_plugin_scroll_painting.html create mode 100644 dom/plugins/test/mochitest/test_pluginstream_geturl.html create mode 100644 dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html create mode 100644 dom/plugins/test/mochitest/test_positioning.html create mode 100644 dom/plugins/test/mochitest/test_queryContentsScaleFactor.html create mode 100644 dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html create mode 100644 dom/plugins/test/mochitest/test_refresh_navigator_plugins.html create mode 100644 dom/plugins/test/moz.build create mode 100644 dom/plugins/test/reftest/border-padding-1-ref.html create mode 100644 dom/plugins/test/reftest/border-padding-1.html create mode 100644 dom/plugins/test/reftest/border-padding-2-ref.html create mode 100644 dom/plugins/test/reftest/border-padding-2.html create mode 100644 dom/plugins/test/reftest/border-padding-3-ref.html create mode 100644 dom/plugins/test/reftest/border-padding-3.html create mode 100644 dom/plugins/test/reftest/div-alpha-opacity.html create mode 100644 dom/plugins/test/reftest/div-alpha-zindex.html create mode 100644 dom/plugins/test/reftest/div-sanity.html create mode 100644 dom/plugins/test/reftest/plugin-alpha-opacity.html create mode 100644 dom/plugins/test/reftest/plugin-alpha-zindex.html create mode 100644 dom/plugins/test/reftest/plugin-background-1-step.html create mode 100644 dom/plugins/test/reftest/plugin-background-10-step.html create mode 100644 dom/plugins/test/reftest/plugin-background-2-step.html create mode 100644 dom/plugins/test/reftest/plugin-background-5-step.html create mode 100644 dom/plugins/test/reftest/plugin-background-ref.html create mode 100644 dom/plugins/test/reftest/plugin-background.css create mode 100644 dom/plugins/test/reftest/plugin-background.html create mode 100644 dom/plugins/test/reftest/plugin-background.js create mode 100644 dom/plugins/test/reftest/plugin-busy-alpha-zindex.html create mode 100644 dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html create mode 100644 dom/plugins/test/reftest/plugin-sanity.html create mode 100644 dom/plugins/test/reftest/plugin-transform-1-ref.html create mode 100644 dom/plugins/test/reftest/plugin-transform-1.html create mode 100644 dom/plugins/test/reftest/plugin-transform-2-ref.html create mode 100644 dom/plugins/test/reftest/plugin-transform-2.html create mode 100644 dom/plugins/test/reftest/plugin-transform-alpha-zindex.html create mode 100644 dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html create mode 100644 dom/plugins/test/reftest/pluginproblemui-direction-1.html create mode 100644 dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html create mode 100644 dom/plugins/test/reftest/pluginproblemui-direction-2.html create mode 100644 dom/plugins/test/reftest/reftest.list create mode 100644 dom/plugins/test/reftest/shrink-1-ref.html create mode 100644 dom/plugins/test/reftest/shrink-1.html create mode 100644 dom/plugins/test/reftest/update-1-ref.html create mode 100644 dom/plugins/test/reftest/update-1.html create mode 100644 dom/plugins/test/reftest/utils.js create mode 100644 dom/plugins/test/reftest/windowless-clipping-1-ref.html create mode 100644 dom/plugins/test/reftest/windowless-clipping-1.html create mode 100644 dom/plugins/test/reftest/windowless-layers-ref.html create mode 100644 dom/plugins/test/reftest/windowless-layers.html create mode 100644 dom/plugins/test/testplugin/Info.plist create mode 100644 dom/plugins/test/testplugin/README create mode 100644 dom/plugins/test/testplugin/flashplugin/Info.plist create mode 100644 dom/plugins/test/testplugin/flashplugin/moz.build create mode 100644 dom/plugins/test/testplugin/flashplugin/nptest.def create mode 100644 dom/plugins/test/testplugin/flashplugin/nptest.rc create mode 100644 dom/plugins/test/testplugin/flashplugin/nptest_name.cpp create mode 100644 dom/plugins/test/testplugin/moz.build create mode 100644 dom/plugins/test/testplugin/nptest.cpp create mode 100644 dom/plugins/test/testplugin/nptest.def create mode 100644 dom/plugins/test/testplugin/nptest.h create mode 100644 dom/plugins/test/testplugin/nptest.rc create mode 100644 dom/plugins/test/testplugin/nptest_droid.cpp create mode 100644 dom/plugins/test/testplugin/nptest_gtk2.cpp create mode 100644 dom/plugins/test/testplugin/nptest_macosx.mm create mode 100644 dom/plugins/test/testplugin/nptest_name.cpp create mode 100644 dom/plugins/test/testplugin/nptest_platform.h create mode 100644 dom/plugins/test/testplugin/nptest_utils.cpp create mode 100644 dom/plugins/test/testplugin/nptest_utils.h create mode 100644 dom/plugins/test/testplugin/nptest_windows.cpp create mode 100644 dom/plugins/test/testplugin/secondplugin/Info.plist create mode 100644 dom/plugins/test/testplugin/secondplugin/moz.build create mode 100644 dom/plugins/test/testplugin/secondplugin/nptest.def create mode 100644 dom/plugins/test/testplugin/secondplugin/nptest.rc create mode 100644 dom/plugins/test/testplugin/secondplugin/nptest_name.cpp create mode 100644 dom/plugins/test/testplugin/testplugin.mozbuild (limited to 'dom/plugins') 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 +# 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 +# include +# ifndef NP_NO_CARBON +# include +# endif +#endif + +#if defined(XP_UNIX) +# include +# if defined(MOZ_X11) +# include +# include +# include "X11UndefineNone.h" +# endif +#endif + +#if defined(XP_SYMBIAN) +# include +# include +#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 + +# 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 + +/* + * 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 +# include +# endif +#elif defined(BEOS) +# include +#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 + +# ifndef __cplusplus +# if !defined(__GNUC__) || (__GNUC__ > 2 || __GNUC_MINOR__ > 95) +# include +# 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.

+ */ + +[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 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 getMimeTypes(); + Array getMimeDescriptions(); + Array 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>::hash(l.mJSObj) ^ + HashGeneric(l.mNpp); + } + + static bool match(const Key& k, const Lookup& l) { + return js::MovableCellHasher>::match(k.mJSObj, + l.mJSObj) && + k.mNpp == l.mNpp; + } +}; + +namespace JS { +template <> +struct GCPolicy { + 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 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 + JSObjWrapperTable; +static UniquePtr 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* 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 proxy, + JS::Handle id, + JS::Handle desc, + JS::ObjectOpResult& result) const override { + ::JS_ReportErrorASCII(cx, + "Trying to add unsupported property on NPObject!"); + return false; + } + + bool getPrototypeIfOrdinary( + JSContext* cx, JS::Handle proxy, bool* isOrdinary, + JS::MutableHandle proto) const override { + *isOrdinary = true; + proto.set(js::GetStaticPrototype(proxy)); + return true; + } + + bool isExtensible(JSContext* cx, JS::Handle 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 proxy, + JS::ObjectOpResult& result) const override { + result.succeed(); + return true; + } + + bool getOwnPropertyDescriptor( + JSContext* cx, JS::Handle proxy, JS::Handle id, + JS::MutableHandle desc) const override; + + bool ownPropertyKeys(JSContext* cx, JS::Handle proxy, + JS::MutableHandleVector properties) const override; + + bool delete_(JSContext* cx, JS::Handle proxy, JS::Handle id, + JS::ObjectOpResult& result) const override; + + bool get(JSContext* cx, JS::Handle proxy, + JS::Handle receiver, JS::Handle id, + JS::MutableHandle vp) const override; + + bool set(JSContext* cx, JS::Handle proxy, JS::Handle id, + JS::Handle vp, JS::Handle receiver, + JS::ObjectOpResult& result) const override; + + bool isCallable(JSObject* obj) const override { return true; } + bool call(JSContext* cx, JS::Handle proxy, + const JS::CallArgs& args) const override; + + bool isConstructor(JSObject* obj) const override { return true; } + bool construct(JSContext* cx, JS::Handle 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 obj, + JS::Handle id, bool* resolved, + JS::MutableHandle method); + +static bool NPObjWrapper_toPrimitive(JSContext* cx, unsigned argc, + JS::Value* vp); + +static bool CreateNPObjectMember(NPP npp, JSContext* cx, + JS::Handle obj, NPObject* npobj, + JS::Handle id, + NPVariant* getPropertyResult, + JS::MutableHandle vp); + +const JSClass sNPObjWrapperProxyClass = + PROXY_CLASS_DEF(NPRUNTIME_JSCLASS_NAME, JSCLASS_HAS_RESERVED_SLOTS(1)); + +typedef struct NPObjectMemberPrivate { + JS::Heap npobjWrapper; + JS::Heap fieldValue; + JS::Heap 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> 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(); + 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 owner = inst->GetOwner(); + NS_ENSURE_TRUE(owner, nullptr); + + nsCOMPtr 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 obj(cx, &val.toObject()); + JS::Rooted 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 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 rval) { + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted obj(cx, objArg); + JS::Rooted 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 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 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 jsobj(cx, npjsobj->mJSObj); + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + MarkCrossZoneNPIdentifier(cx, method); + JS::Rooted 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 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 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 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 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 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 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 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 jsObj(cx, npjsobj->mJSObj); + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + MarkCrossZoneNPIdentifier(cx, npid); + + JS::Rooted v(cx, NPVariantToJSVal(npp, cx, value)); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted 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 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 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 jsobj(cx, npjsobj->mJSObj); + JSAutoRealm ar(cx, npjsobj->mJSObjGlobal); + + JS::Rooted 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 v(cx); + if (!JS_IdToValue(cx, ida[i], &v)) { + free(*idarray); + return false; + } + + NPIdentifier id; + if (v.isString()) { + JS::Rooted 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 obj, + JS::Handle 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 aObj, + bool wrapResult = true) { + JS::Rooted 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 aObj) { + JS::Rooted 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 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 proxy, + JS::Handle 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 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 proxy, + JS::Handle id, + JS::Handle vp, + JS::Handle 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 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 proxy, + JS::Handle receiver, + JS::Handle id, + JS::MutableHandle 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 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(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 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 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 obj(cx, &args.thisv().toObject()); + return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false); +} + +bool NPObjWrapperProxyHandler::getOwnPropertyDescriptor( + JSContext* cx, JS::Handle proxy, JS::Handle id, + JS::MutableHandle desc) const { + bool resolved = false; + JS::Rooted 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 val(cx, JS::ObjectOrNullValue(method)); + desc.initFields(proxy, val, JSPROP_ENUMERATE, nullptr, nullptr); + return true; +} + +bool NPObjWrapperProxyHandler::ownPropertyKeys( + JSContext* cx, JS::Handle proxy, + JS::MutableHandleVector 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 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 obj, + JS::Handle id, bool* resolvedp, + JS::MutableHandle 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 resolvedProps(cx, + NPObjWrapper_GetResolvedProps(cx, obj)); + if (!resolvedProps) return false; + JS::Rooted 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(sNPObjWrappers->Search(npobj)); + if (entry && entry->mJSObj == proxy) { + sNPObjWrappers->Remove(npobj); + } + } + } + + if (!sDelayedReleases) sDelayedReleases = new nsTArray; + 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(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 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 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(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 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(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 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 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(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(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(npobj); + return o->mNpp; + } + + auto entry = + static_cast(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 aObj, NPObject* npobj, + JS::Handle id, + NPVariant* getPropertyResult, + JS::MutableHandle vp) { + if (!npobj || !npobj->_class || !npobj->_class->getProperty || + !npobj->_class->invoke) { + ThrowJSExceptionASCII(cx, "Bad NPObject"); + + return false; + } + + NPObjectMemberPrivate* memberPrivate = new NPObjectMemberPrivate; + + JS::Rooted obj(cx, aObj); + + JS::Rooted 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 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 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 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 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 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 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 mJSObj; + NPP mNpp; +}; + +class nsJSObjWrapper : public NPObject { + public: + JS::Heap 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 mJSObjGlobal; + const NPP mNpp; + bool mDestroyPending; + + static NPObject* GetNewOrUsed(NPP npp, JS::Handle obj, + JS::Handle 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 + +#ifdef MOZ_WIDGET_COCOA +# include +# include +# include +# include "nsCocoaFeatures.h" +# include "PluginUtilsOSX.h" +#endif + +// needed for nppdf plugin +#if (MOZ_WIDGET_GTK) +# include +# include +#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 +# 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 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 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(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 pluginHostCOM = + do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); + nsPluginHost* pluginHost = static_cast(pluginHostCOM.get()); + if (!pluginHost) { + return NPERR_GENERIC_ERROR; + } + + RefPtr 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 owner = inst->GetOwner(); + NS_ENSURE_TRUE(owner, nullptr); + + nsCOMPtr 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 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 outer = doc->GetWindow(); + NS_ENSURE_TRUE(outer, nullptr); + + JS::Rooted windowProxy( + dom::RootingCx(), nsGlobalWindowOuter::Cast(outer)->GetGlobalJSObject()); + JS::Rooted 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(npp->ndata); + if (!inst) return nullptr; + + RefPtr 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 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 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 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 rval(cx); + JS::RootedVector 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 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(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 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 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(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 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 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(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 pluginHostCOM( + do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); + nsPluginHost* pluginHost = static_cast(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(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 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 nsNPAPIPluginInstance::GetDOMWindow() { + if (!mOwner) return nullptr; + + RefPtr kungFuDeathGrip(mOwner); + + nsCOMPtr doc; + kungFuDeathGrip->GetDocument(getter_AddRefs(doc)); + if (!doc) return nullptr; + + RefPtr window = doc->GetWindow(); + + return window.forget(); +} + +nsresult nsNPAPIPluginInstance::GetTagType(nsPluginTagType* result) { + if (!mOwner) { + return NS_ERROR_FAILURE; + } + + return mOwner->GetTagType(result); +} + +nsTArray* +nsNPAPIPluginInstance::StreamListeners() { + return &mStreamListeners; +} + +nsTArray* +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 attributes; + nsTArray 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 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 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(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 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 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(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 doc; + mOwner->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + + nsCOMPtr domwindow = doc->GetWindow(); + NS_ENSURE_TRUE(domwindow, NS_ERROR_FAILURE); + + nsCOMPtr docShell = domwindow->GetDocShell(); + nsCOMPtr 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 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(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 +#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(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 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 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* StreamListeners(); + + nsTArray* 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 mPlugin; + + nsTArray mStreamListeners; + + nsTArray mFileCachedStreamListeners; + + nsTArray 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 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 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 +#include + +nsNPAPIStreamWrapper::nsNPAPIStreamWrapper( + nsIOutputStream* outputStream, + nsNPAPIPluginStreamListener* streamListener) { + mOutputStream = outputStream; + mStreamListener = streamListener; + + memset(&mNPStream, 0, sizeof(mNPStream)); + mNPStream.ndata = static_cast(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* 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 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* 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 oldHttpChannel = do_QueryInterface(oldChannel); + nsCOMPtr 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 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(status), + mNPStreamWrapper->mNPStream.notifyData), + mInst, NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO); +#else + (*pluginFunctions->urlredirectnotify)( + npp, spec.get(), static_cast(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 + 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 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 mDataPumpTimer; + nsCOMPtr mHTTPRedirectCallback; + StreamStopMode mStreamStopMode; + nsresult mPendingStopBindingStatus; + + public: + RefPtr 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 +#include +#include +#include +#include +#include +#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 +# include +#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::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 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 obsService = + mozilla::services::GetObserverService(); + if (obsService) { + obsService->NotifyObservers( + nullptr, "plugin-blocklist-updates-finished", nullptr); + } + } + } + + void ResolvedCallback(JSContext* aCx, JS::Handle 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 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 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 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::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 prev; + RefPtr next; + + for (RefPtr 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 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(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 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(pluginInst); + + nsCOMPtr postStream; + char* dataToPost; + uint32_t newDataToPostLen; + ParsePostBufferToFixHeaders(postData, postDataLen, &dataToPost, + &newDataToPostLen); + if (!dataToPost) return NS_ERROR_UNEXPECTED; + + nsCOMPtr 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 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, 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 instanceOwner = new nsPluginInstanceOwner(); + if (!instanceOwner) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr ourContent = + do_QueryInterface(static_cast(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 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(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; + aOwner->GetDocument(getter_AddRefs(document)); + + nsCOMPtr 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 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 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(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 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 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 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 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>& 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>& 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& 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 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 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 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 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 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(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& sites, + nsTArray& 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 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 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 aInitDictionary, + JSContext* aCx, nsIFakePluginTag** aResult) { + FakePluginTagInit initDictionary; + if (!initDictionary.Init(aCx, aInitDictionary)) { + return NS_ERROR_FAILURE; + } + + RefPtr 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 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 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 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(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& 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 callback; + nsTArray 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 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(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 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& sites) override { + retVal = HandleGetSites(sites); + keepWaiting = false; + return NS_OK; + } + + nsresult HandleGetSites(nsTArray& 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 matches; + nsresult rv = host->EnumerateSiteData(domain, sites, matches, true); + NS_ENSURE_SUCCESS(rv, rv); + + result = !matches.IsEmpty(); + return NS_OK; + } + + nsCString domain; + RefPtr 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(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 closure(new GetSitesClosure(domain, this)); + rv = library->NPP_GetSitesWithData( + nsCOMPtr(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 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 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; + 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 GetProfileChangeTeardownPhase() { + nsCOMPtr asyncShutdownSvc = + services::GetAsyncShutdownService(); + MOZ_ASSERT(asyncShutdownSvc); + if (NS_WARN_IF(!asyncShutdownSvc)) { + return nullptr; + } + + nsCOMPtr shutdownPhase; + DebugOnly 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& aPlugins, + nsTArray& aFakePlugins) { + MOZ_ASSERT(XRE_IsContentProcess()); + + nsTArray plugins; + + nsTArray 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 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 obsService = + mozilla::services::GetObserverService(); + if (obsService) { + obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr); + } + } + + mPluginsLoaded = true; + return NS_OK; +} + +nsresult nsPluginHost::UpdateCachedSerializablePluginList() { + nsTArray> plugins; + GetPlugins(plugins, true); + mSerializablePlugins.Clear(); + mSerializableFakePlugins.Clear(); + + for (size_t i = 0; i < plugins.Length(); i++) { + nsCOMPtr basetag = plugins[i]; + + nsCOMPtr 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(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(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 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 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 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 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 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 listenerPeer = + new nsPluginStreamListenerPeer(); + NS_ENSURE_TRUE(listenerPeer, NS_ERROR_OUT_OF_MEMORY); + + rv = listenerPeer->Initialize(url, aInstance, aListener); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr element; + nsCOMPtr doc; + if (owner) { + owner->GetDOMElement(getter_AddRefs(element)); + owner->GetDocument(getter_AddRefs(doc)); + } + + NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); + + nsCOMPtr 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 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 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 referer; + dom::ReferrerPolicy referrerPolicy = dom::ReferrerPolicy::_empty; + + nsCOMPtr 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 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 postDataSeekable( + do_QueryInterface(aPostStream)); + if (postDataSeekable) + postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + + nsCOMPtr 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 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 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 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 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(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 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 wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!wm) return; + + nsCOMPtr windowList; + wm->GetAppWindowEnumerator(nullptr, getter_AddRefs(windowList)); + if (!windowList) return; + + bool haveWindows; + do { + windowList->HasMoreElements(&haveWindows); + if (!haveWindows) return; + + nsCOMPtr supportsWindow; + windowList->GetNext(getter_AddRefs(supportsWindow)); + nsCOMPtr baseWin(do_QueryInterface(supportsWindow)); + if (baseWin) { + nsCOMPtr 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 obsService = + mozilla::services::GetObserverService(); + nsCOMPtr 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 domElement; + instance->GetDOMElement(getter_AddRefs(domElement)); + nsCOMPtr 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>* 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 domElement; + instance->GetDOMElement(getter_AddRefs(domElement)); + nsCOMPtr 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 { + public: + explicit nsPluginDestroyRunnable(nsNPAPIPluginInstance* aInstance) + : Runnable("nsPluginDestroyRunnable"), mInstance(aInstance) { + sRunnableList.insertBack(this); + } + + ~nsPluginDestroyRunnable() override { this->remove(); } + + NS_IMETHOD Run() override { + RefPtr 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 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 mInstance; + + static mozilla::LinkedList sRunnableList; +}; + +mozilla::LinkedList + nsPluginDestroyRunnable::sRunnableList; + +mozilla::LinkedList PluginDestructionGuard::sList; + +PluginDestructionGuard::PluginDestructionGuard(nsNPAPIPluginInstance* aInstance) + : mInstance(aInstance) { + Init(); +} + +PluginDestructionGuard::PluginDestructionGuard(NPP npp) + : mInstance(npp ? static_cast(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 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 +# 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 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>& 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>* 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& sites, + nsTArray& result, bool firstMatchOnly); + + nsresult UpdateCachedSerializablePluginList(); + nsresult SendPluginsToContent(mozilla::dom::ContentParent* parent); + nsresult SetPluginsInContent( + uint32_t aPluginEpoch, nsTArray& aPlugins, + nsTArray& 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& 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 mPlugins; + + nsTArray> mFakePlugins; + + AutoTArray mSerializablePlugins; + nsTArray 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> 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 mRegKeyHKLM; + nsCOMPtr mRegKeyHKCU; +#endif + + nsCOMPtr mTLDService; + nsCOMPtr 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 sInst; +}; + +class PluginDestructionGuard + : public mozilla::LinkedListElement { + 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 mInstance; + bool mDelayedDestroy; + + static mozilla::LinkedList 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 +# 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 +# include +# include "mozilla/widget/WinMessages.h" +#endif // #ifdef XP_WIN + +#ifdef MOZ_WIDGET_GTK +# include +# include +#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 mContent; + bool mFinished; +}; + +void nsPluginInstanceOwner::NotifyPaintWaiter(nsDisplayListBuilder* aBuilder) { + // This is notification for reftests about async plugin paint start + if (!mWaitingForPaint && !IsUpToDate() && + aBuilder->ShouldSyncDecodeImages()) { + nsCOMPtr content = do_QueryReferent(mContent); + nsCOMPtr 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 nsPluginInstanceOwner::GetImageContainer() { + if (!mInstance) return nullptr; + + RefPtr 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 +nsPluginInstanceOwner::BeginUpdateBackground(const nsIntRect& aRect) { + nsIntRect rect = aRect; + RefPtr 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 pluginHostCOM = + do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); + mPluginHost = static_cast(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 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 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 doc; + GetDocument(getter_AddRefs(doc)); + if (doc) { + if (nsCOMPtr domWindow = doc->GetWindow()) { + nsCOMPtr 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 doc; + nsresult rv = GetDocument(getter_AddRefs(doc)); + nsCOMPtr pDoc(do_QueryInterface(doc)); + + if (pDoc) { + *aMode = NP_FULL; + } else { + *aMode = NP_EMBED; + } + + return rv; +} + +void nsPluginInstanceOwner::GetAttributes( + nsTArray& attributes) { + nsCOMPtr content = do_QueryReferent(mContent); + nsObjectLoadingContent* loadingContent = + static_cast(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 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 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 uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, GetBaseURI()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsCOMPtr headersDataStream; + if (aPostStream && aHeadersData) { + if (!aHeadersDataLen) return NS_ERROR_UNEXPECTED; + + nsCOMPtr 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 triggeringPrincipal; + if (!aDoCheckLoadURIChecks) { + mozilla::OriginAttributes attrs = + BasePrincipal::Cast(content->NodePrincipal())->OriginAttributesRef(); + triggeringPrincipal = BasePrincipal::CreateContentPrincipal(uri, attrs); + } else { + bool useParentContentPrincipal = false; + nsCOMPtr 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 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 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 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 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 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 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 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 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(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(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(aWindowToAdopt)); + return NS_OK; + } + + // Otherwise ask the topmost document window to adopt. + nsCOMPtr 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(aWindowToAdopt)); + return NS_OK; +} + +bool nsPluginInstanceOwner::GetCompositionString(uint32_t aType, + nsTArray* aDist, + int32_t* aLength) { + // Mark pkugin calls ImmGetCompositionStringW correctly + mGotCompositionData = true; + + RefPtr 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(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 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 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 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 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 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 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 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(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(widget); + PuppetWidget* rootWidget = + static_cast(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 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(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 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& parameters) { + nsCOMPtr content = do_QueryReferent(mContent); + nsObjectLoadingContent* loadingContent = + static_cast(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 nsPluginInstanceOwner::sCATimer; +nsTArray* 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(); + } + + if (sCARefreshListeners->Contains(this)) { + return; + } + + sCARefreshListeners->AppendElement(this); + + if (sCARefreshListeners->Length() == 1) { + nsCOMPtr 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 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 fm = nsFocusManager::GetFocusManager()) { + nsCOMPtr 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 nsPluginInstanceOwner::GetTextComposition() { + if (NS_WARN_IF(!mPluginFrame)) { + return nullptr; + } + + nsCOMPtr widget = GetContainingWidgetIfOffset(); + if (!widget) { + widget = GetRootWidgetForPluginFrame(mPluginFrame); + if (NS_WARN_IF(!widget)) { + return nullptr; + } + } + + RefPtr composition = + IMEStateManager::GetTextCompositionFor(widget); + if (NS_WARN_IF(!composition)) { + return nullptr; + } + + return composition.forget(); +} + +void nsPluginInstanceOwner::HandleNoConsumedCompositionMessage( + WidgetCompositionEvent* aCompositionEvent, const NPEvent* aPluginEvent) { + nsCOMPtr 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 composition = GetTextComposition(); + if (NS_WARN_IF(!composition)) { + return NS_ERROR_FAILURE; + } + TextComposition::CompositionChangeEventHandlingMarker + compositionChangeEventHandlingMarker(composition, compositionEvent); + } + + const NPEvent* pPluginEvent = + static_cast(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 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 focusedFrame; + nsCOMPtr 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(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 content = do_QueryReferent(mContent); + nsCOMPtr 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 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(&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(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(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(pPluginEvent->lParam); + break; + case SPI_SETWHEELSCROLLCHARS: + mWheelScrollChars = static_cast(pPluginEvent->lParam); + break; + default: + break; + } + return nsEventStatus_eConsumeNoDefault; + } + + if (pPluginEvent) { + int16_t response = kNPEventNotHandled; + mInstance->HandleEvent(const_cast(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(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(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 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 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(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(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 parentWidget; + Document* doc = nullptr; + nsCOMPtr 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 window = doc->GetWindow()) { + if (nsCOMPtr 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(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 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 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 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 content = do_QueryReferent(mContent); + + // If we already have a frame that is changing or going away... + if (mPluginFrame) { + if (content && content->OwnerDoc()->GetWindow()) { + nsCOMPtr 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 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 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 +#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& parameters); + void GetAttributes(nsTArray& 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 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 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 BeginUpdateBackground(const nsIntRect& aRect); + void EndUpdateBackground(const nsIntRect& aRect); + + bool UseAsyncRendering(); + + nsIURI* GetBaseURI() const; + + bool GetCompositionString(uint32_t aIndex, nsTArray* 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 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 mInstance; + nsPluginFrame* mPluginFrame; + nsWeakPtr mContent; // WEAK, content owns us + nsCString mDocumentBase; + bool mWidgetCreationComplete; + nsCOMPtr mWidget; + RefPtr mPluginHost; + +#ifdef XP_MACOSX + static mozilla::StaticRefPtr sCATimer; + static nsTArray* 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 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=: + +// Choose the and from this list (no quotes): + +// Log Names +#define NPN_LOG_NAME "PluginNPN" +#define NPP_LOG_NAME "PluginNPP" +#define PLUGIN_LOG_NAME "Plugin" + +// Levels +#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(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& 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& 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 mPluginInstance; + nsCOMPtr 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 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 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 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& 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 mWeakRef; + RefPtr 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 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 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 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 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 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 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 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 + sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType + 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 + sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType + 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(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(sUser32SetWindowLongWHookStub( + hWnd, nIndex, reinterpret_cast(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(sUser32SetWindowLongWHookStub( + hWnd, nIndex, reinterpret_cast(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 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& 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 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 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 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 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 callbacks; + channel->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (callbacks) mWeakPtrChannelCallbacks = do_GetWeakReference(callbacks); + + nsCOMPtr 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 fileChannel = do_QueryInterface(channel); + if (fileChannel) { + // file does not exist + mRequestFailed = true; + return NS_ERROR_FAILURE; + } + mLength = 0; + } else { + mLength = uint32_t(length); + } + + nsCOMPtr 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(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 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 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(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 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 streamListener; + rv = mPluginInstance->NewStreamListener(nullptr, nullptr, + getter_AddRefs(streamListener)); + if (NS_FAILED(rv) || !streamListener) { + return NS_ERROR_FAILURE; + } + + mPStreamListener = + static_cast(streamListener.get()); + } + + mPStreamListener->SetStreamListenerPeer(this); + + // get httpChannel to retrieve some info we need for nsIPluginStreamInfo setup + nsCOMPtr channel = do_QueryInterface(request); + nsCOMPtr 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 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(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 owner = mPluginInstance->GetOwner(); + if (!owner) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr 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 webNav = do_GetInterface(window); + nsCOMPtr 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(listener))), + mParent(parent), + mOldChannel(oldChannel), + mNewChannel(newChannel) {} + + ChannelRedirectProxyCallback() = default; + + NS_DECL_ISUPPORTS + + NS_IMETHOD OnRedirectVerifyCallback(nsresult aResult) override { + if (NS_SUCCEEDED(aResult)) { + nsCOMPtr listener = do_QueryReferent(mWeakListener); + if (listener) + static_cast(listener.get()) + ->ReplaceRequest(mOldChannel, mNewChannel); + } + return mParent->OnRedirectVerifyCallback(aResult); + } + + private: + virtual ~ChannelRedirectProxyCallback() = default; + + nsWeakPtr mWeakListener; + nsCOMPtr mParent; + nsCOMPtr mOldChannel; + nsCOMPtr 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 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 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 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 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 requestsCopy(mRequests); + for (int32_t i = 0; i < requestsCopy.Count(); ++i) + requestsCopy[i]->Cancel(status); + } + + void SuspendRequests() { + nsCOMArray requestsCopy(mRequests); + for (int32_t i = 0; i < requestsCopy.Count(); ++i) + requestsCopy[i]->Suspend(); + } + + void ResumeRequests() { + nsCOMArray 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 mURL; + nsCString + mURLSpec; // Have to keep this member because GetURL hands out char* + RefPtr 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 mRequest; + uint32_t mModified; + RefPtr mPluginInstance; + int32_t mStreamOffset; + bool mStreamComplete; + + public: + int32_t mPendingRequests; + nsWeakPtr mWeakPtrChannelCallbacks; + nsWeakPtr mWeakPtrChannelLoadGroup; + nsCOMArray 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 +#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& aExtensions, + const nsTArray& 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 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& aMimeTypes, + const nsTArray& aMimeDescriptions, + const nsTArray& 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(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 aMimeTypes, + nsTArray aMimeDescriptions, + nsTArray 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 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& aResults) { + aResults = mMimeTypes.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetMimeDescriptions(nsTArray& aResults) { + aResults = mMimeDescriptions.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsPluginTag::GetExtensions(nsTArray& 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&& aHandlerURI, + const char* aName, const char* aDescription, + const nsTArray& aMimeTypes, + const nsTArray& aMimeDescriptions, + const nsTArray& 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 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& aResults) { + aResults = mMimeTypes.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetMimeDescriptions(nsTArray& aResults) { + aResults = mMimeDescriptions.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsFakePluginTag::GetExtensions(nsTArray& 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& aMimeTypes, + const nsTArray& aMimeDescriptions, + const nsTArray& aExtensions); + + virtual bool IsEnabled() = 0; + virtual const nsCString& GetNiceFileName() = 0; + + const nsCString& Name() const { return mName; } + const nsCString& Description() const { return mDescription; } + + const nsTArray& MimeTypes() const { return mMimeTypes; } + + const nsTArray& MimeDescriptions() const { + return mMimeDescriptions; + } + + const nsTArray& 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 mMimeTypes; // UTF-8 + nsTArray mMimeDescriptions; // UTF-8 + nsTArray 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 aMimeTypes, + nsTArray aMimeDescriptions, + nsTArray 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 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 mPlugin; + bool mIsFlashPlugin; + bool mSupportsAsyncRender; + nsCString mFullPath; // UTF-8 + int64_t mLastModifiedTime; + nsCOMPtr 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&& aHandlerURI, + const char* aName, const char* aDescription, + const nsTArray& aMimeTypes, + const nsTArray& aMimeDescriptions, + const nsTArray& 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 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 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 +#include +#include +#include + +#include +#include +#include +#include + +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(moz_xmalloc(bufferLength)); + + if (!::CFStringGetCString(cfString, newBuffer, bufferLength, + kCFStringEncodingUTF8)) { + free(newBuffer); + return nullptr; + } + + newBuffer = + static_cast(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(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(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(propertyList), CFSTR("WebPluginMIMETypes")); + if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || + ::CFDictionaryGetCount(static_cast(mimeTypes)) == 0) { + return nullptr; + } + + return static_cast(::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(mimeTypes)) == 0) + return; + mimeDict = static_cast(::CFRetain(mimeTypes)); + } + + AutoCFTypeObject mimeDictAutorelease(mimeDict); + int mimeDictKeyCount = ::CFDictionaryGetCount(mimeDict); + + // Allocate memory for mime data + int mimeDataArraySize = mimeDictKeyCount * sizeof(char*); + info.fMimeTypeArray = static_cast(moz_xmalloc(mimeDataArraySize)); + memset(info.fMimeTypeArray, 0, mimeDataArraySize); + info.fExtensionArray = static_cast(moz_xmalloc(mimeDataArraySize)); + memset(info.fExtensionArray, 0, mimeDataArraySize); + info.fMimeDescriptionArray = + static_cast(moz_xmalloc(mimeDataArraySize)); + memset(info.fMimeDescriptionArray, 0, mimeDataArraySize); + + // Allocate memory for mime dictionary keys and values + mozilla::UniquePtr keys(new CFTypeRef[mimeDictKeyCount]); + mozilla::UniquePtr 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(mimeDict))) { + continue; + } + info.fMimeTypeArray[info.fVariantCount] = + CFStringRefToUTF8Buffer(static_cast(mimeString)); + if (!info.fMimeTypeArray[info.fVariantCount]) { + continue; + } + CFTypeRef extensions = ::CFDictionaryGetValue( + static_cast(mimeDict), CFSTR("WebPluginExtensions")); + if (extensions && ::CFGetTypeID(extensions) == ::CFArrayGetTypeID()) { + int extensionCount = + ::CFArrayGetCount(static_cast(extensions)); + CFMutableStringRef extensionList = + ::CFStringCreateMutable(kCFAllocatorDefault, 0); + for (int j = 0; j < extensionCount; j++) { + CFTypeRef extension = + ::CFArrayGetValueAtIndex(static_cast(extensions), j); + if (extension && ::CFGetTypeID(extension) == ::CFStringGetTypeID()) { + if (j > 0) ::CFStringAppend(extensionList, CFSTR(",")); + ::CFStringAppend(static_cast(extensionList), + static_cast(extension)); + } + } + info.fExtensionArray[info.fVariantCount] = + CFStringRefToUTF8Buffer(static_cast(extensionList)); + ::CFRelease(extensionList); + } + CFTypeRef description = + ::CFDictionaryGetValue(static_cast(mimeDict), + CFSTR("WebPluginTypeDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fMimeDescriptionArray[info.fVariantCount] = + CFStringRefToUTF8Buffer(static_cast(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(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(name)); + } + + // Get fDescription + if (bundle) { + CFTypeRef description = ::CFBundleGetValueForInfoDictionaryKey( + bundle, CFSTR("WebPluginDescription")); + if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID()) + info.fDescription = + CFStringRefToUTF8Buffer(static_cast(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(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(moz_xmalloc(variantCount * sizeof(char*))); + info.fExtensionArray = + static_cast(moz_xmalloc(variantCount * sizeof(char*))); + if (mi.infoStrings) { + info.fMimeDescriptionArray = + static_cast(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 +#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 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 bCheck = SetCurrentDirectoryW(aOrigDir); + NS_ASSERTION(bCheck, "Error in Loading plugin"); + } + + return rv; +} + +/** + * Obtains all of the information currently available for this plugin. + */ +nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, + PRLibrary** outLibrary) { + *outLibrary = nullptr; + + nsresult rv = NS_OK; + DWORD zerome, versionsize; + void* verbuf = nullptr; + + if (!mPlugin) return NS_ERROR_NULL_POINTER; + + nsAutoString fullPath; + if (NS_FAILED(rv = mPlugin->GetPath(fullPath))) return rv; + + if (!CanLoadPlugin(fullPath.get())) return NS_ERROR_FAILURE; + + nsAutoString fileName; + if (NS_FAILED(rv = mPlugin->GetLeafName(fileName))) return rv; + + LPCWSTR lpFilepath = fullPath.get(); + + versionsize = ::GetFileVersionInfoSizeW(lpFilepath, &zerome); + + if (versionsize > 0) verbuf = malloc(versionsize); + if (!verbuf) return NS_ERROR_OUT_OF_MEMORY; + + if (::GetFileVersionInfoW(lpFilepath, 0, versionsize, verbuf)) { + // TODO: get appropriately-localized info from plugin file + UINT lang = 1033; // language = English, 0x409 + UINT cp = 1252; // codepage = Western, 0x4E4 + info.fName = GetKeyValue(verbuf, L"ProductName", lang, cp); + info.fDescription = GetKeyValue(verbuf, L"FileDescription", lang, cp); + info.fSupportsAsyncRender = + GetBooleanFlag(verbuf, L"AsyncDrawingSupport", lang, cp); + + char* mimeType = GetKeyValue(verbuf, L"MIMEType", lang, cp); + char* mimeDescription = GetKeyValue(verbuf, L"FileOpenName", lang, cp); + char* extensions = GetKeyValue(verbuf, L"FileExtents", lang, cp); + + info.fVariantCount = CalculateVariantCount(mimeType); + info.fMimeTypeArray = MakeStringArray(info.fVariantCount, mimeType); + info.fMimeDescriptionArray = + MakeStringArray(info.fVariantCount, mimeDescription); + info.fExtensionArray = MakeStringArray(info.fVariantCount, extensions); + info.fFullPath = PL_strdup(NS_ConvertUTF16toUTF8(fullPath).get()); + info.fFileName = PL_strdup(NS_ConvertUTF16toUTF8(fileName).get()); + info.fVersion = GetVersion(verbuf); + + PL_strfree(mimeType); + PL_strfree(mimeDescription); + PL_strfree(extensions); + } else { + rv = NS_ERROR_FAILURE; + } + + free(verbuf); + + return rv; +} + +nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info) { + if (info.fName) PL_strfree(info.fName); + + if (info.fDescription) PL_strfree(info.fDescription); + + if (info.fMimeTypeArray) + FreeStringArray(info.fVariantCount, info.fMimeTypeArray); + + if (info.fMimeDescriptionArray) + FreeStringArray(info.fVariantCount, info.fMimeDescriptionArray); + + if (info.fExtensionArray) + FreeStringArray(info.fVariantCount, info.fExtensionArray); + + if (info.fFullPath) PL_strfree(info.fFullPath); + + if (info.fFileName) PL_strfree(info.fFileName); + + if (info.fVersion) free(info.fVersion); + + ZeroMemory((void*)&info, sizeof(info)); + + return NS_OK; +} diff --git a/dom/plugins/base/nspluginroot.idl b/dom/plugins/base/nspluginroot.idl new file mode 100644 index 0000000000..6c931d3711 --- /dev/null +++ b/dom/plugins/base/nspluginroot.idl @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +native REFNSIID(REFNSIID); +native nativeVoid(void *); +native nativeChar(const char * *); +[ptr] native constVoidPtr(const void); +[ref] native PRUint32Ref(uint32_t); +[ref] native PRUint16Ref(uint16_t); +[ref] native constCharStarConstStar(const char* const*); +[ptr] native constCharPtr(const char); +[ref] native constCharStarRef(const char *); + +native NPWindowType(NPWindowType); +native NPWindow(NPWindow); +[ptr] native NPWindowPtr(NPWindow); +[ref] native NPWindowStarRef(NPWindow *); +[ptr] native NPPrintPtr(NPPrint); +native NPByteRange(NPByteRange); +[ptr] native NPByteRangePtr(NPByteRange); +native NPPVariable(NPPVariable); +native NPNVariable(NPNVariable); +[ptr] native NPRectPtr(NPRect); +native NPRegion(NPRegion); +native NPDrawingModel(NPDrawingModel); +native NPEventModel(NPEventModel); + +[ptr] native JRIEnvPtr(JRIEnv); +native jref(jref); diff --git a/dom/plugins/ipc/AStream.h b/dom/plugins/ipc/AStream.h new file mode 100644 index 0000000000..06ac822320 --- /dev/null +++ b/dom/plugins/ipc/AStream.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_AStream_h +#define mozilla_plugins_AStream_h + +namespace mozilla { +namespace plugins { + +/** + * When we are passed NPStream->{ndata,pdata} in {NPN,NPP}_DestroyStream, we + * don't know whether it's a plugin stream or a browser stream. This abstract + * class lets us cast to the right type of object and send the appropriate + * message. + */ +class AStream { + public: + virtual bool IsBrowserStream() = 0; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/BrowserStreamChild.cpp b/dom/plugins/ipc/BrowserStreamChild.cpp new file mode 100644 index 0000000000..21e6706ba3 --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamChild.cpp @@ -0,0 +1,217 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/plugins/BrowserStreamChild.h" + +#include "mozilla/Attributes.h" +#include "mozilla/plugins/PluginInstanceChild.h" +#include "mozilla/plugins/StreamNotifyChild.h" + +namespace mozilla::plugins { + +BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance, + const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + StreamNotifyChild* notifyData, + const nsCString& headers) + : mInstance(instance), + mStreamStatus(kStreamOpen), + mDestroyPending(NOT_DESTROYED), + mNotifyPending(false), + mInstanceDying(false), + mState(CONSTRUCTING), + mURL(url), + mHeaders(headers), + mStreamNotify(notifyData), + mDeliveryTracker(this) { + PLUGIN_LOG_DEBUG(("%s (%s, %i, %i, %p, %s)", FULLFUNCTION, url.get(), length, + lastmodified, (void*)notifyData, headers.get())); + + AssertPluginThread(); + + memset(&mStream, 0, sizeof(mStream)); + mStream.ndata = static_cast(this); + mStream.url = NullableStringGet(mURL); + mStream.end = length; + mStream.lastmodified = lastmodified; + mStream.headers = NullableStringGet(mHeaders); + if (notifyData) { + mStream.notifyData = notifyData->mClosure; + notifyData->SetAssociatedStream(this); + } +} + +NPError BrowserStreamChild::StreamConstructed(const nsCString& mimeType, + const bool& seekable, + uint16_t* stype) { + NPError rv = NPERR_NO_ERROR; + + *stype = NP_NORMAL; + rv = mInstance->mPluginIface->newstream( + &mInstance->mData, const_cast(NullableStringGet(mimeType)), + &mStream, seekable, stype); + + // NP_NORMAL is the only permissible stream type + if (*stype != NP_NORMAL) { + rv = NPERR_INVALID_PARAM; + // The plugin thinks the stream is alive, so we kill it explicitly + (void)mInstance->mPluginIface->destroystream(&mInstance->mData, &mStream, + NPRES_NETWORK_ERR); + } + + if (rv != NPERR_NO_ERROR) { + mState = DELETING; + if (mStreamNotify) { + mStreamNotify->SetAssociatedStream(nullptr); + mStreamNotify = nullptr; + } + } else { + mState = ALIVE; + } + + return rv; +} + +BrowserStreamChild::~BrowserStreamChild() { + NS_ASSERTION(!mStreamNotify, "Should have nulled it by now!"); +} + +mozilla::ipc::IPCResult BrowserStreamChild::RecvWrite(const int32_t& offset, + const uint32_t& newlength, + const Buffer& data) { + PLUGIN_LOG_DEBUG_FUNCTION; + + AssertPluginThread(); + + if (ALIVE != mState) + MOZ_CRASH("Unexpected state: received data after NPP_DestroyStream?"); + + if (kStreamOpen != mStreamStatus) return IPC_OK(); + + mStream.end = newlength; + + NS_ASSERTION(data.Length() > 0, "Empty data"); + + PendingData* newdata = mPendingData.AppendElement(); + newdata->offset = offset; + newdata->data = data; + newdata->curpos = 0; + + EnsureDeliveryPending(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserStreamChild::RecvNPP_DestroyStream( + const NPReason& reason) { + PLUGIN_LOG_DEBUG_METHOD; + + if (ALIVE != mState) + MOZ_CRASH("Unexpected state: recevied NPP_DestroyStream twice?"); + + mState = DYING; + mDestroyPending = DESTROY_PENDING; + if (NPRES_DONE != reason) mStreamStatus = reason; + + EnsureDeliveryPending(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserStreamChild::Recv__delete__() { + AssertPluginThread(); + + if (DELETING != mState) MOZ_CRASH("Bad state, not DELETING"); + + return IPC_OK(); +} + +void BrowserStreamChild::EnsureDeliveryPending() { + MessageLoop::current()->PostTask( + mDeliveryTracker.NewRunnableMethod(&BrowserStreamChild::Deliver)); +} + +void BrowserStreamChild::Deliver() { + while (kStreamOpen == mStreamStatus && mPendingData.Length()) { + if (DeliverPendingData() && kStreamOpen == mStreamStatus) { + SetSuspendedTimer(); + return; + } + } + ClearSuspendedTimer(); + + NS_ASSERTION(kStreamOpen != mStreamStatus || 0 == mPendingData.Length(), + "Exit out of the data-delivery loop with pending data"); + mPendingData.Clear(); + + if (DESTROY_PENDING == mDestroyPending) { + mDestroyPending = DESTROYED; + if (mState != DYING) MOZ_CRASH("mDestroyPending but state not DYING"); + + NS_ASSERTION(NPRES_DONE != mStreamStatus, "Success status set too early!"); + if (kStreamOpen == mStreamStatus) mStreamStatus = NPRES_DONE; + + (void)mInstance->mPluginIface->destroystream(&mInstance->mData, &mStream, + mStreamStatus); + } + if (DESTROYED == mDestroyPending && mNotifyPending) { + NS_ASSERTION(mStreamNotify, "mDestroyPending but no mStreamNotify?"); + + mNotifyPending = false; + mStreamNotify->NPP_URLNotify(mStreamStatus); + delete mStreamNotify; + mStreamNotify = nullptr; + } + if (DYING == mState && DESTROYED == mDestroyPending && !mStreamNotify && + !mInstanceDying) { + SendStreamDestroyed(); + mState = DELETING; + } +} + +bool BrowserStreamChild::DeliverPendingData() { + if (mState != ALIVE && mState != DYING) MOZ_CRASH("Unexpected state"); + + NS_ASSERTION(mPendingData.Length(), "Called from Deliver with empty pending"); + + while (mPendingData[0].curpos < + static_cast(mPendingData[0].data.Length())) { + int32_t r = + mInstance->mPluginIface->writeready(&mInstance->mData, &mStream); + if (kStreamOpen != mStreamStatus) return false; + if (0 == r) // plugin wants to suspend delivery + return true; + + r = mInstance->mPluginIface->write( + &mInstance->mData, &mStream, + mPendingData[0].offset + mPendingData[0].curpos, // offset + mPendingData[0].data.Length() - mPendingData[0].curpos, // length + const_cast(mPendingData[0].data.BeginReading() + + mPendingData[0].curpos)); + if (kStreamOpen != mStreamStatus) return false; + if (0 == r) return true; + if (r < 0) { // error condition + mStreamStatus = NPRES_NETWORK_ERR; + + // Set up stream destruction + EnsureDeliveryPending(); + return false; + } + mPendingData[0].curpos += r; + } + mPendingData.RemoveElementAt(0); + return false; +} + +void BrowserStreamChild::SetSuspendedTimer() { + if (mSuspendedTimer.IsRunning()) return; + mSuspendedTimer.Start(base::TimeDelta::FromMilliseconds( + 100), // 100ms copied from Mozilla plugin host + this, &BrowserStreamChild::Deliver); +} + +void BrowserStreamChild::ClearSuspendedTimer() { mSuspendedTimer.Stop(); } + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/BrowserStreamChild.h b/dom/plugins/ipc/BrowserStreamChild.h new file mode 100644 index 0000000000..5df88d9aab --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamChild.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_BrowserStreamChild_h +#define mozilla_plugins_BrowserStreamChild_h 1 + +#include "mozilla/plugins/PBrowserStreamChild.h" +#include "mozilla/plugins/AStream.h" +#include "base/task.h" +#include "base/timer.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceChild; +class StreamNotifyChild; + +class BrowserStreamChild : public PBrowserStreamChild, public AStream { + public: + BrowserStreamChild(PluginInstanceChild* instance, const nsCString& url, + const uint32_t& length, const uint32_t& lastmodified, + StreamNotifyChild* notifyData, const nsCString& headers); + virtual ~BrowserStreamChild(); + + virtual bool IsBrowserStream() override { return true; } + + NPError StreamConstructed(const nsCString& mimeType, const bool& seekable, + uint16_t* stype); + + mozilla::ipc::IPCResult RecvWrite(const int32_t& offset, + const uint32_t& newsize, + const Buffer& data); + mozilla::ipc::IPCResult RecvNPP_DestroyStream(const NPReason& reason); + virtual mozilla::ipc::IPCResult Recv__delete__() override; + + void EnsureCorrectInstance(PluginInstanceChild* i) { + if (i != mInstance) MOZ_CRASH("Incorrect stream instance"); + } + void EnsureCorrectStream(NPStream* s) { + if (s != &mStream) MOZ_CRASH("Incorrect stream data"); + } + + void NotifyPending() { + NS_ASSERTION(!mNotifyPending, "Pending twice?"); + mNotifyPending = true; + EnsureDeliveryPending(); + } + + /** + * During instance destruction, artificially cancel all outstanding streams. + * + * @return false if we are already in the DELETING state. + */ + bool InstanceDying() { + if (DELETING == mState) return false; + + mInstanceDying = true; + return true; + } + + void FinishDelivery() { + NS_ASSERTION(mInstanceDying, "Should only be called after InstanceDying"); + NS_ASSERTION(DELETING != mState, "InstanceDying didn't work?"); + mStreamStatus = NPRES_USER_BREAK; + Deliver(); + NS_ASSERTION(!mStreamNotify, "Didn't deliver NPN_URLNotify?"); + } + + private: + friend class StreamNotifyChild; + + /** + * Post an event to ensure delivery of pending data/destroy/urlnotify events + * outside of the current RPC stack. + */ + void EnsureDeliveryPending(); + + /** + * Deliver data, destruction, notify scheduling + * or cancelling the suspended timer as needed. + */ + void Deliver(); + + /** + * Deliver one chunk of pending data. + * @return true if the plugin indicated a pause was necessary + */ + bool DeliverPendingData(); + + void SetSuspendedTimer(); + void ClearSuspendedTimer(); + + PluginInstanceChild* mInstance; + NPStream mStream; + + static const NPReason kStreamOpen = -1; + + /** + * The plugin's notion of whether a stream has been "closed" (no more + * data delivery) differs from the plugin host due to asynchronous delivery + * of data and stream destruction. While the plugin-visible stream is open, + * mStreamStatus should be kStreamOpen (-1). mStreamStatus will be a + * failure code if either the parent or child indicates stream failure. + */ + NPReason mStreamStatus; + + /** + * Delivery of NPP_DestroyStream and NPP_URLNotify must be postponed until + * all data has been delivered. + */ + enum { + NOT_DESTROYED, // NPP_DestroyStream not yet received + DESTROY_PENDING, // NPP_DestroyStream received, not yet delivered + DESTROYED // NPP_DestroyStream delivered, NPP_URLNotify may still be + // pending + } mDestroyPending; + bool mNotifyPending; + + // When NPP_Destroy is called for our instance (manager), this flag is set + // cancels the stream and avoids sending StreamDestroyed. + bool mInstanceDying; + + enum { CONSTRUCTING, ALIVE, DYING, DELETING } mState; + nsCString mURL; + nsCString mHeaders; + StreamNotifyChild* mStreamNotify; + + struct PendingData { + int32_t offset; + Buffer data; + int32_t curpos; + }; + nsTArray mPendingData; + + /** + * Asynchronous RecvWrite messages are never delivered to the plugin + * immediately, because that may be in the midst of an unexpected RPC + * stack frame. It instead posts a runnable using this tracker to cancel + * in case we are destroyed. + */ + ScopedRunnableMethodFactory mDeliveryTracker; + base::RepeatingTimer mSuspendedTimer; +}; + +} // namespace plugins +} // namespace mozilla + +#endif /* mozilla_plugins_BrowserStreamChild_h */ diff --git a/dom/plugins/ipc/BrowserStreamParent.cpp b/dom/plugins/ipc/BrowserStreamParent.cpp new file mode 100644 index 0000000000..f1a5a0e2dc --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamParent.cpp @@ -0,0 +1,85 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BrowserStreamParent.h" +#include "PluginInstanceParent.h" +#include "nsNPAPIPlugin.h" + +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" + +// How much data are we willing to send across the wire +// in one chunk? +static const int32_t kSendDataChunk = 0xffff; + +namespace mozilla::plugins { + +BrowserStreamParent::BrowserStreamParent(PluginInstanceParent* npp, + NPStream* stream) + : mNPP(npp), mStream(stream), mState(INITIALIZING) { + mStream->pdata = static_cast(this); + nsNPAPIStreamWrapper* wrapper = + reinterpret_cast(mStream->ndata); + if (wrapper) { + mStreamListener = wrapper->GetStreamListener(); + } +} + +BrowserStreamParent::~BrowserStreamParent() { mStream->pdata = nullptr; } + +void BrowserStreamParent::ActorDestroy(ActorDestroyReason aWhy) { + // Implement me! Bug 1005159 +} + +void BrowserStreamParent::NPP_DestroyStream(NPReason reason) { + NS_ASSERTION(ALIVE == mState || INITIALIZING == mState, + "NPP_DestroyStream called twice?"); + bool stillInitializing = INITIALIZING == mState; + if (stillInitializing) { + mState = DEFERRING_DESTROY; + } else { + mState = DYING; + Unused << SendNPP_DestroyStream(reason); + } +} + +mozilla::ipc::IPCResult BrowserStreamParent::RecvStreamDestroyed() { + if (DYING != mState) { + NS_ERROR("Unexpected state"); + return IPC_FAIL_NO_REASON(this); + } + + mStreamPeer = nullptr; + + mState = DELETING; + IProtocol* mgr = Manager(); + if (!Send__delete__(this)) { + return IPC_FAIL_NO_REASON(mgr); + } + return IPC_OK(); +} + +int32_t BrowserStreamParent::WriteReady() { + if (mState == INITIALIZING) { + return 0; + } + return kSendDataChunk; +} + +int32_t BrowserStreamParent::Write(int32_t offset, int32_t len, void* buffer) { + PLUGIN_LOG_DEBUG_FUNCTION; + + NS_ASSERTION(ALIVE == mState, "Sending data after NPP_DestroyStream?"); + NS_ASSERTION(len > 0, "Non-positive length to NPP_Write"); + + if (len > kSendDataChunk) len = kSendDataChunk; + + return SendWrite(offset, mStream->end, + nsCString(static_cast(buffer), len)) + ? len + : -1; +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/BrowserStreamParent.h b/dom/plugins/ipc/BrowserStreamParent.h new file mode 100644 index 0000000000..492288eb3e --- /dev/null +++ b/dom/plugins/ipc/BrowserStreamParent.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_BrowserStreamParent_h +#define mozilla_plugins_BrowserStreamParent_h + +#include "mozilla/plugins/PBrowserStreamParent.h" +#include "mozilla/plugins/AStream.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsPluginStreamListenerPeer.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceParent; + +class BrowserStreamParent : public PBrowserStreamParent, public AStream { + friend class PluginModuleParent; + friend class PluginInstanceParent; + + public: + BrowserStreamParent(PluginInstanceParent* npp, NPStream* stream); + virtual ~BrowserStreamParent(); + + virtual bool IsBrowserStream() override { return true; } + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual mozilla::ipc::IPCResult RecvStreamDestroyed() override; + + int32_t WriteReady(); + int32_t Write(int32_t offset, int32_t len, void* buffer); + + void NPP_DestroyStream(NPReason reason); + + void SetAlive() { + if (mState == INITIALIZING) { + mState = ALIVE; + } + } + + private: + using PBrowserStreamParent::SendNPP_DestroyStream; + + PluginInstanceParent* mNPP; + NPStream* mStream; + nsCOMPtr mStreamPeer; + RefPtr mStreamListener; + + enum { INITIALIZING, DEFERRING_DESTROY, ALIVE, DYING, DELETING } mState; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/ChildTimer.cpp b/dom/plugins/ipc/ChildTimer.cpp new file mode 100644 index 0000000000..e9a096c62c --- /dev/null +++ b/dom/plugins/ipc/ChildTimer.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ChildTimer.h" +#include "PluginInstanceChild.h" +#include "nsComponentManagerUtils.h" + +namespace mozilla::plugins { + +ChildTimer::ChildTimer(PluginInstanceChild* instance, uint32_t interval, + bool repeat, TimerFunc func) + : mInstance(instance), + mFunc(func), + mRepeating(repeat), + mID(gNextTimerID++) { + mTimer.Start(base::TimeDelta::FromMilliseconds(interval), this, + &ChildTimer::Run); +} + +uint32_t ChildTimer::gNextTimerID = 1; + +void ChildTimer::Run() { + if (!mRepeating) mTimer.Stop(); + mFunc(mInstance->GetNPP(), mID); +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/ChildTimer.h b/dom/plugins/ipc/ChildTimer.h new file mode 100644 index 0000000000..8b9ff72ded --- /dev/null +++ b/dom/plugins/ipc/ChildTimer.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_ChildTimer_h +#define mozilla_plugins_ChildTimer_h + +#include "PluginMessageUtils.h" +#include "npapi.h" +#include "base/timer.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceChild; +typedef void (*TimerFunc)(NPP npp, uint32_t timerID); + +class ChildTimer { + public: + /** + * If initialization failed, ID() will return 0. + */ + ChildTimer(PluginInstanceChild* instance, uint32_t interval, bool repeat, + TimerFunc func); + ~ChildTimer() = default; + + uint32_t ID() const { return mID; } + + class IDComparator { + public: + bool Equals(const UniquePtr& t, uint32_t id) const { + return t->ID() == id; + } + }; + + private: + PluginInstanceChild* mInstance; + TimerFunc mFunc; + bool mRepeating; + uint32_t mID; + base::RepeatingTimer mTimer; + + void Run(); + + static uint32_t gNextTimerID; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_ChildTimer_h diff --git a/dom/plugins/ipc/D3D11SurfaceHolder.cpp b/dom/plugins/ipc/D3D11SurfaceHolder.cpp new file mode 100644 index 0000000000..429f3be673 --- /dev/null +++ b/dom/plugins/ipc/D3D11SurfaceHolder.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsDebug.h" +#include "D3D11SurfaceHolder.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/layers/TextureD3D11.h" +#include + +namespace mozilla { +namespace plugins { + +using namespace mozilla::gfx; +using namespace mozilla::layers; + +D3D11SurfaceHolder::D3D11SurfaceHolder(ID3D11Texture2D* back, + SurfaceFormat format, + const IntSize& size) + : mDevice11(DeviceManagerDx::Get()->GetContentDevice()), + mBack(back), + mFormat(format), + mSize(size) {} + +D3D11SurfaceHolder::~D3D11SurfaceHolder() {} + +bool D3D11SurfaceHolder::IsValid() { + // If a TDR occurred, platform devices will be recreated. + if (DeviceManagerDx::Get()->GetContentDevice() != mDevice11) { + return false; + } + return true; +} + +bool D3D11SurfaceHolder::CopyToTextureClient(TextureClient* aClient) { + MOZ_ASSERT(NS_IsMainThread()); + + D3D11TextureData* data = aClient->GetInternalData()->AsD3D11TextureData(); + if (!data) { + // We don't support this yet. We expect to have a D3D11 compositor, and + // therefore D3D11 surfaces. + NS_WARNING("Plugin DXGI surface has unsupported TextureClient"); + return false; + } + + RefPtr context; + mDevice11->GetImmediateContext(getter_AddRefs(context)); + if (!context) { + NS_WARNING("Could not get an immediate D3D11 context"); + return false; + } + + TextureClientAutoLock autoLock(aClient, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return false; + } + + RefPtr mutex; + HRESULT hr = mBack->QueryInterface(__uuidof(IDXGIKeyedMutex), + (void**)getter_AddRefs(mutex)); + if (FAILED(hr) || !mutex) { + NS_WARNING("Could not acquire an IDXGIKeyedMutex"); + return false; + } + + { + AutoTextureLock lock(mutex, hr); + if (hr == WAIT_ABANDONED || hr == WAIT_TIMEOUT || FAILED(hr)) { + NS_WARNING( + "Could not acquire DXGI surface lock - plugin forgot to release?"); + return false; + } + + context->CopyResource(data->GetD3D11Texture(), mBack); + } + return true; +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/D3D11SurfaceHolder.h b/dom/plugins/ipc/D3D11SurfaceHolder.h new file mode 100644 index 0000000000..4031091f7c --- /dev/null +++ b/dom/plugins/ipc/D3D11SurfaceHolder.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _include_dom_plugins_ipc_D3D11SurfaceHolder_h__ +#define _include_dom_plugins_ipc_D3D11SurfaceHolder_h__ + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace layers { +class D3D11ShareHandleImage; +class TextureClient; +} // namespace layers + +namespace plugins { + +class D3D11SurfaceHolder { + public: + D3D11SurfaceHolder(ID3D11Texture2D* back, gfx::SurfaceFormat format, + const gfx::IntSize& size); + + NS_INLINE_DECL_REFCOUNTING(D3D11SurfaceHolder); + + bool IsValid(); + bool CopyToTextureClient(layers::TextureClient* aClient); + + gfx::SurfaceFormat GetFormat() const { return mFormat; } + const gfx::IntSize& GetSize() const { return mSize; } + + private: + ~D3D11SurfaceHolder(); + + private: + RefPtr mDevice11; + RefPtr mBack; + gfx::SurfaceFormat mFormat; + gfx::IntSize mSize; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // _include_dom_plugins_ipc_D3D11nSurfaceHolder_h__ diff --git a/dom/plugins/ipc/FunctionBroker.cpp b/dom/plugins/ipc/FunctionBroker.cpp new file mode 100644 index 0000000000..9068cf322d --- /dev/null +++ b/dom/plugins/ipc/FunctionBroker.cpp @@ -0,0 +1,1429 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FunctionBroker.h" +#include "FunctionBrokerParent.h" +#include "PluginQuirks.h" + +#if defined(XP_WIN) +# include +# include +# include +#endif // defined(XP_WIN) + +using namespace mozilla; +using namespace mozilla::ipc; +using namespace mozilla::plugins; + +namespace mozilla::plugins { + +template +static bool CheckQuirks(int aQuirks) { + return static_cast(aQuirks & QuirkFlag); +} + +void FreeDestructor(void* aObj) { free(aObj); } + +#if defined(XP_WIN) + +// Specialization of EndpointHandlers for Flash file dialog brokering. +struct FileDlgEHContainer { + template + struct EndpointHandler; +}; + +template <> +struct FileDlgEHContainer::EndpointHandler + : public BaseEndpointHandler> { + using BaseEndpointHandler>::Copy; + + inline static void Copy(OpenFileNameIPC& aDest, const LPOPENFILENAMEW& aSrc) { + aDest.CopyFromOfn(aSrc); + } + inline static void Copy(LPOPENFILENAMEW& aDest, + const OpenFileNameRetIPC& aSrc) { + aSrc.AddToOfn(aDest); + } +}; + +template <> +struct FileDlgEHContainer::EndpointHandler + : public BaseEndpointHandler> { + using BaseEndpointHandler>::Copy; + + inline static void Copy(OpenFileNameRetIPC& aDest, + const LPOPENFILENAMEW& aSrc) { + aDest.CopyFromOfn(aSrc); + } + inline static void Copy(ServerCallData* aScd, LPOPENFILENAMEW& aDest, + const OpenFileNameIPC& aSrc) { + MOZ_ASSERT(!aDest); + ServerCallData::DestructorType* destructor = [](void* aObj) { + OpenFileNameIPC::FreeOfnStrings(static_cast(aObj)); + DeleteDestructor(aObj); + }; + aDest = aScd->Allocate(destructor); + aSrc.AllocateOfnStrings(aDest); + aSrc.AddToOfn(aDest); + } +}; + +// FunctionBroker type that uses FileDlgEHContainer +template +using FileDlgFunctionBroker = + FunctionBroker; + +// Specialization of EndpointHandlers for Flash SSL brokering. +struct SslEHContainer { + template + struct EndpointHandler; +}; + +template <> +struct SslEHContainer::EndpointHandler + : public BaseEndpointHandler> { + using BaseEndpointHandler>::Copy; + + inline static void Copy(uint64_t& aDest, const PSecHandle& aSrc) { + MOZ_ASSERT((aSrc->dwLower == aSrc->dwUpper) && IsOdd(aSrc->dwLower)); + aDest = static_cast(aSrc->dwLower); + } + inline static void Copy(PSecHandle& aDest, const uint64_t& aSrc) { + MOZ_ASSERT(IsOdd(aSrc)); + aDest->dwLower = static_cast(aSrc); + aDest->dwUpper = static_cast(aSrc); + } + inline static void Copy(IPCSchannelCred& aDest, const PSCHANNEL_CRED& aSrc) { + if (aSrc) { + aDest.CopyFrom(aSrc); + } + } + inline static void Copy(IPCInternetBuffers& aDest, + const LPINTERNET_BUFFERSA& aSrc) { + aDest.CopyFrom(aSrc); + } +}; + +template <> +struct SslEHContainer::EndpointHandler + : public BaseEndpointHandler> { + using BaseEndpointHandler>::Copy; + + // PSecHandle is the same thing as PCtxtHandle and PCredHandle. + inline static void Copy(uint64_t& aDest, const PSecHandle& aSrc) { + // If the SecHandle was an error then don't store it. + if (!aSrc) { + aDest = 0; + return; + } + + static uint64_t sNextVal = 1; + UlongPair key(aSrc->dwLower, aSrc->dwUpper); + // Fetch val by reference to update the value in the map + uint64_t& val = sPairToIdMap[key]; + if (val == 0) { + MOZ_ASSERT(IsOdd(sNextVal)); + val = sNextVal; + sIdToPairMap[val] = key; + sNextVal += 2; + } + aDest = val; + } + + // HANDLEs and HINTERNETs marshal with obfuscation (for return values) + inline static void Copy(uint64_t& aDest, void* const& aSrc) { + // If the HANDLE/HINTERNET was an error then don't store it. + if (!aSrc) { + aDest = 0; + return; + } + + static uint64_t sNextVal = 1; + // Fetch val by reference to update the value in the map + uint64_t& val = sPtrToIdMap[aSrc]; + if (val == 0) { + MOZ_ASSERT(IsOdd(sNextVal)); + val = sNextVal; + sIdToPtrMap[val] = aSrc; + sNextVal += 2; + } + aDest = val; + } + + // HANDLEs and HINTERNETs unmarshal with obfuscation + inline static void Copy(void*& aDest, const uint64_t& aSrc) { + aDest = nullptr; + MOZ_RELEASE_ASSERT(IsOdd(aSrc)); + + // If the src is not found in the map then we get aDest == 0 + void* ptr = sIdToPtrMap[aSrc]; + aDest = reinterpret_cast(ptr); + MOZ_RELEASE_ASSERT(aDest); + } + + inline static void Copy(PSCHANNEL_CRED& aDest, const IPCSchannelCred& aSrc) { + if (aDest) { + aSrc.CopyTo(aDest); + } + } + + inline static void Copy(ServerCallData* aScd, PSecHandle& aDest, + const uint64_t& aSrc) { + MOZ_ASSERT(!aDest); + MOZ_RELEASE_ASSERT(IsOdd(aSrc)); + + // If the src is not found in the map then we get the pair { 0, 0 } + aDest = aScd->Allocate(); + const UlongPair& pair = sIdToPairMap[aSrc]; + MOZ_RELEASE_ASSERT(pair.first || pair.second); + aDest->dwLower = pair.first; + aDest->dwUpper = pair.second; + } + + inline static void Copy(ServerCallData* aScd, PSCHANNEL_CRED& aDest, + const IPCSchannelCred& aSrc) { + MOZ_ASSERT(!aDest); + aDest = aScd->Allocate(); + Copy(aDest, aSrc); + } + + inline static void Copy(ServerCallData* aScd, LPINTERNET_BUFFERSA& aDest, + const IPCInternetBuffers& aSrc) { + MOZ_ASSERT(!aDest); + aSrc.CopyTo(aDest); + ServerCallData::DestructorType* destructor = [](void* aObj) { + LPINTERNET_BUFFERSA inetBuf = static_cast(aObj); + IPCInternetBuffers::FreeBuffers(inetBuf); + FreeDestructor(inetBuf); + }; + aScd->PostDestructor(aDest, destructor); + } +}; + +// FunctionBroker type that uses SslEHContainer +template +using SslFunctionBroker = + FunctionBroker; + +/* GetKeyState */ + +typedef FunctionBroker GetKeyStateFB; + +template <> +ShouldHookFunc* const GetKeyStateFB::BaseType::mShouldHook = + &CheckQuirks; + +/* SetCursorPos */ + +typedef FunctionBroker SetCursorPosFB; + +/* GetSaveFileNameW */ + +typedef FileDlgFunctionBroker + GetSaveFileNameWFB; + +// Remember files granted access in the chrome process +static void GrantFileAccess(base::ProcessId aClientId, LPOPENFILENAME& aLpofn, + bool isSave) { +# if defined(MOZ_SANDBOX) + if (aLpofn->Flags & OFN_ALLOWMULTISELECT) { + // We only support multiselect with the OFN_EXPLORER flag. + // This guarantees that ofn.lpstrFile follows the pattern below. + MOZ_ASSERT(aLpofn->Flags & OFN_EXPLORER); + + // lpstrFile is one of two things: + // 1. A null terminated full path to a file, or + // 2. A path to a folder, followed by a NULL, followed by a + // list of file names, each NULL terminated, followed by an + // additional NULL (so it is also double-NULL terminated). + std::wstring path = std::wstring(aLpofn->lpstrFile); + MOZ_ASSERT(aLpofn->nFileOffset > 0); + // For condition #1, nFileOffset points to the file name in the path. + // It will be preceeded by a non-NULL character from the path. + if (aLpofn->lpstrFile[aLpofn->nFileOffset - 1] != L'\0') { + FunctionBrokerParent::GetSandboxPermissions()->GrantFileAccess( + aClientId, path.c_str(), isSave); + } else { + // This is condition #2 + wchar_t* nextFile = aLpofn->lpstrFile + path.size() + 1; + while (*nextFile != L'\0') { + std::wstring nextFileStr(nextFile); + std::wstring fullPath = path + std::wstring(L"\\") + nextFileStr; + FunctionBrokerParent::GetSandboxPermissions()->GrantFileAccess( + aClientId, fullPath.c_str(), isSave); + nextFile += nextFileStr.size() + 1; + } + } + } else { + FunctionBrokerParent::GetSandboxPermissions()->GrantFileAccess( + aClientId, aLpofn->lpstrFile, isSave); + } +# else + MOZ_ASSERT_UNREACHABLE( + "GetFileName IPC message is only available on " + "Windows builds with sandbox."); +# endif +} + +template <> +template <> +BROKER_DISABLE_CFGUARD BOOL GetSaveFileNameWFB::RunFunction( + GetSaveFileNameWFB::FunctionType* aOrigFunction, base::ProcessId aClientId, + LPOPENFILENAMEW& aLpofn) const { + BOOL result = aOrigFunction(aLpofn); + if (result) { + // Record any file access permission that was just granted. + GrantFileAccess(aClientId, aLpofn, true); + } + return result; +} + +template <> +template <> +struct GetSaveFileNameWFB::Response::Info::ShouldMarshal<0> { + static const bool value = true; +}; + +/* GetOpenFileNameW */ + +typedef FileDlgFunctionBroker + GetOpenFileNameWFB; + +template <> +template <> +BROKER_DISABLE_CFGUARD BOOL GetOpenFileNameWFB::RunFunction( + GetOpenFileNameWFB::FunctionType* aOrigFunction, base::ProcessId aClientId, + LPOPENFILENAMEW& aLpofn) const { + BOOL result = aOrigFunction(aLpofn); + if (result) { + // Record any file access permission that was just granted. + GrantFileAccess(aClientId, aLpofn, false); + } + return result; +} + +template <> +template <> +struct GetOpenFileNameWFB::Response::Info::ShouldMarshal<0> { + static const bool value = true; +}; + +/* InternetOpenA */ + +typedef SslFunctionBroker + InternetOpenAFB; + +template <> +ShouldHookFunc* const InternetOpenAFB::BaseType::mShouldHook = + &CheckQuirks; + +/* InternetConnectA */ + +typedef SslFunctionBroker + InternetConnectAFB; + +template <> +ShouldHookFunc* const InternetConnectAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef InternetConnectAFB::Request ICAReqHandler; + +template <> +bool ICAReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPCSTR& srv, const INTERNET_PORT& port, + const LPCSTR& user, const LPCSTR& pass, + const DWORD& svc, const DWORD& flags, + const DWORD_PTR& cxt) { + return (endpoint == SERVER) || IsOdd(reinterpret_cast(h)); +} + +/* InternetCloseHandle */ + +typedef SslFunctionBroker + InternetCloseHandleFB; + +template <> +ShouldHookFunc* const InternetCloseHandleFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef InternetCloseHandleFB::Request ICHReqHandler; + +template <> +bool ICHReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> HINTERNET" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || IsOdd(reinterpret_cast(h)); +} + +/* InternetQueryDataAvailable */ + +typedef SslFunctionBroker + InternetQueryDataAvailableFB; + +template <> +ShouldHookFunc* const InternetQueryDataAvailableFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef InternetQueryDataAvailableFB::Request IQDAReq; +typedef InternetQueryDataAvailableFB::RequestDelegate + IQDADelegateReq; + +template <> +void IQDAReq::Marshal(IpdlTuple& aTuple, const HINTERNET& file, + const LPDWORD& nBytes, const DWORD& flags, + const DWORD_PTR& cxt) { + IQDADelegateReq::Marshal(aTuple, file); +} + +template <> +bool IQDAReq::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& file, LPDWORD& nBytes, DWORD& flags, + DWORD_PTR& cxt) { + bool success = IQDADelegateReq::Unmarshal(aScd, aTuple, file); + if (!success) { + return false; + } + flags = 0; + cxt = 0; + nBytes = aScd.Allocate(); + return true; +} + +template <> +bool IQDAReq::ShouldBroker(Endpoint endpoint, const HINTERNET& file, + const LPDWORD& nBytes, const DWORD& flags, + const DWORD_PTR& cxt) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> HINTERNET" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || ((flags == 0) && (cxt == 0) && + IsOdd(reinterpret_cast(file))); +} + +template <> +template <> +struct InternetQueryDataAvailableFB::Response::Info::ShouldMarshal<1> { + static const bool value = true; +}; + +/* InternetReadFile */ + +typedef SslFunctionBroker + InternetReadFileFB; + +template <> +ShouldHookFunc* const InternetReadFileFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef InternetReadFileFB::Request IRFRequestHandler; +typedef InternetReadFileFB::RequestDelegate + IRFDelegateReq; + +template <> +void IRFRequestHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const LPVOID& buf, const DWORD& nBytesToRead, + const LPDWORD& nBytesRead) { + IRFDelegateReq::Marshal(aTuple, h, nBytesToRead); +} + +template <> +bool IRFRequestHandler::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& h, LPVOID& buf, + DWORD& nBytesToRead, LPDWORD& nBytesRead) { + bool ret = IRFDelegateReq::Unmarshal(aScd, aTuple, h, nBytesToRead); + if (!ret) { + return false; + } + + nBytesRead = aScd.Allocate(); + MOZ_ASSERT(nBytesToRead > 0); + aScd.AllocateMemory(nBytesToRead, buf); + return true; +} + +template <> +bool IRFRequestHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPVOID& buf, + const DWORD& nBytesToRead, + const LPDWORD& nBytesRead) { + // For server-side validation, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast(h)); +} + +typedef InternetReadFileFB::Response IRFResponseHandler; +typedef InternetReadFileFB::ResponseDelegate + IRFDelegateResponseHandler; + +// Marshal the output parameter that we sent to the response delegate. +template <> +template <> +struct IRFResponseHandler::Info::ShouldMarshal<0> { + static const bool value = true; +}; + +template <> +void IRFResponseHandler::Marshal(IpdlTuple& aTuple, const BOOL& ret, + const HINTERNET& h, const LPVOID& buf, + const DWORD& nBytesToRead, + const LPDWORD& nBytesRead) { + nsDependentCSubstring str; + if (*nBytesRead) { + str.Assign(static_cast(buf), *nBytesRead); + } + IRFDelegateResponseHandler::Marshal(aTuple, ret, str); +} + +template <> +bool IRFResponseHandler::Unmarshal(const IpdlTuple& aTuple, BOOL& ret, + HINTERNET& h, LPVOID& buf, + DWORD& nBytesToRead, LPDWORD& nBytesRead) { + nsDependentCSubstring str; + bool success = IRFDelegateResponseHandler::Unmarshal(aTuple, ret, str); + if (!success) { + return false; + } + + if (str.Length()) { + memcpy(buf, str.Data(), str.Length()); + *nBytesRead = str.Length(); + } + return true; +} + +/* InternetWriteFile */ + +typedef SslFunctionBroker + InternetWriteFileFB; + +template <> +ShouldHookFunc* const InternetWriteFileFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef InternetWriteFileFB::Request IWFReqHandler; +typedef InternetWriteFileFB::RequestDelegate + IWFDelegateReqHandler; + +template <> +void IWFReqHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& file, + const LPCVOID& buf, const DWORD& nToWrite, + const LPDWORD& nWritten) { + MOZ_ASSERT(nWritten); + IWFDelegateReqHandler::Marshal( + aTuple, file, + nsDependentCSubstring(static_cast(buf), nToWrite)); +} + +template <> +bool IWFReqHandler::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& file, LPCVOID& buf, DWORD& nToWrite, + LPDWORD& nWritten) { + nsDependentCSubstring str; + if (!IWFDelegateReqHandler::Unmarshal(aScd, aTuple, file, str)) { + return false; + } + + aScd.AllocateString(str, buf, false); + nToWrite = str.Length(); + nWritten = aScd.Allocate(); + return true; +} + +template <> +bool IWFReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& file, + const LPCVOID& buf, const DWORD& nToWrite, + const LPDWORD& nWritten) { + // For server-side validation, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast(file)); +} + +template <> +template <> +struct InternetWriteFileFB::Response::Info::ShouldMarshal<3> { + static const bool value = true; +}; + +/* InternetSetOptionA */ + +typedef SslFunctionBroker + InternetSetOptionAFB; + +template <> +ShouldHookFunc* const InternetSetOptionAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef InternetSetOptionAFB::Request ISOAReqHandler; +typedef InternetSetOptionAFB::RequestDelegate + ISOADelegateReqHandler; + +template <> +void ISOAReqHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const DWORD& opt, const LPVOID& buf, + const DWORD& bufLen) { + ISOADelegateReqHandler::Marshal( + aTuple, h, opt, + nsDependentCSubstring(static_cast(buf), bufLen)); +} + +template <> +bool ISOAReqHandler::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& h, DWORD& opt, LPVOID& buf, + DWORD& bufLen) { + nsDependentCSubstring str; + if (!ISOADelegateReqHandler::Unmarshal(aScd, aTuple, h, opt, str)) { + return false; + } + + aScd.AllocateString(str, buf, false); + bufLen = str.Length(); + return true; +} + +template <> +bool ISOAReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const DWORD& opt, const LPVOID& buf, + const DWORD& bufLen) { + // For server-side validation, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast(h)); +} + +/* HttpAddRequestHeadersA */ + +typedef SslFunctionBroker + HttpAddRequestHeadersAFB; + +template <> +ShouldHookFunc* const HttpAddRequestHeadersAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef HttpAddRequestHeadersAFB::Request HARHAReqHandler; + +template <> +bool HARHAReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPCSTR& head, const DWORD& headLen, + const DWORD& mods) { + // For server-side validation, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast(h)); +} + +/* HttpOpenRequestA */ + +typedef SslFunctionBroker + HttpOpenRequestAFB; + +template <> +ShouldHookFunc* const HttpOpenRequestAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef HttpOpenRequestAFB::Request HORAReqHandler; +typedef HttpOpenRequestAFB::RequestDelegate, DWORD, + DWORD_PTR)> + HORADelegateReqHandler; + +template <> +void HORAReqHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const LPCSTR& verb, const LPCSTR& obj, + const LPCSTR& ver, const LPCSTR& ref, + LPCSTR* const& acceptTypes, const DWORD& flags, + const DWORD_PTR& cxt) { + CopyableTArray arrayAcceptTypes; + LPCSTR* curAcceptType = acceptTypes; + if (curAcceptType) { + while (*curAcceptType) { + arrayAcceptTypes.AppendElement(nsCString(*curAcceptType)); + ++curAcceptType; + } + } + // XXX Could we move arrayAcceptTypes here? + HORADelegateReqHandler::Marshal(aTuple, h, verb, obj, ver, ref, + arrayAcceptTypes, flags, cxt); +} + +template <> +bool HORAReqHandler::Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + HINTERNET& h, LPCSTR& verb, LPCSTR& obj, + LPCSTR& ver, LPCSTR& ref, LPCSTR*& acceptTypes, + DWORD& flags, DWORD_PTR& cxt) { + CopyableTArray arrayAcceptTypes; + if (!HORADelegateReqHandler::Unmarshal(aScd, aTuple, h, verb, obj, ver, ref, + arrayAcceptTypes, flags, cxt)) { + return false; + } + if (arrayAcceptTypes.Length() == 0) { + acceptTypes = nullptr; + } else { + aScd.AllocateMemory((arrayAcceptTypes.Length() + 1) * sizeof(LPCSTR), + acceptTypes); + for (size_t i = 0; i < arrayAcceptTypes.Length(); ++i) { + aScd.AllocateString(arrayAcceptTypes[i], acceptTypes[i]); + } + acceptTypes[arrayAcceptTypes.Length()] = nullptr; + } + return true; +} + +template <> +bool HORAReqHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPCSTR& verb, const LPCSTR& obj, + const LPCSTR& ver, const LPCSTR& ref, + LPCSTR* const& acceptTypes, + const DWORD& flags, const DWORD_PTR& cxt) { + // For the server-side test, the HINTERNET deserialization will have + // required it to already be looked up in the IdToPtrMap. At that point, + // any call is valid. + return (endpoint == SERVER) || IsOdd(reinterpret_cast(h)); +} + +/* HttpQueryInfoA */ + +typedef SslFunctionBroker + HttpQueryInfoAFB; + +template <> +ShouldHookFunc* const HttpQueryInfoAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef HttpQueryInfoAFB::Request HQIARequestHandler; +typedef HttpQueryInfoAFB::RequestDelegate + HQIADelegateRequestHandler; + +template <> +void HQIARequestHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const DWORD& lvl, const LPVOID& buf, + const LPDWORD& bufLen, const LPDWORD& idx) { + HQIADelegateRequestHandler::Marshal(aTuple, h, lvl, bufLen != nullptr, + bufLen ? *bufLen : 0, idx != nullptr, + idx ? *idx : 0); +} + +template <> +bool HQIARequestHandler::Unmarshal(ServerCallData& aScd, + const IpdlTuple& aTuple, HINTERNET& h, + DWORD& lvl, LPVOID& buf, LPDWORD& bufLen, + LPDWORD& idx) { + BOOL hasBufLen, hasIdx; + DWORD tempBufLen, tempIdx; + bool success = HQIADelegateRequestHandler::Unmarshal( + aScd, aTuple, h, lvl, hasBufLen, tempBufLen, hasIdx, tempIdx); + if (!success) { + return false; + } + + bufLen = nullptr; + if (hasBufLen) { + aScd.AllocateMemory(tempBufLen, buf, bufLen); + } + + idx = nullptr; + if (hasIdx) { + idx = aScd.Allocate(tempIdx); + } + + return true; +} + +template <> +bool HQIARequestHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const DWORD& lvl, const LPVOID& buf, + const LPDWORD& bufLen, + const LPDWORD& idx) { + return (endpoint == SERVER) || IsOdd(reinterpret_cast(h)); +} + +// Marshal all of the output parameters that we sent to the response delegate. +template <> +template <> +struct HttpQueryInfoAFB::Response::Info::ShouldMarshal<0> { + static const bool value = true; +}; +template <> +template <> +struct HttpQueryInfoAFB::Response::Info::ShouldMarshal<1> { + static const bool value = true; +}; +template <> +template <> +struct HttpQueryInfoAFB::Response::Info::ShouldMarshal<2> { + static const bool value = true; +}; + +typedef HttpQueryInfoAFB::Response HQIAResponseHandler; +typedef HttpQueryInfoAFB::ResponseDelegate + HQIADelegateResponseHandler; + +template <> +void HQIAResponseHandler::Marshal(IpdlTuple& aTuple, const BOOL& ret, + const HINTERNET& h, const DWORD& lvl, + const LPVOID& buf, const LPDWORD& bufLen, + const LPDWORD& idx) { + nsDependentCSubstring str; + if (buf && ret) { + MOZ_ASSERT(bufLen); + str.Assign(static_cast(buf), *bufLen); + } + // Note that we send the bufLen separately to handle the case where buf wasn't + // allocated or large enough to hold the entire return value. bufLen is then + // the required buffer size. + HQIADelegateResponseHandler::Marshal(aTuple, ret, str, bufLen ? *bufLen : 0, + idx ? *idx : 0); +} + +template <> +bool HQIAResponseHandler::Unmarshal(const IpdlTuple& aTuple, BOOL& ret, + HINTERNET& h, DWORD& lvl, LPVOID& buf, + LPDWORD& bufLen, LPDWORD& idx) { + DWORD totalBufLen = *bufLen; + nsDependentCSubstring str; + DWORD tempBufLen, tempIdx; + bool success = HQIADelegateResponseHandler::Unmarshal(aTuple, ret, str, + tempBufLen, tempIdx); + if (!success) { + return false; + } + + if (bufLen) { + *bufLen = tempBufLen; + } + if (idx) { + *idx = tempIdx; + } + + if (buf && ret) { + // When HttpQueryInfo returns strings, the buffer length will not include + // the null terminator. Rather than (brittle-y) trying to determine if the + // return buffer is a string, we always tack on a null terminator if the + // buffer has room for it. + MOZ_ASSERT(str.Length() == *bufLen); + memcpy(buf, str.Data(), str.Length()); + if (str.Length() < totalBufLen) { + char* cbuf = static_cast(buf); + cbuf[str.Length()] = '\0'; + } + } + return true; +} + +/* HttpSendRequestA */ + +typedef SslFunctionBroker + HttpSendRequestAFB; + +template <> +ShouldHookFunc* const HttpSendRequestAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef HttpSendRequestAFB::Request HSRARequestHandler; +typedef HttpSendRequestAFB::RequestDelegate + HSRADelegateRequestHandler; + +template <> +void HSRARequestHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const LPCSTR& head, const DWORD& headLen, + const LPVOID& opt, const DWORD& optLen) { + nsDependentCSubstring headStr; + headStr.SetIsVoid(head == nullptr); + if (head) { + // HttpSendRequest allows headLen == -1L for length of a null terminated + // string. + DWORD ncHeadLen = headLen; + if (ncHeadLen == -1L) { + ncHeadLen = strlen(head); + } + headStr.Rebind(head, ncHeadLen); + } + nsDependentCSubstring optStr; + optStr.SetIsVoid(opt == nullptr); + if (opt) { + optStr.Rebind(static_cast(opt), optLen); + } + HSRADelegateRequestHandler::Marshal(aTuple, h, headStr, optStr); +} + +template <> +bool HSRARequestHandler::Unmarshal(ServerCallData& aScd, + const IpdlTuple& aTuple, HINTERNET& h, + LPCSTR& head, DWORD& headLen, LPVOID& opt, + DWORD& optLen) { + nsDependentCSubstring headStr; + nsDependentCSubstring optStr; + bool success = + HSRADelegateRequestHandler::Unmarshal(aScd, aTuple, h, headStr, optStr); + if (!success) { + return false; + } + + if (headStr.IsVoid()) { + head = nullptr; + MOZ_ASSERT(headLen == 0); + } else { + aScd.AllocateString(headStr, head, false); + headLen = headStr.Length(); + } + + if (optStr.IsVoid()) { + opt = nullptr; + MOZ_ASSERT(optLen == 0); + } else { + aScd.AllocateString(optStr, opt, false); + optLen = optStr.Length(); + } + return true; +} + +template <> +bool HSRARequestHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const LPCSTR& head, const DWORD& headLen, + const LPVOID& opt, const DWORD& optLen) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> HINTERNET" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || IsOdd(reinterpret_cast(h)); +} + +/* HttpSendRequestExA */ + +typedef SslFunctionBroker + HttpSendRequestExAFB; + +template <> +ShouldHookFunc* const HttpSendRequestExAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef RequestInfo HSRExAReqInfo; + +template <> +template <> +struct HSRExAReqInfo::FixedValue<2> { + static const LPINTERNET_BUFFERSA value; +}; +const LPINTERNET_BUFFERSA HSRExAReqInfo::FixedValue<2>::value = nullptr; + +// Docs for HttpSendRequestExA say this parameter 'must' be zero but Flash +// passes other values. +// template<> template<> +// struct HSRExAReqInfo::FixedValue<3> { static const DWORD value = 0; }; + +template <> +template <> +struct HSRExAReqInfo::FixedValue<4> { + static const DWORD_PTR value; +}; +const DWORD_PTR HSRExAReqInfo::FixedValue<4>::value = 0; + +/* HttpEndRequestA */ + +typedef SslFunctionBroker + HttpEndRequestAFB; + +template <> +ShouldHookFunc* const HttpEndRequestAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef RequestInfo HERAReqInfo; + +template <> +template <> +struct HERAReqInfo::FixedValue<1> { + static const LPINTERNET_BUFFERSA value; +}; +const LPINTERNET_BUFFERSA HERAReqInfo::FixedValue<1>::value = nullptr; + +template <> +template <> +struct HERAReqInfo::FixedValue<2> { + static const DWORD value; +}; +const DWORD HERAReqInfo::FixedValue<2>::value = 0; + +template <> +template <> +struct HERAReqInfo::FixedValue<3> { + static const DWORD_PTR value; +}; +const DWORD_PTR HERAReqInfo::FixedValue<3>::value = 0; + +/* InternetQueryOptionA */ + +typedef SslFunctionBroker + InternetQueryOptionAFB; + +template <> +ShouldHookFunc* const InternetQueryOptionAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef InternetQueryOptionAFB::Request IQOARequestHandler; +typedef InternetQueryOptionAFB::RequestDelegate + IQOADelegateRequestHandler; + +template <> +void IQOARequestHandler::Marshal(IpdlTuple& aTuple, const HINTERNET& h, + const DWORD& opt, const LPVOID& buf, + const LPDWORD& bufLen) { + MOZ_ASSERT(bufLen); + IQOADelegateRequestHandler::Marshal(aTuple, h, opt, buf ? *bufLen : 0); +} + +template <> +bool IQOARequestHandler::Unmarshal(ServerCallData& aScd, + const IpdlTuple& aTuple, HINTERNET& h, + DWORD& opt, LPVOID& buf, LPDWORD& bufLen) { + DWORD tempBufLen; + bool success = + IQOADelegateRequestHandler::Unmarshal(aScd, aTuple, h, opt, tempBufLen); + if (!success) { + return false; + } + + aScd.AllocateMemory(tempBufLen, buf, bufLen); + return true; +} + +template <> +bool IQOARequestHandler::ShouldBroker(Endpoint endpoint, const HINTERNET& h, + const DWORD& opt, const LPVOID& buf, + const LPDWORD& bufLen) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> HINTERNET" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || IsOdd(reinterpret_cast(h)); +} + +// Marshal all of the output parameters that we sent to the response delegate. +template <> +template <> +struct InternetQueryOptionAFB::Response::Info::ShouldMarshal<0> { + static const bool value = true; +}; +template <> +template <> +struct InternetQueryOptionAFB::Response::Info::ShouldMarshal<1> { + static const bool value = true; +}; + +typedef InternetQueryOptionAFB::Response IQOAResponseHandler; +typedef InternetQueryOptionAFB::ResponseDelegate + IQOADelegateResponseHandler; + +template <> +void IQOAResponseHandler::Marshal(IpdlTuple& aTuple, const BOOL& ret, + const HINTERNET& h, const DWORD& opt, + const LPVOID& buf, const LPDWORD& bufLen) { + nsDependentCSubstring str; + if (buf && ret) { + MOZ_ASSERT(*bufLen); + str.Assign(static_cast(buf), *bufLen); + } + IQOADelegateResponseHandler::Marshal(aTuple, ret, str, *bufLen); +} + +template <> +bool IQOAResponseHandler::Unmarshal(const IpdlTuple& aTuple, BOOL& ret, + HINTERNET& h, DWORD& opt, LPVOID& buf, + LPDWORD& bufLen) { + nsDependentCSubstring str; + bool success = + IQOADelegateResponseHandler::Unmarshal(aTuple, ret, str, *bufLen); + if (!success) { + return false; + } + + if (buf && ret) { + MOZ_ASSERT(str.Length() == *bufLen); + memcpy(buf, str.Data(), str.Length()); + } + return true; +} + +/* InternetErrorDlg */ + +typedef SslFunctionBroker + InternetErrorDlgFB; + +template <> +ShouldHookFunc* const InternetErrorDlgFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef RequestInfo IEDReqInfo; + +template <> +template <> +struct IEDReqInfo::FixedValue<4> { + static LPVOID* const value; +}; +LPVOID* const IEDReqInfo::FixedValue<4>::value = nullptr; + +typedef InternetErrorDlgFB::Request IEDReqHandler; + +template <> +bool IEDReqHandler::ShouldBroker(Endpoint endpoint, const HWND& hwnd, + const HINTERNET& h, const DWORD& err, + const DWORD& flags, LPVOID* const& data) { + const DWORD SUPPORTED_FLAGS = + FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS | + FLAGS_ERROR_UI_FLAGS_GENERATE_DATA | FLAGS_ERROR_UI_FLAGS_NO_UI; + + // We broker if (1) the handle h is brokered (odd in client), + // (2) we support the requested action flags and (3) there is no user + // data, which wouldn't make sense for our supported flags anyway. + return ((endpoint == SERVER) || IsOdd(reinterpret_cast(h))) && + (!(flags & ~SUPPORTED_FLAGS)) && (data == nullptr); +} + +/* AcquireCredentialsHandleA */ + +typedef SslFunctionBroker + AcquireCredentialsHandleAFB; + +template <> +ShouldHookFunc* const AcquireCredentialsHandleAFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef RequestInfo ACHAReqInfo; + +template <> +template <> +struct ACHAReqInfo::FixedValue<0> { + static const LPSTR value; +}; +const LPSTR ACHAReqInfo::FixedValue<0>::value = nullptr; + +template <> +template <> +struct ACHAReqInfo::FixedValue<1> { + static const LPSTR value; +}; +const LPSTR ACHAReqInfo::FixedValue<1>::value = + const_cast(UNISP_NAME_A); // -Wwritable-strings + +template <> +template <> +struct ACHAReqInfo::FixedValue<2> { + static const unsigned long value; +}; +const unsigned long ACHAReqInfo::FixedValue<2>::value = SECPKG_CRED_OUTBOUND; + +template <> +template <> +struct ACHAReqInfo::FixedValue<3> { + static void* const value; +}; +void* const ACHAReqInfo::FixedValue<3>::value = nullptr; + +template <> +template <> +struct ACHAReqInfo::FixedValue<5> { + static const SEC_GET_KEY_FN value; +}; +const SEC_GET_KEY_FN ACHAReqInfo::FixedValue<5>::value = nullptr; + +template <> +template <> +struct ACHAReqInfo::FixedValue<6> { + static void* const value; +}; +void* const ACHAReqInfo::FixedValue<6>::value = nullptr; + +typedef AcquireCredentialsHandleAFB::Request ACHARequestHandler; +typedef AcquireCredentialsHandleAFB::RequestDelegate + ACHADelegateRequestHandler; + +template <> +void ACHARequestHandler::Marshal(IpdlTuple& aTuple, const LPSTR& principal, + const LPSTR& pkg, const unsigned long& credUse, + const PVOID& logonId, const PVOID& auth, + const SEC_GET_KEY_FN& getKeyFn, + const PVOID& getKeyArg, + const PCredHandle& cred, + const PTimeStamp& expiry) { + const PSCHANNEL_CRED& scCred = reinterpret_cast(auth); + ACHADelegateRequestHandler::Marshal(aTuple, principal, pkg, credUse, logonId, + scCred, getKeyFn, getKeyArg); +} + +template <> +bool ACHARequestHandler::Unmarshal(ServerCallData& aScd, + const IpdlTuple& aTuple, LPSTR& principal, + LPSTR& pkg, unsigned long& credUse, + PVOID& logonId, PVOID& auth, + SEC_GET_KEY_FN& getKeyFn, PVOID& getKeyArg, + PCredHandle& cred, PTimeStamp& expiry) { + PSCHANNEL_CRED& scCred = reinterpret_cast(auth); + if (!ACHADelegateRequestHandler::Unmarshal(aScd, aTuple, principal, pkg, + credUse, logonId, scCred, getKeyFn, + getKeyArg)) { + return false; + } + + cred = aScd.Allocate(); + expiry = aScd.Allocate<::TimeStamp>(); + return true; +} + +typedef ResponseInfo ACHARspInfo; + +// Response phase must send output parameters +template <> +template <> +struct ACHARspInfo::ShouldMarshal<7> { + static const bool value = true; +}; +template <> +template <> +struct ACHARspInfo::ShouldMarshal<8> { + static const bool value = true; +}; + +/* QueryCredentialsAttributesA */ + +typedef SslFunctionBroker + QueryCredentialsAttributesAFB; + +template <> +ShouldHookFunc* const QueryCredentialsAttributesAFB::BaseType::mShouldHook = + &CheckQuirks; + +/* FreeCredentialsHandle */ + +typedef SslFunctionBroker + FreeCredentialsHandleFB; + +template <> +ShouldHookFunc* const FreeCredentialsHandleFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef FreeCredentialsHandleFB::Request FCHReq; + +template <> +bool FCHReq::ShouldBroker(Endpoint endpoint, const PCredHandle& h) { + // If we are server side then we were already validated since we had to be + // looked up in the "uint64_t <-> CredHandle" hashtable. + // In the client, we check that this is a dummy handle. + return (endpoint == SERVER) || ((h->dwLower == h->dwUpper) && + IsOdd(static_cast(h->dwLower))); +} + +/* CreateMutexW */ + +// Get the user's SID as a string. Returns an empty string on failure. +static std::wstring GetUserSid() { + std::wstring ret; + // Get user SID from process token information + HANDLE token; + BOOL success = ::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token); + if (!success) { + return ret; + } + DWORD bufLen; + success = ::GetTokenInformation(token, TokenUser, nullptr, 0, &bufLen); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + return ret; + } + void* buf = malloc(bufLen); + success = ::GetTokenInformation(token, TokenUser, buf, bufLen, &bufLen); + MOZ_ASSERT(success); + if (success) { + TOKEN_USER* tokenUser = static_cast(buf); + PSID sid = tokenUser->User.Sid; + LPWSTR sidStr; + success = ::ConvertSidToStringSid(sid, &sidStr); + if (success) { + ret = sidStr; + ::LocalFree(sidStr); + } + } + free(buf); + ::CloseHandle(token); + return ret; +} + +// Get the name Windows uses for the camera mutex. Returns an empty string +// on failure. +// The camera mutex is identified in Windows code using a hard-coded GUID +// string, "eed3bd3a-a1ad-4e99-987b-d7cb3fcfa7f0", and the user's SID. The GUID +// value was determined by investigating Windows code. It is referenced in +// CCreateSwEnum::CCreateSwEnum(void) in devenum.dll. +static std::wstring GetCameraMutexName() { + std::wstring userSid = GetUserSid(); + if (userSid.empty()) { + return userSid; + } + return std::wstring(L"eed3bd3a-a1ad-4e99-987b-d7cb3fcfa7f0 - ") + userSid; +} + +typedef FunctionBroker CreateMutexWFB; + +template <> +ShouldHookFunc* const CreateMutexWFB::BaseType::mShouldHook = + &CheckQuirks; + +typedef CreateMutexWFB::Request CMWReqHandler; +typedef CMWReqHandler::Info CMWReqInfo; +typedef CreateMutexWFB::Response CMWRspHandler; + +template <> +bool CMWReqHandler::ShouldBroker(Endpoint endpoint, + const LPSECURITY_ATTRIBUTES& aAttribs, + const BOOL& aOwner, const LPCWSTR& aName) { + // Statically hold the camera mutex name so that we dont recompute it for + // every CreateMutexW call in the client process. + static std::wstring camMutexName = GetCameraMutexName(); + + // Only broker if we are requesting the camera mutex. Note that we only + // need to check that the client is actually requesting the camera. The + // command is always valid on the server as long as we can construct the + // mutex name. + if (endpoint == SERVER) { + return !camMutexName.empty(); + } + + return (!aOwner) && aName && (!camMutexName.empty()) && + (camMutexName == aName); +} + +// We dont need to marshal any parameters. We construct all of them +// server-side. +template <> +template <> +struct CMWReqInfo::ShouldMarshal<0> { + static const bool value = false; +}; +template <> +template <> +struct CMWReqInfo::ShouldMarshal<1> { + static const bool value = false; +}; +template <> +template <> +struct CMWReqInfo::ShouldMarshal<2> { + static const bool value = false; +}; + +template <> +template <> +BROKER_DISABLE_CFGUARD HANDLE CreateMutexWFB::RunFunction( + CreateMutexWFB::FunctionType* aOrigFunction, base::ProcessId aClientId, + LPSECURITY_ATTRIBUTES& aAttribs, BOOL& aOwner, LPCWSTR& aName) const { + // Use CreateMutexW to get the camera mutex and DuplicateHandle to open it + // for use in the child process. + // Recall that aAttribs, aOwner and aName are all unmarshaled so they are + // unassigned garbage. + SECURITY_ATTRIBUTES mutexAttrib = {sizeof(SECURITY_ATTRIBUTES), + nullptr /* ignored */, TRUE}; + std::wstring camMutexName = GetCameraMutexName(); + if (camMutexName.empty()) { + return 0; + } + HANDLE serverMutex = + ::CreateMutexW(&mutexAttrib, FALSE, camMutexName.c_str()); + if (serverMutex == 0) { + return 0; + } + ScopedProcessHandle clientProcHandle; + if (!base::OpenProcessHandle(aClientId, &clientProcHandle.rwget())) { + return 0; + } + HANDLE ret; + if (!::DuplicateHandle(::GetCurrentProcess(), serverMutex, clientProcHandle, + &ret, SYNCHRONIZE, FALSE, DUPLICATE_CLOSE_SOURCE)) { + return 0; + } + return ret; +} + +#endif // defined(XP_WIN) + +/*****************************************************************************/ + +#define FUN_HOOK(x) static_cast(x) +void AddBrokeredFunctionHooks(FunctionHookArray& aHooks) { + // We transfer ownership of the FunctionHook objects to the array. +#if defined(XP_WIN) + aHooks[ID_GetKeyState] = + FUN_HOOK(new GetKeyStateFB("user32.dll", "GetKeyState", &GetKeyState)); + aHooks[ID_SetCursorPos] = + FUN_HOOK(new SetCursorPosFB("user32.dll", "SetCursorPos", &SetCursorPos)); + aHooks[ID_GetSaveFileNameW] = FUN_HOOK(new GetSaveFileNameWFB( + "comdlg32.dll", "GetSaveFileNameW", &GetSaveFileNameW)); + aHooks[ID_GetOpenFileNameW] = FUN_HOOK(new GetOpenFileNameWFB( + "comdlg32.dll", "GetOpenFileNameW", &GetOpenFileNameW)); + aHooks[ID_InternetOpenA] = FUN_HOOK( + new InternetOpenAFB("wininet.dll", "InternetOpenA", &InternetOpenA)); + aHooks[ID_InternetConnectA] = FUN_HOOK(new InternetConnectAFB( + "wininet.dll", "InternetConnectA", &InternetConnectA)); + aHooks[ID_InternetCloseHandle] = FUN_HOOK(new InternetCloseHandleFB( + "wininet.dll", "InternetCloseHandle", &InternetCloseHandle)); + aHooks[ID_InternetQueryDataAvailable] = + FUN_HOOK(new InternetQueryDataAvailableFB("wininet.dll", + "InternetQueryDataAvailable", + &InternetQueryDataAvailable)); + aHooks[ID_InternetReadFile] = FUN_HOOK(new InternetReadFileFB( + "wininet.dll", "InternetReadFile", &InternetReadFile)); + aHooks[ID_InternetWriteFile] = FUN_HOOK(new InternetWriteFileFB( + "wininet.dll", "InternetWriteFile", &InternetWriteFile)); + aHooks[ID_InternetSetOptionA] = FUN_HOOK(new InternetSetOptionAFB( + "wininet.dll", "InternetSetOptionA", &InternetSetOptionA)); + aHooks[ID_HttpAddRequestHeadersA] = FUN_HOOK(new HttpAddRequestHeadersAFB( + "wininet.dll", "HttpAddRequestHeadersA", &HttpAddRequestHeadersA)); + aHooks[ID_HttpOpenRequestA] = FUN_HOOK(new HttpOpenRequestAFB( + "wininet.dll", "HttpOpenRequestA", &HttpOpenRequestA)); + aHooks[ID_HttpQueryInfoA] = FUN_HOOK( + new HttpQueryInfoAFB("wininet.dll", "HttpQueryInfoA", &HttpQueryInfoA)); + aHooks[ID_HttpSendRequestA] = FUN_HOOK(new HttpSendRequestAFB( + "wininet.dll", "HttpSendRequestA", &HttpSendRequestA)); + aHooks[ID_HttpSendRequestExA] = FUN_HOOK(new HttpSendRequestExAFB( + "wininet.dll", "HttpSendRequestExA", &HttpSendRequestExA)); + aHooks[ID_HttpEndRequestA] = FUN_HOOK(new HttpEndRequestAFB( + "wininet.dll", "HttpEndRequestA", &HttpEndRequestA)); + aHooks[ID_InternetQueryOptionA] = FUN_HOOK(new InternetQueryOptionAFB( + "wininet.dll", "InternetQueryOptionA", &InternetQueryOptionA)); + aHooks[ID_InternetErrorDlg] = FUN_HOOK(new InternetErrorDlgFB( + "wininet.dll", "InternetErrorDlg", InternetErrorDlg)); + aHooks[ID_AcquireCredentialsHandleA] = + FUN_HOOK(new AcquireCredentialsHandleAFB("sspicli.dll", + "AcquireCredentialsHandleA", + &AcquireCredentialsHandleA)); + aHooks[ID_QueryCredentialsAttributesA] = + FUN_HOOK(new QueryCredentialsAttributesAFB("sspicli.dll", + "QueryCredentialsAttributesA", + &QueryCredentialsAttributesA)); + aHooks[ID_FreeCredentialsHandle] = FUN_HOOK(new FreeCredentialsHandleFB( + "sspicli.dll", "FreeCredentialsHandle", &FreeCredentialsHandle)); + aHooks[ID_CreateMutexW] = FUN_HOOK( + new CreateMutexWFB("kernel32.dll", "CreateMutexW", &CreateMutexW)); +#endif // defined(XP_WIN) +} + +#undef FUN_HOOK + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/FunctionBroker.h b/dom/plugins/ipc/FunctionBroker.h new file mode 100644 index 0000000000..ddbde631e3 --- /dev/null +++ b/dom/plugins/ipc/FunctionBroker.h @@ -0,0 +1,1452 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_ipc_PluginHooksWin_h +#define dom_plugins_ipc_PluginHooksWin_h 1 + +#include +#include +#include +#include "base/task.h" +#include "mozilla/ipc/ProcessChild.h" +#include "FunctionBrokerChild.h" +#include "transport/runnable_utils.h" +#include "PluginMessageUtils.h" +#include "mozilla/Logging.h" +#include "FunctionHook.h" +#include "FunctionBrokerIPCUtils.h" + +#if defined(XP_WIN) +# define SECURITY_WIN32 +# include +# include +# include +# if defined(MOZ_SANDBOX) +# include "sandboxPermissions.h" +# endif +#endif // defined(XP_WIN) + +/** + * This functionality supports automatic method hooking (FunctionHook) and + * brokering (FunctionBroker), which are used to intercept system calls + * (using the nsDllInterceptor) and replace them with new functionality (hook) + * or proxy them on another process (broker). + * There isn't much of a public interface to this (see FunctionHook + * for initialization functionality) since the majority of the behavior + * comes from intercepting calls to DLL methods (making those DLL methods the + * public interface). Generic RPC can be achieved without DLLs or function + * interception by directly calling the FunctionBroker::InterceptorStub. + * + * The system supports the most common logic surrounding brokering by allowing + * the client to supply strategies for them. Some examples of common tasks that + * are supported by automatic brokering: + * + * * Intercepting a new Win32 method: + * + * Step 1: Add a typedef or subclass of either FunctionHook (non-brokering) or + * FunctionBroker (automatic brokering) to FunctionBroker.cpp, using a new + * FunctionHookID (added to that enum). + * For example: + * typedef FunctionBroker GetKeyStateFB + * Use a subclass instead of a typedef if you need to maintain data or state. + * + * Step 2: Add an instance of that object to the FunctionHookList in + * AddFunctionHook(FunctionHookList&) or + * AddBrokeredFunctionHook(FunctionHookList&). + * This typically just means calling the constructor with the correct info. + * At a minimum, this means supplying the names of the DLL and method to + * broker, and a pointer to the original version of the method. + * For example: + * aHooks[ID_GetKeyState] = + * new GetKeyStateFB("user32.dll", "GetKeyState", &GetKeyState); + * + * Step 3: If brokering, make sure the system can (un)marshal the parameters, + * either by the means below or by adding the type to IpdlTuple, which we use + * for type-safely (un)marshaling the parameter list. + * + * * Only brokering _some_ calls to the method: + * + * FunctionBroker's constructor allows the user to supply a ShouldBroker + * function, which takes the parameters of the method call and returns false + * if we should use the original method instead of brokering. + * + * * Only passing _some_ parameters to the brokering process / returning + * parameters to client: + * + * If a system call changes a parameter call-by-reference style then the + * parameter's value needs to be returned to the client. The FunctionBroker + * has "phase" (request/response) objects that it uses to determine which + * parameters are sent/returned. This example tells InternetWriteFileFB to + * return its third parameter: + * template<> template<> + * struct InternetWriteFileFB::Response::Info::ShouldMarshal<3> { + * static const bool value = true; + * }; + * By default, all parameters have ShouldMarshal set in the request phase + * and only the return value (parameter -1) has it set in the response phase. + * + * * Marshalling special parameter/return types: + * + * The IPCTypeMap in FunctionBroker maps a parameter or return type + * to a type that IpdlTuple knows how to marshal. By default, the map is + * the identity but some types need special handling. + * The map is endpoint-specific (it is a member of the EndpointHandler), + * so a different type can be used + * for client -> server and for server -> client. Note that the + * types must be able to Copy() from one another -- the default Copy() + * implementation uses the type's assignment operator. + * The EndpointHandler itself is a template parameter of the FunctionBroker. + * The default EndpointHandler recognizes basic types. + * See e.g. FileDlgEndpointHandler::IPCTypeMap + * for an example of specialization. + * + * * Anything more complex involving parameter transmission: + * + * Sometimes marshaling parameters can require something more complex. In + * those cases, you will need to specialize the Marshal and Unmarshal + * methods of the request or response handler and perform your complex logic + * there. A wise approach is to map your complex parameters into a simpler + * parameter list and delegate the Marshal/Unmarshal calls to them. For + * example, an API might take a void* and an int as a buffer and length. + * Obviously a void* cannot generally be marshaled. However, we can delegate + * this call to a parameter list that takes a string in place of the buffer and + * length. Something like: + * + * typedef RequestHandler + * HookedFuncDelegateReq; + * + * template<> + * void HookedFuncFB::Request::Marshal(IpdlTuple& aTuple, const void*& aBuf, + * const int& aBufLen) + * { + * MOZ_ASSERT(nWritten); + * HookedFuncDelegateReq::Marshal(aTuple, + * nsDependentCSubstring(aBuf, aBufLen)); + * } + * + * template<> + * bool HookedFuncFB::Request::Unmarshal(ServerCallData& aScd, const IpdlTuple& + * aTuple, void*& aBuf, int& aBufLen) + * { + * nsDependentCSubstring str; + * if (!HookedFuncDelegateReq::Unmarshal(aScd, aTuple, str)) { + * return false; + * } + * + * // Request phase unmarshal uses ServerCallData for dynamically-allocating + * // memory. + * aScd.AllocateString(str, aBuf, false); + * aBufLen = str.Length(); + * return true; + * } + * + * See e.g. InternetWriteFileFB for a complete example of delegation. + * + * * Brokering but need the server to do more than just run the function: + * + * Specialize the FunctionBroker's RunFunction. By default, it just runs + * the function. See GetSaveFileNameWFB for an example that does more. + * + */ + +#if defined(XP_WIN) && defined(__clang__) +# if __has_declspec_attribute(guard) +// Workaround for https://bugs.llvm.org/show_bug.cgi?id=47617 +// Some of the brokered function thunks don't get properly marked as call +// targets, so we have to disable CFG when returning to the original function. +# define BROKER_DISABLE_CFGUARD __declspec(guard(nocf)) +# else +# define BROKER_DISABLE_CFGUARD /* nothing */ +# endif +#else +# define BROKER_DISABLE_CFGUARD /* nothing */ +#endif + +namespace mozilla { +namespace plugins { + +#if defined(XP_WIN) + +// Currently, all methods we hook use the WINAPI calling convention. +# define HOOK_CALL WINAPI + +typedef std::pair UlongPair; +typedef std::map UlongPairToIdMap; +extern UlongPairToIdMap sPairToIdMap; +typedef std::map IdToUlongPairMap; +extern IdToUlongPairMap sIdToPairMap; +typedef std::map PtrToIdMap; +extern PtrToIdMap sPtrToIdMap; +typedef std::map IdToPtrMap; +extern IdToPtrMap sIdToPtrMap; + +#else // defined(XP_WIN) + +// Any methods we hook use the default calling convention. +# define HOOK_CALL + +#endif // defined(XP_WIN) + +inline bool IsOdd(uint64_t aVal) { return aVal & 1; } + +// This enum is used to track if this process is currently running the client +// or server side of brokering. +enum Endpoint { SERVER, CLIENT }; +inline const char* EndpointMsg(Endpoint aVal) { + return aVal == SERVER ? "SERVER" : "CLIENT"; +} + +template +inline void LogParameterValue(int aIndex, const ParamType& aParam) { + // To avoid overhead, don't do this in release. +#ifdef DEBUG + if (!MOZ_LOG_TEST(sPluginHooksLog, LogLevel::Verbose)) { + return; + } + std::wstring paramString; + IPC::LogParam(aParam, ¶mString); + HOOK_LOG(LogLevel::Verbose, + ("Parameter %d: %S", aIndex, paramString.c_str())); +#endif +} + +// This specialization is needed to log the common pattern where null is used +// as a fixed value for a pointer-type that is unknown to IPC. +template +inline void LogParameterValue(int aIndex, ParamType* const& aParam) { +#ifdef DEBUG + HOOK_LOG(LogLevel::Verbose, + ("Parameter %d: pointer value - %p", aIndex, aParam)); +#endif +} + +template <> +inline void LogParameterValue(int aIndex, const nsDependentCSubstring& aParam) { +#ifdef DEBUG + HOOK_LOG(LogLevel::Verbose, + ("Parameter %d : %s", aIndex, FormatBlob(aParam).Data())); +#endif +} + +template <> +inline void LogParameterValue(int aIndex, char* const& aParam) { +#ifdef DEBUG + // A char* can be a block of raw memory. + nsDependentCSubstring str; + if (aParam) { + str.Rebind(const_cast(aParam), + strnlen(aParam, MAX_BLOB_CHARS_TO_LOG)); + } else { + str.SetIsVoid(true); + } + LogParameterValue(aIndex, str); +#endif +} + +template <> +inline void LogParameterValue(int aIndex, const char* const& aParam) { +#ifdef DEBUG + LogParameterValue(aIndex, const_cast(aParam)); +#endif +} + +#if defined(XP_WIN) +template <> +inline void LogParameterValue(int aIndex, const SEC_GET_KEY_FN& aParam) { +# ifdef DEBUG + MOZ_ASSERT(aParam == nullptr); + HOOK_LOG(LogLevel::Verbose, ("Parameter %d: null function.", aIndex)); +# endif +} + +template <> +inline void LogParameterValue(int aIndex, LPVOID* const& aParam) { +# ifdef DEBUG + MOZ_ASSERT(aParam == nullptr); + HOOK_LOG(LogLevel::Verbose, ("Parameter %d: null void pointer.", aIndex)); +# endif +} +#endif // defined(XP_WIN) + +// Used to check if a fixed parameter value is equal to the parameter given +// in the original function call. +template +inline bool ParameterEquality(const ParamType& aParam1, + const ParamType& aParam2) { + return aParam1 == aParam2; +} + +// Specialization: char* equality is string equality +template <> +inline bool ParameterEquality(char* const& aParam1, char* const& aParam2) { + return ((!aParam1 && !aParam2) || + (aParam1 && aParam2 && !strcmp(aParam1, aParam2))); +} + +// Specialization: const char* const equality is string equality +template <> +inline bool ParameterEquality(const char* const& aParam1, + const char* const& aParam2) { + return ParameterEquality(const_cast(aParam1), + const_cast(aParam2)); +} + +/** + * A type map _from_ the type of a parameter in the original function + * we are brokering _to_ a type that we can marshal. We must be able + * to Copy() the marshaled type using the parameter type. + * The default maps from type T back to type T. + */ +template +struct IPCTypeMap { + typedef OrigType ipc_type; +}; +template <> +struct IPCTypeMap { + typedef nsDependentCSubstring ipc_type; +}; +template <> +struct IPCTypeMap { + typedef nsDependentCSubstring ipc_type; +}; +template <> +struct IPCTypeMap { + typedef nsString ipc_type; +}; +template <> +struct IPCTypeMap { + typedef nsString ipc_type; +}; +template <> +struct IPCTypeMap { + typedef int32_t ipc_type; +}; +template <> +struct IPCTypeMap { + typedef uint32_t ipc_type; +}; + +#if defined(XP_WIN) +template <> +struct IPCTypeMap { + typedef uint64_t ipc_type; +}; +template <> +struct IPCTypeMap { + typedef uint64_t ipc_type; +}; +template <> +struct IPCTypeMap { + typedef uint64_t ipc_type; +}; // HANDLEs +template <> +struct IPCTypeMap { + typedef NativeWindowHandle ipc_type; +}; +template <> +struct IPCTypeMap { + typedef IPCSchannelCred ipc_type; +}; +template <> +struct IPCTypeMap { + typedef IPCInternetBuffers ipc_type; +}; +template <> +struct IPCTypeMap { + typedef uint32_t ipc_type; +}; +#endif + +template +static void DeleteDestructor(void* aObj) { + delete static_cast(aObj); +} + +extern void FreeDestructor(void* aObj); + +// The ServerCallData is a list of ServerCallItems that should be freed when +// the server has completed a function call and marshaled a response. +class ServerCallData { + public: + typedef void(DestructorType)(void*); + + // Allocate a certain type. + template + AllocType* Allocate( + DestructorType* aDestructor = &DeleteDestructor) { + AllocType* ret = new AllocType(); + mList.AppendElement(FreeItem(ret, aDestructor)); + return ret; + } + + template + AllocType* Allocate( + const AllocType& aValueToCopy, + DestructorType* aDestructor = &DeleteDestructor) { + AllocType* ret = Allocate(aDestructor); + *ret = aValueToCopy; + return ret; + } + + // Allocate memory, storing the pointer in buf. + template + void AllocateMemory(unsigned long aBufLen, PtrType& aBuf) { + if (aBufLen) { + aBuf = static_cast(malloc(aBufLen)); + mList.AppendElement(FreeItem(aBuf, FreeDestructor)); + } else { + aBuf = nullptr; + } + } + + template + void AllocateString(const nsACString& aStr, PtrType& aBuf, + bool aCopyNullTerminator = true) { + uint32_t nullByte = aCopyNullTerminator ? 1 : 0; + char* tempBuf = static_cast(malloc(aStr.Length() + nullByte)); + memcpy(tempBuf, aStr.Data(), aStr.Length() + nullByte); + mList.AppendElement(FreeItem(tempBuf, FreeDestructor)); + aBuf = tempBuf; + } + + // Run the given destructor on the given memory, for special cases where + // memory is allocated elsewhere but must still be freed. + void PostDestructor(void* aMem, DestructorType* aDestructor) { + mList.AppendElement(FreeItem(aMem, aDestructor)); + } + +#if defined(XP_WIN) + // Allocate memory and a DWORD block-length, storing them in the + // corresponding parameters. + template + void AllocateMemory(DWORD aBufLen, PtrType& aBuf, LPDWORD& aBufLenCopy) { + aBufLenCopy = static_cast(malloc(sizeof(DWORD))); + *aBufLenCopy = aBufLen; + mList.AppendElement(FreeItem(aBufLenCopy, FreeDestructor)); + AllocateMemory(aBufLen, aBuf); + } +#endif // defined(XP_WIN) + + private: + // FreeItems are used to free objects that were temporarily needed for + // dispatch, such as buffers that are given as a parameter. + class FreeItem { + void* mPtr; + DestructorType* mDestructor; + FreeItem(FreeItem& aOther); // revoked + public: + explicit FreeItem(void* aPtr, DestructorType* aDestructor) + : mPtr(aPtr), mDestructor(aDestructor) { + MOZ_ASSERT(mDestructor || !aPtr); + } + + FreeItem(FreeItem&& aOther) + : mPtr(aOther.mPtr), mDestructor(aOther.mDestructor) { + aOther.mPtr = nullptr; + aOther.mDestructor = nullptr; + } + + ~FreeItem() { + if (mDestructor) { + mDestructor(mPtr); + } + } + }; + + typedef nsTArray FreeItemList; + FreeItemList mList; +}; + +// Holds an IpdlTuple and a ServerCallData. This is used by the phase handlers +// (RequestHandler and ResponseHandler) in the Unmarshaling phase. +// Server-side unmarshaling (during the request phase) uses a ServerCallData +// to keep track of allocated memory. In the client, ServerCallDatas are +// not used and that value will always be null. +class IpdlTupleContext { + public: + explicit IpdlTupleContext(const IpdlTuple* aTuple, + ServerCallData* aScd = nullptr) + : mTuple(aTuple), mScd(aScd) { + MOZ_ASSERT(aTuple); + } + + ServerCallData* GetServerCallData() { return mScd; } + const IpdlTuple* GetIpdlTuple() { return mTuple; } + + private: + const IpdlTuple* mTuple; + ServerCallData* mScd; +}; + +template +inline void Copy(DestType& aDest, const SrcType& aSrc) { + aDest = (DestType)aSrc; +} + +template <> +inline void Copy(nsDependentCSubstring& aDest, + const nsDependentCSubstring& aSrc) { + if (aSrc.IsVoid()) { + aDest.SetIsVoid(true); + } else { + aDest.Rebind(aSrc.Data(), aSrc.Length()); + } +} + +#if defined(XP_WIN) + +template <> +inline void Copy(uint64_t& aDest, const PTimeStamp& aSrc) { + aDest = static_cast(aSrc->QuadPart); +} + +template <> +inline void Copy(PTimeStamp& aDest, const uint64_t& aSrc) { + aDest->QuadPart = static_cast(aSrc); +} + +#endif // defined(XP_WIN) + +template +struct BaseEndpointHandler; +template +struct BaseEndpointHandler { + static const Endpoint OtherSide = SERVER; + + template + inline static void Copy(ServerCallData* aScd, DestType& aDest, + const SrcType& aSrc) { + MOZ_ASSERT(!aScd); // never used in the CLIENT + SelfType::Copy(aDest, aSrc); + } + + template + inline static void Copy(DestType& aDest, const SrcType& aSrc) { + mozilla::plugins::Copy(aDest, aSrc); + } + + // const char* should be null terminated but this is not always the case. + // In those cases, we must override this default behavior. + inline static void Copy(nsDependentCSubstring& aDest, + const char* const& aSrc) { + // In the client, we just bind to the caller's string + if (aSrc) { + aDest.Rebind(aSrc, strlen(aSrc)); + } else { + aDest.SetIsVoid(true); + } + } + + inline static void Copy(const char*& aDest, + const nsDependentCSubstring& aSrc) { + MOZ_ASSERT_UNREACHABLE("Cannot return const parameters."); + } + + inline static void Copy(nsDependentCSubstring& aDest, char* const& aSrc) { + // In the client, we just bind to the caller's string + if (aSrc) { + aDest.Rebind(aSrc, strlen(aSrc)); + } else { + aDest.SetIsVoid(true); + } + } + + inline static void Copy(nsString& aDest, wchar_t* const& aSrc) { + if (aSrc) { + // We are using nsString as a "raw" container for a wchar_t string. We + // just use its data as a wchar_t* later (so the reinterpret_cast is + // safe). + aDest.Rebind(reinterpret_cast(aSrc), wcslen(aSrc)); + } else { + aDest.SetIsVoid(true); + } + } + + inline static void Copy(char*& aDest, const nsDependentCSubstring& aSrc) { + MOZ_ASSERT_UNREACHABLE("Returning char* parameters is not yet suported."); + } + +#if defined(XP_WIN) + inline static void Copy(uint32_t& aDest, const LPDWORD& aSrc) { + aDest = *aSrc; + } + + inline static void Copy(LPDWORD& aDest, const uint32_t& aSrc) { + *aDest = aSrc; + } +#endif // #if defined(XP_WIN) +}; + +template +struct BaseEndpointHandler { + static const Endpoint OtherSide = CLIENT; + + // Specializations of this method may allocate memory for types that need it + // during Unmarshaling. They record the allocation in the ServerCallData. + // When copying values in the SERVER, we should be sure to carefully validate + // the information that came from the client as the client may be compromised + // by malicious code. + template + inline static void Copy(ServerCallData* aScd, DestType& aDest, + const SrcType& aSrc) { + SelfType::Copy(aDest, aSrc); + } + + template + inline static void Copy(DestType& aDest, const SrcType& aSrc) { + mozilla::plugins::Copy(aDest, aSrc); + } + + inline static void Copy(nsDependentCSubstring& aDest, + const nsDependentCSubstring& aSrc) { + aDest.Rebind(aSrc.Data(), aSrc.Length()); + aDest.SetIsVoid(aSrc.IsVoid()); + } + + // const char* should be null terminated but this is not always the case. + // In those cases, we override this default behavior. + inline static void Copy(nsDependentCSubstring& aDest, + const char* const& aSrc) { + MOZ_ASSERT_UNREACHABLE( + "Const parameter cannot be returned by brokering process."); + } + + inline static void Copy(nsDependentCSubstring& aDest, char* const& aSrc) { + MOZ_ASSERT_UNREACHABLE("Returning char* parameters is not yet suported."); + } + + inline static void Copy(ServerCallData* aScd, char*& aDest, + const nsDependentCSubstring& aSrc) { + // In the parent, we must allocate the string. + MOZ_ASSERT(aScd); + if (aSrc.IsVoid()) { + aDest = nullptr; + return; + } + aScd->AllocateMemory(aSrc.Length() + 1, aDest); + memcpy(aDest, aSrc.Data(), aSrc.Length()); + aDest[aSrc.Length()] = '\0'; + } + + inline static void Copy(ServerCallData* aScd, const char*& aDest, + const nsDependentCSubstring& aSrc) { + char* nonConstDest; + Copy(aScd, nonConstDest, aSrc); + aDest = nonConstDest; + } + + inline static void Copy(ServerCallData* aScd, wchar_t*& aDest, + const nsString& aSrc) { + // Allocating the string with aScd means it will last during the server call + // and be freed when the call is complete. + MOZ_ASSERT(aScd); + if (aSrc.IsVoid()) { + aDest = nullptr; + return; + } + aScd->AllocateMemory((aSrc.Length() + 1) * sizeof(wchar_t), aDest); + memcpy(aDest, aSrc.Data(), aSrc.Length() * sizeof(wchar_t)); + aDest[aSrc.Length()] = L'\0'; + } + + inline static void Copy(ServerCallData* aScd, const wchar_t*& aDest, + const nsString& aSrc) { + wchar_t* nonConstDest; + Copy(aScd, nonConstDest, aSrc); + aDest = nonConstDest; + } + +#if defined(XP_WIN) + inline static void Copy(uint32_t& aDest, const LPDWORD& aSrc) { + aDest = *aSrc; + } + + inline static void Copy(LPDWORD& aDest, const uint32_t& aSrc) { + MOZ_RELEASE_ASSERT(aDest); + *aDest = aSrc; + } + + inline static void Copy(ServerCallData* aScd, PTimeStamp& aDest, + const uint64_t& aSrc) { + MOZ_ASSERT(!aDest); + aDest = aScd->Allocate<::TimeStamp>(); + Copy(aDest, aSrc); + } +#endif // defined(XP_WIN) +}; + +// PhaseHandler is a RequestHandler or a ResponseHandler. +template +struct Marshaler { + // Driver + template + static void Marshal(IpdlTuple& aMarshaledTuple, const VarParams&... aParams) { + MarshalParameters(aMarshaledTuple, aParams...); + } + + // Driver + template + static bool Unmarshal(IpdlTupleContext& aUnmarshaledTuple, + VarParams&... aParams) { + return UnmarshalParameters(aUnmarshaledTuple, 0, aParams...); + } + + template ::value> + struct MaybeMarshalParameter {}; + + /** + * shouldMarshal = true case + */ + template + struct MaybeMarshalParameter { + template ::ipc_type> + static void MarshalParameter(IpdlTuple& aMarshaledTuple, + const OrigType& aParam) { + HOOK_LOG(LogLevel::Verbose, ("%s marshaling parameter %d.", + EndpointMsg(endpoint), paramIndex)); + IPCType ipcObject; + // EndpointHandler must be able to Copy() from OrigType to IPCType + PhaseHandler::EHContainer::template EndpointHandler::Copy( + ipcObject, aParam); + LogParameterValue(paramIndex, ipcObject); + aMarshaledTuple.AddElement(ipcObject); + } + }; + + /** + * shouldMarshal = false case + */ + template + struct MaybeMarshalParameter { + static void MarshalParameter(IpdlTuple& aMarshaledTuple, + const OrigType& aParam) { + HOOK_LOG(LogLevel::Verbose, ("%s not marshaling parameter %d.", + EndpointMsg(endpoint), paramIndex)); + } + }; + + /** + * Recursive case: marshals aFirstParam to aMarshaledTuple (if desired), + * then marshals the aRemainingParams. + */ + template + static void MarshalParameters(IpdlTuple& aMarshaledTuple, + const VarParam& aFirstParam, + const VarParams&... aRemainingParams) { + MaybeMarshalParameter::MarshalParameter( + aMarshaledTuple, aFirstParam); + MarshalParameters(aMarshaledTuple, + aRemainingParams...); + } + + /** + * Base case: empty parameter list -- nothing to marshal. + */ + template + static void MarshalParameters(IpdlTuple& aMarshaledTuple) {} + + template ::value, + bool hasFixedValue = + PhaseHandler::Info::template HasFixedValue::value> + struct MaybeUnmarshalParameter {}; + + /** + * ShouldMarshal = true case. HasFixedValue must be false in that case. + */ + template + struct MaybeUnmarshalParameter { + template ::ipc_type> + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + VarParam& aParam) { + const IPCType* ipcObject = + aUnmarshaledTuple.GetIpdlTuple()->Element(aNextTupleIdx); + if (!ipcObject) { + HOOK_LOG(LogLevel::Error, ("%s failed to unmarshal parameter %d.", + EndpointMsg(endpoint), tupleIndex)); + return false; + } + HOOK_LOG(LogLevel::Verbose, ("%s unmarshaled parameter %d.", + EndpointMsg(endpoint), tupleIndex)); + LogParameterValue(tupleIndex, *ipcObject); + PhaseHandler::EHContainer::template EndpointHandler::Copy( + aUnmarshaledTuple.GetServerCallData(), aParam, *ipcObject); + ++aNextTupleIdx; + return true; + } + }; + + /** + * ShouldMarshal = true : nsDependentCSubstring specialization + */ + template + struct MaybeUnmarshalParameter { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + nsDependentCSubstring& aParam) { + // Deserialize as an nsCString and then copy the info into the + // nsDependentCSubstring + const nsCString* ipcObject = + aUnmarshaledTuple.GetIpdlTuple()->Element(aNextTupleIdx); + if (!ipcObject) { + HOOK_LOG(LogLevel::Error, ("%s failed to unmarshal parameter %d.", + EndpointMsg(endpoint), tupleIndex)); + return false; + } + HOOK_LOG(LogLevel::Verbose, ("%s unmarshaled parameter %d.", + EndpointMsg(endpoint), tupleIndex)); + + aParam.Rebind(ipcObject->Data(), ipcObject->Length()); + aParam.SetIsVoid(ipcObject->IsVoid()); + LogParameterValue(tupleIndex, aParam); + ++aNextTupleIdx; + return true; + } + }; + + /** + * ShouldMarshal = true : char* specialization + */ + template + struct MaybeUnmarshalParameter { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, char*& aParam) { + nsDependentCSubstring tempStr; + bool ret = + MaybeUnmarshalParameter::UnmarshalParameter(aUnmarshaledTuple, + aNextTupleIdx, + tempStr); + PhaseHandler::EHContainer::template EndpointHandler::Copy( + aUnmarshaledTuple.GetServerCallData(), aParam, tempStr); + return ret; + } + }; + + /** + * ShouldMarshal = true : const char* specialization + */ + template + struct MaybeUnmarshalParameter { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + const char*& aParam) { + char* tempStr; + bool ret = + MaybeUnmarshalParameter::UnmarshalParameter(aUnmarshaledTuple, + aNextTupleIdx, + tempStr); + aParam = tempStr; + return ret; + } + }; + + /** + * ShouldMarshal = false, fixed parameter case + */ + template + struct MaybeUnmarshalParameter { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + VarParam& aParam) { + // Copy default value if this is client->server communication (and if it + // exists) + PhaseHandler::template CopyFixedParam(aParam); + HOOK_LOG(LogLevel::Verbose, + ("%s parameter %d not unmarshaling -- using fixed value.", + EndpointMsg(endpoint), tupleIndex)); + LogParameterValue(tupleIndex, aParam); + return true; + } + }; + + /** + * ShouldMarshal = false, unfixed parameter case. Assume user has done + * special handling. + */ + template + struct MaybeUnmarshalParameter { + static inline bool UnmarshalParameter(IpdlTupleContext& aUnmarshaledTuple, + int& aNextTupleIdx, + VarParam& aParam) { + HOOK_LOG(LogLevel::Verbose, + ("%s parameter %d not automatically unmarshaling.", + EndpointMsg(endpoint), tupleIndex)); + // DLP: TODO: specializations fail LogParameterValue(tupleIndex, aParam); + return true; + } + }; + + /** + * Recursive case: unmarshals aFirstParam to aUnmarshaledTuple (if desired), + * then unmarshals the aRemainingParams. + * The endpoint specifies the side this process is on: client or server. + */ + template + static bool UnmarshalParameters(IpdlTupleContext& aUnmarshaledTuple, + int aNextTupleIdx, VarParam& aFirstParam, + VarParams&... aRemainingParams) { + // TODO: DLP: I currently increment aNextTupleIdx in the method (its a + // reference). This is awful. + if (!MaybeUnmarshalParameter::UnmarshalParameter( + aUnmarshaledTuple, aNextTupleIdx, aFirstParam)) { + return false; + } + return UnmarshalParameters( + aUnmarshaledTuple, aNextTupleIdx, aRemainingParams...); + } + + /** + * Base case: empty parameter list -- nothing to unmarshal. + */ + template + static bool UnmarshalParameters(IpdlTupleContext& aUnmarshaledTuple, + int aNextTupleIdx) { + return true; + } +}; + +// The default marshals all parameters. +template +struct RequestInfo { + template + struct FixedValue; + + template + struct HasFixedValue { + static const bool value = false; + }; + template + struct HasFixedValue::value, 0)> { + static const bool value = true; + }; + + // By default we the request should marshal any non-fixed parameters. + template + struct ShouldMarshal { + static const bool value = !HasFixedValue::value; + }; +}; + +/** + * This base stores the RequestHandler's IPCTypeMap. It really only + * exists to circumvent the arbitrary C++ rule (enforced by mingw) forbidding + * full class specialization of a class (IPCTypeMap) inside of an + * unspecialized template class (RequestHandler). + */ +struct RequestHandlerBase { + // Default to the namespace-level IPCTypeMap + template + struct IPCTypeMap { + typedef typename mozilla::plugins::IPCTypeMap::ipc_type ipc_type; + }; +}; + +#if defined(XP_WIN) + +// Request phase uses OpenFileNameIPC for an LPOPENFILENAMEW parameter. +template <> +struct RequestHandlerBase::IPCTypeMap { + typedef OpenFileNameIPC ipc_type; +}; + +#endif // defined(XP_WIN) + +struct BaseEHContainer { + template + struct EndpointHandler : public BaseEndpointHandler> {}; +}; + +template +struct RequestHandler; + +template +struct RequestHandler : public RequestHandlerBase { + typedef ResultType(HOOK_CALL FunctionType)(ParamTypes...); + typedef RequestHandler SelfType; + typedef RequestInfo Info; + typedef EHContainerType EHContainer; + + static void Marshal(IpdlTuple& aTuple, const ParamTypes&... aParams) { + ReqMarshaler::Marshal(aTuple, aParams...); + } + + static bool Unmarshal(ServerCallData& aScd, const IpdlTuple& aTuple, + ParamTypes&... aParams) { + IpdlTupleContext cxt(&aTuple, &aScd); + return ReqUnmarshaler::Unmarshal(cxt, aParams...); + } + + typedef Marshaler ReqMarshaler; + typedef Marshaler ReqUnmarshaler; + + /** + * Returns true if a call made with the given parameters should be + * brokered (vs. passed-through to the original function). + */ + static bool ShouldBroker(Endpoint aEndpoint, const ParamTypes&... aParams) { + // True if all filtered parameters match their filter value. + return CheckFixedParams(aParams...); + } + + template + static void CopyFixedParam(VarParam& aParam) { + aParam = Info::template FixedValue::value; + } + + protected: + // Returns true if filtered parameters match their filter value. + static bool CheckFixedParams(const ParamTypes&... aParams) { + return CheckFixedParamsHelper<0>(aParams...); + } + + // If no FixedValue is defined and equal to FixedType then always + // pass. + template + struct CheckFixedParam { + template + static inline bool Check(const ParamType& aParam) { + return true; + } + }; + + // If FixedValue is defined then check equality. + template + struct CheckFixedParam< + paramIndex, decltype(Info::template FixedValue::value, 0)> { + template + static inline bool Check(ParamType& aParam) { + return ParameterEquality(aParam, + Info::template FixedValue::value); + } + }; + + // Recursive case: Chcek head parameter, then tail parameters. + template + static bool CheckFixedParamsHelper(const VarParam& aParam, + const VarParams&... aParams) { + if (!CheckFixedParam::Check(aParam)) { + return false; // didn't match a fixed parameter + } + return CheckFixedParamsHelper(aParams...); + } + + // Base case: All fixed parameters matched. + template + static bool CheckFixedParamsHelper() { + return true; + } +}; + +// The default returns no parameters -- only the return value. +template +struct ResponseInfo { + template + struct HasFixedValue { + static const bool value = + RequestInfo::template HasFixedValue::value; + }; + + // Only the return value (index -1) is sent by default. + template + struct ShouldMarshal { + static const bool value = (paramIndex == -1); + }; + + // This is the condition on the function result that we use to determine if + // the windows thread-local error state should be sent to the client. The + // error is typically only relevant if the function did not succeed. + template + static bool ShouldTransmitError(const ResultType& aResult) { + return !static_cast(aResult); + } +}; + +/** + * Same rationale as for RequestHandlerBase. + */ +struct ResponseHandlerBase { + // Default to the namespace-level IPCTypeMap + template + struct IPCTypeMap { + typedef typename mozilla::plugins::IPCTypeMap::ipc_type ipc_type; + }; +}; + +#if defined(XP_WIN) + +// Response phase uses OpenFileNameRetIPC for an LPOPENFILENAMEW parameter. +template <> +struct ResponseHandlerBase::IPCTypeMap { + typedef OpenFileNameRetIPC ipc_type; +}; + +#endif + +template +struct ResponseHandler; + +template +struct ResponseHandler : public ResponseHandlerBase { + typedef ResultType(HOOK_CALL FunctionType)(ParamTypes...); + typedef ResponseHandler SelfType; + typedef ResponseInfo Info; + typedef EHContainerType EHContainer; + + static void Marshal(IpdlTuple& aTuple, const ResultType& aResult, + const ParamTypes&... aParams) { + // Note that this "trick" means that the first parameter we marshal is + // considered to be parameter #-1 when checking the ResponseInfo. + // The parameters in the list therefore start at index 0. + RspMarshaler::template Marshal<-1>(aTuple, aResult, aParams...); + } + static bool Unmarshal(const IpdlTuple& aTuple, ResultType& aResult, + ParamTypes&... aParams) { + IpdlTupleContext cxt(&aTuple); + return RspUnmarshaler::template Unmarshal<-1>(cxt, aResult, aParams...); + } + + typedef Marshaler RspMarshaler; + typedef Marshaler RspUnmarshaler; + + // Fixed parameters are not used in the response phase. + template + static void CopyFixedParam(VarParam& aParam) {} +}; + +/** + * Reference-counted monitor, used to synchronize communication between a + * thread using a brokered API and the FunctionDispatch thread. + */ +class FDMonitor : public Monitor { + public: + FDMonitor() : Monitor("FunctionDispatchThread lock") {} + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FDMonitor) + + private: + ~FDMonitor() = default; +}; + +/** + * Data for hooking a function that we automatically broker in a remote + * process. + */ +template +class FunctionBroker; + +template +class FunctionBroker + : public BasicFunctionHook { + public: + typedef Tuple TupleParamTypes; + typedef Tuple...> TupleMaybeParamTypes; + typedef Tuple TupleParamPtrTypes; + typedef Tuple TupleParamRefTypes; + static const size_t numParams = sizeof...(ParamTypes); + + typedef ResultType(HOOK_CALL FunctionType)(ParamTypes...); + typedef FunctionBroker SelfType; + typedef BasicFunctionHook FunctionHookInfoType; + typedef FunctionHookInfoType BaseType; + + typedef RequestHandler Request; + typedef ResponseHandler Response; + + template + using RequestDelegate = + RequestHandler; + template + using ResponseDelegate = + ResponseHandler; + + FunctionBroker(const char* aModuleName, const char* aMethodName, + FunctionType* aOriginalFunction) + : BasicFunctionHook( + aModuleName, aMethodName, aOriginalFunction, InterceptorStub) {} + + // This is the function used to replace the original DLL-intercepted function. + static ResultType HOOK_CALL InterceptorStub(ParamTypes... aParams) { + MOZ_ASSERT(functionId < FunctionHook::GetHooks()->Length()); + FunctionHook* self = FunctionHook::GetHooks()->ElementAt(functionId); + MOZ_ASSERT(self && self->FunctionId() == functionId); + const SelfType* broker = static_cast(self); + return broker->MaybeBrokerCallClient(aParams...); + } + + /** + * Handle a call by running the original version or brokering, depending on + * ShouldBroker. All parameter types (including the result type) + * must have IPDL ParamTraits specializations or appear in this object's + * IPCTypeMap. If brokering fails for any reason then this falls back to + * calling the original version of the function. + */ + ResultType MaybeBrokerCallClient(ParamTypes&... aParameters) const; + + /** + * Called server-side to run the original function using aInTuple + * as parameter values. The return value and returned parameters + * (in that order) are added to aOutTuple. + */ + bool RunOriginalFunction(base::ProcessId aClientId, + const IPC::IpdlTuple& aInTuple, + IPC::IpdlTuple* aOutTuple) const override { + return BrokerCallServer(aClientId, aInTuple, aOutTuple); + } + + protected: + bool BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple) const { + return BrokerCallServer(aClientId, aInTuple, aOutTuple, + std::index_sequence_for{}); + } + + bool BrokerCallClient(uint32_t& aWinError, ResultType& aResult, + ParamTypes&... aParameters) const; + bool PostToDispatchThread(uint32_t& aWinError, ResultType& aRet, + ParamTypes&... aParameters) const; + + static void PostToDispatchHelper(const SelfType* bmhi, + RefPtr monitor, bool* notified, + bool* ok, uint32_t* winErr, ResultType* r, + ParamTypes*... p) { + // Note: p is also non-null... its just hard to assert that. + MOZ_ASSERT(bmhi && monitor && notified && ok && winErr && r); + MOZ_ASSERT(*notified == false); + *ok = bmhi->BrokerCallClient(*winErr, *r, *p...); + + { + // We need to grab the lock to make sure that Wait() has been + // called in PostToDispatchThread. We need that since we wake it with + // Notify(). + MonitorAutoLock lock(*monitor); + *notified = true; + } + + monitor->Notify(); + }; + + template + BROKER_DISABLE_CFGUARD ResultType RunFunction(FunctionType* aFunction, + base::ProcessId aClientId, + VarParams&... aParams) const { + return aFunction(aParams...); + }; + + bool BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple, ParamTypes&... aParams) const; + + template + bool BrokerCallServer(base::ProcessId aClientId, const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple, + std::index_sequence) const { + TupleParamTypes paramTuple; + return BrokerCallServer(aClientId, aInTuple, aOutTuple, + Get(paramTuple)...); + } +}; + +template +ResultType FunctionBroker< + functionId, ResultType HOOK_CALL(ParamTypes...), + EHContainer>::MaybeBrokerCallClient(ParamTypes&... aParameters) const { + MOZ_ASSERT(FunctionBrokerChild::GetInstance()); + + // Broker the call if ShouldBroker says to. Otherwise, or if brokering + // fails, then call the original implementation. + if (!FunctionBrokerChild::GetInstance()) { + HOOK_LOG(LogLevel::Error, + ("[%s] Client attempted to broker call without actor.", + FunctionHookInfoType::mFunctionName.Data())); + } else if (Request::ShouldBroker(CLIENT, aParameters...)) { + HOOK_LOG(LogLevel::Debug, ("[%s] Client attempting to broker call.", + FunctionHookInfoType::mFunctionName.Data())); + uint32_t winError; + ResultType ret; + bool success = BrokerCallClient(winError, ret, aParameters...); + HOOK_LOG(LogLevel::Info, + ("[%s] Client brokering %s.", + FunctionHookInfoType::mFunctionName.Data(), SuccessMsg(success))); + if (success) { +#if defined(XP_WIN) + if (Response::Info::ShouldTransmitError(ret)) { + HOOK_LOG(LogLevel::Debug, + ("[%s] Client setting thread error code: %08x.", + FunctionHookInfoType::mFunctionName.Data(), winError)); + ::SetLastError(winError); + } +#endif + return ret; + } + } + + HOOK_LOG(LogLevel::Info, + ("[%s] Client could not broker. Running original version.", + FunctionHookInfoType::mFunctionName.Data())); + return FunctionHookInfoType::mOldFunction(aParameters...); +} + +template +bool FunctionBroker::BrokerCallClient(uint32_t& aWinError, + ResultType& aResult, + ParamTypes&... aParameters) + const { + if (!FunctionBrokerChild::GetInstance()->IsDispatchThread()) { + return PostToDispatchThread(aWinError, aResult, aParameters...); + } + + if (FunctionBrokerChild::GetInstance()) { + IpdlTuple sending, returned; + HOOK_LOG(LogLevel::Debug, ("[%s] Client marshaling parameters.", + FunctionHookInfoType::mFunctionName.Data())); + Request::Marshal(sending, aParameters...); + HOOK_LOG(LogLevel::Info, ("[%s] Client sending broker message.", + FunctionHookInfoType::mFunctionName.Data())); + if (FunctionBrokerChild::GetInstance()->SendBrokerFunction( + FunctionHookInfoType::FunctionId(), sending, &returned)) { + HOOK_LOG(LogLevel::Debug, + ("[%s] Client received broker message response.", + FunctionHookInfoType::mFunctionName.Data())); + bool success = Response::Unmarshal(returned, aResult, aParameters...); + HOOK_LOG(LogLevel::Info, ("[%s] Client response unmarshaling: %s.", + FunctionHookInfoType::mFunctionName.Data(), + SuccessMsg(success))); +#if defined(XP_WIN) + if (success && Response::Info::ShouldTransmitError(aResult)) { + uint32_t* winError = + returned.Element(returned.NumElements() - 1); + if (!winError) { + HOOK_LOG(LogLevel::Error, + ("[%s] Client failed to unmarshal error code.", + FunctionHookInfoType::mFunctionName.Data())); + return false; + } + HOOK_LOG(LogLevel::Debug, + ("[%s] Client response unmarshaled error code: %08x.", + FunctionHookInfoType::mFunctionName.Data(), *winError)); + aWinError = *winError; + } +#endif + return success; + } + } + + HOOK_LOG(LogLevel::Error, ("[%s] Client failed to broker call.", + FunctionHookInfoType::mFunctionName.Data())); + return false; +} + +template +bool FunctionBroker::BrokerCallServer(base::ProcessId aClientId, + const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple, + ParamTypes&... aParams) + const { + HOOK_LOG(LogLevel::Info, ("[%s] Server brokering function.", + FunctionHookInfoType::mFunctionName.Data())); + + ServerCallData scd; + if (!Request::Unmarshal(scd, aInTuple, aParams...)) { + HOOK_LOG(LogLevel::Info, ("[%s] Server failed to unmarshal.", + FunctionHookInfoType::mFunctionName.Data())); + return false; + } + + // Make sure that this call was legal -- do not execute a call that + // shouldn't have been brokered in the first place. + if (!Request::ShouldBroker(SERVER, aParams...)) { + HOOK_LOG(LogLevel::Error, ("[%s] Server rejected brokering request.", + FunctionHookInfoType::mFunctionName.Data())); + return false; + } + + // Run the function we are brokering. + HOOK_LOG(LogLevel::Info, ("[%s] Server broker running function.", + FunctionHookInfoType::mFunctionName.Data())); + ResultType ret = + RunFunction(FunctionHookInfoType::mOldFunction, aClientId, aParams...); + +#if defined(XP_WIN) + // Record the thread-local error state (before it is changed) if needed. + uint32_t err = UINT_MAX; + bool transmitError = Response::Info::ShouldTransmitError(ret); + if (transmitError) { + err = ::GetLastError(); + HOOK_LOG(LogLevel::Info, ("[%s] Server returning thread error code: %08x.", + FunctionHookInfoType::mFunctionName.Data(), err)); + } +#endif + + // Add the result, win thread error and any returned parameters to the + // returned tuple. + Response::Marshal(*aOutTuple, ret, aParams...); +#if defined(XP_WIN) + if (transmitError) { + aOutTuple->AddElement(err); + } +#endif + + return true; +} + +template +bool FunctionBroker< + functionId, ResultType HOOK_CALL(ParamTypes...), + EHContainer>::PostToDispatchThread(uint32_t& aWinError, ResultType& aRet, + ParamTypes&... aParameters) const { + MOZ_ASSERT(!FunctionBrokerChild::GetInstance()->IsDispatchThread()); + HOOK_LOG(LogLevel::Debug, ("Posting broker task '%s' to dispatch thread", + FunctionHookInfoType::mFunctionName.Data())); + + // Run PostToDispatchHelper on the dispatch thread. It will notify our + // waiting monitor when it is done. + RefPtr monitor(new FDMonitor()); + MonitorAutoLock lock(*monitor); + bool success = false; + bool notified = false; + FunctionBrokerChild::GetInstance()->PostToDispatchThread(NewRunnableFunction( + "FunctionDispatchThreadRunnable", &PostToDispatchHelper, this, monitor, + ¬ified, &success, &aWinError, &aRet, &aParameters...)); + + // We wait to be notified, testing that notified was actually set to make + // sure this isn't a spurious wakeup. + while (!notified) { + monitor->Wait(); + } + return success; +} + +void AddBrokeredFunctionHooks(FunctionHookArray& aHooks); + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_ipc_PluginHooksWin_h diff --git a/dom/plugins/ipc/FunctionBrokerChild.cpp b/dom/plugins/ipc/FunctionBrokerChild.cpp new file mode 100644 index 0000000000..a0780d853f --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerChild.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FunctionBrokerChild.h" +#include "FunctionBrokerThread.h" + +#include "mozilla/ipc/Endpoint.h" + +namespace mozilla::plugins { + +FunctionBrokerChild* FunctionBrokerChild::sInstance = nullptr; + +bool FunctionBrokerChild::IsDispatchThread() { return mThread->IsOnThread(); } + +void FunctionBrokerChild::PostToDispatchThread( + already_AddRefed&& runnable) { + mThread->Dispatch(std::move(runnable)); +} + +/* static */ +bool FunctionBrokerChild::Initialize( + Endpoint&& aBrokerEndpoint) { + MOZ_RELEASE_ASSERT( + XRE_IsPluginProcess(), + "FunctionBrokerChild can only be used in plugin processes"); + + MOZ_ASSERT(!sInstance); + FunctionBrokerThread* thread = FunctionBrokerThread::Create(); + if (!thread) { + return false; + } + sInstance = new FunctionBrokerChild(thread, std::move(aBrokerEndpoint)); + return true; +} + +/* static */ +FunctionBrokerChild* FunctionBrokerChild::GetInstance() { + MOZ_RELEASE_ASSERT( + XRE_IsPluginProcess(), + "FunctionBrokerChild can only be used in plugin processes"); + + MOZ_ASSERT(sInstance, "Must initialize FunctionBrokerChild before using it"); + return sInstance; +} + +FunctionBrokerChild::FunctionBrokerChild( + FunctionBrokerThread* aThread, Endpoint&& aEndpoint) + : mThread(aThread), + mShutdownDone(false), + mMonitor("FunctionBrokerChild Lock") { + MOZ_ASSERT(aThread); + PostToDispatchThread( + NewNonOwningRunnableMethod&&>( + "FunctionBrokerChild::Bind", this, &FunctionBrokerChild::Bind, + std::move(aEndpoint))); +} + +void FunctionBrokerChild::Bind(Endpoint&& aEndpoint) { + MOZ_RELEASE_ASSERT(mThread->IsOnThread()); + DebugOnly ok = aEndpoint.Bind(this); + MOZ_ASSERT(ok); +} + +void FunctionBrokerChild::ShutdownOnDispatchThread() { + MOZ_ASSERT(mThread->IsOnThread()); + + // Set mShutdownDone and notify waiting thread (if any) that we are done. + MonitorAutoLock lock(mMonitor); + mShutdownDone = true; + mMonitor.Notify(); +} + +void FunctionBrokerChild::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_ASSERT(mThread->IsOnThread()); + + // Queue up a task on the PD thread. When that task is executed then + // we know that anything queued before ActorDestroy has completed. + // At that point, we can set mShutdownDone and alert any waiting + // threads that it is safe to destroy us. + sInstance->PostToDispatchThread(NewNonOwningRunnableMethod( + "FunctionBrokerChild::ShutdownOnDispatchThread", sInstance, + &FunctionBrokerChild::ShutdownOnDispatchThread)); +} + +void FunctionBrokerChild::Destroy() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!sInstance) { + return; + } + + // mShutdownDone will tell us when ActorDestroy has been run and any tasks + // on the FunctionBrokerThread have completed. At that point, we can + // safely delete the actor. + { + MonitorAutoLock lock(sInstance->mMonitor); + while (!sInstance->mShutdownDone) { + // Release lock and wait. Regain lock when we are notified that + // we have ShutdownOnDispatchThread. + sInstance->mMonitor.Wait(); + } + } + + delete sInstance; + sInstance = nullptr; +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/FunctionBrokerChild.h b/dom/plugins/ipc/FunctionBrokerChild.h new file mode 100644 index 0000000000..767aaab170 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerChild.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_functionbrokerchild_h +#define mozilla_plugins_functionbrokerchild_h + +#include "mozilla/plugins/PFunctionBrokerChild.h" + +namespace mozilla { +namespace plugins { + +class FunctionBrokerThread; + +/** + * Dispatches brokered methods to the Parent process to allow functionality + * that is otherwise blocked by the sandbox. + */ +class FunctionBrokerChild : public PFunctionBrokerChild { + public: + static bool Initialize(Endpoint&& aBrokerEndpoint); + static FunctionBrokerChild* GetInstance(); + static void Destroy(); + + bool IsDispatchThread(); + void PostToDispatchThread(already_AddRefed&& runnable); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + explicit FunctionBrokerChild(FunctionBrokerThread* aThread, + Endpoint&& aEndpoint); + void ShutdownOnDispatchThread(); + void Bind(Endpoint&& aEndpoint); + + UniquePtr mThread; + + // True if tasks on the FunctionBrokerThread have completed + bool mShutdownDone; + // This monitor guards mShutdownDone. + Monitor mMonitor; + + static FunctionBrokerChild* sInstance; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_functionbrokerchild_h diff --git a/dom/plugins/ipc/FunctionBrokerIPCUtils.cpp b/dom/plugins/ipc/FunctionBrokerIPCUtils.cpp new file mode 100644 index 0000000000..e0ee31e635 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerIPCUtils.cpp @@ -0,0 +1,334 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8; -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FunctionBrokerIPCUtils.h" + +#if defined(XP_WIN) + +# include + +/* these defines are missing from mingw headers */ +# ifndef SP_PROT_TLS1_1_CLIENT +# define SP_PROT_TLS1_1_CLIENT 0x00000200 +# endif + +# ifndef SP_PROT_TLS1_2_CLIENT +# define SP_PROT_TLS1_2_CLIENT 0x00000800 +# endif + +namespace mozilla { +namespace plugins { + +mozilla::LazyLogModule sPluginHooksLog("PluginHooks"); + +static const DWORD SCHANNEL_SUPPORTED_PROTOCOLS = + SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT; + +static const DWORD SCHANNEL_SUPPORTED_FLAGS = + SCH_CRED_MANUAL_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS | + SCH_CRED_REVOCATION_CHECK_END_CERT; + +void OpenFileNameIPC::CopyFromOfn(LPOPENFILENAMEW aLpofn) { + mHwndOwner = nullptr; + + // Filter is double-NULL terminated. mFilter should include the double-NULL. + mHasFilter = aLpofn->lpstrFilter != nullptr; + if (mHasFilter) { + uint32_t dNullIdx = 0; + while (aLpofn->lpstrFilter[dNullIdx] != L'\0' || + aLpofn->lpstrFilter[dNullIdx + 1] != L'\0') { + dNullIdx++; + } + mFilter.assign(aLpofn->lpstrFilter, dNullIdx + 2); + } + mHasCustomFilter = aLpofn->lpstrCustomFilter != nullptr; + if (mHasCustomFilter) { + mCustomFilterIn = std::wstring(aLpofn->lpstrCustomFilter); + mNMaxCustFilterOut = + aLpofn->nMaxCustFilter - (wcslen(aLpofn->lpstrCustomFilter) + 1); + } else { + mNMaxCustFilterOut = 0; + } + mFilterIndex = aLpofn->nFilterIndex; + mFile = std::wstring(aLpofn->lpstrFile); + mNMaxFile = aLpofn->nMaxFile; + mNMaxFileTitle = + aLpofn->lpstrFileTitle != nullptr ? aLpofn->nMaxFileTitle : 0; + mHasInitialDir = aLpofn->lpstrInitialDir != nullptr; + if (mHasInitialDir) { + mInitialDir = std::wstring(aLpofn->lpstrInitialDir); + } + mHasTitle = aLpofn->lpstrTitle != nullptr; + if (mHasTitle) { + mTitle = std::wstring(aLpofn->lpstrTitle); + } + mHasDefExt = aLpofn->lpstrDefExt != nullptr; + if (mHasDefExt) { + mDefExt = std::wstring(aLpofn->lpstrDefExt); + } + + mFlags = aLpofn->Flags; + // If the user sets OFN_ALLOWMULTISELECT then we require OFN_EXPLORER + // as well. Without OFN_EXPLORER, the method has ancient legacy + // behavior that we don't support. + MOZ_ASSERT((mFlags & OFN_EXPLORER) || !(mFlags & OFN_ALLOWMULTISELECT)); + + // We ignore any visual customization and callbacks that the user set. + mFlags &= ~(OFN_ENABLEHOOK | OFN_ENABLETEMPLATEHANDLE | OFN_ENABLETEMPLATE); + + mFlagsEx = aLpofn->FlagsEx; +} + +void OpenFileNameIPC::AddToOfn(LPOPENFILENAMEW aLpofn) const { + aLpofn->lStructSize = sizeof(OPENFILENAMEW); + aLpofn->hwndOwner = mHwndOwner; + if (mHasFilter) { + memcpy(const_cast(aLpofn->lpstrFilter), mFilter.data(), + mFilter.size() * sizeof(wchar_t)); + } + if (mHasCustomFilter) { + aLpofn->nMaxCustFilter = mCustomFilterIn.size() + 1 + mNMaxCustFilterOut; + wcscpy(aLpofn->lpstrCustomFilter, mCustomFilterIn.c_str()); + memset(aLpofn->lpstrCustomFilter + mCustomFilterIn.size() + 1, 0, + mNMaxCustFilterOut * sizeof(wchar_t)); + } else { + aLpofn->nMaxCustFilter = 0; + } + aLpofn->nFilterIndex = mFilterIndex; + if (mNMaxFile > 0) { + wcsncpy(aLpofn->lpstrFile, mFile.c_str(), + std::min(static_cast(mFile.size() + 1), mNMaxFile)); + aLpofn->lpstrFile[mNMaxFile - 1] = L'\0'; + } + aLpofn->nMaxFile = mNMaxFile; + aLpofn->nMaxFileTitle = mNMaxFileTitle; + if (mHasInitialDir) { + wcscpy(const_cast(aLpofn->lpstrInitialDir), mInitialDir.c_str()); + } + if (mHasTitle) { + wcscpy(const_cast(aLpofn->lpstrTitle), mTitle.c_str()); + } + aLpofn->Flags = mFlags; /* TODO: Consider adding OFN_NOCHANGEDIR */ + if (mHasDefExt) { + wcscpy(const_cast(aLpofn->lpstrDefExt), mDefExt.c_str()); + } + aLpofn->FlagsEx = mFlagsEx; +} + +void OpenFileNameIPC::AllocateOfnStrings(LPOPENFILENAMEW aLpofn) const { + if (mHasFilter) { + // mFilter is double-NULL terminated and it includes the double-NULL in its + // length. + aLpofn->lpstrFilter = + static_cast(moz_xmalloc(sizeof(wchar_t) * (mFilter.size()))); + } + if (mHasCustomFilter) { + aLpofn->lpstrCustomFilter = static_cast(moz_xmalloc( + sizeof(wchar_t) * (mCustomFilterIn.size() + 1 + mNMaxCustFilterOut))); + } + aLpofn->lpstrFile = + static_cast(moz_xmalloc(sizeof(wchar_t) * mNMaxFile)); + if (mNMaxFileTitle > 0) { + aLpofn->lpstrFileTitle = + static_cast(moz_xmalloc(sizeof(wchar_t) * mNMaxFileTitle)); + } + if (mHasInitialDir) { + aLpofn->lpstrInitialDir = static_cast( + moz_xmalloc(sizeof(wchar_t) * (mInitialDir.size() + 1))); + } + if (mHasTitle) { + aLpofn->lpstrTitle = static_cast( + moz_xmalloc(sizeof(wchar_t) * (mTitle.size() + 1))); + } + if (mHasDefExt) { + aLpofn->lpstrDefExt = static_cast( + moz_xmalloc(sizeof(wchar_t) * (mDefExt.size() + 1))); + } +} + +// static +void OpenFileNameIPC::FreeOfnStrings(LPOPENFILENAMEW aLpofn) { + if (aLpofn->lpstrFilter) { + free(const_cast(aLpofn->lpstrFilter)); + } + if (aLpofn->lpstrCustomFilter) { + free(aLpofn->lpstrCustomFilter); + } + if (aLpofn->lpstrFile) { + free(aLpofn->lpstrFile); + } + if (aLpofn->lpstrFileTitle) { + free(aLpofn->lpstrFileTitle); + } + if (aLpofn->lpstrInitialDir) { + free(const_cast(aLpofn->lpstrInitialDir)); + } + if (aLpofn->lpstrTitle) { + free(const_cast(aLpofn->lpstrTitle)); + } + if (aLpofn->lpstrDefExt) { + free(const_cast(aLpofn->lpstrDefExt)); + } +} + +void OpenFileNameRetIPC::CopyFromOfn(LPOPENFILENAMEW aLpofn) { + if (aLpofn->lpstrCustomFilter != nullptr) { + mCustomFilterOut = std::wstring(aLpofn->lpstrCustomFilter + + wcslen(aLpofn->lpstrCustomFilter) + 1); + } + mFile.assign(aLpofn->lpstrFile, aLpofn->nMaxFile); + if (aLpofn->lpstrFileTitle != nullptr) { + mFileTitle.assign(aLpofn->lpstrFileTitle, + wcslen(aLpofn->lpstrFileTitle) + 1); + } + mFileOffset = aLpofn->nFileOffset; + mFileExtension = aLpofn->nFileExtension; +} + +void OpenFileNameRetIPC::AddToOfn(LPOPENFILENAMEW aLpofn) const { + if (aLpofn->lpstrCustomFilter) { + LPWSTR secondString = + aLpofn->lpstrCustomFilter + wcslen(aLpofn->lpstrCustomFilter) + 1; + const wchar_t* customFilterOut = mCustomFilterOut.c_str(); + MOZ_ASSERT(wcslen(aLpofn->lpstrCustomFilter) + 1 + wcslen(customFilterOut) + + 1 + 1 <= + aLpofn->nMaxCustFilter); + wcscpy(secondString, customFilterOut); + secondString[wcslen(customFilterOut) + 1] = + L'\0'; // terminated with two NULLs + } + MOZ_ASSERT(mFile.size() <= aLpofn->nMaxFile); + memcpy(aLpofn->lpstrFile, mFile.data(), mFile.size() * sizeof(wchar_t)); + if (aLpofn->lpstrFileTitle != nullptr) { + MOZ_ASSERT(mFileTitle.size() + 1 < aLpofn->nMaxFileTitle); + wcscpy(aLpofn->lpstrFileTitle, mFileTitle.c_str()); + } + aLpofn->nFileOffset = mFileOffset; + aLpofn->nFileExtension = mFileExtension; +} + +void IPCSchannelCred::CopyFrom(const PSCHANNEL_CRED& aSCred) { + // We assert that the aSCred fields take supported values. + // If they do not then we ignore the values we were given. + MOZ_ASSERT(aSCred->dwVersion == SCHANNEL_CRED_VERSION); + MOZ_ASSERT(aSCred->cCreds == 0); + MOZ_ASSERT(aSCred->paCred == nullptr); + MOZ_ASSERT(aSCred->hRootStore == nullptr); + MOZ_ASSERT(aSCred->cMappers == 0); + MOZ_ASSERT(aSCred->aphMappers == nullptr); + MOZ_ASSERT(aSCred->cSupportedAlgs == 0); + MOZ_ASSERT(aSCred->palgSupportedAlgs == nullptr); + MOZ_ASSERT((aSCred->grbitEnabledProtocols & SCHANNEL_SUPPORTED_PROTOCOLS) == + aSCred->grbitEnabledProtocols); + mEnabledProtocols = + aSCred->grbitEnabledProtocols & SCHANNEL_SUPPORTED_PROTOCOLS; + mMinStrength = aSCred->dwMinimumCipherStrength; + mMaxStrength = aSCred->dwMaximumCipherStrength; + MOZ_ASSERT(aSCred->dwSessionLifespan == 0); + MOZ_ASSERT((aSCred->dwFlags & SCHANNEL_SUPPORTED_FLAGS) == aSCred->dwFlags); + mFlags = aSCred->dwFlags & SCHANNEL_SUPPORTED_FLAGS; + MOZ_ASSERT(aSCred->dwCredFormat == 0); +} + +void IPCSchannelCred::CopyTo(PSCHANNEL_CRED& aSCred) const { + // Validate values as they come from an untrusted process. + memset(aSCred, 0, sizeof(SCHANNEL_CRED)); + aSCred->dwVersion = SCHANNEL_CRED_VERSION; + aSCred->grbitEnabledProtocols = + mEnabledProtocols & SCHANNEL_SUPPORTED_PROTOCOLS; + aSCred->dwMinimumCipherStrength = mMinStrength; + aSCred->dwMaximumCipherStrength = mMaxStrength; + aSCred->dwFlags = mFlags & SCHANNEL_SUPPORTED_FLAGS; +} + +void IPCInternetBuffers::CopyFrom(const LPINTERNET_BUFFERSA& aBufs) { + mBuffers.Clear(); + + LPINTERNET_BUFFERSA inetBuf = aBufs; + while (inetBuf) { + MOZ_ASSERT(inetBuf->dwStructSize == sizeof(INTERNET_BUFFERSA)); + Buffer* ipcBuf = mBuffers.AppendElement(); + + ipcBuf->mHeader.SetIsVoid(inetBuf->lpcszHeader == nullptr); + if (inetBuf->lpcszHeader) { + ipcBuf->mHeader.Assign(inetBuf->lpcszHeader, inetBuf->dwHeadersLength); + } + ipcBuf->mHeaderTotal = inetBuf->dwHeadersTotal; + + ipcBuf->mBuffer.SetIsVoid(inetBuf->lpvBuffer == nullptr); + if (inetBuf->lpvBuffer) { + ipcBuf->mBuffer.Assign(static_cast(inetBuf->lpvBuffer), + inetBuf->dwBufferLength); + } + ipcBuf->mBufferTotal = inetBuf->dwBufferTotal; + inetBuf = inetBuf->Next; + } +} + +void IPCInternetBuffers::CopyTo(LPINTERNET_BUFFERSA& aBufs) const { + MOZ_ASSERT(!aBufs); + + LPINTERNET_BUFFERSA lastBuf = nullptr; + for (size_t idx = 0; idx < mBuffers.Length(); ++idx) { + const Buffer& ipcBuf = mBuffers[idx]; + LPINTERNET_BUFFERSA newBuf = static_cast( + moz_xcalloc(1, sizeof(INTERNET_BUFFERSA))); + if (idx == 0) { + aBufs = newBuf; + } else { + MOZ_ASSERT(lastBuf); + lastBuf->Next = newBuf; + lastBuf = newBuf; + } + + newBuf->dwStructSize = sizeof(INTERNET_BUFFERSA); + + newBuf->dwHeadersTotal = ipcBuf.mHeaderTotal; + if (!ipcBuf.mHeader.IsVoid()) { + newBuf->lpcszHeader = + static_cast(moz_xmalloc(ipcBuf.mHeader.Length())); + memcpy(const_cast(newBuf->lpcszHeader), ipcBuf.mHeader.Data(), + ipcBuf.mHeader.Length()); + newBuf->dwHeadersLength = ipcBuf.mHeader.Length(); + } + + newBuf->dwBufferTotal = ipcBuf.mBufferTotal; + if (!ipcBuf.mBuffer.IsVoid()) { + newBuf->lpvBuffer = moz_xmalloc(ipcBuf.mBuffer.Length()); + memcpy(newBuf->lpvBuffer, ipcBuf.mBuffer.Data(), ipcBuf.mBuffer.Length()); + newBuf->dwBufferLength = ipcBuf.mBuffer.Length(); + } + } +} + +/* static */ +void IPCInternetBuffers::FreeBuffers(LPINTERNET_BUFFERSA& aBufs) { + if (!aBufs) { + return; + } + while (aBufs) { + LPINTERNET_BUFFERSA temp = aBufs->Next; + free(const_cast(aBufs->lpcszHeader)); + free(aBufs->lpvBuffer); + free(aBufs); + aBufs = temp; + } +} + +void IPCPrintDlg::CopyFrom(const LPPRINTDLGW& aDlg) { + // DLP: Trouble -- my prior impl "worked" but didn't return anything + // AFAIR. So... ??? But it printed a page!!! How?! + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); +} + +void IPCPrintDlg::CopyTo(LPPRINTDLGW& aDlg) const { + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); +} + +} // namespace plugins +} // namespace mozilla + +#endif // defined(XP_WIN) diff --git a/dom/plugins/ipc/FunctionBrokerIPCUtils.h b/dom/plugins/ipc/FunctionBrokerIPCUtils.h new file mode 100644 index 0000000000..c4b72dbe95 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerIPCUtils.h @@ -0,0 +1,436 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_ipc_functionbrokeripcutils_h +#define dom_plugins_ipc_functionbrokeripcutils_h 1 + +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "PluginMessageUtils.h" + +#if defined(XP_WIN) + +# define SECURITY_WIN32 +# include +# include +# include +# include + +#endif // defined(XP_WIN) + +namespace mozilla { +namespace plugins { + +/** + * This enum represents all of the methods hooked by the main facility in + * BrokerClient. It is used to allow quick lookup in the sFunctionsToHook + * structure. + */ +enum FunctionHookId { +#if defined(XP_WIN) + ID_GetWindowInfo = 0, + ID_GetKeyState, + ID_SetCursorPos, + ID_GetSaveFileNameW, + ID_GetOpenFileNameW, + ID_InternetOpenA, + ID_InternetConnectA, + ID_InternetCloseHandle, + ID_InternetQueryDataAvailable, + ID_InternetReadFile, + ID_InternetWriteFile, + ID_InternetSetOptionA, + ID_HttpAddRequestHeadersA, + ID_HttpOpenRequestA, + ID_HttpQueryInfoA, + ID_HttpSendRequestA, + ID_HttpSendRequestExA, + ID_HttpEndRequestA, + ID_InternetQueryOptionA, + ID_InternetErrorDlg, + ID_AcquireCredentialsHandleA, + ID_QueryCredentialsAttributesA, + ID_FreeCredentialsHandle, + ID_PrintDlgW, + ID_CreateMutexW +# if defined(MOZ_SANDBOX) + , + ID_GetFileAttributesW +# endif // defined(MOZ_SANDBOX) + , + ID_FunctionHookCount +#else // defined(XP_WIN) + ID_FunctionHookCount +#endif // defined(XP_WIN) +}; + +// Max number of bytes to show when logging a blob of raw memory +static const uint32_t MAX_BLOB_CHARS_TO_LOG = 12; + +// Format strings for safe logging despite the fact that they are sometimes +// used as raw binary blobs. +inline nsCString FormatBlob(const nsACString& aParam) { + if (aParam.IsVoid() || aParam.IsEmpty()) { + return nsCString(aParam.IsVoid() ? "" : ""); + } + + nsCString str; + uint32_t totalLen = std::min(MAX_BLOB_CHARS_TO_LOG, aParam.Length()); + // If we are printing only a portion of the string then follow it with + // ellipsis + const char* maybeEllipsis = + (MAX_BLOB_CHARS_TO_LOG < aParam.Length()) ? "..." : ""; + for (uint32_t idx = 0; idx < totalLen; ++idx) { + // Should be %02x but I've run into a AppendPrintf bug... + str.AppendPrintf("0x%2x ", aParam.Data()[idx] & 0xff); + } + str.AppendPrintf("%s | '", maybeEllipsis); + for (uint32_t idx = 0; idx < totalLen; ++idx) { + str.AppendPrintf("%c", (aParam.Data()[idx] > 0) ? aParam.Data()[idx] : '.'); + } + str.AppendPrintf("'%s", maybeEllipsis); + return str; +} + +#if defined(XP_WIN) + +// Values indicate GetOpenFileNameW and GetSaveFileNameW. +enum GetFileNameFunc { OPEN_FUNC, SAVE_FUNC }; + +typedef CopyableTArray StringArray; + +// IPC-capable version of the Windows OPENFILENAMEW struct. +typedef struct _OpenFileNameIPC { + // Allocates memory for the strings in this object. This should usually + // be used with a zeroed out OPENFILENAMEW structure. + void AllocateOfnStrings(LPOPENFILENAMEW aLpofn) const; + static void FreeOfnStrings(LPOPENFILENAMEW aLpofn); + void AddToOfn(LPOPENFILENAMEW aLpofn) const; + void CopyFromOfn(LPOPENFILENAMEW aLpofn); + bool operator==(const _OpenFileNameIPC& o) const { + return (o.mHwndOwner == mHwndOwner) && (o.mFilter == mFilter) && + (o.mHasFilter == mHasFilter) && + (o.mCustomFilterIn == mCustomFilterIn) && + (o.mHasCustomFilter == mHasCustomFilter) && + (o.mNMaxCustFilterOut == mNMaxCustFilterOut) && + (o.mFilterIndex == mFilterIndex) && (o.mFile == mFile) && + (o.mNMaxFile == mNMaxFile) && (o.mNMaxFileTitle == mNMaxFileTitle) && + (o.mInitialDir == mInitialDir) && + (o.mHasInitialDir == mHasInitialDir) && (o.mTitle == mTitle) && + (o.mHasTitle == mHasTitle) && (o.mFlags == mFlags) && + (o.mDefExt == mDefExt) && (o.mHasDefExt == mHasDefExt) && + (o.mFlagsEx == mFlagsEx); + } + + NativeWindowHandle mHwndOwner; + std::wstring + mFilter; // Double-NULL terminated (i.e. L"\0\0") if mHasFilter is true + bool mHasFilter; + std::wstring mCustomFilterIn; + bool mHasCustomFilter; + uint32_t mNMaxCustFilterOut; + uint32_t mFilterIndex; + std::wstring mFile; + uint32_t mNMaxFile; + uint32_t mNMaxFileTitle; + std::wstring mInitialDir; + bool mHasInitialDir; + std::wstring mTitle; + bool mHasTitle; + uint32_t mFlags; + std::wstring mDefExt; + bool mHasDefExt; + uint32_t mFlagsEx; +} OpenFileNameIPC; + +// GetOpenFileNameW and GetSaveFileNameW overwrite fields of their OPENFILENAMEW +// parameter. This represents those values so that they can be returned via +// IPC. +typedef struct _OpenFileNameRetIPC { + void CopyFromOfn(LPOPENFILENAMEW aLpofn); + void AddToOfn(LPOPENFILENAMEW aLpofn) const; + bool operator==(const _OpenFileNameRetIPC& o) const { + return (o.mCustomFilterOut == mCustomFilterOut) && (o.mFile == mFile) && + (o.mFileTitle == mFileTitle) && (o.mFileOffset == mFileOffset) && + (o.mFileExtension == mFileExtension); + } + + std::wstring mCustomFilterOut; + std::wstring mFile; // Double-NULL terminated (i.e. L"\0\0") + std::wstring mFileTitle; + uint16_t mFileOffset; + uint16_t mFileExtension; +} OpenFileNameRetIPC; + +typedef struct _IPCSchannelCred { + void CopyFrom(const PSCHANNEL_CRED& aSCred); + void CopyTo(PSCHANNEL_CRED& aSCred) const; + bool operator==(const _IPCSchannelCred& o) const { + return (o.mEnabledProtocols == mEnabledProtocols) && + (o.mMinStrength == mMinStrength) && + (o.mMaxStrength == mMaxStrength) && (o.mFlags == mFlags); + } + + DWORD mEnabledProtocols; + DWORD mMinStrength; + DWORD mMaxStrength; + DWORD mFlags; +} IPCSchannelCred; + +typedef struct _IPCInternetBuffers { + void CopyFrom(const LPINTERNET_BUFFERSA& aBufs); + void CopyTo(LPINTERNET_BUFFERSA& aBufs) const; + bool operator==(const _IPCInternetBuffers& o) const { + return o.mBuffers == mBuffers; + } + static void FreeBuffers(LPINTERNET_BUFFERSA& aBufs); + + struct Buffer { + nsCString mHeader; + uint32_t mHeaderTotal; + nsCString mBuffer; + uint32_t mBufferTotal; + bool operator==(const Buffer& o) const { + return (o.mHeader == mHeader) && (o.mHeaderTotal == mHeaderTotal) && + (o.mBuffer == mBuffer) && (o.mBufferTotal == mBufferTotal); + } + }; + CopyableTArray mBuffers; +} IPCInternetBuffers; + +typedef struct _IPCPrintDlg { + void CopyFrom(const LPPRINTDLGW& aDlg); + void CopyTo(LPPRINTDLGW& aDlg) const; + bool operator==(const _IPCPrintDlg& o) const { + MOZ_ASSERT_UNREACHABLE("DLP: TODO:"); + return false; + } +} IPCPrintDlg; + +#endif // defined(XP_WIN) + +} // namespace plugins +} // namespace mozilla + +namespace IPC { + +using mozilla::plugins::FunctionHookId; + +#if defined(XP_WIN) + +using mozilla::plugins::IPCInternetBuffers; +using mozilla::plugins::IPCPrintDlg; +using mozilla::plugins::IPCSchannelCred; +using mozilla::plugins::NativeWindowHandle; +using mozilla::plugins::OpenFileNameIPC; +using mozilla::plugins::OpenFileNameRetIPC; +using mozilla::plugins::StringArray; + +template <> +struct ParamTraits { + typedef OpenFileNameIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mHwndOwner); + WriteParam(aMsg, aParam.mFilter); + WriteParam(aMsg, aParam.mHasFilter); + WriteParam(aMsg, aParam.mCustomFilterIn); + WriteParam(aMsg, aParam.mHasCustomFilter); + WriteParam(aMsg, aParam.mNMaxCustFilterOut); + WriteParam(aMsg, aParam.mFilterIndex); + WriteParam(aMsg, aParam.mFile); + WriteParam(aMsg, aParam.mNMaxFile); + WriteParam(aMsg, aParam.mNMaxFileTitle); + WriteParam(aMsg, aParam.mInitialDir); + WriteParam(aMsg, aParam.mHasInitialDir); + WriteParam(aMsg, aParam.mTitle); + WriteParam(aMsg, aParam.mHasTitle); + WriteParam(aMsg, aParam.mFlags); + WriteParam(aMsg, aParam.mDefExt); + WriteParam(aMsg, aParam.mHasDefExt); + WriteParam(aMsg, aParam.mFlagsEx); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (ReadParam(aMsg, aIter, &aResult->mHwndOwner) && + ReadParam(aMsg, aIter, &aResult->mFilter) && + ReadParam(aMsg, aIter, &aResult->mHasFilter) && + ReadParam(aMsg, aIter, &aResult->mCustomFilterIn) && + ReadParam(aMsg, aIter, &aResult->mHasCustomFilter) && + ReadParam(aMsg, aIter, &aResult->mNMaxCustFilterOut) && + ReadParam(aMsg, aIter, &aResult->mFilterIndex) && + ReadParam(aMsg, aIter, &aResult->mFile) && + ReadParam(aMsg, aIter, &aResult->mNMaxFile) && + ReadParam(aMsg, aIter, &aResult->mNMaxFileTitle) && + ReadParam(aMsg, aIter, &aResult->mInitialDir) && + ReadParam(aMsg, aIter, &aResult->mHasInitialDir) && + ReadParam(aMsg, aIter, &aResult->mTitle) && + ReadParam(aMsg, aIter, &aResult->mHasTitle) && + ReadParam(aMsg, aIter, &aResult->mFlags) && + ReadParam(aMsg, aIter, &aResult->mDefExt) && + ReadParam(aMsg, aIter, &aResult->mHasDefExt) && + ReadParam(aMsg, aIter, &aResult->mFlagsEx)) { + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%ls, %ls, %ls, %ls]", aParam.mFilter.c_str(), + aParam.mCustomFilterIn.c_str(), + aParam.mFile.c_str(), aParam.mTitle.c_str())); + } +}; + +template <> +struct ParamTraits { + typedef OpenFileNameRetIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mCustomFilterOut); + WriteParam(aMsg, aParam.mFile); + WriteParam(aMsg, aParam.mFileTitle); + WriteParam(aMsg, aParam.mFileOffset); + WriteParam(aMsg, aParam.mFileExtension); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (ReadParam(aMsg, aIter, &aResult->mCustomFilterOut) && + ReadParam(aMsg, aIter, &aResult->mFile) && + ReadParam(aMsg, aIter, &aResult->mFileTitle) && + ReadParam(aMsg, aIter, &aResult->mFileOffset) && + ReadParam(aMsg, aIter, &aResult->mFileExtension)) { + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%ls, %ls, %ls, %d, %d]", + aParam.mCustomFilterOut.c_str(), + aParam.mFile.c_str(), aParam.mFileTitle.c_str(), + aParam.mFileOffset, aParam.mFileExtension)); + } +}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::plugins::GetFileNameFunc, mozilla::plugins::OPEN_FUNC, + mozilla::plugins::SAVE_FUNC> {}; + +template <> +struct ParamTraits { + typedef mozilla::plugins::IPCSchannelCred paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, static_cast(aParam.mEnabledProtocols)); + WriteParam(aMsg, static_cast(aParam.mMinStrength)); + WriteParam(aMsg, static_cast(aParam.mMaxStrength)); + WriteParam(aMsg, static_cast(aParam.mFlags)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t proto, minStr, maxStr, flags; + if (!ReadParam(aMsg, aIter, &proto) || !ReadParam(aMsg, aIter, &minStr) || + !ReadParam(aMsg, aIter, &maxStr) || !ReadParam(aMsg, aIter, &flags)) { + return false; + } + aResult->mEnabledProtocols = proto; + aResult->mMinStrength = minStr; + aResult->mMaxStrength = maxStr; + aResult->mFlags = flags; + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%d,%d,%d,%d]", aParam.mEnabledProtocols, + aParam.mMinStrength, aParam.mMaxStrength, + aParam.mFlags)); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::plugins::IPCInternetBuffers::Buffer paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mHeader); + WriteParam(aMsg, aParam.mHeaderTotal); + WriteParam(aMsg, aParam.mBuffer); + WriteParam(aMsg, aParam.mBufferTotal); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mHeader) && + ReadParam(aMsg, aIter, &aResult->mHeaderTotal) && + ReadParam(aMsg, aIter, &aResult->mBuffer) && + ReadParam(aMsg, aIter, &aResult->mBufferTotal); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + nsCString head = mozilla::plugins::FormatBlob(aParam.mHeader); + nsCString buffer = mozilla::plugins::FormatBlob(aParam.mBuffer); + std::string msg = + StringPrintf("[%s, %d, %s, %d]", head.Data(), aParam.mHeaderTotal, + buffer.Data(), aParam.mBufferTotal); + aLog->append(msg.begin(), msg.end()); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::plugins::IPCInternetBuffers paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mBuffers); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mBuffers); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + ParamTraits>::Log(aParam.mBuffers, + aLog); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::plugins::IPCPrintDlg paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + MOZ_ASSERT_UNREACHABLE("TODO: DLP:"); + } +}; + +#endif // defined(XP_WIN) + +template <> +struct ParamTraits + : public ContiguousEnumSerializer(0), + FunctionHookId::ID_FunctionHookCount> {}; + +} // namespace IPC + +#endif /* dom_plugins_ipc_functionbrokeripcutils_h */ diff --git a/dom/plugins/ipc/FunctionBrokerParent.cpp b/dom/plugins/ipc/FunctionBrokerParent.cpp new file mode 100644 index 0000000000..51e3b62899 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerParent.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FunctionBrokerParent.h" +#include "FunctionBroker.h" +#include "FunctionBrokerThread.h" + +#include "mozilla/ipc/Endpoint.h" + +namespace mozilla::plugins { + +#if defined(XP_WIN) +UlongPairToIdMap sPairToIdMap; +IdToUlongPairMap sIdToPairMap; +PtrToIdMap sPtrToIdMap; +IdToPtrMap sIdToPtrMap; +#endif // defined(XP_WIN) + +/* static */ +FunctionBrokerParent* FunctionBrokerParent::Create( + Endpoint&& aParentEnd) { + FunctionBrokerThread* thread = FunctionBrokerThread::Create(); + if (!thread) { + return nullptr; + } + + // We get the FunctionHooks so that they are created here, not on the + // message thread. + FunctionHook::GetHooks(); + + return new FunctionBrokerParent(thread, std::move(aParentEnd)); +} + +FunctionBrokerParent::FunctionBrokerParent( + FunctionBrokerThread* aThread, Endpoint&& aParentEnd) + : mThread(aThread), + mMonitor("FunctionBrokerParent Lock"), + mShutdownDone(false) { + MOZ_ASSERT(mThread); + mThread->Dispatch( + NewNonOwningRunnableMethod&&>( + "FunctionBrokerParent::Bind", this, &FunctionBrokerParent::Bind, + std::move(aParentEnd))); +} + +FunctionBrokerParent::~FunctionBrokerParent() { +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + // Clean up any file permissions that we granted to the child process. + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + RemovePermissionsForProcess(OtherPid()); +#endif +} + +void FunctionBrokerParent::Bind(Endpoint&& aEnd) { + MOZ_RELEASE_ASSERT(mThread->IsOnThread()); + DebugOnly ok = aEnd.Bind(this); + MOZ_ASSERT(ok); +} + +void FunctionBrokerParent::ShutdownOnBrokerThread() { + MOZ_ASSERT(mThread->IsOnThread()); + Close(); + + // Notify waiting thread that we are done. + MonitorAutoLock lock(mMonitor); + mShutdownDone = true; + mMonitor.Notify(); +} + +void FunctionBrokerParent::Destroy(FunctionBrokerParent* aInst) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aInst); + + { + // Hold the lock while we destroy the actor on the broker thread. + MonitorAutoLock lock(aInst->mMonitor); + aInst->mThread->Dispatch(NewNonOwningRunnableMethod( + "FunctionBrokerParent::ShutdownOnBrokerThread", aInst, + &FunctionBrokerParent::ShutdownOnBrokerThread)); + + // Wait for broker thread to complete destruction. + while (!aInst->mShutdownDone) { + aInst->mMonitor.Wait(); + } + } + + delete aInst; +} + +void FunctionBrokerParent::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_RELEASE_ASSERT(mThread->IsOnThread()); +} + +mozilla::ipc::IPCResult FunctionBrokerParent::RecvBrokerFunction( + const FunctionHookId& aFunctionId, const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple) { +#if defined(XP_WIN) + MOZ_ASSERT(mThread->IsOnThread()); + if (RunBrokeredFunction(OtherPid(), aFunctionId, aInTuple, aOutTuple)) { + return IPC_OK(); + } + return IPC_FAIL_NO_REASON(this); +#else + MOZ_ASSERT_UNREACHABLE( + "BrokerFunction is currently only implemented on Windows."); + return IPC_FAIL_NO_REASON(this); +#endif +} + +// static +bool FunctionBrokerParent::RunBrokeredFunction( + base::ProcessId aClientId, const FunctionHookId& aFunctionId, + const IPC::IpdlTuple& aInTuple, IPC::IpdlTuple* aOutTuple) { + if ((size_t)aFunctionId >= FunctionHook::GetHooks()->Length()) { + MOZ_ASSERT_UNREACHABLE("Invalid function ID"); + return false; + } + + FunctionHook* hook = FunctionHook::GetHooks()->ElementAt(aFunctionId); + MOZ_ASSERT(hook->FunctionId() == aFunctionId); + return hook->RunOriginalFunction(aClientId, aInTuple, aOutTuple); +} + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + +mozilla::SandboxPermissions FunctionBrokerParent::sSandboxPermissions; + +// static +void FunctionBrokerParent::RemovePermissionsForProcess( + base::ProcessId aClientId) { + sSandboxPermissions.RemovePermissionsForProcess(aClientId); +} + +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/FunctionBrokerParent.h b/dom/plugins/ipc/FunctionBrokerParent.h new file mode 100644 index 0000000000..5ad26678b8 --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerParent.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_functionbrokerparent_h +#define mozilla_plugins_functionbrokerparent_h + +#include "mozilla/plugins/PFunctionBrokerParent.h" +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "sandboxPermissions.h" +#endif + +namespace mozilla { +namespace plugins { + +class FunctionBrokerThread; + +/** + * Top-level actor run on the process to which we broker calls from sandboxed + * plugin processes. + */ +class FunctionBrokerParent : public PFunctionBrokerParent { + public: + static FunctionBrokerParent* Create( + Endpoint&& aParentEnd); + static void Destroy(FunctionBrokerParent* aInst); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult RecvBrokerFunction(const FunctionHookId& aFunctionId, + const IpdlTuple& aInTuple, + IpdlTuple* aOutTuple) override; + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + static mozilla::SandboxPermissions* GetSandboxPermissions() { + return &sSandboxPermissions; + } +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + + private: + explicit FunctionBrokerParent(FunctionBrokerThread* aThread, + Endpoint&& aParentEnd); + ~FunctionBrokerParent(); + void ShutdownOnBrokerThread(); + void Bind(Endpoint&& aEnd); + + static bool RunBrokeredFunction(base::ProcessId aClientId, + const FunctionHookId& aFunctionId, + const IPC::IpdlTuple& aInTuple, + IPC::IpdlTuple* aOutTuple); + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + static void RemovePermissionsForProcess(base::ProcessId aClientId); + static mozilla::SandboxPermissions sSandboxPermissions; +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + + UniquePtr mThread; + Monitor mMonitor; + bool mShutdownDone; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_functionbrokerparent_hk diff --git a/dom/plugins/ipc/FunctionBrokerThread.h b/dom/plugins/ipc/FunctionBrokerThread.h new file mode 100644 index 0000000000..f5fe66e9ef --- /dev/null +++ b/dom/plugins/ipc/FunctionBrokerThread.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_functionbrokerthread_h +#define mozilla_plugins_functionbrokerthread_h + +#include "nsThreadManager.h" + +namespace mozilla { +namespace plugins { + +class FunctionBrokerThread { + public: + void Dispatch(already_AddRefed&& aRunnable) { + mThread->Dispatch(std::move(aRunnable), nsIEventTarget::NS_DISPATCH_NORMAL); + } + + bool IsOnThread() { + bool on; + return NS_SUCCEEDED(mThread->IsOnCurrentThread(&on)) && on; + } + + static FunctionBrokerThread* Create() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + nsCOMPtr thread; + if (NS_FAILED( + NS_NewNamedThread("Function Broker", getter_AddRefs(thread)))) { + return nullptr; + } + return new FunctionBrokerThread(thread); + } + + ~FunctionBrokerThread() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + mThread->Shutdown(); + } + + private: + explicit FunctionBrokerThread(nsIThread* aThread) : mThread(aThread) { + MOZ_ASSERT(mThread); + } + + nsCOMPtr mThread; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_functionbrokerthread_h diff --git a/dom/plugins/ipc/FunctionHook.cpp b/dom/plugins/ipc/FunctionHook.cpp new file mode 100644 index 0000000000..b9a0ed9e3e --- /dev/null +++ b/dom/plugins/ipc/FunctionHook.cpp @@ -0,0 +1,359 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/TextUtils.h" + +#include "FunctionHook.h" +#include "FunctionBroker.h" +#include "nsClassHashtable.h" +#include "mozilla/ClearOnShutdown.h" + +#if defined(XP_WIN) +# include +# include "PluginModuleChild.h" +#endif + +namespace mozilla::plugins { + +StaticAutoPtr FunctionHook::sFunctionHooks; + +bool AlwaysHook(int) { return true; } + +FunctionHookArray* FunctionHook::GetHooks() { + if (sFunctionHooks) { + return sFunctionHooks; + } + + // sFunctionHooks is the StaticAutoPtr to the singleton array of FunctionHook + // objects. We free it by clearing the StaticAutoPtr on shutdown. + sFunctionHooks = new FunctionHookArray(); + ClearOnShutdown(&sFunctionHooks); + sFunctionHooks->SetLength(ID_FunctionHookCount); + + AddFunctionHooks(*sFunctionHooks); + AddBrokeredFunctionHooks(*sFunctionHooks); + return sFunctionHooks; +} + +void FunctionHook::HookFunctions(int aQuirks) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Plugin); + FunctionHookArray* hooks = FunctionHook::GetHooks(); + MOZ_ASSERT(hooks); + for (size_t i = 0; i < hooks->Length(); ++i) { + FunctionHook* mhb = hooks->ElementAt(i); + // Check that the FunctionHook array is in the same order as the + // FunctionHookId enum. + MOZ_ASSERT((size_t)mhb->FunctionId() == i); + mhb->Register(aQuirks); + } +} + +#if defined(XP_WIN) + +// This cache is created when a DLL is registered with a FunctionHook. +// It is cleared on a call to ClearDllInterceptorCache(). It +// must be freed before exit to avoid leaks. +typedef nsClassHashtable + DllInterceptors; +DllInterceptors* sDllInterceptorCache = nullptr; + +WindowsDllInterceptor* FunctionHook::GetDllInterceptorFor( + const char* aModuleName) { + if (!sDllInterceptorCache) { + sDllInterceptorCache = new DllInterceptors(); + } + + MOZ_ASSERT(IsAsciiNullTerminated(aModuleName), + "Non-ASCII module names are not supported"); + NS_ConvertASCIItoUTF16 moduleName(aModuleName); + + WindowsDllInterceptor* ret = sDllInterceptorCache->LookupOrAdd(moduleName); + MOZ_ASSERT(ret); + ret->Init(moduleName.get()); + return ret; +} + +void FunctionHook::ClearDllInterceptorCache() { + delete sDllInterceptorCache; + sDllInterceptorCache = nullptr; +} + +/* GetWindowInfo */ + +typedef BasicFunctionHook + GetWindowInfoFH; + +template <> +ShouldHookFunc* const GetWindowInfoFH::mShouldHook = + &CheckQuirks; + +static const wchar_t* kMozillaWindowClass = L"MozillaWindowClass"; +static HWND sBrowserHwnd = nullptr; + +INTERCEPTOR_DISABLE_CFGUARD BOOL WINAPI GetWindowInfoHook(HWND hWnd, + PWINDOWINFO pwi) { + if (!pwi) { + return FALSE; + } + + MOZ_ASSERT(ID_GetWindowInfo < FunctionHook::GetHooks()->Length()); + GetWindowInfoFH* functionHook = static_cast( + FunctionHook::GetHooks()->ElementAt(ID_GetWindowInfo)); + if (!functionHook->OriginalFunction()) { + NS_ASSERTION(FALSE, "Something is horribly wrong in PHGetWindowInfoHook!"); + return FALSE; + } + + if (!sBrowserHwnd) { + wchar_t szClass[20]; + // GetClassNameW returns the length it copied w/o null terminator. + // Therefore, if the name and null-terminator fit then it returns a + // value less than the buffer's length. + int nameLen = GetClassNameW(hWnd, szClass, ArrayLength(szClass)); + if ((nameLen < (int)ArrayLength(szClass)) && + !wcscmp(szClass, kMozillaWindowClass)) { + sBrowserHwnd = hWnd; + } + } + + // Oddity: flash does strange rect comparisons for mouse input destined for + // it's internal settings window. Post removing sub widgets for tabs, touch + // this up so they get the rect they expect. + // XXX potentially tie this to a specific major version? + typedef BOOL(WINAPI * GetWindowInfoPtr)(HWND hwnd, PWINDOWINFO pwi); + GetWindowInfoPtr gwiFunc = + static_cast(functionHook->OriginalFunction()); + BOOL result = gwiFunc(hWnd, pwi); + if (sBrowserHwnd && sBrowserHwnd == hWnd) { + pwi->rcWindow = pwi->rcClient; + } + return result; +} + +/* PrintDlgW */ + +typedef BasicFunctionHook PrintDlgWFH; + +template <> +ShouldHookFunc* const PrintDlgWFH::mShouldHook = + &CheckQuirks; + +INTERCEPTOR_DISABLE_CFGUARD BOOL WINAPI PrintDlgWHook(LPPRINTDLGW aDlg) { + // Zero out the HWND supplied by the plugin. We are sacrificing window + // parentage for the ability to run in the NPAPI sandbox. + HWND hwnd = aDlg->hwndOwner; + aDlg->hwndOwner = 0; + MOZ_ASSERT(ID_PrintDlgW < FunctionHook::GetHooks()->Length()); + PrintDlgWFH* functionHook = static_cast( + FunctionHook::GetHooks()->ElementAt(ID_PrintDlgW)); + MOZ_ASSERT(functionHook); + BOOL ret = functionHook->OriginalFunction()(aDlg); + aDlg->hwndOwner = hwnd; + return ret; +} + +// Hooking CreateFileW for protected-mode magic +static WindowsDllInterceptor sKernel32Intercept; +typedef HANDLE(WINAPI* CreateFileWPtr)(LPCWSTR aFname, DWORD aAccess, + DWORD aShare, + LPSECURITY_ATTRIBUTES aSecurity, + DWORD aCreation, DWORD aFlags, + HANDLE aFTemplate); +static WindowsDllInterceptor::FuncHookType sCreateFileWStub; +typedef HANDLE(WINAPI* CreateFileAPtr)(LPCSTR aFname, DWORD aAccess, + DWORD aShare, + LPSECURITY_ATTRIBUTES aSecurity, + DWORD aCreation, DWORD aFlags, + HANDLE aFTemplate); +static WindowsDllInterceptor::FuncHookType sCreateFileAStub; + +// Windows 8 RTM (kernelbase's version is 6.2.9200.16384) doesn't call +// CreateFileW from CreateFileA. +// So we hook CreateFileA too to use CreateFileW hook. +static HANDLE WINAPI CreateFileAHookFn(LPCSTR aFname, DWORD aAccess, + DWORD aShare, + LPSECURITY_ATTRIBUTES aSecurity, + DWORD aCreation, DWORD aFlags, + HANDLE aFTemplate) { + while (true) { // goto out + // Our hook is for mms.cfg into \Windows\System32\Macromed\Flash + // We don't require supporting too long path. + WCHAR unicodeName[MAX_PATH]; + size_t len = strlen(aFname); + + if (len >= MAX_PATH) { + break; + } + + // We call to CreateFileW for workaround of Windows 8 RTM + int newLen = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, aFname, len, + unicodeName, MAX_PATH); + if (newLen == 0 || newLen >= MAX_PATH) { + break; + } + unicodeName[newLen] = '\0'; + + return CreateFileW(unicodeName, aAccess, aShare, aSecurity, aCreation, + aFlags, aFTemplate); + } + + return sCreateFileAStub(aFname, aAccess, aShare, aSecurity, aCreation, aFlags, + aFTemplate); +} + +static bool GetLocalLowTempPath(size_t aLen, LPWSTR aPath) { + constexpr auto tempname = u"\\Temp"_ns; + LPWSTR path; + if (SUCCEEDED( + SHGetKnownFolderPath(FOLDERID_LocalAppDataLow, 0, nullptr, &path))) { + if (wcslen(path) + tempname.Length() < aLen) { + wcscpy(aPath, path); + wcscat(aPath, tempname.get()); + CoTaskMemFree(path); + return true; + } + CoTaskMemFree(path); + } + + // XP doesn't support SHGetKnownFolderPath and LocalLow + if (!GetTempPathW(aLen, aPath)) { + return false; + } + return true; +} + +HANDLE WINAPI CreateFileWHookFn(LPCWSTR aFname, DWORD aAccess, DWORD aShare, + LPSECURITY_ATTRIBUTES aSecurity, + DWORD aCreation, DWORD aFlags, + HANDLE aFTemplate) { + static const WCHAR kConfigFile[] = L"mms.cfg"; + static const size_t kConfigLength = ArrayLength(kConfigFile) - 1; + + while (true) { // goto out, in sheep's clothing + size_t len = wcslen(aFname); + if (len < kConfigLength) { + break; + } + if (wcscmp(aFname + len - kConfigLength, kConfigFile) != 0) { + break; + } + + // This is the config file we want to rewrite + WCHAR tempPath[MAX_PATH + 1]; + if (GetLocalLowTempPath(MAX_PATH, tempPath) == 0) { + break; + } + WCHAR tempFile[MAX_PATH + 1]; + if (GetTempFileNameW(tempPath, L"fx", 0, tempFile) == 0) { + break; + } + HANDLE replacement = sCreateFileWStub( + tempFile, GENERIC_READ | GENERIC_WRITE, aShare, aSecurity, + TRUNCATE_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + if (replacement == INVALID_HANDLE_VALUE) { + break; + } + + HANDLE original = sCreateFileWStub(aFname, aAccess, aShare, aSecurity, + aCreation, aFlags, aFTemplate); + if (original != INVALID_HANDLE_VALUE) { + // copy original to replacement + static const size_t kBufferSize = 1024; + char buffer[kBufferSize]; + DWORD bytes; + while (ReadFile(original, buffer, kBufferSize, &bytes, NULL)) { + if (bytes == 0) { + break; + } + DWORD wbytes; + WriteFile(replacement, buffer, bytes, &wbytes, NULL); + if (bytes < kBufferSize) { + break; + } + } + CloseHandle(original); + } + static const char kSettingString[] = "\nProtectedMode=0\n"; + DWORD wbytes; + WriteFile(replacement, static_cast(kSettingString), + sizeof(kSettingString) - 1, &wbytes, NULL); + SetFilePointer(replacement, 0, NULL, FILE_BEGIN); + return replacement; + } + return sCreateFileWStub(aFname, aAccess, aShare, aSecurity, aCreation, aFlags, + aFTemplate); +} + +void FunctionHook::HookProtectedMode() { + // Legacy code. Uses the nsWindowsDLLInterceptor directly instead of + // using the FunctionHook + sKernel32Intercept.Init("kernel32.dll"); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Plugin); + sCreateFileWStub.Set(sKernel32Intercept, "CreateFileW", &CreateFileWHookFn); + sCreateFileAStub.Set(sKernel32Intercept, "CreateFileA", &CreateFileAHookFn); +} + +# if defined(MOZ_SANDBOX) + +/* GetFileAttributesW */ + +typedef BasicFunctionHook + GetFileAttributesWFH; + +INTERCEPTOR_DISABLE_CFGUARD DWORD WINAPI +GetFileAttributesWHook(LPCWSTR aFilename) { + MOZ_ASSERT(ID_GetFileAttributesW < FunctionHook::GetHooks()->Length()); + GetFileAttributesWFH* functionHook = static_cast( + FunctionHook::GetHooks()->ElementAt(ID_GetFileAttributesW)); + if (!functionHook->OriginalFunction()) { + NS_ASSERTION(FALSE, + "Something is horribly wrong in GetFileAttributesWHook!"); + return FALSE; + } + + DWORD ret = functionHook->OriginalFunction()(aFilename); + if (ret != INVALID_FILE_ATTRIBUTES) { + return ret; + } + + // If aFilename is a parent of PluginModuleChild::GetFlashRoamingPath then + // assume it was blocked by the sandbox and just report it as a plain + // directory. + size_t len = wcslen(aFilename); + std::wstring roamingPath = PluginModuleChild::GetFlashRoamingPath(); + bool isParent = (len > 0) && (aFilename[len - 1] == L'\\') && + (_wcsnicmp(aFilename, roamingPath.c_str(), len) == 0); + if (!isParent) { + return ret; + } + return FILE_ATTRIBUTE_DIRECTORY; +} + +# endif // defined(MOZ_SANDBOX) + +#endif // defined(XP_WIN) + +#define FUN_HOOK(x) static_cast(x) + +void FunctionHook::AddFunctionHooks(FunctionHookArray& aHooks) { + // We transfer ownership of the FunctionHook objects to the array. +#if defined(XP_WIN) + aHooks[ID_GetWindowInfo] = FUN_HOOK(new GetWindowInfoFH( + "user32.dll", "GetWindowInfo", &GetWindowInfo, &GetWindowInfoHook)); + aHooks[ID_PrintDlgW] = FUN_HOOK( + new PrintDlgWFH("comdlg32.dll", "PrintDlgW", &PrintDlgW, PrintDlgWHook)); +# if defined(MOZ_SANDBOX) + aHooks[ID_GetFileAttributesW] = FUN_HOOK( + new GetFileAttributesWFH("kernel32.dll", "GetFileAttributesW", + &GetFileAttributesW, &GetFileAttributesWHook)); +# endif // defined(MOZ_SANDBOX) +#endif // defined(XP_WIN) +} + +#undef FUN_HOOK + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/FunctionHook.h b/dom/plugins/ipc/FunctionHook.h new file mode 100644 index 0000000000..ac259d6bd3 --- /dev/null +++ b/dom/plugins/ipc/FunctionHook.h @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_ipc_functionhook_h +#define dom_plugins_ipc_functionhook_h 1 + +#include "IpdlTuple.h" +#include "base/process.h" +#include "mozilla/Atomics.h" + +#if defined(XP_WIN) +# include "nsWindowsDllInterceptor.h" +#endif + +namespace mozilla { + +template +class StaticAutoPtr; + +namespace plugins { + +// "PluginHooks" logging helpers +extern mozilla::LazyLogModule sPluginHooksLog; +#define HOOK_LOG(lvl, msg) MOZ_LOG(mozilla::plugins::sPluginHooksLog, lvl, msg); +inline const char* SuccessMsg(bool aVal) { + return aVal ? "succeeded" : "failed"; +} + +class FunctionHook; +class FunctionHookArray; + +class FunctionHook { + public: + virtual ~FunctionHook() = default; + + virtual FunctionHookId FunctionId() const = 0; + + /** + * Register to hook the function represented by this class. + * Returns false if we should have hooked but didn't. + */ + virtual bool Register(int aQuirks) = 0; + + /** + * Run the original function with parameters stored in a tuple. + * This is only supported on server-side and for auto-brokered methods. + */ + virtual bool RunOriginalFunction(base::ProcessId aClientId, + const IPC::IpdlTuple& aInTuple, + IPC::IpdlTuple* aOutTuple) const = 0; + + /** + * Hook the Win32 methods needed by the plugin process. + */ + static void HookFunctions(int aQuirks); + + static FunctionHookArray* GetHooks(); + +#if defined(XP_WIN) + /** + * Special handler for hooking some kernel32.dll methods that we use to + * disable Flash protected mode. + */ + static void HookProtectedMode(); + + /** + * Get the WindowsDllInterceptor for the given module. Creates a cache of + * WindowsDllInterceptors by name. + */ + static WindowsDllInterceptor* GetDllInterceptorFor(const char* aModuleName); + + /** + * Must be called to clear the cache created by calls to GetDllInterceptorFor. + */ + static void ClearDllInterceptorCache(); +#endif // defined(XP_WIN) + + private: + static StaticAutoPtr sFunctionHooks; + static void AddFunctionHooks(FunctionHookArray& aHooks); +}; + +// The FunctionHookArray deletes its FunctionHook objects when freed. +class FunctionHookArray : public nsTArray { + public: + ~FunctionHookArray() { + for (uint32_t idx = 0; idx < Length(); ++idx) { + FunctionHook* elt = ElementAt(idx); + MOZ_ASSERT(elt); + delete elt; + } + } +}; + +// Type of function that returns true if a function should be hooked according +// to quirks. +typedef bool(ShouldHookFunc)(int aQuirks); + +template +class BasicFunctionHook : public FunctionHook { +#if defined(XP_WIN) + using FuncHookType = WindowsDllInterceptor::FuncHookType; +#endif // defined(XP_WIN) + + public: + BasicFunctionHook(const char* aModuleName, const char* aFunctionName, + FunctionType* aOldFunction, FunctionType* aNewFunction) + : mOldFunction(aOldFunction), + mRegistration(UNREGISTERED), + mModuleName(aModuleName), + mFunctionName(aFunctionName), + mNewFunction(aNewFunction) { + MOZ_ASSERT(mOldFunction); + MOZ_ASSERT(mNewFunction); + } + + /** + * Hooks the function if we haven't already and if ShouldHook() says to. + */ + bool Register(int aQuirks) override; + + /** + * Can be specialized to perform "extra" operations when running the + * function on the server side. + */ + bool RunOriginalFunction(base::ProcessId aClientId, + const IPC::IpdlTuple& aInTuple, + IPC::IpdlTuple* aOutTuple) const override { + return false; + } + + FunctionHookId FunctionId() const override { return functionId; } + + FunctionType* OriginalFunction() const { return mOldFunction; } + + protected: + // Once the function is hooked, this field will take the value of a pointer to + // a function that performs the old behavior. Before that, it is a pointer to + // the original function. + Atomic mOldFunction; +#if defined(XP_WIN) + FuncHookType mStub; +#endif // defined(XP_WIN) + + enum RegistrationStatus { UNREGISTERED, FAILED, SUCCEEDED }; + RegistrationStatus mRegistration; + + // The name of the module containing the function to hook. E.g. "user32.dll". + const nsCString mModuleName; + // The name of the function in the module. + const nsCString mFunctionName; + // The function that we should replace functionName with. The signature of + // newFunction must match that of functionName. + FunctionType* const mNewFunction; + static ShouldHookFunc* const mShouldHook; +}; + +// Default behavior is to hook every registered function. +extern bool AlwaysHook(int); +template +ShouldHookFunc* const BasicFunctionHook::mShouldHook = + AlwaysHook; + +template +bool BasicFunctionHook::Register(int aQuirks) { + MOZ_RELEASE_ASSERT(XRE_IsPluginProcess()); + + // If we have already attempted to hook this function or if quirks tell us + // not to then don't hook. + if (mRegistration != UNREGISTERED || !mShouldHook(aQuirks)) { + return true; + } + + bool isHooked = false; + mRegistration = FAILED; + +#if defined(XP_WIN) + WindowsDllInterceptor* dllInterceptor = + FunctionHook::GetDllInterceptorFor(mModuleName.Data()); + if (!dllInterceptor) { + return false; + } + + isHooked = mStub.Set(*dllInterceptor, mFunctionName.Data(), mNewFunction); +#endif + + if (isHooked) { +#if defined(XP_WIN) + mOldFunction = mStub.GetStub(); +#endif + mRegistration = SUCCEEDED; + } + + HOOK_LOG(LogLevel::Debug, ("Registering to intercept function '%s' : '%s'", + mFunctionName.Data(), SuccessMsg(isHooked))); + + return isHooked; +} + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_ipc_functionhook_h diff --git a/dom/plugins/ipc/IpdlTuple.h b/dom/plugins/ipc/IpdlTuple.h new file mode 100644 index 0000000000..06db9bfddb --- /dev/null +++ b/dom/plugins/ipc/IpdlTuple.h @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_ipc_ipdltuple_h +#define dom_plugins_ipc_ipdltuple_h + +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/plugins/FunctionBrokerIPCUtils.h" +#include "mozilla/Variant.h" + +namespace mozilla { +namespace plugins { + +// The stuff in this "internal" namespace used to be inside the IpdlTuple +// class, but that prevented the MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR +// that is needed on the IpdlTupleElement struct. Without this, nsTArray can end +// up using a move constructor on this struct, which is not memmovable on +// Windows. +namespace internal { + +struct InvalidType {}; + +// Like Variant but with a default constructor. +template +struct MaybeVariant { + public: + MaybeVariant() : mValue(InvalidType()) {} + MaybeVariant(MaybeVariant&& o) : mValue(std::move(o.mValue)) {} + + template + void Set(const Param& aParam) { + mValue = mozilla::AsVariant(aParam); + } + + typedef mozilla::Variant MaybeVariantType; + MaybeVariantType& GetVariant() { return mValue; } + const MaybeVariantType& GetVariant() const { return mValue; } + + private: + MaybeVariantType mValue; +}; + +#if defined(XP_WIN) +typedef MaybeVariant + IpdlTupleElement; +#else +typedef MaybeVariant + IpdlTupleElement; +#endif // defined(XP_WIN) + +} // namespace internal +} // namespace plugins +} // namespace mozilla + +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::plugins::internal::IpdlTupleElement) + +namespace mozilla { +namespace plugins { + +/** + * IpdlTuple is used by automatic function brokering to pass parameter + * lists for brokered functions. It supports a limited set of types + * (see IpdlTuple::IpdlTupleElement). + */ +class IpdlTuple { + public: + uint32_t NumElements() const { return mTupleElements.Length(); } + + template + EltType* Element(uint32_t index) { + if ((index >= mTupleElements.Length()) || + !mTupleElements[index].GetVariant().is()) { + return nullptr; + } + return &mTupleElements[index].GetVariant().as(); + } + + template + const EltType* Element(uint32_t index) const { + return const_cast(this)->Element(index); + } + + template + void AddElement(const EltType& aElt) { + IpdlTupleElement* newEntry = mTupleElements.AppendElement(); + newEntry->Set(aElt); + } + + private: + typedef mozilla::plugins::internal::InvalidType InvalidType; + typedef mozilla::plugins::internal::IpdlTupleElement IpdlTupleElement; + + friend struct IPC::ParamTraits; + friend struct IPC::ParamTraits; + friend struct IPC::ParamTraits; + + nsTArray mTupleElements; +}; + +namespace internal { +template <> +template <> +inline void IpdlTupleElement::Set( + const nsDependentCSubstring& aParam) { + mValue = MaybeVariantType(mozilla::VariantType(), aParam); +} +} // namespace internal + +} // namespace plugins +} // namespace mozilla + +namespace IPC { + +using namespace mozilla::plugins; + +template <> +struct ParamTraits { + typedef IpdlTuple paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mTupleElements); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aParam) { + return ReadParam(aMsg, aIter, &aParam->mTupleElements); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + LogParam(aParam.mTupleElements, aLog); + } +}; + +template <> +struct ParamTraits { + typedef IpdlTuple::IpdlTupleElement paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + MOZ_RELEASE_ASSERT(!aParam.GetVariant().is()); + WriteParam(aMsg, aParam.GetVariant()); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aParam) { + bool ret = ReadParam(aMsg, aIter, &aParam->GetVariant()); + MOZ_RELEASE_ASSERT(!aParam->GetVariant().is()); + return ret; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aParam.GetVariant().match( + [aLog](const auto& aParam) { LogParam(aParam, aLog); }); + } +}; + +template <> +struct ParamTraits { + typedef IpdlTuple::InvalidType paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + MOZ_ASSERT_UNREACHABLE("Attempt to serialize an invalid tuple element"); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aParam) { + MOZ_ASSERT_UNREACHABLE("Attempt to deserialize an invalid tuple element"); + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(L""); + } +}; + +} // namespace IPC + +#endif /* dom_plugins_ipc_ipdltuple_h */ diff --git a/dom/plugins/ipc/MiniShmParent.cpp b/dom/plugins/ipc/MiniShmParent.cpp new file mode 100644 index 0000000000..874b7fe339 --- /dev/null +++ b/dom/plugins/ipc/MiniShmParent.cpp @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MiniShmParent.h" + +#include "base/scoped_handle.h" + +#include + +namespace mozilla { +namespace plugins { + +// static +const unsigned int MiniShmParent::kDefaultMiniShmSectionSize = 0x1000; + +MiniShmParent::MiniShmParent() + : mSectionSize(0), + mParentEvent(nullptr), + mParentGuard(nullptr), + mChildEvent(nullptr), + mChildGuard(nullptr), + mRegWait(nullptr), + mFileMapping(nullptr), + mView(nullptr), + mIsConnected(false), + mTimeout(INFINITE) {} + +MiniShmParent::~MiniShmParent() { CleanUp(); } + +void MiniShmParent::CleanUp() { + if (mRegWait) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = nullptr; + } + if (mParentEvent) { + ::CloseHandle(mParentEvent); + mParentEvent = nullptr; + } + if (mParentGuard) { + ::CloseHandle(mParentGuard); + mParentGuard = nullptr; + } + if (mChildEvent) { + ::CloseHandle(mChildEvent); + mChildEvent = nullptr; + } + if (mChildGuard) { + ::CloseHandle(mChildGuard); + mChildGuard = nullptr; + } + if (mView) { + ::UnmapViewOfFile(mView); + mView = nullptr; + } + if (mFileMapping) { + ::CloseHandle(mFileMapping); + mFileMapping = nullptr; + } +} + +nsresult MiniShmParent::Init(MiniShmObserver* aObserver, const DWORD aTimeout, + const unsigned int aSectionSize) { + if (!aObserver || !aSectionSize || (aSectionSize % 0x1000) || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (mFileMapping) { + return NS_ERROR_ALREADY_INITIALIZED; + } + SECURITY_ATTRIBUTES securityAttributes = {sizeof(securityAttributes), nullptr, + TRUE}; + ScopedHandle parentEvent( + ::CreateEvent(&securityAttributes, FALSE, FALSE, nullptr)); + if (!parentEvent.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle parentGuard( + ::CreateEvent(&securityAttributes, FALSE, TRUE, nullptr)); + if (!parentGuard.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle childEvent( + ::CreateEvent(&securityAttributes, FALSE, FALSE, nullptr)); + if (!childEvent.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle childGuard( + ::CreateEvent(&securityAttributes, FALSE, TRUE, nullptr)); + if (!childGuard.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedHandle mapping(::CreateFileMapping(INVALID_HANDLE_VALUE, + &securityAttributes, PAGE_READWRITE, + 0, aSectionSize, nullptr)); + if (!mapping.IsValid()) { + return NS_ERROR_FAILURE; + } + ScopedMappedFileView view(::MapViewOfFile(mapping, FILE_MAP_WRITE, 0, 0, 0)); + if (!view.IsValid()) { + return NS_ERROR_FAILURE; + } + nsresult rv = SetView(view, aSectionSize, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetGuard(childGuard, aTimeout); + NS_ENSURE_SUCCESS(rv, rv); + + MiniShmInit* initStruct = nullptr; + rv = GetWritePtrInternal(initStruct); + NS_ENSURE_SUCCESS(rv, rv); + initStruct->mParentEvent = parentEvent; + initStruct->mParentGuard = parentGuard; + initStruct->mChildEvent = childEvent; + initStruct->mChildGuard = childGuard; + + if (!::RegisterWaitForSingleObject(&mRegWait, parentEvent, &SOnEvent, this, + INFINITE, WT_EXECUTEDEFAULT)) { + return NS_ERROR_FAILURE; + } + + mParentEvent = parentEvent.Take(); + mParentGuard = parentGuard.Take(); + mChildEvent = childEvent.Take(); + mChildGuard = childGuard.Take(); + mFileMapping = mapping.Take(); + mView = view.Take(); + mSectionSize = aSectionSize; + SetObserver(aObserver); + mTimeout = aTimeout; + return NS_OK; +} + +nsresult MiniShmParent::GetCookie(std::wstring& cookie) { + if (!mFileMapping) { + return NS_ERROR_NOT_INITIALIZED; + } + std::wostringstream oss; + oss << mFileMapping; + if (!oss) { + return NS_ERROR_FAILURE; + } + cookie = oss.str(); + return NS_OK; +} + +nsresult MiniShmParent::Send() { + if (!mChildEvent) { + return NS_ERROR_NOT_INITIALIZED; + } + if (!::SetEvent(mChildEvent)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +bool MiniShmParent::IsConnected() const { return mIsConnected; } + +void MiniShmParent::OnEvent() { + if (mIsConnected) { + MiniShmBase::OnEvent(); + } else { + FinalizeConnection(); + } + ::SetEvent(mParentGuard); +} + +void MiniShmParent::FinalizeConnection() { + const MiniShmInitComplete* initCompleteStruct = nullptr; + nsresult rv = GetReadPtr(initCompleteStruct); + mIsConnected = NS_SUCCEEDED(rv) && initCompleteStruct->mSucceeded; + if (mIsConnected) { + OnConnect(); + } +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/MiniShmParent.h b/dom/plugins/ipc/MiniShmParent.h new file mode 100644 index 0000000000..06eec62560 --- /dev/null +++ b/dom/plugins/ipc/MiniShmParent.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_MiniShmParent_h +#define mozilla_plugins_MiniShmParent_h + +#include "MiniShmBase.h" + +#include + +namespace mozilla { +namespace plugins { + +/** + * This class provides a lightweight shared memory interface for a parent + * process in Win32. + * This code assumes that there is a parent-child relationship between + * processes, as it creates inheritable handles. + * Note that this class is *not* an IPDL actor. + * + * @see MiniShmChild + */ +class MiniShmParent : public MiniShmBase { + public: + MiniShmParent(); + virtual ~MiniShmParent(); + + static const unsigned int kDefaultMiniShmSectionSize; + + /** + * Initialize shared memory on the parent side. + * + * @param aObserver A MiniShmObserver object to receive event notifications. + * @param aTimeout Timeout in milliseconds. + * @param aSectionSize Desired size of the shared memory section. This is + * expected to be a multiple of 0x1000 (4KiB). + * @return nsresult error code + */ + nsresult Init(MiniShmObserver* aObserver, const DWORD aTimeout, + const unsigned int aSectionSize = kDefaultMiniShmSectionSize); + + /** + * Destroys the shared memory section. Useful to explicitly release + * resources if it is known that they won't be needed again. + */ + void CleanUp(); + + /** + * Provides a cookie string that should be passed to MiniShmChild + * during its initialization. + * + * @param aCookie A std::wstring variable to receive the cookie. + * @return nsresult error code + */ + nsresult GetCookie(std::wstring& aCookie); + + virtual nsresult Send() override; + + bool IsConnected() const; + + protected: + void OnEvent() override; + + private: + void FinalizeConnection(); + + unsigned int mSectionSize; + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + HANDLE mRegWait; + HANDLE mFileMapping; + LPVOID mView; + bool mIsConnected; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmParent); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmParent_h diff --git a/dom/plugins/ipc/NPEventAndroid.h b/dom/plugins/ipc/NPEventAndroid.h new file mode 100644 index 0000000000..0aa94a1bc1 --- /dev/null +++ b/dom/plugins/ipc/NPEventAndroid.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This is a NPEventX11.h derived stub for Android +// Plugins aren't actually supported yet + +#ifndef mozilla_dom_plugins_NPEventAndroid_h +#define mozilla_dom_plugins_NPEventAndroid_h + +#include "npapi.h" + +namespace mozilla { + +namespace plugins { + +struct NPRemoteEvent { + NPEvent event; +}; + +} // namespace plugins + +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteBytes(&aParam, sizeof(paramType)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType)); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + // TODO + aLog->append(L"(AndroidEvent)"); + } +}; + +} // namespace IPC + +#endif // mozilla_dom_plugins_NPEventAndroid_h diff --git a/dom/plugins/ipc/NPEventOSX.h b/dom/plugins/ipc/NPEventOSX.h new file mode 100644 index 0000000000..9e24c426e5 --- /dev/null +++ b/dom/plugins/ipc/NPEventOSX.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_plugins_NPEventOSX_h +#define mozilla_dom_plugins_NPEventOSX_h 1 + +#include "npapi.h" + +namespace mozilla { + +namespace plugins { + +struct NPRemoteEvent { + NPCocoaEvent event; + double contentsScaleFactor; +}; + +} // namespace plugins + +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteInt(aParam.event.type); + aMsg->WriteUInt32(aParam.event.version); + switch (aParam.event.type) { + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseEntered: + case NPCocoaEventMouseExited: + case NPCocoaEventMouseDragged: + case NPCocoaEventScrollWheel: + aMsg->WriteUInt32(aParam.event.data.mouse.modifierFlags); + aMsg->WriteDouble(aParam.event.data.mouse.pluginX); + aMsg->WriteDouble(aParam.event.data.mouse.pluginY); + aMsg->WriteInt32(aParam.event.data.mouse.buttonNumber); + aMsg->WriteInt32(aParam.event.data.mouse.clickCount); + aMsg->WriteDouble(aParam.event.data.mouse.deltaX); + aMsg->WriteDouble(aParam.event.data.mouse.deltaY); + aMsg->WriteDouble(aParam.event.data.mouse.deltaZ); + break; + case NPCocoaEventKeyDown: + case NPCocoaEventKeyUp: + case NPCocoaEventFlagsChanged: + aMsg->WriteUInt32(aParam.event.data.key.modifierFlags); + WriteParam(aMsg, aParam.event.data.key.characters); + WriteParam(aMsg, aParam.event.data.key.charactersIgnoringModifiers); + aMsg->WriteUnsignedChar(aParam.event.data.key.isARepeat); + aMsg->WriteUInt16(aParam.event.data.key.keyCode); + break; + case NPCocoaEventFocusChanged: + case NPCocoaEventWindowFocusChanged: + aMsg->WriteUnsignedChar(aParam.event.data.focus.hasFocus); + break; + case NPCocoaEventDrawRect: + // We don't write out the context pointer, it would always be + // nullptr and is just filled in as such on the read. + aMsg->WriteDouble(aParam.event.data.draw.x); + aMsg->WriteDouble(aParam.event.data.draw.y); + aMsg->WriteDouble(aParam.event.data.draw.width); + aMsg->WriteDouble(aParam.event.data.draw.height); + break; + case NPCocoaEventTextInput: + WriteParam(aMsg, aParam.event.data.text.text); + break; + default: + MOZ_ASSERT_UNREACHABLE( + "Attempted to serialize unknown event " + "type."); + return; + } + aMsg->WriteDouble(aParam.contentsScaleFactor); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + int type = 0; + if (!aMsg->ReadInt(aIter, &type)) { + return false; + } + aResult->event.type = static_cast(type); + + if (!aMsg->ReadUInt32(aIter, &aResult->event.version)) { + return false; + } + + switch (aResult->event.type) { + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseEntered: + case NPCocoaEventMouseExited: + case NPCocoaEventMouseDragged: + case NPCocoaEventScrollWheel: + if (!aMsg->ReadUInt32(aIter, + &aResult->event.data.mouse.modifierFlags)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.pluginX)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.pluginY)) { + return false; + } + if (!aMsg->ReadInt32(aIter, &aResult->event.data.mouse.buttonNumber)) { + return false; + } + if (!aMsg->ReadInt32(aIter, &aResult->event.data.mouse.clickCount)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaX)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaY)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaZ)) { + return false; + } + break; + case NPCocoaEventKeyDown: + case NPCocoaEventKeyUp: + case NPCocoaEventFlagsChanged: + if (!aMsg->ReadUInt32(aIter, &aResult->event.data.key.modifierFlags)) { + return false; + } + if (!ReadParam(aMsg, aIter, &aResult->event.data.key.characters)) { + return false; + } + if (!ReadParam(aMsg, aIter, + &aResult->event.data.key.charactersIgnoringModifiers)) { + return false; + } + if (!aMsg->ReadUnsignedChar(aIter, + &aResult->event.data.key.isARepeat)) { + return false; + } + if (!aMsg->ReadUInt16(aIter, &aResult->event.data.key.keyCode)) { + return false; + } + break; + case NPCocoaEventFocusChanged: + case NPCocoaEventWindowFocusChanged: + if (!aMsg->ReadUnsignedChar(aIter, + &aResult->event.data.focus.hasFocus)) { + return false; + } + break; + case NPCocoaEventDrawRect: + aResult->event.data.draw.context = nullptr; + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.x)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.y)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.width)) { + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.height)) { + return false; + } + break; + case NPCocoaEventTextInput: + if (!ReadParam(aMsg, aIter, &aResult->event.data.text.text)) { + return false; + } + break; + default: + MOZ_ASSERT_UNREACHABLE( + "Attempted to de-serialize unknown " + "event type."); + return false; + } + if (!aMsg->ReadDouble(aIter, &aResult->contentsScaleFactor)) { + return false; + } + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(L"(NPCocoaEvent)"); + } +}; + +} // namespace IPC + +#endif // ifndef mozilla_dom_plugins_NPEventOSX_h diff --git a/dom/plugins/ipc/NPEventUnix.h b/dom/plugins/ipc/NPEventUnix.h new file mode 100644 index 0000000000..55494b4d8c --- /dev/null +++ b/dom/plugins/ipc/NPEventUnix.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_plugins_NPEventUnix_h +#define mozilla_dom_plugins_NPEventUnix_h 1 + +#include "npapi.h" + +#ifdef MOZ_X11 +# include "mozilla/X11Util.h" +#endif + +namespace mozilla { + +namespace plugins { + +struct NPRemoteEvent { + NPEvent event; +}; + +} // namespace plugins + +} // namespace mozilla + +// +// XEvent is defined as a union of all more specific X*Events. +// Luckily, as of xorg 1.6.0 / X protocol 11 rev 0, the only pointer +// field contained in any of these specific X*Event structs is a +// |Display*|. So to simplify serializing these XEvents, we make the +// +// ********** XXX ASSUMPTION XXX ********** +// +// that the process to which the event is forwarded shares the same +// display as the process on which the event originated. +// +// With this simplification, serialization becomes a simple memcpy to +// the output stream. Deserialization starts as just a memcpy from +// the input stream, BUT we then have to write the correct |Display*| +// into the right field of each X*Event that contains one. +// + +namespace IPC { + +template <> +struct ParamTraits // synonym for XEvent +{ + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteBytes(&aParam, sizeof(paramType)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType))) { + return false; + } + +#ifdef MOZ_X11 + SetXDisplay(aResult->event); +#endif + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + // TODO + aLog->append(L"(XEvent)"); + } + +#ifdef MOZ_X11 + private: + static void SetXDisplay(XEvent& ev) { + Display* display = mozilla::DefaultXDisplay(); + if (ev.type >= KeyPress) { + ev.xany.display = display; + } else { + // XXX assuming that this is an error event + // (type == 0? not clear from Xlib.h) + ev.xerror.display = display; + } + } +#endif +}; + +} // namespace IPC + +#endif // ifndef mozilla_dom_plugins_NPEventX11_h diff --git a/dom/plugins/ipc/NPEventWindows.h b/dom/plugins/ipc/NPEventWindows.h new file mode 100644 index 0000000000..b2429883f8 --- /dev/null +++ b/dom/plugins/ipc/NPEventWindows.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_plugins_NPEventWindows_h +#define mozilla_dom_plugins_NPEventWindows_h 1 + +#ifndef WM_MOUSEHWHEEL +# define WM_MOUSEHWHEEL (0x020E) +#endif + +#include "npapi.h" +namespace mozilla { + +namespace plugins { + +// We use an NPRemoteEvent struct so that we can store the extra data on +// the stack so that we don't need to worry about managing the memory. +struct NPRemoteEvent { + NPEvent event; + union { + RECT rect; + WINDOWPOS windowpos; + } lParamData; + double contentsScaleFactor; +}; + +} // namespace plugins + +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::plugins::NPRemoteEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + // Make a non-const copy of aParam so that we can muck with + // its insides for tranport + paramType paramCopy; + + paramCopy.event = aParam.event; + + // We can't blindly ipc events because they may sometimes contain + // pointers to memory in the sending process. For example, the + // WM_IME_CONTROL with the IMC_GETCOMPOSITIONFONT message has lParam + // set to a pointer to a LOGFONT structure. + switch (paramCopy.event.event) { + case WM_WINDOWPOSCHANGED: + // The lParam parameter of WM_WINDOWPOSCHANGED holds a pointer to + // a WINDOWPOS structure that contains information about the + // window's new size and position + paramCopy.lParamData.windowpos = + *(reinterpret_cast(paramCopy.event.lParam)); + break; + case WM_PAINT: + // The lParam parameter of WM_PAINT holds a pointer to an RECT + // structure specifying the bounding box of the update area. + paramCopy.lParamData.rect = + *(reinterpret_cast(paramCopy.event.lParam)); + break; + + // the white list of events that we will ipc to the client + case WM_CHAR: + case WM_SYSCHAR: + + case WM_KEYUP: + case WM_SYSKEYUP: + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + case WM_CONTEXTMENU: + + case WM_CUT: + case WM_COPY: + case WM_PASTE: + case WM_CLEAR: + case WM_UNDO: + + case WM_MOUSELEAVE: + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + + case WM_SETFOCUS: + case WM_KILLFOCUS: + + case WM_IME_STARTCOMPOSITION: + case WM_IME_COMPOSITION: + case WM_IME_ENDCOMPOSITION: + case WM_IME_CHAR: + case WM_IME_SETCONTEXT: + case WM_IME_COMPOSITIONFULL: + case WM_IME_KEYDOWN: + case WM_IME_KEYUP: + case WM_IME_SELECT: + case WM_INPUTLANGCHANGEREQUEST: + case WM_INPUTLANGCHANGE: + break; + + default: + // RegisterWindowMessage events should be passed. + if (paramCopy.event.event >= 0xC000) break; + + // FIXME/bug 567465: temporarily work around unhandled + // events by forwarding a "dummy event". The eventual + // fix will be to stop trying to send these events + // entirely. + paramCopy.event.event = WM_NULL; + break; + } + + aMsg->WriteBytes(¶mCopy, sizeof(paramType)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType))) { + return false; + } + + if (aResult->event.event == WM_PAINT) { + // restore the lParam to point at the RECT + aResult->event.lParam = + reinterpret_cast(&aResult->lParamData.rect); + } else if (aResult->event.event == WM_WINDOWPOSCHANGED) { + // restore the lParam to point at the WINDOWPOS + aResult->event.lParam = + reinterpret_cast(&aResult->lParamData.windowpos); + } + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(L"(WINEvent)"); + } +}; + +} // namespace IPC + +#endif // ifndef mozilla_dom_plugins_NPEventWindows_h diff --git a/dom/plugins/ipc/PBrowserStream.ipdl b/dom/plugins/ipc/PBrowserStream.ipdl new file mode 100644 index 0000000000..65548ca06f --- /dev/null +++ b/dom/plugins/ipc/PBrowserStream.ipdl @@ -0,0 +1,42 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PPluginInstance; + + +using mozilla::plugins::Buffer from "mozilla/plugins/PluginMessageUtils.h"; +using mozilla::plugins::IPCByteRanges from "mozilla/plugins/PluginMessageUtils.h"; + +using NPError from "npapi.h"; +using NPReason from "npapi.h"; + +namespace mozilla { +namespace plugins { + +/** + * NPBrowserStream represents a NPStream sent from the browser to the plugin. + */ + +intr protocol PBrowserStream +{ + manager PPluginInstance; + +child: + async Write(int32_t offset, uint32_t newlength, + Buffer data); + + /** + * NPP_DestroyStream may race with other messages: the child acknowledges + * the message with StreamDestroyed before this actor is deleted. + */ + async NPP_DestroyStream(NPReason reason); + async __delete__(); + +parent: + async StreamDestroyed(); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PFunctionBroker.ipdl b/dom/plugins/ipc/PFunctionBroker.ipdl new file mode 100644 index 0000000000..d8ecb76746 --- /dev/null +++ b/dom/plugins/ipc/PFunctionBroker.ipdl @@ -0,0 +1,23 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +using mozilla::plugins::FunctionHookId from "mozilla/plugins/FunctionBrokerIPCUtils.h"; +using IPC::IpdlTuple from "mozilla/plugins/IpdlTuple.h"; + +namespace mozilla { +namespace plugins { + +/** + * Top-level actor that brokers functions for the client process. + */ +sync protocol PFunctionBroker +{ +parent: + sync BrokerFunction(FunctionHookId aFunctionId, IpdlTuple aFunctionParams) + returns (IpdlTuple aFunctionRet); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl b/dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl new file mode 100644 index 0000000000..d8c552fd86 --- /dev/null +++ b/dom/plugins/ipc/PPluginBackgroundDestroyer.ipdl @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PPluginInstance; + +namespace mozilla { +namespace plugins { + +/** + * This protocol exists to allow us to correctly destroy background + * surfaces. The browser owns the surfaces, but shares a "reference" + * with the plugin. The browser needs to notify the plugin when the + * background is going to be destroyed, but it can't rely on the + * plugin to destroy it because the plugin may crash at any time. So + * the plugin instance relinquishes destruction of the its old + * background to actors of this protocol, which can deal with crashy + * corner cases more easily than the instance. + */ +protocol PPluginBackgroundDestroyer { + manager PPluginInstance; + + // The ctor message for this protocol serves double-duty as + // notification that that the background is stale. + +parent: + async __delete__(); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginInstance.ipdl b/dom/plugins/ipc/PPluginInstance.ipdl new file mode 100644 index 0000000000..4b54f61e3b --- /dev/null +++ b/dom/plugins/ipc/PPluginInstance.ipdl @@ -0,0 +1,294 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PPluginBackgroundDestroyer; +include protocol PPluginModule; +include protocol PPluginScriptableObject; +include protocol PBrowserStream; +include protocol PStreamNotify; +include protocol PPluginSurface; + +include "gfxipc/ShadowLayerUtils.h"; +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; + +using NPError from "npapi.h"; +using struct mozilla::plugins::NPRemoteWindow from "mozilla/plugins/PluginMessageUtils.h"; +using struct mozilla::plugins::NPRemoteEvent from "mozilla/plugins/PluginMessageUtils.h"; +using NPRect from "npapi.h"; +using NPNURLVariable from "npapi.h"; +using NPCoordinateSpace from "npapi.h"; +using NPNVariable from "npapi.h"; +using mozilla::plugins::NativeWindowHandle from "mozilla/plugins/PluginMessageUtils.h"; +using gfxSurfaceType from "gfxTypes.h"; +using mozilla::gfx::IntSize from "mozilla/gfx/2D.h"; +using mozilla::gfx::IntRect from "mozilla/gfx/2D.h"; +using struct mozilla::null_t from "mozilla/ipc/IPCCore.h"; +using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h"; +using mozilla::plugins::WindowsSharedMemoryHandle from "mozilla/plugins/PluginMessageUtils.h"; +using mozilla::layers::SurfaceDescriptorX11 from "gfxipc/SurfaceDescriptor.h"; +using nsIntRect from "nsRect.h"; +using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h"; +using struct DxgiAdapterDesc from "mozilla/D3DMessageUtils.h"; +using struct mozilla::widget::CandidateWindowPosition from "ipc/nsGUIEventIPC.h"; +using class mozilla::NativeEventData from "ipc/nsGUIEventIPC.h"; + +namespace mozilla { +namespace plugins { + +struct IOSurfaceDescriptor { + uint32_t surfaceId; + double contentsScaleFactor; +}; + +union SurfaceDescriptor { + Shmem; + SurfaceDescriptorX11; + PPluginSurface; // used on Windows + IOSurfaceDescriptor; // used on OSX 10.5+ + // Descriptor can be null here in case + // 1) of first Show call (prevSurface is null) + // 2) when child is going to destroy + // and it just want to grab prevSurface + // back without giving new surface + null_t; +}; + +intr protocol PPluginInstance +{ + manager PPluginModule; + + manages PPluginBackgroundDestroyer; + manages PPluginScriptableObject; + manages PBrowserStream; + manages PStreamNotify; + manages PPluginSurface; + +child: + async __delete__(); + + // This is only used on Windows and, for windowed plugins, must be called + // before the first call to NPP_SetWindow. + intr CreateChildPluginWindow() + returns (NativeWindowHandle childPluginWindow); + + // This is only used on Windows and, for windowless plugins. + async CreateChildPopupSurrogate(NativeWindowHandle netscapeWindow); + + intr NPP_SetWindow(NPRemoteWindow window); + + intr NPP_GetValue_NPPVpluginWantsAllNetworkStreams() + returns (bool value, NPError result); + + intr NPP_GetValue_NPPVpluginScriptableNPObject() + returns (nullable PPluginScriptableObject value, NPError result); + + intr NPP_SetValue_NPNVprivateModeBool(bool value) returns (NPError result); + intr NPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId() + returns (nsCString plug_id, NPError result); + + intr NPP_SetValue_NPNVCSSZoomFactor(double value) returns (NPError result); + + intr NPP_SetValue_NPNVmuteAudioBool(bool muted) returns (NPError result); + + intr NPP_HandleEvent(NPRemoteEvent event) + returns (int16_t handled); + // special cases where we need to a shared memory buffer + intr NPP_HandleEvent_Shmem(NPRemoteEvent event, Shmem buffer) + returns (int16_t handled, Shmem rtnbuffer); + // special cases where we need an iosurface + intr NPP_HandleEvent_IOSurface(NPRemoteEvent event, uint32_t surfaceid) + returns (int16_t handled); + // special cases of HandleEvent to make mediating races simpler + intr Paint(NPRemoteEvent event) + returns (int16_t handled); + // this is only used on windows to forward WM_WINDOWPOSCHANGE + async WindowPosChanged(NPRemoteEvent event); + // used on OS X to tell the child the contents scale factor + // of its parent has changed + async ContentsScaleFactorChanged(double aContentsScaleFactor); + + // ********************** Async plugins rendering + // see https://wiki.mozilla.org/Gecko:AsyncPluginPainting + // ********************** + + // Async version of SetWindow call + // @param surfaceType - gfxASurface::gfxSurfaceType + // plugin child must create offscreen buffer + // with type equals to surfaceType + async AsyncSetWindow(gfxSurfaceType surfaceType, NPRemoteWindow window); + + // There is now an opaque background behind this instance (or the + // background was updated). The changed area is |rect|. The + // browser owns the background surface, and it's read-only from + // within the plugin process. |background| is either null_t to + // refer to the existing background or a fresh descriptor. + async UpdateBackground(SurfaceDescriptor background, nsIntRect rect); + + async NPP_DidComposite(); + + intr NPP_Destroy() + returns (NPError rv); + + // HandledWindowedPluginKeyEvent() is always called after posting a native + // key event with OnWindowedPluginKeyEvent(). + // + // @param aKeyEventData The key event which was posted to the parent + // process. + // @param aIsConsumed true if aKeyEventData is consumed in the + // parent process. Otherwise, false. + async HandledWindowedPluginKeyEvent(NativeEventData aKeyEventData, + bool aIsConsumed); + +parent: + intr NPN_GetValue_NPNVWindowNPObject() + returns (nullable PPluginScriptableObject value, NPError result); + intr NPN_GetValue_NPNVPluginElementNPObject() + returns (nullable PPluginScriptableObject value, NPError result); + intr NPN_GetValue_NPNVprivateModeBool() + returns (bool value, NPError result); + intr NPN_GetValue_NPNVnetscapeWindow() + returns (NativeWindowHandle value, NPError result); + intr NPN_GetValue_NPNVdocumentOrigin() + returns (nsCString value, NPError result); + intr NPN_GetValue_DrawingModelSupport(NPNVariable model) + returns (bool value); + intr NPN_GetValue_SupportsAsyncBitmapSurface() + returns (bool value); + intr NPN_GetValue_SupportsAsyncDXGISurface() + returns (bool value); + intr NPN_GetValue_PreferredDXGIAdapter() + returns (DxgiAdapterDesc desc); + + intr NPN_SetValue_NPPVpluginWindow(bool windowed) + returns (NPError result); + intr NPN_SetValue_NPPVpluginTransparent(bool transparent) + returns (NPError result); + intr NPN_SetValue_NPPVpluginUsesDOMForCursor(bool useDOMForCursor) + returns (NPError result); + intr NPN_SetValue_NPPVpluginDrawingModel(int drawingModel) + returns (NPError result); + intr NPN_SetValue_NPPVpluginEventModel(int eventModel) + returns (NPError result); + intr NPN_SetValue_NPPVpluginIsPlayingAudio(bool isAudioPlaying) + returns (NPError result); + + intr NPN_GetURL(nsCString url, nsCString target) + returns (NPError result); + intr NPN_PostURL(nsCString url, nsCString target, nsCString buffer, bool file) + returns (NPError result); + + /** + * Covers both NPN_GetURLNotify and NPN_PostURLNotify. + * @TODO This would be more readable as an overloaded method, + * but IPDL doesn't allow that for constructors. + */ + intr PStreamNotify(nsCString url, nsCString target, bool post, + nsCString buffer, bool file) + returns (NPError result); + + async NPN_InvalidateRect(NPRect rect); + + // Clear the current plugin image. + sync RevokeCurrentDirectSurface(); + + // Create a new DXGI shared surface with the given format and size. The + // returned handle, on success, can be opened as an ID3D10Texture2D or + // ID3D11Texture2D on a corresponding device. + sync InitDXGISurface(SurfaceFormat format, IntSize size) + returns (WindowsHandle handle, NPError result); + + // Destroy a surface previously allocated with InitDXGISurface(). + sync FinalizeDXGISurface(WindowsHandle handle); + + // Set the current plugin image to the bitmap in the given shmem buffer. The + // format must be B8G8R8A8 or B8G8R8X8. + sync ShowDirectBitmap(Shmem buffer, + SurfaceFormat format, + uint32_t stride, + IntSize size, + IntRect dirty); + + // Set the current plugin image to the DXGI surface in |handle|. + sync ShowDirectDXGISurface(WindowsHandle handle, + IntRect dirty); + + // Give |newSurface|, containing this instance's updated pixels, to + // the browser for compositing. When this method returns, any surface + // previously passed to Show may be destroyed. + // + // @param rect - actually updated rectangle, comparing to prevSurface content + // could be used for partial render of layer to topLevel context + // @param newSurface - remotable surface + // @param prevSurface - if the previous surface was shared-memory, returns + // the shmem for reuse + sync Show(NPRect updatedRect, SurfaceDescriptor newSurface) + returns (SurfaceDescriptor prevSurface); + + async PPluginSurface(WindowsSharedMemoryHandle handle, + IntSize size, + bool transparent); + + intr NPN_PushPopupsEnabledState(bool aState); + + intr NPN_PopPopupsEnabledState(); + + intr NPN_GetValueForURL(NPNURLVariable variable, nsCString url) + returns (nsCString value, NPError result); + + intr NPN_SetValueForURL(NPNURLVariable variable, nsCString url, + nsCString value) + returns (NPError result); + + intr NPN_ConvertPoint(double sourceX, bool ignoreDestX, double sourceY, bool ignoreDestY, NPCoordinateSpace sourceSpace, + NPCoordinateSpace destSpace) + returns (double destX, double destY, bool result); + + async RedrawPlugin(); + + // Sends a native window to be adopted by the native window that would be + // returned by NPN_GetValue_NPNVnetscapeWindow. Only used on Windows. + async SetNetscapeWindowAsParent(NativeWindowHandle childWindow); + + sync GetCompositionString(uint32_t aType) + returns (uint8_t[] aDist, int32_t aLength); + async RequestCommitOrCancel(bool aCommitted); + + // Notifies the parent process of a plugin instance receiving key event + // directly. + // + // @param aKeyEventData The native key event which will be sent to + // plugin from native event handler. + async OnWindowedPluginKeyEvent(NativeEventData aKeyEventData); + +both: + async PPluginScriptableObject(); + +child: + /* NPP_NewStream */ + async PBrowserStream(nsCString url, + uint32_t length, + uint32_t lastmodified, + nullable PStreamNotify notifyData, + nsCString headers); + + // Implements the legacy (synchronous) version of NPP_NewStream for when + // async plugin init is preffed off. + intr NPP_NewStream(PBrowserStream actor, nsCString mimeType, bool seekable) + returns (NPError rv, + uint16_t stype); + +parent: + intr PluginFocusChange(bool gotFocus); + +child: + intr SetPluginFocus(); + intr UpdateWindow(); + + async PPluginBackgroundDestroyer(); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginModule.ipdl b/dom/plugins/ipc/PPluginModule.ipdl new file mode 100644 index 0000000000..6d98408c2f --- /dev/null +++ b/dom/plugins/ipc/PPluginModule.ipdl @@ -0,0 +1,155 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PPluginInstance; +include protocol PPluginScriptableObject; +include protocol PContent; +include protocol PProfiler; +include protocol PFunctionBroker; + +using NPError from "npapi.h"; +using NPNVariable from "npapi.h"; +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; +using class mac_plugin_interposing::NSCursorInfo from "mozilla/plugins/PluginMessageUtils.h"; +using struct nsID from "nsID.h"; +using struct mozilla::plugins::NPAudioDeviceChangeDetailsIPC from "mozilla/plugins/PluginMessageUtils.h"; +using struct mozilla::plugins::NPAudioDeviceStateChangedIPC from "mozilla/plugins/PluginMessageUtils.h"; + +namespace mozilla { +namespace plugins { + +struct PluginSettings +{ + // These settings correspond to NPNVariable. They are fetched from + // mozilla::plugins::parent::_getvalue. + bool javascriptEnabled; + bool asdEnabled; + bool isOffline; + bool supportsXembed; + bool supportsWindowless; + + // These settings come from elsewhere. + nsCString userAgent; + bool nativeCursorsSupported; +}; + +intr protocol PPluginModule +{ + manages PPluginInstance; + +both: + // Window-specific message which instructs the interrupt mechanism to enter + // a nested event loop for the current interrupt call. + async ProcessNativeEventsInInterruptCall(); + +child: + async InitProfiler(Endpoint aEndPoint); + + async DisableFlashProtectedMode(); + + // Sync query to check if a Flash library indicates it + // supports async rendering mode. + intr ModuleSupportsAsyncRender() + returns (bool result); + + // Forces the child process to update its plugin function table. + intr NP_GetEntryPoints() + returns (NPError rv); + + intr NP_Initialize(PluginSettings settings) + returns (NPError rv); + + async PPluginInstance(nsCString aMimeType, + nsCString[] aNames, + nsCString[] aValues); + + // Implements the synchronous version of NPP_New for when async plugin init + // is preffed off. + intr SyncNPP_New(PPluginInstance aActor) + returns (NPError rv); + + intr NP_Shutdown() + returns (NPError rv); + + intr OptionalFunctionsSupported() + returns (bool aURLRedirectNotify, bool aClearSiteData, + bool aGetSitesWithData); + + async NPP_ClearSiteData(nsCString site, uint64_t flags, uint64_t maxAge, uint64_t aCallbackId); + + async NPP_GetSitesWithData(uint64_t aCallbackId); + + // Windows specific message to set up an audio session in the plugin process + async SetAudioSessionData(nsID aID, + nsString aDisplayName, + nsString aIconPath); + + async SetParentHangTimeout(uint32_t seconds); + + intr InitCrashReporter() + returns (NativeThreadId tid); + + async SettingChanged(PluginSettings settings); + + async NPP_SetValue_NPNVaudioDeviceChangeDetails(NPAudioDeviceChangeDetailsIPC changeDetails); + async NPP_SetValue_NPNVaudioDeviceStateChanged(NPAudioDeviceStateChangedIPC deviceState); + + async InitPluginModuleChild(Endpoint endpoint); + + async InitPluginFunctionBroker(Endpoint endpoint); + +parent: + /** + * This message is only used on X11 platforms. + * + * Send a dup of the plugin process's X socket to the parent + * process. In theory, this scheme keeps the plugin's X resources + * around until after both the plugin process shuts down *and* the + * parent process closes the dup fd. This is used to prevent the + * parent process from crashing on X errors if, e.g., the plugin + * crashes *just before* a repaint and the parent process tries to + * use the newly-invalid surface. + */ + async BackUpXResources(FileDescriptor aXSocketFd); + + // Wake up and process a few native events. Periodically called by + // Gtk-specific code upon detecting that the plugin process has + // entered a nested event loop. If the browser doesn't process + // native events, then "livelock" and some other glitches can occur. + intr ProcessSomeEvents(); + + // OS X Specific calls to manage the plugin's window + // when interposing system calls. + async PluginShowWindow(uint32_t aWindowId, bool aModal, + int32_t aX, int32_t aY, + double aWidth, double aHeight); + async PluginHideWindow(uint32_t aWindowId); + + // OS X Specific calls to allow the plugin to manage the cursor. + async SetCursor(NSCursorInfo cursorInfo); + async ShowCursor(bool show); + async PushCursor(NSCursorInfo cursorInfo); + async PopCursor(); + + sync NPN_SetException(nsCString message); + + async NPN_ReloadPlugins(bool aReloadPages); + + // Notifies the chrome process that a PluginModuleChild linked to a content + // process was destroyed. The chrome process may choose to asynchronously shut + // down the plugin process in response. + async NotifyContentModuleDestroyed(); + + // Answers to request about site data + async ReturnClearSiteData(NPError aRv, uint64_t aCallbackId); + + async ReturnSitesWithData(nsCString[] aSites, uint64_t aCallbackId); + + intr NPN_SetValue_NPPVpluginRequiresAudioDeviceChanges(bool shouldRegister) + returns (NPError result); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginScriptableObject.ipdl b/dom/plugins/ipc/PPluginScriptableObject.ipdl new file mode 100644 index 0000000000..8bee7b1997 --- /dev/null +++ b/dom/plugins/ipc/PPluginScriptableObject.ipdl @@ -0,0 +1,102 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PPluginInstance; +include PluginTypes; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +using struct mozilla::null_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace plugins { + +union Variant { + void_t; + null_t; + bool; + int; + double; + nsCString; + nullable PPluginScriptableObject; +}; + +intr protocol PPluginScriptableObject +{ + manager PPluginInstance; + +both: + async __delete__(); + +parent: + intr NPN_Evaluate(nsCString aScript) + returns (Variant aResult, + bool aSuccess); + +child: + intr Invalidate(); + +both: + // NPClass methods + intr HasMethod(PluginIdentifier aId) + returns (bool aHasMethod); + + intr Invoke(PluginIdentifier aId, + Variant[] aArgs) + returns (Variant aResult, + bool aSuccess); + + intr InvokeDefault(Variant[] aArgs) + returns (Variant aResult, + bool aSuccess); + + intr HasProperty(PluginIdentifier aId) + returns (bool aHasProperty); + + intr SetProperty(PluginIdentifier aId, + Variant aValue) + returns (bool aSuccess); + + intr RemoveProperty(PluginIdentifier aId) + returns (bool aSuccess); + + intr Enumerate() + returns (PluginIdentifier[] aProperties, + bool aSuccess); + + intr Construct(Variant[] aArgs) + returns (Variant aResult, + bool aSuccess); + + // Objects are initially unprotected, and the Protect and Unprotect functions + // only affect protocol objects that represent NPObjects created in the same + // process (rather than protocol objects that are a proxy for an NPObject + // created in another process). Protocol objects representing local NPObjects + // are protected after an NPObject has been associated with the protocol + // object. Sending the protocol object as an argument to the other process + // temporarily protects the protocol object again for the duration of the call. + async Protect(); + async Unprotect(); + + /** + * GetProperty is slightly wonky due to the way we support NPObjects that have + * methods and properties with the same name. When child calls parent we + * simply return a property. When parent calls child, however, we need to do + * several checks at once and return all the results simultaneously. + */ +parent: + intr GetParentProperty(PluginIdentifier aId) + returns (Variant aResult, + bool aSuccess); + +child: + intr GetChildProperty(PluginIdentifier aId) + returns (bool aHasProperty, + bool aHasMethod, + Variant aResult, + bool aSuccess); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PPluginSurface.ipdl b/dom/plugins/ipc/PPluginSurface.ipdl new file mode 100644 index 0000000000..7be038c604 --- /dev/null +++ b/dom/plugins/ipc/PPluginSurface.ipdl @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PPluginInstance; + +namespace mozilla { +namespace plugins { + +async protocol PPluginSurface { + manager PPluginInstance; + +parent: + async __delete__(); +}; + +} +} diff --git a/dom/plugins/ipc/PStreamNotify.ipdl b/dom/plugins/ipc/PStreamNotify.ipdl new file mode 100644 index 0000000000..3e196acab8 --- /dev/null +++ b/dom/plugins/ipc/PStreamNotify.ipdl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +include protocol PPluginInstance; + + +using NPReason from "npapi.h"; + +namespace mozilla { +namespace plugins { + +intr protocol PStreamNotify +{ + manager PPluginInstance; + +parent: + + /** + * Represents NPN_URLRedirectResponse + */ + async RedirectNotifyResponse(bool allow); + +child: + /** + * Represents NPP_URLRedirectNotify + */ + async RedirectNotify(nsCString url, int32_t status); + + /** + * Represents NPP_URLNotify + */ + async __delete__(NPReason reason); +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginBackgroundDestroyer.cpp b/dom/plugins/ipc/PluginBackgroundDestroyer.cpp new file mode 100644 index 0000000000..9c0b50d5e2 --- /dev/null +++ b/dom/plugins/ipc/PluginBackgroundDestroyer.cpp @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginBackgroundDestroyer.h" +#include "gfxSharedImageSurface.h" + +using namespace mozilla; +using namespace plugins; + +PluginBackgroundDestroyerParent::PluginBackgroundDestroyerParent( + gfxASurface* aDyingBackground) + : mDyingBackground(aDyingBackground) {} + +PluginBackgroundDestroyerParent::~PluginBackgroundDestroyerParent() = default; + +void PluginBackgroundDestroyerParent::ActorDestroy(ActorDestroyReason why) { + switch (why) { + case Deletion: + case AncestorDeletion: + if (gfxSharedImageSurface::IsSharedImage(mDyingBackground)) { + gfxSharedImageSurface* s = + static_cast(mDyingBackground.get()); + DeallocShmem(s->GetShmem()); + } + break; + default: + // We're shutting down or crashed, let automatic cleanup + // take care of our shmem, if we have one. + break; + } +} diff --git a/dom/plugins/ipc/PluginBackgroundDestroyer.h b/dom/plugins/ipc/PluginBackgroundDestroyer.h new file mode 100644 index 0000000000..627b09fdc4 --- /dev/null +++ b/dom/plugins/ipc/PluginBackgroundDestroyer.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginBackgroundDestroyer +#define dom_plugins_PluginBackgroundDestroyer + +#include "mozilla/plugins/PPluginBackgroundDestroyerChild.h" +#include "mozilla/plugins/PPluginBackgroundDestroyerParent.h" + +#include "gfxSharedImageSurface.h" + +class gfxASurface; + +namespace mozilla { +namespace plugins { + +/** + * When instances of this class are destroyed, the old background goes + * along with them, completing the destruction process (whether or not + * the plugin stayed alive long enough to ack). + */ +class PluginBackgroundDestroyerParent + : public PPluginBackgroundDestroyerParent { + public: + explicit PluginBackgroundDestroyerParent(gfxASurface* aDyingBackground); + + virtual ~PluginBackgroundDestroyerParent(); + + private: + virtual void ActorDestroy(ActorDestroyReason why) override; + + RefPtr mDyingBackground; +}; + +/** + * This class exists solely to instruct its instance to release its + * current background, a new one may be coming. + */ +class PluginBackgroundDestroyerChild : public PPluginBackgroundDestroyerChild { + public: + PluginBackgroundDestroyerChild() = default; + virtual ~PluginBackgroundDestroyerChild() = default; + + private: + // Implementing this for good hygiene. + virtual void ActorDestroy(ActorDestroyReason why) override {} +}; + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_PluginBackgroundDestroyer diff --git a/dom/plugins/ipc/PluginBridge.h b/dom/plugins/ipc/PluginBridge.h new file mode 100644 index 0000000000..312b36f97d --- /dev/null +++ b/dom/plugins/ipc/PluginBridge.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginBridge_h +#define mozilla_plugins_PluginBridge_h + +#include "base/process.h" + +namespace mozilla { + +namespace dom { +class ContentParent; +} // namespace dom + +namespace ipc { +template +class Endpoint; +} // namespace ipc + +namespace plugins { + +class PPluginModuleParent; + +bool SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent, + nsresult* aResult, uint32_t* aRunID, + ipc::Endpoint* aEndpoint); + +void TakeFullMinidump(uint32_t aPluginId, base::ProcessId aContentProcessId, + const nsAString& aBrowserDumpId, nsString& aDumpId); + +void TerminatePlugin(uint32_t aPluginId, base::ProcessId aContentProcessId, + const nsCString& aMonitorDescription, + const nsAString& aDumpId); + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginBridge_h diff --git a/dom/plugins/ipc/PluginHangUIParent.cpp b/dom/plugins/ipc/PluginHangUIParent.cpp new file mode 100644 index 0000000000..4f0dab52ab --- /dev/null +++ b/dom/plugins/ipc/PluginHangUIParent.cpp @@ -0,0 +1,400 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginHangUI.h" + +#include "PluginHangUIParent.h" + +#include "base/command_line.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/plugins/PluginModuleParent.h" + +#include "nsContentUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsIWindowMediator.h" +#include "nsIWinTaskbar.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +#include "WidgetUtils.h" + +#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1" + +using base::ProcessHandle; + +using mozilla::widget::WidgetUtils; + +using std::string; +using std::vector; + +namespace { +class nsPluginHangUITelemetry : public mozilla::Runnable { + public: + nsPluginHangUITelemetry(int aResponseCode, int aDontAskCode, + uint32_t aResponseTimeMs, uint32_t aTimeoutMs) + : Runnable("nsPluginHangUITelemetry"), + mResponseCode(aResponseCode), + mDontAskCode(aDontAskCode), + mResponseTimeMs(aResponseTimeMs), + mTimeoutMs(aTimeoutMs) {} + + NS_IMETHOD + Run() override { + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::PLUGIN_HANG_UI_USER_RESPONSE, mResponseCode); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLUGIN_HANG_UI_DONT_ASK, + mDontAskCode); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::PLUGIN_HANG_UI_RESPONSE_TIME, mResponseTimeMs); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLUGIN_HANG_TIME, + mTimeoutMs + mResponseTimeMs); + return NS_OK; + } + + private: + int mResponseCode; + int mDontAskCode; + uint32_t mResponseTimeMs; + uint32_t mTimeoutMs; +}; +} // namespace + +namespace mozilla { +namespace plugins { + +PluginHangUIParent::PluginHangUIParent(PluginModuleChromeParent* aModule, + const int32_t aHangUITimeoutPref, + const int32_t aChildTimeoutPref) + : mMutex("mozilla::plugins::PluginHangUIParent::mMutex"), + mModule(aModule), + mTimeoutPrefMs(static_cast(aHangUITimeoutPref) * 1000U), + mIPCTimeoutMs(static_cast(aChildTimeoutPref) * 1000U), + mMainThreadMessageLoop(MessageLoop::current()), + mIsShowing(false), + mLastUserResponse(0), + mHangUIProcessHandle(nullptr), + mMainWindowHandle(nullptr), + mRegWait(nullptr), + mShowEvent(nullptr), + mShowTicks(0), + mResponseTicks(0) {} + +PluginHangUIParent::~PluginHangUIParent() { + { // Scope for lock + MutexAutoLock lock(mMutex); + UnwatchHangUIChildProcess(true); + } + if (mShowEvent) { + ::CloseHandle(mShowEvent); + } + if (mHangUIProcessHandle) { + ::CloseHandle(mHangUIProcessHandle); + } +} + +bool PluginHangUIParent::DontShowAgain() const { + return (mLastUserResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); +} + +bool PluginHangUIParent::WasLastHangStopped() const { + return (mLastUserResponse & HANGUI_USER_RESPONSE_STOP); +} + +unsigned int PluginHangUIParent::LastShowDurationMs() const { + // We only return something if there was a user response + if (!mLastUserResponse) { + return 0; + } + return static_cast(mResponseTicks - mShowTicks); +} + +bool PluginHangUIParent::Init(const nsString& aPluginName) { + if (mHangUIProcessHandle) { + return false; + } + + nsresult rv; + rv = mMiniShm.Init(this, ::IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr directoryService( + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + if (!directoryService) { + return false; + } + nsCOMPtr greDir; + rv = directoryService->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + return false; + } + nsAutoString path; + greDir->GetPath(path); + + FilePath exePath(path.get()); + exePath = exePath.AppendASCII(MOZ_HANGUI_PROCESS_NAME); + CommandLine commandLine(exePath.value()); + + nsAutoString localizedStr; + rv = nsContentUtils::FormatLocalizedString( + localizedStr, nsContentUtils::eDOM_PROPERTIES, "PluginHangUIMessage", + aPluginName); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(localizedStr.get()); + + const char* keys[] = {"PluginHangUITitle", "PluginHangUIWaitButton", + "PluginHangUIStopButton", "DontAskAgain"}; + for (unsigned int i = 0; i < ArrayLength(keys); ++i) { + rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + keys[i], localizedStr); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(localizedStr.get()); + } + + rv = GetHangUIOwnerWindowHandle(mMainWindowHandle); + if (NS_FAILED(rv)) { + return false; + } + nsAutoString hwndStr; + hwndStr.AppendPrintf("%p", mMainWindowHandle); + commandLine.AppendLooseValue(hwndStr.get()); + + ScopedHandle procHandle( + ::OpenProcess(SYNCHRONIZE, TRUE, GetCurrentProcessId())); + if (!procHandle.IsValid()) { + return false; + } + nsAutoString procHandleStr; + procHandleStr.AppendPrintf("%p", procHandle.Get()); + commandLine.AppendLooseValue(procHandleStr.get()); + + // On Win7+, pass the application user model to the child, so it can + // register with it. This insures windows created by the Hang UI + // properly group with the parent app on the Win7 taskbar. + nsCOMPtr taskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID); + if (taskbarInfo) { + bool isSupported = false; + taskbarInfo->GetAvailable(&isSupported); + nsAutoString appId; + if (isSupported && NS_SUCCEEDED(taskbarInfo->GetDefaultGroupId(appId))) { + commandLine.AppendLooseValue(appId.get()); + } else { + commandLine.AppendLooseValue(L"-"); + } + } else { + commandLine.AppendLooseValue(L"-"); + } + + nsAutoString ipcTimeoutStr; + ipcTimeoutStr.AppendInt(mIPCTimeoutMs); + commandLine.AppendLooseValue(ipcTimeoutStr.get()); + + std::wstring ipcCookie; + rv = mMiniShm.GetCookie(ipcCookie); + if (NS_FAILED(rv)) { + return false; + } + commandLine.AppendLooseValue(ipcCookie); + + ScopedHandle showEvent(::CreateEventW(nullptr, FALSE, FALSE, nullptr)); + if (!showEvent.IsValid()) { + return false; + } + mShowEvent = showEvent.Get(); + + MutexAutoLock lock(mMutex); + STARTUPINFO startupInfo = {sizeof(STARTUPINFO)}; + PROCESS_INFORMATION processInfo = {nullptr}; + BOOL isProcessCreated = ::CreateProcess( + exePath.value().c_str(), + const_cast(commandLine.command_line_string().c_str()), nullptr, + nullptr, TRUE, DETACHED_PROCESS, nullptr, nullptr, &startupInfo, + &processInfo); + if (isProcessCreated) { + ::CloseHandle(processInfo.hThread); + mHangUIProcessHandle = processInfo.hProcess; + ::RegisterWaitForSingleObject(&mRegWait, processInfo.hProcess, + &SOnHangUIProcessExit, this, INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + ::WaitForSingleObject(mShowEvent, + ::IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); + // Setting this to true even if we time out on mShowEvent. This timeout + // typically occurs when the machine is thrashing so badly that + // plugin-hang-ui.exe is taking a while to start. If we didn't set + // this to true, Firefox would keep spawning additional plugin-hang-ui + // processes, which is not what we want. + mIsShowing = true; + } + mShowEvent = nullptr; + return !(!isProcessCreated); +} + +// static +VOID CALLBACK PluginHangUIParent::SOnHangUIProcessExit(PVOID aContext, + BOOLEAN aIsTimer) { + PluginHangUIParent* object = static_cast(aContext); + MutexAutoLock lock(object->mMutex); + // If the Hang UI child process died unexpectedly, act as if the UI cancelled + if (object->IsShowing()) { + object->RecvUserResponse(HANGUI_USER_RESPONSE_CANCEL); + // Firefox window was disabled automatically when the Hang UI was shown. + // If plugin-hang-ui.exe was unexpectedly terminated, we need to re-enable. + ::EnableWindow(object->mMainWindowHandle, TRUE); + } +} + +// A precondition for this function is that the caller has locked mMutex +bool PluginHangUIParent::UnwatchHangUIChildProcess(bool aWait) { + mMutex.AssertCurrentThreadOwns(); + if (mRegWait) { + // If aWait is false then we want to pass a nullptr (i.e. default + // constructor) completionEvent + ScopedHandle completionEvent; + if (aWait) { + completionEvent.Set(::CreateEventW(nullptr, FALSE, FALSE, nullptr)); + if (!completionEvent.IsValid()) { + return false; + } + } + + // if aWait == false and UnregisterWaitEx fails with ERROR_IO_PENDING, + // it is okay to clear mRegWait; Windows is telling us that the wait's + // callback is running but will be cleaned up once the callback returns. + if (::UnregisterWaitEx(mRegWait, completionEvent) || + (!aWait && ::GetLastError() == ERROR_IO_PENDING)) { + mRegWait = nullptr; + if (aWait) { + // We must temporarily unlock mMutex while waiting for the registered + // wait callback to complete, or else we could deadlock. + MutexAutoUnlock unlock(mMutex); + ::WaitForSingleObject(completionEvent, INFINITE); + } + return true; + } + } + return false; +} + +bool PluginHangUIParent::Cancel() { + MutexAutoLock lock(mMutex); + bool result = mIsShowing && SendCancel(); + if (result) { + mIsShowing = false; + } + return result; +} + +bool PluginHangUIParent::SendCancel() { + PluginHangUICommand* cmd = nullptr; + nsresult rv = mMiniShm.GetWritePtr(cmd); + if (NS_FAILED(rv)) { + return false; + } + cmd->mCode = PluginHangUICommand::HANGUI_CMD_CANCEL; + return NS_SUCCEEDED(mMiniShm.Send()); +} + +// A precondition for this function is that the caller has locked mMutex +bool PluginHangUIParent::RecvUserResponse(const unsigned int& aResponse) { + mMutex.AssertCurrentThreadOwns(); + if (!mIsShowing && !(aResponse & HANGUI_USER_RESPONSE_CANCEL)) { + // Don't process a user response if a cancellation is already pending + return true; + } + mLastUserResponse = aResponse; + mResponseTicks = ::GetTickCount(); + mIsShowing = false; + // responseCode: 1 = Stop, 2 = Continue, 3 = Cancel + int responseCode; + if (aResponse & HANGUI_USER_RESPONSE_STOP) { + // User clicked Stop + mModule->TerminateChildProcess(mMainThreadMessageLoop, + mozilla::ipc::kInvalidProcessId, + "ModalHangUI"_ns, u""_ns); + responseCode = 1; + } else if (aResponse & HANGUI_USER_RESPONSE_CONTINUE) { + mModule->OnHangUIContinue(); + // User clicked Continue + responseCode = 2; + } else { + // Dialog was cancelled + responseCode = 3; + } + int dontAskCode = (aResponse & HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN) ? 1 : 0; + nsCOMPtr workItem = new nsPluginHangUITelemetry( + responseCode, dontAskCode, LastShowDurationMs(), mTimeoutPrefMs); + NS_DispatchToMainThread(workItem); + return true; +} + +nsresult PluginHangUIParent::GetHangUIOwnerWindowHandle( + NativeWindowHandle& windowHandle) { + windowHandle = nullptr; + + nsresult rv; + nsCOMPtr winMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr navWin; + rv = winMediator->GetMostRecentWindow(u"navigator:browser", + getter_AddRefs(navWin)); + NS_ENSURE_SUCCESS(rv, rv); + if (!navWin) { + return NS_ERROR_FAILURE; + } + + nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin); + nsCOMPtr widget = WidgetUtils::DOMWindowToWidget(win); + if (!widget) { + return NS_ERROR_FAILURE; + } + + windowHandle = reinterpret_cast( + widget->GetNativeData(NS_NATIVE_WINDOW)); + if (!windowHandle) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void PluginHangUIParent::OnMiniShmEvent(MiniShmBase* aMiniShmObj) { + const PluginHangUIResponse* response = nullptr; + nsresult rv = aMiniShmObj->GetReadPtr(response); + NS_ASSERTION(NS_SUCCEEDED(rv), "Couldn't obtain read pointer OnMiniShmEvent"); + if (NS_SUCCEEDED(rv)) { + // The child process has returned a response so we shouldn't worry about + // its state anymore. + MutexAutoLock lock(mMutex); + UnwatchHangUIChildProcess(false); + RecvUserResponse(response->mResponseBits); + } +} + +void PluginHangUIParent::OnMiniShmConnect(MiniShmBase* aMiniShmObj) { + PluginHangUICommand* cmd = nullptr; + nsresult rv = aMiniShmObj->GetWritePtr(cmd); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Couldn't obtain write pointer OnMiniShmConnect"); + if (NS_FAILED(rv)) { + return; + } + cmd->mCode = PluginHangUICommand::HANGUI_CMD_SHOW; + if (NS_SUCCEEDED(aMiniShmObj->Send())) { + mShowTicks = ::GetTickCount(); + } + ::SetEvent(mShowEvent); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginHangUIParent.h b/dom/plugins/ipc/PluginHangUIParent.h new file mode 100644 index 0000000000..8853d5c425 --- /dev/null +++ b/dom/plugins/ipc/PluginHangUIParent.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginHangUIParent_h +#define mozilla_plugins_PluginHangUIParent_h + +#include "nsString.h" + +#include "base/process.h" +#include "base/process_util.h" + +#include "mozilla/Mutex.h" +#include "mozilla/plugins/PluginMessageUtils.h" + +#include "MiniShmParent.h" + +namespace mozilla { +namespace plugins { + +class PluginModuleChromeParent; + +/** + * This class is responsible for launching and communicating with the + * plugin-hang-ui process. + * + * NOTE: PluginHangUIParent is *not* an IPDL actor! In this case, "Parent" + * is describing the fact that firefox is the parent process to the + * plugin-hang-ui process, which is the PluginHangUIChild. + * PluginHangUIParent and PluginHangUIChild are a matched pair. + * @see PluginHangUIChild + */ +class PluginHangUIParent : public MiniShmObserver { + public: + PluginHangUIParent(PluginModuleChromeParent* aModule, + const int32_t aHangUITimeoutPref, + const int32_t aChildTimeoutPref); + virtual ~PluginHangUIParent(); + + /** + * Spawn the plugin-hang-ui.exe child process and terminate the given + * plugin container process if the user elects to stop the hung plugin. + * + * @param aPluginName Human-readable name of the affected plugin. + * @return true if the plugin hang ui process was successfully launched, + * otherwise false. + */ + bool Init(const nsString& aPluginName); + + /** + * If the Plugin Hang UI is being shown, send a cancel notification to the + * Plugin Hang UI child process. + * + * @return true if the UI was shown and the cancel command was successfully + * sent to the child process, otherwise false. + */ + bool Cancel(); + + /** + * Returns whether the Plugin Hang UI is currently being displayed. + * + * @return true if the Plugin Hang UI is showing, otherwise false. + */ + bool IsShowing() const { return mIsShowing; } + + /** + * Returns whether this Plugin Hang UI instance has been shown. Note + * that this does not necessarily mean that the UI is showing right now. + * + * @return true if the Plugin Hang UI has shown, otherwise false. + */ + bool WasShown() const { return mIsShowing || mLastUserResponse != 0; } + + /** + * Returns whether the user checked the "Don't ask me again" checkbox. + * + * @return true if the user does not want to see the Hang UI again. + */ + bool DontShowAgain() const; + + /** + * Returns whether the user clicked stop during the last time that the + * Plugin Hang UI was displayed, if applicable. + * + * @return true if the UI was shown and the user chose to stop the + * plugin, otherwise false + */ + bool WasLastHangStopped() const; + + /** + * @return unsigned int containing the response bits from the last + * time the Plugin Hang UI ran. + */ + unsigned int LastUserResponse() const { return mLastUserResponse; } + + /** + * @return unsigned int containing the number of milliseconds that + * the Plugin Hang UI was displayed before the user responded. + * Returns 0 if the Plugin Hang UI has not been shown or was cancelled. + */ + unsigned int LastShowDurationMs() const; + + virtual void OnMiniShmEvent(MiniShmBase* aMiniShmObj) override; + + virtual void OnMiniShmConnect(MiniShmBase* aMiniShmObj) override; + + private: + nsresult GetHangUIOwnerWindowHandle(NativeWindowHandle& windowHandle); + + bool SendCancel(); + + bool RecvUserResponse(const unsigned int& aResponse); + + bool UnwatchHangUIChildProcess(bool aWait); + + static VOID CALLBACK SOnHangUIProcessExit(PVOID aContext, BOOLEAN aIsTimer); + + private: + Mutex mMutex; + PluginModuleChromeParent* mModule; + const uint32_t mTimeoutPrefMs; + const uint32_t mIPCTimeoutMs; + MessageLoop* mMainThreadMessageLoop; + bool mIsShowing; + unsigned int mLastUserResponse; + base::ProcessHandle mHangUIProcessHandle; + NativeWindowHandle mMainWindowHandle; + HANDLE mRegWait; + HANDLE mShowEvent; + DWORD mShowTicks; + DWORD mResponseTicks; + MiniShmParent mMiniShm; + + DISALLOW_COPY_AND_ASSIGN(PluginHangUIParent); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUIParent_h diff --git a/dom/plugins/ipc/PluginInstanceChild.cpp b/dom/plugins/ipc/PluginInstanceChild.cpp new file mode 100644 index 0000000000..403a735c83 --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceChild.cpp @@ -0,0 +1,4045 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginBackgroundDestroyer.h" +#include "PluginInstanceChild.h" +#include "PluginModuleChild.h" +#include "BrowserStreamChild.h" +#include "StreamNotifyChild.h" +#include "PluginProcessChild.h" +#include "gfxASurface.h" +#include "gfxPlatform.h" +#include "gfx2DGlue.h" +#include "nsNPAPIPluginInstance.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#ifdef MOZ_X11 +# include "gfxXlibSurface.h" +#endif +#ifdef XP_WIN +# include "mozilla/D3DMessageUtils.h" +# include "mozilla/gfx/SharedDIBSurface.h" +# include "nsCrashOnException.h" +# include "gfxWindowsPlatform.h" +extern const wchar_t* kFlashFullscreenClass; +using mozilla::gfx::SharedDIBSurface; +#endif +#include "gfxSharedImageSurface.h" +#include "gfxUtils.h" +#include "gfxAlphaRecovery.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "ImageContainer.h" + +using namespace mozilla; +using namespace mozilla::plugins; +using namespace mozilla::layers; +using namespace mozilla::gfx; +using namespace mozilla::widget; + +#ifdef MOZ_WIDGET_GTK + +# include +# include +# include + +#elif defined(OS_WIN) + +# include +# include + +# include "mozilla/widget/WinMessages.h" +# include "mozilla/widget/WinModifierKeyState.h" +# include "mozilla/widget/WinNativeEventData.h" +# include "nsWindowsDllInterceptor.h" +# include "X11UndefineNone.h" + +typedef BOOL(WINAPI* User32TrackPopupMenu)(HMENU hMenu, UINT uFlags, int x, + int y, int nReserved, HWND hWnd, + CONST RECT* prcRect); +static WindowsDllInterceptor sUser32Intercept; +static HWND sWinlessPopupSurrogateHWND = nullptr; +static WindowsDllInterceptor::FuncHookType + sUser32TrackPopupMenuStub; + +static WindowsDllInterceptor sImm32Intercept; +static WindowsDllInterceptor::FuncHookType + sImm32ImmGetContextStub; +static WindowsDllInterceptor::FuncHookType + sImm32ImmGetCompositionStringStub; +static WindowsDllInterceptor::FuncHookType + sImm32ImmSetCandidateWindowStub; +static WindowsDllInterceptor::FuncHookType + sImm32ImmNotifyIME; +static WindowsDllInterceptor::FuncHookType + sImm32ImmAssociateContextExStub; + +static PluginInstanceChild* sCurrentPluginInstance = nullptr; +static const HIMC sHookIMC = (const HIMC)0xefefefef; + +using mozilla::gfx::SharedDIB; + +// Flash WM_USER message delay time for PostDelayedTask. Borrowed +// from Chromium's web plugin delegate src. See 'flash msg throttling +// helpers' section for details. +const int kFlashWMUSERMessageThrottleDelayMs = 5; + +static const TCHAR kPluginIgnoreSubclassProperty[] = + TEXT("PluginIgnoreSubclassProperty"); + +#elif defined(XP_MACOSX) +# include +# include "PluginUtilsOSX.h" +#endif // defined(XP_MACOSX) + +/** + * We can't use gfxPlatform::CreateDrawTargetForSurface() because calling + * gfxPlatform::GetPlatform() instantiates the prefs service, and that's not + * allowed from processes other than the main process. So we have our own + * version here. + */ +static RefPtr CreateDrawTargetForSurface(gfxASurface* aSurface) { + SurfaceFormat format = aSurface->GetSurfaceFormat(); + RefPtr drawTarget = Factory::CreateDrawTargetForCairoSurface( + aSurface->CairoSurface(), aSurface->GetSize(), &format); + if (!drawTarget) { + MOZ_CRASH("CreateDrawTargetForSurface failed in plugin"); + } + return drawTarget; +} + +bool PluginInstanceChild::sIsIMEComposing = false; + +PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface, + const nsCString& aMimeType, + const nsTArray& aNames, + const nsTArray& aValues) + : mPluginIface(aPluginIface), + mMimeType(aMimeType), + mNames(aNames.Clone()), + mValues(aValues.Clone()) +#if defined(XP_DARWIN) || defined(XP_WIN) + , + mContentsScaleFactor(1.0) +#endif + , + mCSSZoomFactor(0.0), + mPostingKeyEvents(0), + mPostingKeyEventsOutdated(0), + mDrawingModel(kDefaultDrawingModel), + mCurrentDirectSurface(nullptr), + mAsyncInvalidateMutex("PluginInstanceChild::mAsyncInvalidateMutex"), + mAsyncInvalidateTask(0), + mCachedWindowActor(nullptr), + mCachedElementActor(nullptr) +#if defined(OS_WIN) + , + mPluginWindowHWND(0), + mPluginWndProc(0), + mPluginParentHWND(0), + mCachedWinlessPluginHWND(0), + mWinlessPopupSurrogateHWND(0), + mWinlessThrottleOldWndProc(0), + mWinlessHiddenMsgHWND(0) +#endif // OS_WIN +#if defined(MOZ_WIDGET_COCOA) +# if defined(__i386__) + , + mEventModel(NPEventModelCarbon) +# endif + , + mShColorSpace(nullptr), + mShContext(nullptr), + mCGLayer(nullptr), + mCARefreshTimer(0), + mCurrentEvent(nullptr) +#endif + , + mLayersRendering(false) +#ifdef XP_WIN + , + mCurrentSurfaceActor(nullptr), + mBackSurfaceActor(nullptr) +#endif + , + mAccumulatedInvalidRect(0, 0, 0, 0), + mIsTransparent(false), + mSurfaceType(gfxSurfaceType::Max), + mPendingPluginCall(false), + mDoAlphaExtraction(false), + mHasPainted(false), + mSurfaceDifferenceRect(0, 0, 0, 0), + mDestroyed(false) +#ifdef XP_WIN + , + mLastKeyEventConsumed(false), + mLastEnableIMEState(true) +#endif // #ifdef XP_WIN + , + mStackDepth(0) { + memset(&mWindow, 0, sizeof(mWindow)); + mWindow.type = NPWindowTypeWindow; + mData.ndata = (void*)this; + mData.pdata = nullptr; +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + mWindow.ws_info = &mWsInfo; + memset(&mWsInfo, 0, sizeof(mWsInfo)); +# ifdef MOZ_WIDGET_GTK + mWsInfo.display = nullptr; +# else + mWsInfo.display = DefaultXDisplay(); +# endif +#endif // MOZ_X11 && XP_UNIX && !XP_MACOSX +#if defined(OS_WIN) + InitPopupMenuHook(); + InitImm32Hook(); +#endif // OS_WIN +} + +PluginInstanceChild::~PluginInstanceChild() { +#if defined(OS_WIN) + NS_ASSERTION(!mPluginWindowHWND, + "Destroying PluginInstanceChild without NPP_Destroy?"); + // In the event that we registered for audio device changes, stop. + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome(); + if (chromeInstance) { + chromeInstance->PluginRequiresAudioDeviceChanges(this, false); + } +#endif +#if defined(MOZ_WIDGET_COCOA) + if (mShColorSpace) { + ::CGColorSpaceRelease(mShColorSpace); + } + if (mShContext) { + ::CGContextRelease(mShContext); + } + if (mCGLayer) { + PluginUtilsOSX::ReleaseCGLayer(mCGLayer); + } + if (mDrawingModel == NPDrawingModelCoreAnimation) { + UnscheduleTimer(mCARefreshTimer); + } +#endif +} + +NPError PluginInstanceChild::DoNPP_New() { + // unpack the arguments into a C format + int argc = mNames.Length(); + NS_ASSERTION(argc == (int)mValues.Length(), "argn.length != argv.length"); + + UniquePtr argn(new char*[1 + argc]); + UniquePtr argv(new char*[1 + argc]); + argn[argc] = 0; + argv[argc] = 0; + + for (int i = 0; i < argc; ++i) { + argn[i] = const_cast(NullableStringGet(mNames[i])); + argv[i] = const_cast(NullableStringGet(mValues[i])); + } + + NPP npp = GetNPP(); + + NPError rv = mPluginIface->newp((char*)NullableStringGet(mMimeType), npp, + NP_EMBED, argc, argn.get(), argv.get(), 0); + if (NPERR_NO_ERROR != rv) { + return rv; + } + + if (!Initialize()) { + rv = NPERR_MODULE_LOAD_FAILED_ERROR; + } + return rv; +} + +int PluginInstanceChild::GetQuirks() { + return PluginModuleChild::GetChrome()->GetQuirks(); +} + +NPError PluginInstanceChild::InternalGetNPObjectForValue(NPNVariable aValue, + NPObject** aObject) { + PluginScriptableObjectChild* actor = nullptr; + NPError result = NPERR_NO_ERROR; + + switch (aValue) { + case NPNVWindowNPObject: + if (!(actor = mCachedWindowActor)) { + result = NPERR_GENERIC_ERROR; + PPluginScriptableObjectChild* actorProtocol; + if (CallNPN_GetValue_NPNVWindowNPObject(&actorProtocol, &result) && + (result == NPERR_NO_ERROR)) { + actor = mCachedWindowActor = + static_cast(actorProtocol); + NS_ASSERTION(actor, "Null actor!"); + if (!actor->GetObject(false)) { + return NPERR_GENERIC_ERROR; + } + PluginModuleChild::sBrowserFuncs.retainobject( + actor->GetObject(false)); + } + } + break; + + case NPNVPluginElementNPObject: + if (!(actor = mCachedElementActor)) { + result = NPERR_GENERIC_ERROR; + PPluginScriptableObjectChild* actorProtocol; + if (CallNPN_GetValue_NPNVPluginElementNPObject(&actorProtocol, + &result) && + (result == NPERR_NO_ERROR)) { + actor = mCachedElementActor = + static_cast(actorProtocol); + NS_ASSERTION(actor, "Null actor!"); + if (!actor->GetObject(false)) { + return NPERR_GENERIC_ERROR; + } + PluginModuleChild::sBrowserFuncs.retainobject( + actor->GetObject(false)); + } + } + break; + + default: + result = NPERR_GENERIC_ERROR; + MOZ_ASSERT_UNREACHABLE( + "Don't know what to do with this value " + "type!"); + } + +#ifdef DEBUG + { + NPError currentResult; + PPluginScriptableObjectChild* currentActor = nullptr; + + switch (aValue) { + case NPNVWindowNPObject: + CallNPN_GetValue_NPNVWindowNPObject(¤tActor, ¤tResult); + break; + case NPNVPluginElementNPObject: + CallNPN_GetValue_NPNVPluginElementNPObject(¤tActor, + ¤tResult); + break; + default: + MOZ_ASSERT(false); + } + + // Make sure that the current actor returned by the parent matches our + // cached actor! + NS_ASSERTION(!currentActor || static_cast( + currentActor) == actor, + "Cached actor is out of date!"); + } +#endif + + if (result != NPERR_NO_ERROR) { + return result; + } + + NPObject* object; + if (!(object = actor->GetObject(false))) { + return NPERR_GENERIC_ERROR; + } + + *aObject = PluginModuleChild::sBrowserFuncs.retainobject(object); + return NPERR_NO_ERROR; +} + +NPError PluginInstanceChild::NPN_GetValue(NPNVariable aVar, void* aValue) { + PLUGIN_LOG_DEBUG(("%s (aVar=%i)", FULLFUNCTION, (int)aVar)); + AssertPluginThread(); + AutoStackHelper guard(this); + + switch (aVar) { +#if defined(MOZ_X11) + case NPNVToolkit: + *((NPNToolkitType*)aValue) = NPNVGtk2; + return NPERR_NO_ERROR; + + case NPNVxDisplay: + if (!mWsInfo.display) { + // We are called before Initialize() so we have to call it now. + if (!Initialize()) { + return NPERR_GENERIC_ERROR; + } + NS_ASSERTION(mWsInfo.display, "We should have a valid display!"); + } + *(void**)aValue = mWsInfo.display; + return NPERR_NO_ERROR; + +#elif defined(OS_WIN) + case NPNVToolkit: + return NPERR_GENERIC_ERROR; +#endif + case NPNVprivateModeBool: { + bool v = false; + NPError result; + if (!CallNPN_GetValue_NPNVprivateModeBool(&v, &result)) { + return NPERR_GENERIC_ERROR; + } + *static_cast(aValue) = v; + return result; + } + + case NPNVdocumentOrigin: { + nsCString v; + NPError result; + if (!CallNPN_GetValue_NPNVdocumentOrigin(&v, &result)) { + return NPERR_GENERIC_ERROR; + } + if (result == NPERR_NO_ERROR || + (GetQuirks() & QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN)) { + *static_cast(aValue) = ToNewCString(v); + } + return result; + } + + case NPNVWindowNPObject: // Intentional fall-through + case NPNVPluginElementNPObject: { + NPObject* object; + *((NPObject**)aValue) = nullptr; + NPError result = InternalGetNPObjectForValue(aVar, &object); + if (result == NPERR_NO_ERROR) { + *((NPObject**)aValue) = object; + } + return result; + } + + case NPNVnetscapeWindow: { +#ifdef XP_WIN + if (mWindow.type == NPWindowTypeDrawable) { + if (mCachedWinlessPluginHWND) { + *static_cast(aValue) = mCachedWinlessPluginHWND; + return NPERR_NO_ERROR; + } + NPError result; + if (!CallNPN_GetValue_NPNVnetscapeWindow(&mCachedWinlessPluginHWND, + &result)) { + return NPERR_GENERIC_ERROR; + } + *static_cast(aValue) = mCachedWinlessPluginHWND; + return result; + } else { + *static_cast(aValue) = mPluginWindowHWND; + return NPERR_NO_ERROR; + } +#elif defined(MOZ_X11) + NPError result; + CallNPN_GetValue_NPNVnetscapeWindow(static_cast(aValue), &result); + return result; +#else + return NPERR_GENERIC_ERROR; +#endif + } + + case NPNVsupportsAsyncBitmapSurfaceBool: { + bool value = false; + CallNPN_GetValue_SupportsAsyncBitmapSurface(&value); + *((NPBool*)aValue) = value; + return NPERR_NO_ERROR; + } + +#ifdef XP_WIN + case NPNVsupportsAsyncWindowsDXGISurfaceBool: { + bool value = false; + CallNPN_GetValue_SupportsAsyncDXGISurface(&value); + *((NPBool*)aValue) = value; + return NPERR_NO_ERROR; + } +#endif + +#ifdef XP_WIN + case NPNVpreferredDXGIAdapter: { + DxgiAdapterDesc desc; + if (!CallNPN_GetValue_PreferredDXGIAdapter(&desc)) { + return NPERR_GENERIC_ERROR; + } + *reinterpret_cast(aValue) = desc.ToDesc(); + return NPERR_NO_ERROR; + } +#endif + +#ifdef XP_MACOSX + case NPNVsupportsCoreGraphicsBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsCoreAnimationBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsInvalidatingCoreAnimationBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsCompositingCoreAnimationPluginsBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + + case NPNVsupportsCocoaBool: { + *((NPBool*)aValue) = true; + return NPERR_NO_ERROR; + } + +# ifndef NP_NO_CARBON + case NPNVsupportsCarbonBool: { + *((NPBool*)aValue) = false; + return NPERR_NO_ERROR; + } +# endif + + case NPNVsupportsUpdatedCocoaTextInputBool: { + *static_cast(aValue) = true; + return NPERR_NO_ERROR; + } + +# ifndef NP_NO_QUICKDRAW + case NPNVsupportsQuickDrawBool: { + *((NPBool*)aValue) = false; + return NPERR_NO_ERROR; + } +# endif /* NP_NO_QUICKDRAW */ +#endif /* XP_MACOSX */ + +#if defined(XP_MACOSX) || defined(XP_WIN) + case NPNVcontentsScaleFactor: { + *static_cast(aValue) = mContentsScaleFactor; + return NPERR_NO_ERROR; + } +#endif /* defined(XP_MACOSX) || defined(XP_WIN) */ + + case NPNVCSSZoomFactor: { + *static_cast(aValue) = mCSSZoomFactor; + return NPERR_NO_ERROR; + } + +#ifdef DEBUG + case NPNVjavascriptEnabledBool: + case NPNVasdEnabledBool: + case NPNVisOfflineBool: + case NPNVSupportsXEmbedBool: + case NPNVSupportsWindowless: + MOZ_FALLTHROUGH_ASSERT( + "NPNVariable should be handled in " + "PluginModuleChild."); +#endif + + default: + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceChild::NPN_GetValue: Unhandled NPNVariable %i " + "(%s)", + (int)aVar, NPNVariableToString(aVar))); + return NPERR_GENERIC_ERROR; + } +} + +#ifdef MOZ_WIDGET_COCOA +# define DEFAULT_REFRESH_MS 20 // CoreAnimation: 50 FPS + +void CAUpdate(NPP npp, uint32_t timerID) { + static_cast(npp->ndata)->Invalidate(); +} + +void PluginInstanceChild::Invalidate() { + NPRect windowRect = {0, 0, uint16_t(mWindow.height), uint16_t(mWindow.width)}; + + InvalidateRect(&windowRect); +} +#endif + +NPError PluginInstanceChild::NPN_SetValue(NPPVariable aVar, void* aValue) { + MOZ_LOG(GetPluginLog(), LogLevel::Debug, + ("%s (aVar=%i, aValue=%p)", FULLFUNCTION, (int)aVar, aValue)); + + AssertPluginThread(); + + AutoStackHelper guard(this); + + switch (aVar) { + case NPPVpluginWindowBool: { + NPError rv; + bool windowed = (NPBool)(intptr_t)aValue; + + if (windowed) { + return NPERR_GENERIC_ERROR; + } + + if (!CallNPN_SetValue_NPPVpluginWindow(windowed, &rv)) + return NPERR_GENERIC_ERROR; + + mWindow.type = NPWindowTypeDrawable; + return rv; + } + + case NPPVpluginTransparentBool: { + NPError rv; + mIsTransparent = (!!aValue); + + if (!CallNPN_SetValue_NPPVpluginTransparent(mIsTransparent, &rv)) + return NPERR_GENERIC_ERROR; + + return rv; + } + + case NPPVpluginUsesDOMForCursorBool: { + NPError rv = NPERR_GENERIC_ERROR; + if (!CallNPN_SetValue_NPPVpluginUsesDOMForCursor((NPBool)(intptr_t)aValue, + &rv)) { + return NPERR_GENERIC_ERROR; + } + return rv; + } + + case NPPVpluginDrawingModel: { + NPError rv; + int drawingModel = (int16_t)(intptr_t)aValue; + + if (!CallNPN_SetValue_NPPVpluginDrawingModel(drawingModel, &rv)) + return NPERR_GENERIC_ERROR; + + mDrawingModel = drawingModel; + +#ifdef XP_MACOSX + if (drawingModel == NPDrawingModelCoreAnimation) { + mCARefreshTimer = ScheduleTimer(DEFAULT_REFRESH_MS, true, CAUpdate); + } +#endif + + PLUGIN_LOG_DEBUG( + (" Plugin requested drawing model id #%i\n", mDrawingModel)); + + return rv; + } + +#ifdef XP_MACOSX + case NPPVpluginEventModel: { + NPError rv; + int eventModel = (int16_t)(intptr_t)aValue; + + if (!CallNPN_SetValue_NPPVpluginEventModel(eventModel, &rv)) + return NPERR_GENERIC_ERROR; +# if defined(__i386__) + mEventModel = static_cast(eventModel); +# endif + + PLUGIN_LOG_DEBUG( + (" Plugin requested event model id # %i\n", eventModel)); + + return rv; + } +#endif + + case NPPVpluginIsPlayingAudio: { + NPError rv = NPERR_GENERIC_ERROR; + if (!CallNPN_SetValue_NPPVpluginIsPlayingAudio((NPBool)(intptr_t)aValue, + &rv)) { + return NPERR_GENERIC_ERROR; + } + return rv; + } + +#ifdef XP_WIN + case NPPVpluginRequiresAudioDeviceChanges: { + // Many other NPN_SetValue variables are forwarded to our + // PluginInstanceParent, which runs on a content process. We + // instead forward this message to the PluginModuleParent, which runs + // on the chrome process. This is because our audio + // API calls should run the chrome proc, not content. + NPError rv = NPERR_GENERIC_ERROR; + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome(); + if (chromeInstance) { + rv = chromeInstance->PluginRequiresAudioDeviceChanges( + this, (NPBool)(intptr_t)aValue); + } + return rv; + } +#endif + + default: + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceChild::NPN_SetValue: Unhandled NPPVariable %i " + "(%s)", + (int)aVar, NPPVariableToString(aVar))); + return NPERR_GENERIC_ERROR; + } +} + +mozilla::ipc::IPCResult +PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginWantsAllNetworkStreams( + bool* wantsAllStreams, NPError* rv) { + AssertPluginThread(); + AutoStackHelper guard(this); + + uint32_t value = 0; + if (!mPluginIface->getvalue) { + *rv = NPERR_GENERIC_ERROR; + } else { + *rv = mPluginIface->getvalue(GetNPP(), NPPVpluginWantsAllNetworkStreams, + &value); + } + *wantsAllStreams = value; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginScriptableNPObject( + PPluginScriptableObjectChild** aValue, NPError* aResult) { + AssertPluginThread(); + AutoStackHelper guard(this); + + NPObject* object = nullptr; + NPError result = NPERR_GENERIC_ERROR; + if (mPluginIface->getvalue) { + result = + mPluginIface->getvalue(GetNPP(), NPPVpluginScriptableNPObject, &object); + } + if (result == NPERR_NO_ERROR && object) { + PluginScriptableObjectChild* actor = GetActorForNPObject(object); + + // If we get an actor then it has retained. Otherwise we don't need it + // any longer. + PluginModuleChild::sBrowserFuncs.releaseobject(object); + if (actor) { + *aValue = actor; + *aResult = NPERR_NO_ERROR; + return IPC_OK(); + } + + NS_ERROR("Failed to get actor!"); + result = NPERR_GENERIC_ERROR; + } else { + result = NPERR_GENERIC_ERROR; + } + + *aValue = nullptr; + *aResult = result; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceChild::AnswerNPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId( + nsCString* aPlugId, NPError* aResult) { + AssertPluginThread(); + AutoStackHelper guard(this); + +#if MOZ_ACCESSIBILITY_ATK + + char* plugId = nullptr; + NPError result = NPERR_GENERIC_ERROR; + if (mPluginIface->getvalue) { + result = mPluginIface->getvalue( + GetNPP(), NPPVpluginNativeAccessibleAtkPlugId, &plugId); + } + + *aPlugId = nsCString(plugId); + *aResult = result; + return IPC_OK(); + +#else + + MOZ_CRASH("shouldn't be called on non-ATK platforms"); + +#endif +} + +mozilla::ipc::IPCResult +PluginInstanceChild::AnswerNPP_SetValue_NPNVprivateModeBool(const bool& value, + NPError* result) { + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return IPC_OK(); + } + + NPBool v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVprivateModeBool, &v); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceChild::AnswerNPP_SetValue_NPNVCSSZoomFactor(const double& value, + NPError* result) { + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return IPC_OK(); + } + + mCSSZoomFactor = value; + double v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVCSSZoomFactor, &v); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceChild::AnswerNPP_SetValue_NPNVmuteAudioBool(const bool& value, + NPError* result) { + if (!mPluginIface->setvalue) { + *result = NPERR_GENERIC_ERROR; + return IPC_OK(); + } + + NPBool v = value; + *result = mPluginIface->setvalue(GetNPP(), NPNVmuteAudioBool, &v); + return IPC_OK(); +} + +#if defined(XP_WIN) +NPError PluginInstanceChild::DefaultAudioDeviceChanged( + NPAudioDeviceChangeDetails& details) { + if (!mPluginIface->setvalue) { + return NPERR_GENERIC_ERROR; + } + return mPluginIface->setvalue(GetNPP(), NPNVaudioDeviceChangeDetails, + (void*)&details); +} + +NPError PluginInstanceChild::AudioDeviceStateChanged( + NPAudioDeviceStateChanged& aDeviceState) { + if (!mPluginIface->setvalue) { + return NPERR_GENERIC_ERROR; + } + return mPluginIface->setvalue(GetNPP(), NPNVaudioDeviceStateChanged, + (void*)&aDeviceState); +} + +void SetMouseEventWParam(NPEvent* aEvent) { + // Fill in potentially missing key state info. See + // nsPluginInstanceOwner::ProcessEvent for circumstances where this happens. + const auto kMouseMessages = mozilla::Array( + WM_LBUTTONDOWN, WM_MBUTTONDOWN, WM_RBUTTONDOWN, WM_LBUTTONUP, + WM_MBUTTONUP, WM_RBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOUSEHWHEEL); + + bool isInvalidWParam = + (aEvent->wParam == NPAPI_INVALID_WPARAM) && + (std::find(kMouseMessages.begin(), kMouseMessages.end(), + static_cast(aEvent->event)) != kMouseMessages.end()); + + if (!isInvalidWParam) { + return; + } + + aEvent->wParam = (::GetKeyState(VK_CONTROL) ? MK_CONTROL : 0) | + (::GetKeyState(VK_SHIFT) ? MK_SHIFT : 0) | + (::GetKeyState(VK_LBUTTON) ? MK_LBUTTON : 0) | + (::GetKeyState(VK_MBUTTON) ? MK_MBUTTON : 0) | + (::GetKeyState(VK_RBUTTON) ? MK_RBUTTON : 0) | + (::GetKeyState(VK_XBUTTON1) ? MK_XBUTTON1 : 0) | + (::GetKeyState(VK_XBUTTON2) ? MK_XBUTTON2 : 0); +} +#endif + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent( + const NPRemoteEvent& event, int16_t* handled) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + AutoStackHelper guard(this); + +#if defined(MOZ_X11) && defined(DEBUG) + if (GraphicsExpose == event.event.type) + PLUGIN_LOG_DEBUG( + (" received drawable 0x%lx\n", event.event.xgraphicsexpose.drawable)); +#endif + +#ifdef XP_MACOSX + // Mac OS X does not define an NPEvent structure. It defines more specific + // types. + NPCocoaEvent evcopy = event.event; + + // Make sure we reset mCurrentEvent in case of an exception + AutoRestore savePreviousEvent(mCurrentEvent); + + // Track the current event for NPN_PopUpContextMenu. + mCurrentEvent = &event.event; +#else + // Make a copy since we may modify values. + NPEvent evcopy = event.event; +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + // event.contentsScaleFactor <= 0 is a signal we shouldn't use it, + // for example when AnswerNPP_HandleEvent() is called from elsewhere + // in the child process (not via rpc code from the parent process). + if (event.contentsScaleFactor > 0) { + mContentsScaleFactor = event.contentsScaleFactor; + } +#endif + +#ifdef OS_WIN + // FIXME/bug 567645: temporarily drop the "dummy event" on the floor + if (WM_NULL == evcopy.event) return IPC_OK(); + + SetMouseEventWParam(&evcopy); + *handled = WinlessHandleEvent(evcopy); + return IPC_OK(); +#endif + + // XXX A previous call to mPluginIface->event might block, e.g. right click + // for context menu. Still, we might get here again, calling into the plugin + // a second time while it's in the previous call. + if (!mPluginIface->event) + *handled = false; + else + *handled = mPluginIface->event(&mData, reinterpret_cast(&evcopy)); + +#ifdef XP_MACOSX + // Release any reference counted objects created in the child process. + if (evcopy.type == NPCocoaEventKeyDown || evcopy.type == NPCocoaEventKeyUp) { + ::CFRelease((CFStringRef)evcopy.data.key.characters); + ::CFRelease((CFStringRef)evcopy.data.key.charactersIgnoringModifiers); + } else if (evcopy.type == NPCocoaEventTextInput) { + ::CFRelease((CFStringRef)evcopy.data.text.text); + } +#endif + +#ifdef MOZ_X11 + if (GraphicsExpose == event.event.type) { + // Make sure the X server completes the drawing before the parent + // draws on top and destroys the Drawable. + // + // XSync() waits for the X server to complete. Really this child + // process does not need to wait; the parent is the process that needs + // to wait. A possibly-slightly-better alternative would be to send + // an X event to the parent that the parent would wait for. + XSync(mWsInfo.display, X11False); + } +#endif + + return IPC_OK(); +} + +#ifdef XP_MACOSX + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent_Shmem( + const NPRemoteEvent& event, Shmem&& mem, int16_t* handled, Shmem* rtnmem) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + AutoStackHelper guard(this); + + PaintTracker pt; + + NPCocoaEvent evcopy = event.event; + mContentsScaleFactor = event.contentsScaleFactor; + + if (evcopy.type == NPCocoaEventDrawRect) { + int scaleFactor = ceil(mContentsScaleFactor); + if (!mShColorSpace) { + mShColorSpace = CreateSystemColorSpace(); + if (!mShColorSpace) { + PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace.")); + *handled = false; + *rtnmem = mem; + return IPC_OK(); + } + } + if (!mShContext) { + void* cgContextByte = mem.get(); + mShContext = ::CGBitmapContextCreate( + cgContextByte, mWindow.width * scaleFactor, + mWindow.height * scaleFactor, 8, mWindow.width * 4 * scaleFactor, + mShColorSpace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host); + + if (!mShContext) { + PLUGIN_LOG_DEBUG(("Could not allocate CGBitmapContext.")); + *handled = false; + *rtnmem = mem; + return IPC_OK(); + } + } + CGRect clearRect = ::CGRectMake(0, 0, mWindow.width, mWindow.height); + ::CGContextClearRect(mShContext, clearRect); + evcopy.data.draw.context = mShContext; + } else { + PLUGIN_LOG_DEBUG(("Invalid event type for AnswerNNP_HandleEvent_Shmem.")); + *handled = false; + *rtnmem = mem; + return IPC_OK(); + } + + if (!mPluginIface->event) { + *handled = false; + } else { + ::CGContextSaveGState(evcopy.data.draw.context); + *handled = mPluginIface->event(&mData, reinterpret_cast(&evcopy)); + ::CGContextRestoreGState(evcopy.data.draw.context); + } + + *rtnmem = mem; + return IPC_OK(); +} + +#else +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent_Shmem( + const NPRemoteEvent& event, Shmem&& mem, int16_t* handled, Shmem* rtnmem) { + MOZ_CRASH("not reached."); + *rtnmem = mem; + return IPC_OK(); +} +#endif + +#ifdef XP_MACOSX + +void CallCGDraw(CGContextRef ref, void* aPluginInstance, + nsIntRect aUpdateRect) { + PluginInstanceChild* pluginInstance = (PluginInstanceChild*)aPluginInstance; + + pluginInstance->CGDraw(ref, aUpdateRect); +} + +bool PluginInstanceChild::CGDraw(CGContextRef ref, nsIntRect aUpdateRect) { + NPCocoaEvent drawEvent; + drawEvent.type = NPCocoaEventDrawRect; + drawEvent.version = 0; + drawEvent.data.draw.x = aUpdateRect.x; + drawEvent.data.draw.y = aUpdateRect.y; + drawEvent.data.draw.width = aUpdateRect.width; + drawEvent.data.draw.height = aUpdateRect.height; + drawEvent.data.draw.context = ref; + + NPRemoteEvent remoteDrawEvent = {drawEvent}; + // Signal to AnswerNPP_HandleEvent() not to use this value + remoteDrawEvent.contentsScaleFactor = -1.0; + + int16_t handled; + AnswerNPP_HandleEvent(remoteDrawEvent, &handled); + return handled == true; +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent_IOSurface( + const NPRemoteEvent& event, const uint32_t& surfaceid, int16_t* handled) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + AutoStackHelper guard(this); + + PaintTracker pt; + + NPCocoaEvent evcopy = event.event; + mContentsScaleFactor = event.contentsScaleFactor; + RefPtr surf = + MacIOSurface::LookupSurface(surfaceid, mContentsScaleFactor); + if (!surf) { + NS_ERROR("Invalid IOSurface."); + *handled = false; + return IPC_FAIL_NO_REASON(this); + } + + if (!mCARenderer) { + mCARenderer = new nsCARenderer(); + } + + if (evcopy.type == NPCocoaEventDrawRect) { + mCARenderer->AttachIOSurface(surf); + if (!mCARenderer->isInit()) { + void* caLayer = nullptr; + NPError result = mPluginIface->getvalue( + GetNPP(), NPPVpluginCoreAnimationLayer, &caLayer); + + if (result != NPERR_NO_ERROR || !caLayer) { + PLUGIN_LOG_DEBUG( + ("Plugin requested CoreAnimation but did not " + "provide CALayer.")); + *handled = false; + return IPC_FAIL_NO_REASON(this); + } + + mCARenderer->SetupRenderer(caLayer, mWindow.width, mWindow.height, + mContentsScaleFactor, + GetQuirks() & QUIRK_ALLOW_OFFLINE_RENDERER + ? ALLOW_OFFLINE_RENDERER + : DISALLOW_OFFLINE_RENDERER); + + // Flash needs to have the window set again after this step + if (mPluginIface->setwindow) + (void)mPluginIface->setwindow(&mData, &mWindow); + } + } else { + PLUGIN_LOG_DEBUG( + ("Invalid event type for " + "AnswerNNP_HandleEvent_IOSurface.")); + *handled = false; + return IPC_FAIL_NO_REASON(this); + } + + mCARenderer->Render(mWindow.width, mWindow.height, mContentsScaleFactor, + nullptr); + + return IPC_OK(); +} + +#else +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent_IOSurface( + const NPRemoteEvent& event, const uint32_t& surfaceid, int16_t* handled) { + MOZ_CRASH("NPP_HandleEvent_IOSurface is a OSX-only message"); +} +#endif + +mozilla::ipc::IPCResult PluginInstanceChild::RecvWindowPosChanged( + const NPRemoteEvent& event) { + NS_ASSERTION(!mLayersRendering && !mPendingPluginCall, + "Shouldn't be receiving WindowPosChanged with layer rendering"); + +#ifdef OS_WIN + int16_t dontcare; + return AnswerNPP_HandleEvent(event, &dontcare); +#else + MOZ_CRASH("WindowPosChanged is a windows-only message"); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvContentsScaleFactorChanged( + const double& aContentsScaleFactor) { +#if defined(XP_MACOSX) || defined(XP_WIN) + mContentsScaleFactor = aContentsScaleFactor; +# if defined(XP_MACOSX) + if (mShContext) { + // Release the shared context so that it is reallocated + // with the new size. + ::CGContextRelease(mShContext); + mShContext = nullptr; + } +# endif + return IPC_OK(); +#else + MOZ_CRASH("ContentsScaleFactorChanged is an Windows or OSX only message"); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerCreateChildPluginWindow( + NativeWindowHandle* aChildPluginWindow) { +#if defined(XP_WIN) + MOZ_ASSERT(!mPluginWindowHWND); + + if (!CreatePluginWindow()) { + return IPC_FAIL_NO_REASON(this); + } + + MOZ_ASSERT(mPluginWindowHWND); + + *aChildPluginWindow = mPluginWindowHWND; + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("CreateChildPluginWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvCreateChildPopupSurrogate( + const NativeWindowHandle& aNetscapeWindow) { +#if defined(XP_WIN) + mCachedWinlessPluginHWND = aNetscapeWindow; + CreateWinlessPopupSurrogate(); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("CreateChildPluginWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_SetWindow( + const NPRemoteWindow& aWindow) { + PLUGIN_LOG_DEBUG(("%s (aWindow=)", + FULLFUNCTION, aWindow.window, aWindow.x, aWindow.y, + aWindow.width, aWindow.height)); + NS_ASSERTION(!mLayersRendering && !mPendingPluginCall, + "Shouldn't be receiving NPP_SetWindow with layer rendering"); + AssertPluginThread(); + AutoStackHelper guard(this); + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + NS_ASSERTION(mWsInfo.display, "We should have a valid display!"); + + // The minimum info is sent over IPC to allow this + // code to determine the rest. + + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.clipRect = aWindow.clipRect; + mWindow.type = aWindow.type; + + mWsInfo.colormap = aWindow.colormap; + int depth; + FindVisualAndDepth(mWsInfo.display, aWindow.visualID, &mWsInfo.visual, + &depth); + mWsInfo.depth = depth; + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Answer_SetWindow w=, " + "clip=", + this, mWindow.x, mWindow.y, mWindow.width, mWindow.height, + mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, + mWindow.clipRect.bottom)); + + if (mPluginIface->setwindow) (void)mPluginIface->setwindow(&mData, &mWindow); + +#elif defined(OS_WIN) + switch (aWindow.type) { + case NPWindowTypeWindow: { + MOZ_ASSERT(mPluginWindowHWND, + "Child plugin window must exist before call to SetWindow"); + + HWND parentHWND = reinterpret_cast(aWindow.window); + if (mPluginWindowHWND != parentHWND) { + mPluginParentHWND = parentHWND; + ShowWindow(mPluginWindowHWND, SW_SHOWNA); + } + + SizePluginWindow(aWindow.width, aWindow.height); + + mWindow.window = (void*)mPluginWindowHWND; + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.type = aWindow.type; + mContentsScaleFactor = aWindow.contentsScaleFactor; + + if (mPluginIface->setwindow) { + SetProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty, (HANDLE)1); + (void)mPluginIface->setwindow(&mData, &mWindow); + WNDPROC wndProc = reinterpret_cast( + GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC)); + if (wndProc != PluginWindowProc) { + mPluginWndProc = reinterpret_cast( + SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast(PluginWindowProc))); + NS_ASSERTION(mPluginWndProc != PluginWindowProc, "WTF?"); + } + RemoveProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty); + HookSetWindowLongPtr(); + } + } break; + + default: + MOZ_ASSERT_UNREACHABLE("Bad plugin window type."); + return IPC_FAIL_NO_REASON(this); + break; + } + +#elif defined(XP_MACOSX) + + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.clipRect = aWindow.clipRect; + mWindow.type = aWindow.type; + mContentsScaleFactor = aWindow.contentsScaleFactor; + + if (mShContext) { + // Release the shared context so that it is reallocated + // with the new size. + ::CGContextRelease(mShContext); + mShContext = nullptr; + } + + if (mPluginIface->setwindow) (void)mPluginIface->setwindow(&mData, &mWindow); + +#elif defined(ANDROID) + // TODO: Need Android impl +#elif defined(MOZ_WIDGET_UIKIT) || defined(MOZ_WAYLAND) + // Don't care +#else +# error Implement me for your OS +#endif + + return IPC_OK(); +} + +bool PluginInstanceChild::Initialize() { +#if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) + if (mWsInfo.display) { + // Already initialized + return true; + } + + // Request for windowless plugins is set in newp(), before this call. + if (mWindow.type == NPWindowTypeWindow) { + return false; + } + + mWsInfo.display = DefaultXDisplay(); +#endif + +#if defined(XP_MACOSX) && defined(__i386__) + // If an i386 Mac OS X plugin has selected the Carbon event model then + // we have to fail. We do not support putting Carbon event model plugins + // out of process. Note that Carbon is the default model so out of process + // plugins need to actively negotiate something else in order to work + // out of process. + if (EventModel() == NPEventModelCarbon) { + return false; + } +#endif + + return true; +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvHandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, const bool& aIsConsumed) { +#if defined(OS_WIN) + const WinNativeKeyEventData* eventData = + static_cast(aKeyEventData); + switch (eventData->mMessage) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + mLastKeyEventConsumed = aIsConsumed; + break; + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + // If preceding keydown or keyup event is consumed by the chrome + // process, we should consume WM_*CHAR messages too. + if (mLastKeyEventConsumed) { + return IPC_OK(); + } + default: + MOZ_CRASH("Needs to handle all messages posted to the parent"); + } +#endif // #if defined(OS_WIN) + + // Unknown key input shouldn't be sent to plugin for security. + // XXX Is this possible if a plugin process which posted the message + // already crashed and this plugin process is recreated? + if (NS_WARN_IF(!mPostingKeyEvents && !mPostingKeyEventsOutdated)) { + return IPC_OK(); + } + + // If there is outdated posting key events, we should consume the key + // events. + if (mPostingKeyEventsOutdated) { + mPostingKeyEventsOutdated--; + return IPC_OK(); + } + + mPostingKeyEvents--; + + // If composition has been started after posting the key event, + // we should discard the event since if we send the event to plugin, + // the plugin may be confused and the result may be broken because + // the event order is shuffled. + if (aIsConsumed || sIsIMEComposing) { + return IPC_OK(); + } + +#if defined(OS_WIN) + UINT message = 0; + switch (eventData->mMessage) { + case WM_KEYDOWN: + message = MOZ_WM_KEYDOWN; + break; + case WM_SYSKEYDOWN: + message = MOZ_WM_SYSKEYDOWN; + break; + case WM_KEYUP: + message = MOZ_WM_KEYUP; + break; + case WM_SYSKEYUP: + message = MOZ_WM_SYSKEYUP; + break; + case WM_CHAR: + message = MOZ_WM_CHAR; + break; + case WM_SYSCHAR: + message = MOZ_WM_SYSCHAR; + break; + case WM_DEADCHAR: + message = MOZ_WM_DEADCHAR; + break; + case WM_SYSDEADCHAR: + message = MOZ_WM_SYSDEADCHAR; + break; + default: + MOZ_CRASH("Needs to handle all messages posted to the parent"); + } + PluginWindowProcInternal(mPluginWindowHWND, message, eventData->mWParam, + eventData->mLParam); +#endif + return IPC_OK(); +} + +#if defined(OS_WIN) + +static const TCHAR kWindowClassName[] = TEXT("GeckoPluginWindow"); +static const TCHAR kPluginInstanceChildProperty[] = + TEXT("PluginInstanceChildProperty"); +static const TCHAR kFlashThrottleProperty[] = + TEXT("MozillaFlashThrottleProperty"); + +// static +bool PluginInstanceChild::RegisterWindowClass() { + static bool alreadyRegistered = false; + if (alreadyRegistered) return true; + + alreadyRegistered = true; + + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = DummyWindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(nullptr); + wcex.hIcon = 0; + wcex.hCursor = 0; + wcex.hbrBackground = reinterpret_cast(COLOR_WINDOW + 1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = kWindowClassName; + wcex.hIconSm = 0; + + return RegisterClassEx(&wcex); +} + +bool PluginInstanceChild::CreatePluginWindow() { + // already initialized + if (mPluginWindowHWND) return true; + + if (!RegisterWindowClass()) return false; + + mPluginWindowHWND = CreateWindowEx( + WS_EX_LEFT | WS_EX_LTRREADING | + WS_EX_NOPARENTNOTIFY | // XXXbent Get rid of this! + WS_EX_RIGHTSCROLLBAR, + kWindowClassName, 0, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, + 0, 0, nullptr, 0, GetModuleHandle(nullptr), 0); + if (!mPluginWindowHWND) return false; + if (!SetProp(mPluginWindowHWND, kPluginInstanceChildProperty, this)) + return false; + + // Apparently some plugins require an ASCII WndProc. + SetWindowLongPtrA(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast(DefWindowProcA)); + + return true; +} + +void PluginInstanceChild::DestroyPluginWindow() { + if (mPluginWindowHWND) { + // Unsubclass the window. + WNDPROC wndProc = reinterpret_cast( + GetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC)); + // Removed prior to SetWindowLongPtr, see HookSetWindowLongPtr. + RemoveProp(mPluginWindowHWND, kPluginInstanceChildProperty); + if (wndProc == PluginWindowProc) { + NS_ASSERTION(mPluginWndProc, "Should have old proc here!"); + SetWindowLongPtr(mPluginWindowHWND, GWLP_WNDPROC, + reinterpret_cast(mPluginWndProc)); + mPluginWndProc = 0; + } + DestroyWindow(mPluginWindowHWND); + mPluginWindowHWND = 0; + } +} + +void PluginInstanceChild::SizePluginWindow(int width, int height) { + if (mPluginWindowHWND) { + mPluginSize.x = width; + mPluginSize.y = height; + SetWindowPos(mPluginWindowHWND, nullptr, 0, 0, width, height, + SWP_NOZORDER | SWP_NOREPOSITION); + } +} + +// See chromium's webplugin_delegate_impl.cc for explanation of this function. +// static +LRESULT CALLBACK PluginInstanceChild::DummyWindowProc(HWND hWnd, UINT message, + WPARAM wParam, + LPARAM lParam) { + return CallWindowProc(DefWindowProc, hWnd, message, wParam, lParam); +} + +// static +LRESULT CALLBACK PluginInstanceChild::PluginWindowProc(HWND hWnd, UINT message, + WPARAM wParam, + LPARAM lParam) { + return mozilla::CallWindowProcCrashProtected(PluginWindowProcInternal, hWnd, + message, wParam, lParam); +} + +// static +LRESULT CALLBACK PluginInstanceChild::PluginWindowProcInternal(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) { + NS_ASSERTION(!mozilla::ipc::MessageChannel::IsPumpingMessages(), + "Failed to prevent a nonqueued message from running!"); + PluginInstanceChild* self = reinterpret_cast( + GetProp(hWnd, kPluginInstanceChildProperty)); + if (!self) { + MOZ_ASSERT_UNREACHABLE("Badness!"); + return 0; + } + + NS_ASSERTION(self->mPluginWindowHWND == hWnd, "Wrong window!"); + NS_ASSERTION( + self->mPluginWndProc != PluginWindowProc, + "Self-referential windowproc. Infinite recursion will happen soon."); + + bool isIMECompositionMessage = false; + switch (message) { + // Adobe's shockwave positions the plugin window relative to the browser + // frame when it initializes. With oopp disabled, this wouldn't have an + // effect. With oopp, GeckoPluginWindow is a child of the parent plugin + // window, so the move offsets the child within the parent. Generally + // we don't want plugins moving or sizing our window, so we prevent + // these changes here. + case WM_WINDOWPOSCHANGING: { + WINDOWPOS* pos = reinterpret_cast(lParam); + if (pos && (!(pos->flags & SWP_NOMOVE) || !(pos->flags & SWP_NOSIZE))) { + pos->x = pos->y = 0; + pos->cx = self->mPluginSize.x; + pos->cy = self->mPluginSize.y; + LRESULT res = + CallWindowProc(self->mPluginWndProc, hWnd, message, wParam, lParam); + pos->x = pos->y = 0; + pos->cx = self->mPluginSize.x; + pos->cy = self->mPluginSize.y; + return res; + } + break; + } + + case WM_SETFOCUS: + // If this gets focus, ensure that there is no pending key events. + // Even if there were, we should ignore them for performance reason. + // Although, such case shouldn't occur. + NS_WARNING_ASSERTION(self->mPostingKeyEvents == 0, "pending events"); + self->mPostingKeyEvents = 0; + self->mLastKeyEventConsumed = false; + break; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: + if (self->MaybePostKeyMessage(message, wParam, lParam)) { + // If PreHandleKeyMessage() posts the message to the parent + // process, we need to wait RecvOnKeyEventHandledBeforePlugin() + // to be called. + return 0; // Consume current message temporarily. + } + break; + + case MOZ_WM_KEYDOWN: + message = WM_KEYDOWN; + break; + case MOZ_WM_SYSKEYDOWN: + message = WM_SYSKEYDOWN; + break; + case MOZ_WM_KEYUP: + message = WM_KEYUP; + break; + case MOZ_WM_SYSKEYUP: + message = WM_SYSKEYUP; + break; + case MOZ_WM_CHAR: + message = WM_CHAR; + break; + case MOZ_WM_SYSCHAR: + message = WM_SYSCHAR; + break; + case MOZ_WM_DEADCHAR: + message = WM_DEADCHAR; + break; + case MOZ_WM_SYSDEADCHAR: + message = WM_SYSDEADCHAR; + break; + + case WM_IME_STARTCOMPOSITION: + isIMECompositionMessage = true; + sIsIMEComposing = true; + break; + case WM_IME_ENDCOMPOSITION: + isIMECompositionMessage = true; + sIsIMEComposing = false; + break; + case WM_IME_COMPOSITION: + isIMECompositionMessage = true; + // XXX Some old IME may not send WM_IME_COMPOSITION_START or + // WM_IME_COMPSOITION_END properly. So, we need to check + // WM_IME_COMPSOITION and if it includes commit string. + sIsIMEComposing = !(lParam & GCS_RESULTSTR); + break; + + // The plugin received keyboard focus, let the parent know so the dom + // is up to date. + case WM_MOUSEACTIVATE: + self->CallPluginFocusChange(true); + break; + } + + // When a composition is committed, there may be pending key + // events which were posted to the parent process before starting + // the composition. Then, we shouldn't handle it since they are + // now outdated. + if (isIMECompositionMessage && !sIsIMEComposing) { + self->mPostingKeyEventsOutdated += self->mPostingKeyEvents; + self->mPostingKeyEvents = 0; + } + + // Prevent lockups due to plugins making rpc calls when the parent + // is making a synchronous SendMessage call to the child window. Add + // more messages as needed. + if ((InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) { + switch (message) { + case WM_CHILDACTIVATE: + case WM_KILLFOCUS: + ReplyMessage(0); + break; + } + } + + if (message == WM_KILLFOCUS) { + self->CallPluginFocusChange(false); + } + + if (message == WM_USER + 1 && + (self->GetQuirks() & QUIRK_FLASH_THROTTLE_WMUSER_EVENTS)) { + self->FlashThrottleMessage(hWnd, message, wParam, lParam, true); + return 0; + } + + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, + "Self-referential windowproc happened inside our hook proc. " + "Infinite recursion will happen soon."); + + LRESULT res = + CallWindowProc(self->mPluginWndProc, hWnd, message, wParam, lParam); + + // Make sure capture is released by the child on mouse events. Fixes a + // problem with flash full screen mode mouse input. Appears to be + // caused by a bug in flash, since we are not setting the capture + // on the window. + if (message == WM_LBUTTONDOWN && + self->GetQuirks() & QUIRK_FLASH_FIXUP_MOUSE_CAPTURE) { + wchar_t szClass[26]; + HWND hwnd = GetForegroundWindow(); + if (hwnd && + GetClassNameW(hwnd, szClass, sizeof(szClass) / sizeof(char16_t)) && + !wcscmp(szClass, kFlashFullscreenClass)) { + ReleaseCapture(); + SetFocus(hwnd); + } + } + + if (message == WM_CLOSE) { + self->DestroyPluginWindow(); + } + + if (message == WM_NCDESTROY) { + RemoveProp(hWnd, kPluginInstanceChildProperty); + } + + return res; +} + +bool PluginInstanceChild::ShouldPostKeyMessage(UINT message, WPARAM wParam, + LPARAM lParam) { + // If there is a composition, we shouldn't post the key message to the + // parent process because we cannot handle IME messages asynchronously. + // Therefore, if we posted key events to the parent process, the event + // order of the posted key events and IME events are shuffled. + if (sIsIMEComposing) { + return false; + } + + // If there are some pending keyboard events which are not handled in + // the parent process, we should post the message for avoiding to shuffle + // the key event order. + if (mPostingKeyEvents) { + return true; + } + + // If we are not waiting calls of RecvOnKeyEventHandledBeforePlugin(), + // we don't need to post WM_*CHAR messages. + switch (message) { + case WM_CHAR: + case WM_SYSCHAR: + case WM_DEADCHAR: + case WM_SYSDEADCHAR: + return false; + } + + // Otherwise, we should post key message which might match with a + // shortcut key. + ModifierKeyState modifierState; + if (!modifierState.MaybeMatchShortcutKey()) { + // For better UX, we shouldn't use IPC when user tries to + // input character(s). + return false; + } + + // Ignore modifier key events and keys already handled by IME. + switch (wParam) { + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + case VK_LWIN: + case VK_RWIN: + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + // Following virtual keycodes shouldn't come with WM_(SYS)KEY* message + // but check it for avoiding unnecessary cross process communication. + case VK_LSHIFT: + case VK_RSHIFT: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_LMENU: + case VK_RMENU: + case VK_PROCESSKEY: + case VK_PACKET: + case 0xFF: // 0xFF could be sent with unidentified key by the layout. + return false; + default: + break; + } + return true; +} + +bool PluginInstanceChild::MaybePostKeyMessage(UINT message, WPARAM wParam, + LPARAM lParam) { + if (!ShouldPostKeyMessage(message, wParam, lParam)) { + return false; + } + + ModifierKeyState modifierState; + WinNativeKeyEventData winNativeKeyData(message, wParam, lParam, + modifierState); + NativeEventData nativeKeyData; + nativeKeyData.Copy(winNativeKeyData); + if (NS_WARN_IF(!SendOnWindowedPluginKeyEvent(nativeKeyData))) { + return false; + } + + mPostingKeyEvents++; + return true; +} + +/* set window long ptr hook for flash */ + +/* + * Flash will reset the subclass of our widget at various times. + * (Notably when entering and exiting full screen mode.) This + * occurs independent of the main plugin window event procedure. + * We trap these subclass calls to prevent our subclass hook from + * getting dropped. + * Note, ascii versions can be nixed once flash versions < 10.1 + * are considered obsolete. + */ + +# ifdef _WIN64 +typedef LONG_PTR(WINAPI* User32SetWindowLongPtrA)(HWND hWnd, int nIndex, + LONG_PTR dwNewLong); +typedef LONG_PTR(WINAPI* User32SetWindowLongPtrW)(HWND hWnd, int nIndex, + LONG_PTR dwNewLong); +static WindowsDllInterceptor::FuncHookType + sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType + 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 + sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType + sUser32SetWindowLongWHookStub; +# endif + +extern LRESULT CALLBACK NeuteredWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, + LPARAM lParam); + +const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc"; + +// static +bool PluginInstanceChild::SetWindowLongHookCheck(HWND hWnd, int nIndex, + LONG_PTR newLong) { + // Let this go through if it's not a subclass + if (nIndex != GWLP_WNDPROC || + // if it's not a subclassed plugin window + !GetProp(hWnd, kPluginInstanceChildProperty) || + // if we're not disabled + GetProp(hWnd, kPluginIgnoreSubclassProperty) || + // if the subclass is set to a known procedure + newLong == reinterpret_cast(PluginWindowProc) || + newLong == reinterpret_cast(NeuteredWindowProc) || + newLong == reinterpret_cast(DefWindowProcA) || + newLong == reinterpret_cast(DefWindowProcW) || + // if the subclass is a WindowsMessageLoop subclass restore + GetProp(hWnd, kOldWndProcProp)) + return true; + // prevent the subclass + return false; +} + +# ifdef _WIN64 +LONG_PTR WINAPI PluginInstanceChild::SetWindowLongPtrAHook(HWND hWnd, + int nIndex, + LONG_PTR newLong) +# else +LONG WINAPI PluginInstanceChild::SetWindowLongAHook(HWND hWnd, int nIndex, + LONG newLong) +# endif +{ + if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) + return sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); + + // Set flash's new subclass to get the result. + LONG_PTR proc = sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); + + // We already checked this in SetWindowLongHookCheck + PluginInstanceChild* self = reinterpret_cast( + GetProp(hWnd, kPluginInstanceChildProperty)); + + // Hook our subclass back up, just like we do on setwindow. + WNDPROC currentProc = + reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (currentProc != PluginWindowProc) { + self->mPluginWndProc = + reinterpret_cast(sUser32SetWindowLongWHookStub( + hWnd, nIndex, reinterpret_cast(PluginWindowProc))); + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, + "Infinite recursion coming up!"); + } + return proc; +} + +# ifdef _WIN64 +LONG_PTR WINAPI PluginInstanceChild::SetWindowLongPtrWHook(HWND hWnd, + int nIndex, + LONG_PTR newLong) +# else +LONG WINAPI PluginInstanceChild::SetWindowLongWHook(HWND hWnd, int nIndex, + LONG newLong) +# endif +{ + if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) + return sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); + + // Set flash's new subclass to get the result. + LONG_PTR proc = sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); + + // We already checked this in SetWindowLongHookCheck + PluginInstanceChild* self = reinterpret_cast( + GetProp(hWnd, kPluginInstanceChildProperty)); + + // Hook our subclass back up, just like we do on setwindow. + WNDPROC currentProc = + reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (currentProc != PluginWindowProc) { + self->mPluginWndProc = + reinterpret_cast(sUser32SetWindowLongWHookStub( + hWnd, nIndex, reinterpret_cast(PluginWindowProc))); + NS_ASSERTION(self->mPluginWndProc != PluginWindowProc, + "Infinite recursion coming up!"); + } + return proc; +} + +void PluginInstanceChild::HookSetWindowLongPtr() { + if (!(GetQuirks() & QUIRK_FLASH_HOOK_SETLONGPTR)) { + return; + } + + sUser32Intercept.Init("user32.dll"); +# ifdef _WIN64 + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongPtrA", + &SetWindowLongPtrAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongPtrW", + &SetWindowLongPtrWHook); +# else + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongA", + &SetWindowLongAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongW", + &SetWindowLongWHook); +# endif +} + +/* windowless track popup menu helpers */ + +BOOL WINAPI PluginInstanceChild::TrackPopupHookProc(HMENU hMenu, UINT uFlags, + int x, int y, int nReserved, + HWND hWnd, + CONST RECT* prcRect) { + if (!sUser32TrackPopupMenuStub) { + NS_ERROR("TrackPopupMenu stub isn't set! Badness!"); + return 0; + } + + // Only change the parent when we know this is a context on the plugin + // surface within the browser. Prevents resetting the parent on child ui + // displayed by plugins that have working parent-child relationships. + wchar_t szClass[21]; + bool haveClass = GetClassNameW(hWnd, szClass, ArrayLength(szClass)); + if (!haveClass || (wcscmp(szClass, L"MozillaWindowClass") && + wcscmp(szClass, L"SWFlash_Placeholder"))) { + // Unrecognized parent + return sUser32TrackPopupMenuStub(hMenu, uFlags, x, y, nReserved, hWnd, + prcRect); + } + + // Called on an unexpected event, warn. + if (!sWinlessPopupSurrogateHWND) { + NS_WARNING("Untraced TrackPopupHookProc call! Menu might not work right!"); + return sUser32TrackPopupMenuStub(hMenu, uFlags, x, y, nReserved, hWnd, + prcRect); + } + + HWND surrogateHwnd = sWinlessPopupSurrogateHWND; + sWinlessPopupSurrogateHWND = nullptr; + + // Popups that don't use TPM_RETURNCMD expect a final command message + // when an item is selected and the context closes. Since we replace + // the parent, we need to forward this back to the real parent so it + // can act on the menu item selected. + bool isRetCmdCall = (uFlags & TPM_RETURNCMD); + + DWORD res = sUser32TrackPopupMenuStub(hMenu, uFlags | TPM_RETURNCMD, x, y, + nReserved, surrogateHwnd, prcRect); + + if (!isRetCmdCall && res) { + SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(res, 0), 0); + } + + return res; +} + +void PluginInstanceChild::InitPopupMenuHook() { + if (!(GetQuirks() & QUIRK_WINLESS_TRACKPOPUP_HOOK)) { + return; + } + + // Note, once WindowsDllInterceptor is initialized for a module, + // it remains initialized for that particular module for it's + // lifetime. Additional instances are needed if other modules need + // to be hooked. + sUser32Intercept.Init("user32.dll"); + sUser32TrackPopupMenuStub.Set(sUser32Intercept, "TrackPopupMenu", + &TrackPopupHookProc); +} + +void PluginInstanceChild::CreateWinlessPopupSurrogate() { + // already initialized + if (mWinlessPopupSurrogateHWND) return; + + mWinlessPopupSurrogateHWND = + CreateWindowEx(WS_EX_NOPARENTNOTIFY, L"Static", nullptr, WS_POPUP, 0, 0, + 0, 0, nullptr, 0, GetModuleHandle(nullptr), 0); + if (!mWinlessPopupSurrogateHWND) { + NS_ERROR("CreateWindowEx failed for winless placeholder!"); + return; + } + + SendSetNetscapeWindowAsParent(mWinlessPopupSurrogateHWND); +} + +// static +HIMC PluginInstanceChild::ImmGetContextProc(HWND aWND) { + if (!sCurrentPluginInstance) { + return sImm32ImmGetContextStub(aWND); + } + + wchar_t szClass[21]; + int haveClass = GetClassNameW(aWND, szClass, ArrayLength(szClass)); + if (!haveClass || wcscmp(szClass, L"SWFlash_PlaceholderX")) { + NS_WARNING("We cannot recongnize hooked window class"); + return sImm32ImmGetContextStub(aWND); + } + + return sHookIMC; +} + +// static +LONG PluginInstanceChild::ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex, + LPVOID aBuf, DWORD aLen) { + if (aIMC != sHookIMC) { + return sImm32ImmGetCompositionStringStub(aIMC, aIndex, aBuf, aLen); + } + if (!sCurrentPluginInstance) { + return IMM_ERROR_GENERAL; + } + AutoTArray dist; + int32_t length = 0; // IMM_ERROR_NODATA + sCurrentPluginInstance->SendGetCompositionString(aIndex, &dist, &length); + if (length == IMM_ERROR_NODATA || length == IMM_ERROR_GENERAL) { + return length; + } + + if (aBuf && aLen >= static_cast(length)) { + memcpy(aBuf, dist.Elements(), length); + } + return length; +} + +// staitc +BOOL PluginInstanceChild::ImmSetCandidateWindowProc(HIMC aIMC, + LPCANDIDATEFORM aForm) { + return FALSE; +} + +// static +BOOL PluginInstanceChild::ImmNotifyIME(HIMC aIMC, DWORD aAction, DWORD aIndex, + DWORD aValue) { + if (aIMC != sHookIMC) { + return sImm32ImmNotifyIME(aIMC, aAction, aIndex, aValue); + } + + // We only supports NI_COMPOSITIONSTR because Flash uses it only + if (!sCurrentPluginInstance || aAction != NI_COMPOSITIONSTR || + (aIndex != CPS_COMPLETE && aIndex != CPS_CANCEL)) { + return FALSE; + } + + sCurrentPluginInstance->SendRequestCommitOrCancel(aAction == CPS_COMPLETE); + return TRUE; +} + +// static +BOOL PluginInstanceChild::ImmAssociateContextExProc(HWND hWND, HIMC hImc, + DWORD dwFlags) { + PluginInstanceChild* self = sCurrentPluginInstance; + if (!self) { + // If ImmAssociateContextEx calls unexpected window message, + // we can use child instance object from window property if available. + self = reinterpret_cast( + GetProp(hWND, kFlashThrottleProperty)); + NS_WARNING_ASSERTION(self, "Cannot find PluginInstanceChild"); + } + + // HIMC is always nullptr on Flash's windowless + if (!hImc && self) { + // Store the last IME state since Flash may call ImmAssociateContextEx + // before taking focus. + self->mLastEnableIMEState = !!(dwFlags & IACE_DEFAULT); + } + return sImm32ImmAssociateContextExStub(hWND, hImc, dwFlags); +} + +void PluginInstanceChild::InitImm32Hook() { + if (!(GetQuirks() & QUIRK_WINLESS_HOOK_IME)) { + return; + } + + // When using windowless plugin, IMM API won't work due to OOP. + // + // ImmReleaseContext on Windows 7+ just returns TRUE only, so we don't + // need to hook this. + + sImm32Intercept.Init("imm32.dll"); + sImm32ImmGetContextStub.Set(sImm32Intercept, "ImmGetContext", + &ImmGetContextProc); + sImm32ImmGetCompositionStringStub.Set(sImm32Intercept, + "ImmGetCompositionStringW", + &ImmGetCompositionStringProc); + sImm32ImmSetCandidateWindowStub.Set(sImm32Intercept, "ImmSetCandidateWindow", + &ImmSetCandidateWindowProc); + sImm32ImmNotifyIME.Set(sImm32Intercept, "ImmNotifyIME", &ImmNotifyIME); + sImm32ImmAssociateContextExStub.Set(sImm32Intercept, "ImmAssociateContextEx", + &ImmAssociateContextExProc); +} + +void PluginInstanceChild::DestroyWinlessPopupSurrogate() { + if (mWinlessPopupSurrogateHWND) DestroyWindow(mWinlessPopupSurrogateHWND); + mWinlessPopupSurrogateHWND = nullptr; +} + +int16_t PluginInstanceChild::WinlessHandleEvent(NPEvent& event) { + if (!mPluginIface->event) return false; + + // Events that might generate nested event dispatch loops need + // special handling during delivery. + int16_t handled; + + HWND focusHwnd = nullptr; + + // TrackPopupMenu will fail if the parent window is not associated with + // our ui thread. So we hook TrackPopupMenu so we can hand in a surrogate + // parent created in the child process. + if ((GetQuirks() & + QUIRK_WINLESS_TRACKPOPUP_HOOK) && // XXX turn on by default? + (event.event == WM_RBUTTONDOWN || // flash + event.event == WM_RBUTTONUP)) { // silverlight + sWinlessPopupSurrogateHWND = mWinlessPopupSurrogateHWND; + + // A little trick scrounged from chromium's code - set the focus + // to our surrogate parent so keyboard nav events go to the menu. + focusHwnd = SetFocus(mWinlessPopupSurrogateHWND); + } + + AutoRestore pluginInstance(sCurrentPluginInstance); + if (event.event == WM_IME_STARTCOMPOSITION || + event.event == WM_IME_COMPOSITION || event.event == WM_LBUTTONDOWN || + event.event == WM_KILLFOCUS) { + sCurrentPluginInstance = this; + } + + MessageLoop* loop = MessageLoop::current(); + AutoRestore modalLoop(loop->os_modal_loop()); + + handled = mPluginIface->event(&mData, reinterpret_cast(&event)); + + sWinlessPopupSurrogateHWND = nullptr; + + if (IsWindow(focusHwnd)) { + SetFocus(focusHwnd); + } + + // This is hack of Flash's behaviour. + // + // When moving focus from chrome to plugin by mouse click, Gecko sends + // mouse message (such as WM_LBUTTONDOWN etc) at first, then sends + // WM_SETFOCUS. But Flash will call ImmAssociateContextEx on WM_LBUTTONDOWN + // even if it doesn't receive WM_SETFOCUS. + // + // In this situation, after sending mouse message, content process will be + // activated and set input context with PLUGIN. So after activating + // content process, we have to set current IME state again. + + if (event.event == WM_KILLFOCUS) { + // Flash always calls ImmAssociateContextEx by taking focus. + // Although this flag doesn't have to be reset, I add it for safety. + mLastEnableIMEState = true; + } + + return handled; +} + +/* flash msg throttling helpers */ + +// Flash has the unfortunate habit of flooding dispatch loops with custom +// windowing events they use for timing. We throttle these by dropping the +// delivery priority below any other event, including pending ipc io +// notifications. We do this for both windowed and windowless controls. +// Note flash's windowless msg window can last longer than our instance, +// so we try to unhook when the window is destroyed and in NPP_Destroy. + +void PluginInstanceChild::UnhookWinlessFlashThrottle() { + // We may have already unhooked + if (!mWinlessThrottleOldWndProc) return; + + WNDPROC tmpProc = mWinlessThrottleOldWndProc; + mWinlessThrottleOldWndProc = nullptr; + + NS_ASSERTION(mWinlessHiddenMsgHWND, + "Missing mWinlessHiddenMsgHWND w/subclass set??"); + + // reset the subclass + SetWindowLongPtr(mWinlessHiddenMsgHWND, GWLP_WNDPROC, + reinterpret_cast(tmpProc)); + + // Remove our instance prop + RemoveProp(mWinlessHiddenMsgHWND, kFlashThrottleProperty); + mWinlessHiddenMsgHWND = nullptr; +} + +// static +LRESULT CALLBACK PluginInstanceChild::WinlessHiddenFlashWndProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) { + PluginInstanceChild* self = reinterpret_cast( + GetProp(hWnd, kFlashThrottleProperty)); + if (!self) { + MOZ_ASSERT_UNREACHABLE("Badness!"); + return 0; + } + + NS_ASSERTION(self->mWinlessThrottleOldWndProc, + "Missing subclass procedure!!"); + + // Throttle + if (message == WM_USER + 1) { + self->FlashThrottleMessage(hWnd, message, wParam, lParam, false); + return 0; + } + + // Unhook + if (message == WM_CLOSE || message == WM_NCDESTROY) { + WNDPROC tmpProc = self->mWinlessThrottleOldWndProc; + self->UnhookWinlessFlashThrottle(); + LRESULT res = CallWindowProc(tmpProc, hWnd, message, wParam, lParam); + return res; + } + + return CallWindowProc(self->mWinlessThrottleOldWndProc, hWnd, message, wParam, + lParam); +} + +// Enumerate all thread windows looking for flash's hidden message window. +// Once we find it, sub class it so we can throttle user msgs. +// static +BOOL CALLBACK PluginInstanceChild::EnumThreadWindowsCallback(HWND hWnd, + LPARAM aParam) { + PluginInstanceChild* self = reinterpret_cast(aParam); + if (!self) { + MOZ_ASSERT_UNREACHABLE("Enum befuddled!"); + return FALSE; + } + + wchar_t className[64]; + if (!GetClassNameW(hWnd, className, sizeof(className) / sizeof(char16_t))) + return TRUE; + + if (!wcscmp(className, L"SWFlash_PlaceholderX")) { + WNDPROC oldWndProc = + reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + // Only set this if we haven't already. + if (oldWndProc != WinlessHiddenFlashWndProc) { + if (self->mWinlessThrottleOldWndProc) { + NS_WARNING("mWinlessThrottleWndProc already set???"); + return FALSE; + } + // Subsclass and store self as a property + self->mWinlessHiddenMsgHWND = hWnd; + self->mWinlessThrottleOldWndProc = + reinterpret_cast(SetWindowLongPtr( + hWnd, GWLP_WNDPROC, + reinterpret_cast(WinlessHiddenFlashWndProc))); + SetProp(hWnd, kFlashThrottleProperty, self); + NS_ASSERTION(self->mWinlessThrottleOldWndProc, + "SetWindowLongPtr failed?!"); + } + // Return no matter what once we find the right window. + return FALSE; + } + + return TRUE; +} + +void PluginInstanceChild::SetupFlashMsgThrottle() { + if (mWindow.type == NPWindowTypeDrawable) { + // Search for the flash hidden message window and subclass it. Only + // search for flash windows belonging to our ui thread! + if (mWinlessThrottleOldWndProc) return; + EnumThreadWindows(GetCurrentThreadId(), EnumThreadWindowsCallback, + reinterpret_cast(this)); + } else { + // Already setup through quirks and the subclass. + return; + } +} + +WNDPROC +PluginInstanceChild::FlashThrottleMsg::GetProc() { + if (mInstance) { + return mWindowed ? mInstance->mPluginWndProc + : mInstance->mWinlessThrottleOldWndProc; + } + return nullptr; +} + +NS_IMETHODIMP +PluginInstanceChild::FlashThrottleMsg::Run() { + if (!mInstance) { + return NS_OK; + } + + mInstance->mPendingFlashThrottleMsgs.RemoveElement(this); + + // GetProc() checks mInstance, and pulls the procedure from + // PluginInstanceChild. We don't transport sub-class procedure + // ptrs around in FlashThrottleMsg msgs. + if (!GetProc()) return NS_OK; + + // deliver the event to flash + CallWindowProc(GetProc(), GetWnd(), GetMsg(), GetWParam(), GetLParam()); + return NS_OK; +} + +nsresult PluginInstanceChild::FlashThrottleMsg::Cancel() { + MOZ_ASSERT(mInstance); + mInstance = nullptr; + return NS_OK; +} + +void PluginInstanceChild::FlashThrottleMessage(HWND aWnd, UINT aMsg, + WPARAM aWParam, LPARAM aLParam, + bool isWindowed) { + // We save a reference to the FlashThrottleMsg so we can cancel it in + // Destroy if it's still alive. + RefPtr task = + new FlashThrottleMsg(this, aWnd, aMsg, aWParam, aLParam, isWindowed); + + mPendingFlashThrottleMsgs.AppendElement(task); + + MessageLoop::current()->PostDelayedTask(task.forget(), + kFlashWMUSERMessageThrottleDelayMs); +} + +#endif // OS_WIN + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerSetPluginFocus() { + MOZ_LOG(GetPluginLog(), LogLevel::Debug, ("%s", FULLFUNCTION)); + +#if defined(OS_WIN) + // Parent is letting us know the dom set focus to the plugin. Note, + // focus can change during transit in certain edge cases, for example + // when a button click brings up a full screen window. Since we send + // this in response to a WM_SETFOCUS event on our parent, the parent + // should have focus when we receive this. If not, ignore the call. + if (::GetFocus() == mPluginWindowHWND) return IPC_OK(); + ::SetFocus(mPluginWindowHWND); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("AnswerSetPluginFocus not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerUpdateWindow() { + MOZ_LOG(GetPluginLog(), LogLevel::Debug, ("%s", FULLFUNCTION)); + +#if defined(OS_WIN) + if (mPluginWindowHWND) { + RECT rect; + if (GetUpdateRect(GetParent(mPluginWindowHWND), &rect, FALSE)) { + ::InvalidateRect(mPluginWindowHWND, &rect, FALSE); + } + UpdateWindow(mPluginWindowHWND); + } + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("AnswerUpdateWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvNPP_DidComposite() { + if (mPluginIface->didComposite) { + mPluginIface->didComposite(GetNPP()); + } + return IPC_OK(); +} + +PPluginScriptableObjectChild* +PluginInstanceChild::AllocPPluginScriptableObjectChild() { + AssertPluginThread(); + return new PluginScriptableObjectChild(Proxy); +} + +bool PluginInstanceChild::DeallocPPluginScriptableObjectChild( + PPluginScriptableObjectChild* aObject) { + AssertPluginThread(); + delete aObject; + return true; +} + +mozilla::ipc::IPCResult +PluginInstanceChild::RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectChild* aActor) { + AssertPluginThread(); + + // This is only called in response to the parent process requesting the + // creation of an actor. This actor will represent an NPObject that is + // created by the browser and returned to the plugin. + PluginScriptableObjectChild* actor = + static_cast(aActor); + NS_ASSERTION(!actor->GetObject(false), "Actor already has an object?!"); + + actor->InitializeProxy(); + NS_ASSERTION(actor->GetObject(false), "Actor should have an object!"); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvPBrowserStreamConstructor( + PBrowserStreamChild* aActor, const nsCString& url, const uint32_t& length, + const uint32_t& lastmodified, PStreamNotifyChild* notifyData, + const nsCString& headers) { + return IPC_OK(); +} + +NPError PluginInstanceChild::DoNPP_NewStream(BrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, + uint16_t* stype) { + AssertPluginThread(); + AutoStackHelper guard(this); + NPError rv = actor->StreamConstructed(mimeType, seekable, stype); + return rv; +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_NewStream( + PBrowserStreamChild* actor, const nsCString& mimeType, const bool& seekable, + NPError* rv, uint16_t* stype) { + *rv = DoNPP_NewStream(static_cast(actor), mimeType, + seekable, stype); + return IPC_OK(); +} + +PBrowserStreamChild* PluginInstanceChild::AllocPBrowserStreamChild( + const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, + PStreamNotifyChild* notifyData, const nsCString& headers) { + AssertPluginThread(); + return new BrowserStreamChild(this, url, length, lastmodified, + static_cast(notifyData), + headers); +} + +bool PluginInstanceChild::DeallocPBrowserStreamChild( + PBrowserStreamChild* stream) { + AssertPluginThread(); + delete stream; + return true; +} + +PStreamNotifyChild* PluginInstanceChild::AllocPStreamNotifyChild( + const nsCString& url, const nsCString& target, const bool& post, + const nsCString& buffer, const bool& file, NPError* result) { + AssertPluginThread(); + MOZ_CRASH("not reached"); + return nullptr; +} + +void StreamNotifyChild::ActorDestroy(ActorDestroyReason why) { + if (AncestorDeletion == why && mBrowserStream) { + NS_ERROR("Pending NPP_URLNotify not called when closing an instance."); + + // reclaim responsibility for deleting ourself + mBrowserStream->mStreamNotify = nullptr; + mBrowserStream = nullptr; + } +} + +void StreamNotifyChild::SetAssociatedStream(BrowserStreamChild* bs) { + NS_ASSERTION(!mBrowserStream, "Two streams for one streamnotify?"); + + mBrowserStream = bs; +} + +mozilla::ipc::IPCResult StreamNotifyChild::Recv__delete__( + const NPReason& reason) { + AssertPluginThread(); + + if (mBrowserStream) + mBrowserStream->NotifyPending(); + else + NPP_URLNotify(reason); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult StreamNotifyChild::RecvRedirectNotify( + const nsCString& url, const int32_t& status) { + // NPP_URLRedirectNotify requires a non-null closure. Since core logic + // assumes that all out-of-process notify streams have non-null closure + // data it will assume that the plugin was notified at this point and + // expect a response otherwise the redirect will hang indefinitely. + if (!mClosure) { + SendRedirectNotifyResponse(false); + } + + PluginInstanceChild* instance = static_cast(Manager()); + if (instance->mPluginIface->urlredirectnotify) + instance->mPluginIface->urlredirectnotify(instance->GetNPP(), url.get(), + status, mClosure); + + return IPC_OK(); +} + +void StreamNotifyChild::NPP_URLNotify(NPReason reason) { + PluginInstanceChild* instance = static_cast(Manager()); + + if (mClosure) + instance->mPluginIface->urlnotify(instance->GetNPP(), mURL.get(), reason, + mClosure); +} + +bool PluginInstanceChild::DeallocPStreamNotifyChild( + PStreamNotifyChild* notifyData) { + AssertPluginThread(); + + if (!static_cast(notifyData)->mBrowserStream) + delete notifyData; + return true; +} + +PluginScriptableObjectChild* PluginInstanceChild::GetActorForNPObject( + NPObject* aObject) { + AssertPluginThread(); + NS_ASSERTION(aObject, "Null pointer!"); + + if (aObject->_class == PluginScriptableObjectChild::GetClass()) { + // One of ours! It's a browser-provided object. + ChildNPObject* object = static_cast(aObject); + NS_ASSERTION(object->parent, "Null actor!"); + return object->parent; + } + + PluginScriptableObjectChild* actor = + PluginScriptableObjectChild::GetActorForNPObject(aObject); + if (actor) { + // Plugin-provided object that we've previously wrapped. + return actor; + } + + actor = new PluginScriptableObjectChild(LocalObject); + if (!SendPPluginScriptableObjectConstructor(actor)) { + NS_ERROR("Failed to send constructor message!"); + return nullptr; + } + + actor->InitializeLocal(aObject); + return actor; +} + +void PluginInstanceChild::NPN_URLRedirectResponse(void* notifyData, + NPBool allow) { + if (!notifyData) { + return; + } + + nsTArray notifyStreams; + ManagedPStreamNotifyChild(notifyStreams); + uint32_t notifyStreamCount = notifyStreams.Length(); + for (uint32_t i = 0; i < notifyStreamCount; i++) { + StreamNotifyChild* sn = static_cast(notifyStreams[i]); + if (sn->mClosure == notifyData) { + sn->SendRedirectNotifyResponse(static_cast(allow)); + return; + } + } + NS_ASSERTION(false, "Couldn't find stream for redirect response!"); +} + +bool PluginInstanceChild::IsUsingDirectDrawing() { + return IsDrawingModelDirect(mDrawingModel); +} + +PluginInstanceChild::DirectBitmap::DirectBitmap(PluginInstanceChild* aOwner, + const Shmem& shmem, + const IntSize& size, + uint32_t stride, + SurfaceFormat format) + : mOwner(aOwner), + mShmem(shmem), + mFormat(format), + mSize(size), + mStride(stride) {} + +PluginInstanceChild::DirectBitmap::~DirectBitmap() { + mOwner->DeallocShmem(mShmem); +} + +static inline SurfaceFormat NPImageFormatToSurfaceFormat( + NPImageFormat aFormat) { + switch (aFormat) { + case NPImageFormatBGRA32: + return SurfaceFormat::B8G8R8A8; + case NPImageFormatBGRX32: + return SurfaceFormat::B8G8R8X8; + default: + MOZ_ASSERT_UNREACHABLE("unknown NPImageFormat"); + return SurfaceFormat::UNKNOWN; + } +} + +static inline gfx::IntRect NPRectToIntRect(const NPRect& in) { + return IntRect(in.left, in.top, in.right - in.left, in.bottom - in.top); +} + +NPError PluginInstanceChild::NPN_InitAsyncSurface(NPSize* size, + NPImageFormat format, + void* initData, + NPAsyncSurface* surface) { + AssertPluginThread(); + AutoStackHelper guard(this); + + if (!IsUsingDirectDrawing()) { + return NPERR_INVALID_PARAM; + } + if (format != NPImageFormatBGRA32 && format != NPImageFormatBGRX32) { + return NPERR_INVALID_PARAM; + } + + PodZero(surface); + + // NPAPI guarantees that the SetCurrentAsyncSurface call will release the + // previous surface if it was different. However, no functionality exists + // within content to synchronize a non-shadow-layers transaction with the + // compositor. + // + // To get around this, we allocate two surfaces: a child copy, which we + // hand off to the plugin, and a parent copy, which we will hand off to + // the compositor. Each call to SetCurrentAsyncSurface will copy the + // invalid region from the child surface to its parent. + switch (mDrawingModel) { + case NPDrawingModelAsyncBitmapSurface: { + // Validate that the caller does not expect initial data to be set. + if (initData) { + return NPERR_INVALID_PARAM; + } + + // Validate that we're not double-allocating a surface. + RefPtr holder; + if (mDirectBitmaps.Get(surface, getter_AddRefs(holder))) { + return NPERR_INVALID_PARAM; + } + + SurfaceFormat mozformat = NPImageFormatToSurfaceFormat(format); + int32_t bytesPerPixel = BytesPerPixel(mozformat); + + if (size->width <= 0 || size->height <= 0) { + return NPERR_INVALID_PARAM; + } + + CheckedInt nbytes = + SafeBytesForBitmap(size->width, size->height, bytesPerPixel); + if (!nbytes.isValid()) { + return NPERR_INVALID_PARAM; + } + + Shmem shmem; + if (!AllocUnsafeShmem(nbytes.value(), SharedMemory::TYPE_BASIC, &shmem)) { + return NPERR_OUT_OF_MEMORY_ERROR; + } + MOZ_ASSERT(shmem.Size() == nbytes.value()); + + surface->version = 0; + surface->size = *size; + surface->format = format; + surface->bitmap.data = shmem.get(); + surface->bitmap.stride = size->width * bytesPerPixel; + + // Hold the shmem alive until Finalize() is called or this actor dies. + holder = new DirectBitmap(this, shmem, IntSize(size->width, size->height), + surface->bitmap.stride, mozformat); + mDirectBitmaps.Put(surface, std::move(holder)); + return NPERR_NO_ERROR; + } +#if defined(XP_WIN) + case NPDrawingModelAsyncWindowsDXGISurface: { + // Validate that the caller does not expect initial data to be set. + if (initData) { + return NPERR_INVALID_PARAM; + } + + // Validate that we're not double-allocating a surface. + WindowsHandle handle = 0; + if (mDxgiSurfaces.Get(surface, &handle)) { + return NPERR_INVALID_PARAM; + } + + NPError error = NPERR_NO_ERROR; + SurfaceFormat mozformat = NPImageFormatToSurfaceFormat(format); + if (!SendInitDXGISurface(mozformat, IntSize(size->width, size->height), + &handle, &error)) { + return NPERR_GENERIC_ERROR; + } + if (error != NPERR_NO_ERROR) { + return error; + } + + surface->version = 0; + surface->size = *size; + surface->format = format; + surface->sharedHandle = reinterpret_cast(handle); + + mDxgiSurfaces.Put(surface, handle); + return NPERR_NO_ERROR; + } +#endif + default: + MOZ_ASSERT_UNREACHABLE("unknown drawing model"); + } + + return NPERR_INVALID_PARAM; +} + +NPError PluginInstanceChild::NPN_FinalizeAsyncSurface(NPAsyncSurface* surface) { + AssertPluginThread(); + + if (!IsUsingDirectDrawing()) { + return NPERR_GENERIC_ERROR; + } + + switch (mDrawingModel) { + case NPDrawingModelAsyncBitmapSurface: { + RefPtr bitmap; + if (!mDirectBitmaps.Get(surface, getter_AddRefs(bitmap))) { + return NPERR_INVALID_PARAM; + } + + PodZero(surface); + mDirectBitmaps.Remove(surface); + return NPERR_NO_ERROR; + } +#if defined(XP_WIN) + case NPDrawingModelAsyncWindowsDXGISurface: { + WindowsHandle handle; + if (!mDxgiSurfaces.Get(surface, &handle)) { + return NPERR_INVALID_PARAM; + } + + SendFinalizeDXGISurface(handle); + mDxgiSurfaces.Remove(surface); + return NPERR_NO_ERROR; + } +#endif + default: + MOZ_ASSERT_UNREACHABLE("unknown drawing model"); + } + + return NPERR_INVALID_PARAM; +} + +void PluginInstanceChild::NPN_SetCurrentAsyncSurface(NPAsyncSurface* surface, + NPRect* changed) { + AssertPluginThread(); + + if (!IsUsingDirectDrawing()) { + return; + } + + mCurrentDirectSurface = surface; + + if (!surface) { + SendRevokeCurrentDirectSurface(); + return; + } + + switch (mDrawingModel) { + case NPDrawingModelAsyncBitmapSurface: { + RefPtr bitmap; + if (!mDirectBitmaps.Get(surface, getter_AddRefs(bitmap))) { + return; + } + + IntRect dirty = changed ? NPRectToIntRect(*changed) + : IntRect(IntPoint(0, 0), bitmap->mSize); + + // Need a holder since IPDL zaps the object for mysterious reasons. + Shmem shmemHolder = bitmap->mShmem; + SendShowDirectBitmap(std::move(shmemHolder), bitmap->mFormat, + bitmap->mStride, bitmap->mSize, dirty); + break; + } +#if defined(XP_WIN) + case NPDrawingModelAsyncWindowsDXGISurface: { + WindowsHandle handle; + if (!mDxgiSurfaces.Get(surface, &handle)) { + return; + } + + IntRect dirty = + changed ? NPRectToIntRect(*changed) + : IntRect(IntPoint(0, 0), + IntSize(surface->size.width, surface->size.height)); + + SendShowDirectDXGISurface(handle, dirty); + break; + } +#endif + default: + MOZ_ASSERT_UNREACHABLE("unknown drawing model"); + } +} + +void PluginInstanceChild::DoAsyncRedraw() { + { + MutexAutoLock autoLock(mAsyncInvalidateMutex); + mAsyncInvalidateTask = nullptr; + } + + SendRedrawPlugin(); +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvAsyncSetWindow( + const gfxSurfaceType& aSurfaceType, const NPRemoteWindow& aWindow) { + AssertPluginThread(); + + AutoStackHelper guard(this); + NS_ASSERTION(!aWindow.window, "Remote window should be null."); + + if (mCurrentAsyncSetWindowTask) { + mCurrentAsyncSetWindowTask->Cancel(); + mCurrentAsyncSetWindowTask = nullptr; + } + + // We shouldn't process this now because it may be received within a nested + // RPC call, and both Flash and Java don't expect to receive setwindow calls + // at arbitrary times. + mCurrentAsyncSetWindowTask = + NewNonOwningCancelableRunnableMethod( + "plugins::PluginInstanceChild::DoAsyncSetWindow", this, + &PluginInstanceChild::DoAsyncSetWindow, aSurfaceType, aWindow, true); + RefPtr addrefedTask = mCurrentAsyncSetWindowTask; + MessageLoop::current()->PostTask(addrefedTask.forget()); + + return IPC_OK(); +} + +void PluginInstanceChild::DoAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow, + bool aIsAsync) { + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] AsyncSetWindow to ", this, + aWindow.x, aWindow.y, aWindow.width, aWindow.height)); + + AssertPluginThread(); + NS_ASSERTION(!aWindow.window, "Remote window should be null."); + NS_ASSERTION(!mPendingPluginCall, "Can't do SetWindow during plugin call!"); + + if (aIsAsync) { + if (!mCurrentAsyncSetWindowTask) { + return; + } + mCurrentAsyncSetWindowTask = nullptr; + } + + mWindow.window = nullptr; + if (mWindow.width != aWindow.width || mWindow.height != aWindow.height || + mWindow.clipRect.top != aWindow.clipRect.top || + mWindow.clipRect.left != aWindow.clipRect.left || + mWindow.clipRect.bottom != aWindow.clipRect.bottom || + mWindow.clipRect.right != aWindow.clipRect.right) + mAccumulatedInvalidRect = nsIntRect(0, 0, aWindow.width, aWindow.height); + + mWindow.x = aWindow.x; + mWindow.y = aWindow.y; + mWindow.width = aWindow.width; + mWindow.height = aWindow.height; + mWindow.clipRect = aWindow.clipRect; + mWindow.type = aWindow.type; +#if defined(XP_MACOSX) || defined(XP_WIN) + mContentsScaleFactor = aWindow.contentsScaleFactor; +#endif + + mLayersRendering = true; + mSurfaceType = aSurfaceType; + UpdateWindowAttributes(true); + +#ifdef XP_WIN + if (GetQuirks() & QUIRK_FLASH_THROTTLE_WMUSER_EVENTS) SetupFlashMsgThrottle(); +#endif + + if (!mAccumulatedInvalidRect.IsEmpty()) { + AsyncShowPluginFrame(); + } +} + +bool PluginInstanceChild::CreateOptSurface(void) { + MOZ_ASSERT(mSurfaceType != gfxSurfaceType::Max, + "Need a valid surface type here"); + NS_ASSERTION(!mCurrentSurface, "mCurrentSurfaceActor can get out of sync."); + + // Use an opaque surface unless we're transparent and *don't* have + // a background to source from. + gfxImageFormat format = (mIsTransparent && !mBackground) + ? SurfaceFormat::A8R8G8B8_UINT32 + : SurfaceFormat::X8R8G8B8_UINT32; + +#ifdef MOZ_X11 + Display* dpy = mWsInfo.display; + Screen* screen = DefaultScreenOfDisplay(dpy); + if (format == SurfaceFormat::X8R8G8B8_UINT32 && + DefaultDepth(dpy, DefaultScreen(dpy)) == 16) { + format = SurfaceFormat::R5G6B5_UINT16; + } + + if (mSurfaceType == gfxSurfaceType::Xlib) { + if (!mIsTransparent || mBackground) { + Visual* defaultVisual = DefaultVisualOfScreen(screen); + mCurrentSurface = gfxXlibSurface::Create( + screen, defaultVisual, IntSize(mWindow.width, mWindow.height)); + return mCurrentSurface != nullptr; + } + + XRenderPictFormat* xfmt = + XRenderFindStandardFormat(dpy, PictStandardARGB32); + if (!xfmt) { + NS_ERROR("Need X falback surface, but FindRenderFormat failed"); + return false; + } + mCurrentSurface = gfxXlibSurface::Create( + screen, xfmt, IntSize(mWindow.width, mWindow.height)); + return mCurrentSurface != nullptr; + } +#endif + +#ifdef XP_WIN + if (mSurfaceType == gfxSurfaceType::Win32) { + bool willHaveTransparentPixels = mIsTransparent && !mBackground; + + SharedDIBSurface* s = new SharedDIBSurface(); + if (!s->Create(reinterpret_cast(mWindow.window), mWindow.width, + mWindow.height, willHaveTransparentPixels)) + return false; + + mCurrentSurface = s; + return true; + } + + MOZ_CRASH("Shared-memory drawing not expected on Windows."); +#endif + + // Make common shmem implementation working for any platform + mCurrentSurface = gfxSharedImageSurface::CreateUnsafe( + this, IntSize(mWindow.width, mWindow.height), format); + return !!mCurrentSurface; +} + +bool PluginInstanceChild::MaybeCreatePlatformHelperSurface(void) { + if (!mCurrentSurface) { + NS_ERROR("Cannot create helper surface without mCurrentSurface"); + return false; + } + +#ifdef MOZ_X11 + bool supportNonDefaultVisual = false; + Screen* screen = DefaultScreenOfDisplay(mWsInfo.display); + Visual* defaultVisual = DefaultVisualOfScreen(screen); + Visual* visual = nullptr; + Colormap colormap = 0; + mDoAlphaExtraction = false; + bool createHelperSurface = false; + + if (mCurrentSurface->GetType() == gfxSurfaceType::Xlib) { + static_cast(mCurrentSurface.get()) + ->GetColormapAndVisual(&colormap, &visual); + // Create helper surface if layer surface visual not same as default + // and we don't support non-default visual rendering + if (!visual || (defaultVisual != visual && !supportNonDefaultVisual)) { + createHelperSurface = true; + visual = defaultVisual; + mDoAlphaExtraction = mIsTransparent; + } + } else if (mCurrentSurface->GetType() == gfxSurfaceType::Image) { + // For image layer surface we should always create helper surface + createHelperSurface = true; + // Check if we can create helper surface with non-default visual + visual = gfxXlibSurface::FindVisual( + screen, static_cast(mCurrentSurface.get())->Format()); + if (!visual || (defaultVisual != visual && !supportNonDefaultVisual)) { + visual = defaultVisual; + mDoAlphaExtraction = mIsTransparent; + } + } + + if (createHelperSurface) { + if (!visual) { + NS_ERROR("Need X falback surface, but visual failed"); + return false; + } + mHelperSurface = + gfxXlibSurface::Create(screen, visual, mCurrentSurface->GetSize()); + if (!mHelperSurface) { + NS_WARNING("Fail to create create helper surface"); + return false; + } + } +#elif defined(XP_WIN) + mDoAlphaExtraction = mIsTransparent && !mBackground; +#endif + + return true; +} + +bool PluginInstanceChild::EnsureCurrentBuffer(void) { +#ifndef XP_DARWIN + nsIntRect toInvalidate(0, 0, 0, 0); + IntSize winSize = IntSize(mWindow.width, mWindow.height); + + if (mBackground && mBackground->GetSize() != winSize) { + // It would be nice to keep the old background here, but doing + // so can lead to cases in which we permanently keep the old + // background size. + mBackground = nullptr; + toInvalidate.UnionRect(toInvalidate, + nsIntRect(0, 0, winSize.width, winSize.height)); + } + + if (mCurrentSurface) { + IntSize surfSize = mCurrentSurface->GetSize(); + if (winSize != surfSize || (mBackground && !CanPaintOnBackground()) || + (mBackground && + gfxContentType::COLOR != mCurrentSurface->GetContentType()) || + (!mBackground && mIsTransparent && + gfxContentType::COLOR == mCurrentSurface->GetContentType())) { + // Don't try to use an old, invalid DC. + mWindow.window = nullptr; + ClearCurrentSurface(); + toInvalidate.UnionRect(toInvalidate, + nsIntRect(0, 0, winSize.width, winSize.height)); + } + } + + mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect, toInvalidate); + + if (mCurrentSurface) { + return true; + } + + if (!CreateOptSurface()) { + NS_ERROR("Cannot create optimized surface"); + return false; + } + + if (!MaybeCreatePlatformHelperSurface()) { + NS_ERROR("Cannot create helper surface"); + return false; + } +#elif defined(XP_MACOSX) + + if (!mDoubleBufferCARenderer.HasCALayer()) { + void* caLayer = nullptr; + if (mDrawingModel == NPDrawingModelCoreGraphics) { + if (!mCGLayer) { + caLayer = mozilla::plugins::PluginUtilsOSX::GetCGLayer( + CallCGDraw, this, mContentsScaleFactor); + + if (!caLayer) { + PLUGIN_LOG_DEBUG(("GetCGLayer failed.")); + return false; + } + } + mCGLayer = caLayer; + } else { + NPError result = mPluginIface->getvalue( + GetNPP(), NPPVpluginCoreAnimationLayer, &caLayer); + if (result != NPERR_NO_ERROR || !caLayer) { + PLUGIN_LOG_DEBUG( + ("Plugin requested CoreAnimation but did not " + "provide CALayer.")); + return false; + } + } + mDoubleBufferCARenderer.SetCALayer(caLayer); + } + + if (mDoubleBufferCARenderer.HasFrontSurface() && + (mDoubleBufferCARenderer.GetFrontSurfaceWidth() != mWindow.width || + mDoubleBufferCARenderer.GetFrontSurfaceHeight() != mWindow.height || + mDoubleBufferCARenderer.GetContentsScaleFactor() != + mContentsScaleFactor)) { + mDoubleBufferCARenderer.ClearFrontSurface(); + } + + if (!mDoubleBufferCARenderer.HasFrontSurface()) { + bool allocSurface = mDoubleBufferCARenderer.InitFrontSurface( + mWindow.width, mWindow.height, mContentsScaleFactor, + GetQuirks() & QUIRK_ALLOW_OFFLINE_RENDERER ? ALLOW_OFFLINE_RENDERER + : DISALLOW_OFFLINE_RENDERER); + if (!allocSurface) { + PLUGIN_LOG_DEBUG(("Fail to allocate front IOSurface")); + return false; + } + + if (mPluginIface->setwindow) + (void)mPluginIface->setwindow(&mData, &mWindow); + + nsIntRect toInvalidate(0, 0, mWindow.width, mWindow.height); + mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect, toInvalidate); + } +#endif + + return true; +} + +void PluginInstanceChild::UpdateWindowAttributes(bool aForceSetWindow) { +#if defined(MOZ_X11) || defined(XP_WIN) + RefPtr curSurface = + mHelperSurface ? mHelperSurface : mCurrentSurface; +#endif // Only used within MOZ_X11 or XP_WIN blocks. Unused variable otherwise + bool needWindowUpdate = aForceSetWindow; +#ifdef MOZ_X11 + Visual* visual = nullptr; + Colormap colormap = 0; + if (curSurface && curSurface->GetType() == gfxSurfaceType::Xlib) { + static_cast(curSurface.get()) + ->GetColormapAndVisual(&colormap, &visual); + if (visual != mWsInfo.visual || colormap != mWsInfo.colormap) { + mWsInfo.visual = visual; + mWsInfo.colormap = colormap; + needWindowUpdate = true; + } + } +#endif // MOZ_X11 +#ifdef XP_WIN + HDC dc = nullptr; + + if (curSurface) { + if (!SharedDIBSurface::IsSharedDIBSurface(curSurface)) + MOZ_CRASH("Expected SharedDIBSurface!"); + + SharedDIBSurface* dibsurf = + static_cast(curSurface.get()); + dc = dibsurf->GetHDC(); + } + if (mWindow.window != dc) { + mWindow.window = dc; + needWindowUpdate = true; + } +#endif // XP_WIN + + if (!needWindowUpdate) { + return; + } + +#ifndef XP_MACOSX + // Adjusting the window isn't needed for OSX +# ifndef XP_WIN + // On Windows, we translate the device context, in order for the window + // origin to be correct. + mWindow.x = mWindow.y = 0; +# endif + + if (IsVisible()) { + // The clip rect is relative to drawable top-left. + nsIntRect clipRect; + + // Don't ask the plugin to draw outside the drawable. The clip rect + // is in plugin coordinates, not window coordinates. + // This also ensures that the unsigned clip rectangle offsets won't be -ve. + clipRect.SetRect(0, 0, mWindow.width, mWindow.height); + + mWindow.clipRect.left = 0; + mWindow.clipRect.top = 0; + mWindow.clipRect.right = clipRect.XMost(); + mWindow.clipRect.bottom = clipRect.YMost(); + } +#endif // XP_MACOSX + +#ifdef XP_WIN + // Windowless plugins on Windows need a WM_WINDOWPOSCHANGED event to update + // their location... or at least Flash does: Silverlight uses the + // window.x/y passed to NPP_SetWindow + + if (mPluginIface->event) { + // width and height are stored as units, but narrow to ints here + MOZ_RELEASE_ASSERT(mWindow.width <= INT_MAX); + MOZ_RELEASE_ASSERT(mWindow.height <= INT_MAX); + + WINDOWPOS winpos = {0, + 0, + mWindow.x, + mWindow.y, + (int32_t)mWindow.width, + (int32_t)mWindow.height, + 0}; + NPEvent pluginEvent = {WM_WINDOWPOSCHANGED, 0, (LPARAM)&winpos}; + mPluginIface->event(&mData, &pluginEvent); + } +#endif + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] UpdateWindow w=, " + "clip=", + this, mWindow.x, mWindow.y, mWindow.width, mWindow.height, + mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, + mWindow.clipRect.bottom)); + + if (mPluginIface->setwindow) { + mPluginIface->setwindow(&mData, &mWindow); + } +} + +void PluginInstanceChild::PaintRectToPlatformSurface(const nsIntRect& aRect, + gfxASurface* aSurface) { + UpdateWindowAttributes(); + + // We should not send an async surface if we're using direct rendering. + MOZ_ASSERT(!IsUsingDirectDrawing()); + +#ifdef MOZ_X11 + { + NS_ASSERTION(aSurface->GetType() == gfxSurfaceType::Xlib, + "Non supported platform surface type"); + + NPEvent pluginEvent; + XGraphicsExposeEvent& exposeEvent = pluginEvent.xgraphicsexpose; + exposeEvent.type = GraphicsExpose; + exposeEvent.display = mWsInfo.display; + exposeEvent.drawable = static_cast(aSurface)->XDrawable(); + exposeEvent.x = aRect.x; + exposeEvent.y = aRect.y; + exposeEvent.width = aRect.width; + exposeEvent.height = aRect.height; + exposeEvent.count = 0; + // information not set: + exposeEvent.serial = 0; + exposeEvent.send_event = X11False; + exposeEvent.major_code = 0; + exposeEvent.minor_code = 0; + mPluginIface->event(&mData, reinterpret_cast(&exposeEvent)); + } +#elif defined(XP_WIN) + NS_ASSERTION(SharedDIBSurface::IsSharedDIBSurface(aSurface), + "Expected (SharedDIB) image surface."); + + // This rect is in the window coordinate space. aRect is in the plugin + // coordinate space. + RECT rect = {mWindow.x + aRect.x, mWindow.y + aRect.y, + mWindow.x + aRect.XMost(), mWindow.y + aRect.YMost()}; + NPEvent paintEvent = {WM_PAINT, uintptr_t(mWindow.window), intptr_t(&rect)}; + + ::SetViewportOrgEx((HDC)mWindow.window, -mWindow.x, -mWindow.y, nullptr); + ::SelectClipRgn((HDC)mWindow.window, nullptr); + ::IntersectClipRect((HDC)mWindow.window, rect.left, rect.top, rect.right, + rect.bottom); + mPluginIface->event(&mData, reinterpret_cast(&paintEvent)); +#else + MOZ_CRASH("Surface type not implemented."); +#endif +} + +void PluginInstanceChild::PaintRectToSurface(const nsIntRect& aRect, + gfxASurface* aSurface, + const DeviceColor& aColor) { + // Render using temporary X surface, with copy to image surface + nsIntRect plPaintRect(aRect); + RefPtr renderSurface = aSurface; +#ifdef MOZ_X11 + if (mIsTransparent && (GetQuirks() & QUIRK_FLASH_EXPOSE_COORD_TRANSLATION)) { + // Work around a bug in Flash up to 10.1 d51 at least, where expose event + // top left coordinates within the plugin-rect and not at the drawable + // origin are misinterpreted. (We can move the top left coordinate + // provided it is within the clipRect.), see bug 574583 + plPaintRect.SetRect(0, 0, aRect.XMost(), aRect.YMost()); + } + if (mHelperSurface) { + // On X11 we can paint to non Xlib surface only with HelperSurface + renderSurface = mHelperSurface; + } +#endif + + if (mIsTransparent && !CanPaintOnBackground()) { + RefPtr dt = CreateDrawTargetForSurface(renderSurface); + gfx::Rect rect(plPaintRect.x, plPaintRect.y, plPaintRect.width, + plPaintRect.height); + // Moz2D treats OP_SOURCE operations as unbounded, so we need to + // clip to the rect that we want to fill: + dt->PushClipRect(rect); + dt->FillRect(rect, + ColorPattern(aColor), // aColor is already a device color + DrawOptions(1.f, CompositionOp::OP_SOURCE)); + dt->PopClip(); + dt->Flush(); + } + + PaintRectToPlatformSurface(plPaintRect, renderSurface); + + if (renderSurface != aSurface) { + RefPtr dt; + if (aSurface == mCurrentSurface && + aSurface->GetType() == gfxSurfaceType::Image && + aSurface->GetSurfaceFormat() == SurfaceFormat::B8G8R8X8) { + gfxImageSurface* imageSurface = static_cast(aSurface); + // Bug 1196927 - Reinterpret target surface as BGRA to fill alpha with + // opaque. Certain backends (i.e. Skia) may not truly support BGRX + // formats, so they must be emulated by filling the alpha channel opaque + // as if it was BGRA data. Cairo leaves the alpha zeroed out for BGRX, so + // we cause Cairo to fill it as opaque by handling the copy target as a + // BGRA surface. + dt = Factory::CreateDrawTargetForData( + BackendType::CAIRO, imageSurface->Data(), imageSurface->GetSize(), + imageSurface->Stride(), SurfaceFormat::B8G8R8A8); + } else { + // Copy helper surface content to target + dt = CreateDrawTargetForSurface(aSurface); + } + if (dt && dt->IsValid()) { + RefPtr surface = + gfxPlatform::GetSourceSurfaceForSurface(dt, renderSurface); + dt->CopySurface(surface, aRect, aRect.TopLeft()); + } else { + gfxWarning() << "PluginInstanceChild::PaintRectToSurface failure"; + } + } +} + +void PluginInstanceChild::PaintRectWithAlphaExtraction(const nsIntRect& aRect, + gfxASurface* aSurface) { + MOZ_ASSERT(aSurface->GetContentType() == gfxContentType::COLOR_ALPHA, + "Refusing to pointlessly recover alpha"); + + nsIntRect rect(aRect); + // If |aSurface| can be used to paint and can have alpha values + // recovered directly to it, do that to save a tmp surface and + // copy. + bool useSurfaceSubimageForBlack = false; + if (gfxSurfaceType::Image == aSurface->GetType()) { + gfxImageSurface* surfaceAsImage = static_cast(aSurface); + useSurfaceSubimageForBlack = + (surfaceAsImage->Format() == SurfaceFormat::A8R8G8B8_UINT32); + // If we're going to use a subimage, nudge the rect so that we + // can use optimal alpha recovery. If we're not using a + // subimage, the temporaries should automatically get + // fast-path alpha recovery so we don't need to do anything. + if (useSurfaceSubimageForBlack) { + rect = + gfxAlphaRecovery::AlignRectForSubimageRecovery(aRect, surfaceAsImage); + } + } + + RefPtr whiteImage; + RefPtr blackImage; + gfxRect targetRect(rect.x, rect.y, rect.width, rect.height); + IntSize targetSize(rect.width, rect.height); + + // We always use a temporary "white image" + whiteImage = new gfxImageSurface(targetSize, SurfaceFormat::X8R8G8B8_UINT32); + if (whiteImage->CairoStatus()) { + return; + } + +#ifdef XP_WIN + // On windows, we need an HDC and so can't paint directly to + // vanilla image surfaces. Bifurcate this painting code so that + // we don't accidentally attempt that. + if (!SharedDIBSurface::IsSharedDIBSurface(aSurface)) + MOZ_CRASH("Expected SharedDIBSurface!"); + + // Paint the plugin directly onto the target, with a white + // background and copy the result + PaintRectToSurface(rect, aSurface, DeviceColor::MaskOpaqueWhite()); + { + RefPtr dt = CreateDrawTargetForSurface(whiteImage); + RefPtr surface = + gfxPlatform::GetSourceSurfaceForSurface(dt, aSurface); + dt->CopySurface(surface, rect, IntPoint()); + } + + // Paint the plugin directly onto the target, with a black + // background + PaintRectToSurface(rect, aSurface, DeviceColor::MaskOpaqueBlack()); + + // Don't copy the result, just extract a subimage so that we can + // recover alpha directly into the target + gfxImageSurface* image = static_cast(aSurface); + blackImage = image->GetSubimage(targetRect); + +#else + gfxPoint deviceOffset = -targetRect.TopLeft(); + // Paint onto white background + whiteImage->SetDeviceOffset(deviceOffset); + PaintRectToSurface(rect, whiteImage, DeviceColor::MaskOpaqueWhite()); + + if (useSurfaceSubimageForBlack) { + gfxImageSurface* surface = static_cast(aSurface); + blackImage = surface->GetSubimage(targetRect); + } else { + blackImage = + new gfxImageSurface(targetSize, SurfaceFormat::A8R8G8B8_UINT32); + } + + // Paint onto black background + blackImage->SetDeviceOffset(deviceOffset); + PaintRectToSurface(rect, blackImage, DeviceColor::MaskOpaqueBlack()); +#endif + + MOZ_ASSERT(whiteImage && blackImage, "Didn't paint enough!"); + + // Extract alpha from black and white image and store to black + // image + if (!gfxAlphaRecovery::RecoverAlpha(blackImage, whiteImage)) { + return; + } + + // If we had to use a temporary black surface, copy the pixels + // with alpha back to the target + if (!useSurfaceSubimageForBlack) { + RefPtr dt = CreateDrawTargetForSurface(aSurface); + RefPtr surface = + gfxPlatform::GetSourceSurfaceForSurface(dt, blackImage); + dt->CopySurface(surface, IntRect(0, 0, rect.width, rect.height), + rect.TopLeft()); + } +} + +bool PluginInstanceChild::CanPaintOnBackground() { + return (mBackground && mCurrentSurface && + mCurrentSurface->GetSize() == mBackground->GetSize()); +} + +bool PluginInstanceChild::ShowPluginFrame() { + // mLayersRendering can be false if we somehow get here without + // receiving AsyncSetWindow() first. mPendingPluginCall is our + // re-entrancy guard; we can't paint while nested inside another + // paint. + if (!mLayersRendering || mPendingPluginCall) { + return false; + } + + // We should not attempt to asynchronously show the plugin if we're using + // direct rendering. + MOZ_ASSERT(!IsUsingDirectDrawing()); + + AutoRestore pending(mPendingPluginCall); + mPendingPluginCall = true; + + bool temporarilyMakeVisible = !IsVisible() && !mHasPainted; + if (temporarilyMakeVisible && mWindow.width && mWindow.height) { + mWindow.clipRect.right = mWindow.width; + mWindow.clipRect.bottom = mWindow.height; + } else if (!IsVisible()) { + // If we're not visible, don't bother painting a <0,0,0,0> + // rect. If we're eventually made visible, the visibility + // change will invalidate our window. + ClearCurrentSurface(); + return true; + } + + if (!EnsureCurrentBuffer()) { + return false; + } + +#ifdef MOZ_WIDGET_COCOA + // We can't use the thebes code with CoreAnimation so we will + // take a different code path. + if (mDrawingModel == NPDrawingModelCoreAnimation || + mDrawingModel == NPDrawingModelInvalidatingCoreAnimation || + mDrawingModel == NPDrawingModelCoreGraphics) { + if (!IsVisible()) { + return true; + } + + if (!mDoubleBufferCARenderer.HasFrontSurface()) { + NS_ERROR("CARenderer not initialized for rendering"); + return false; + } + + // Clear accRect here to be able to pass + // test_invalidate_during_plugin_paint test + nsIntRect rect = mAccumulatedInvalidRect; + mAccumulatedInvalidRect.SetEmpty(); + + // Fix up old invalidations that might have been made when our + // surface was a different size + rect.IntersectRect( + rect, nsIntRect(0, 0, mDoubleBufferCARenderer.GetFrontSurfaceWidth(), + mDoubleBufferCARenderer.GetFrontSurfaceHeight())); + + if (mDrawingModel == NPDrawingModelCoreGraphics) { + mozilla::plugins::PluginUtilsOSX::Repaint(mCGLayer, rect); + } + + mDoubleBufferCARenderer.Render(); + + NPRect r = {(uint16_t)rect.y, (uint16_t)rect.x, (uint16_t)rect.YMost(), + (uint16_t)rect.XMost()}; + SurfaceDescriptor currSurf; + currSurf = + IOSurfaceDescriptor(mDoubleBufferCARenderer.GetFrontSurfaceID(), + mDoubleBufferCARenderer.GetContentsScaleFactor()); + + mHasPainted = true; + + SurfaceDescriptor returnSurf; + + if (!SendShow(r, currSurf, &returnSurf)) { + return false; + } + + SwapSurfaces(); + return true; + } else { + NS_ERROR("Unsupported drawing model for async layer rendering"); + return false; + } +#endif + + NS_ASSERTION(mWindow.width == uint32_t(mWindow.clipRect.right - + mWindow.clipRect.left) && + mWindow.height == + uint32_t(mWindow.clipRect.bottom - mWindow.clipRect.top), + "Clip rect should be same size as window when using layers"); + + // Clear accRect here to be able to pass + // test_invalidate_during_plugin_paint test + nsIntRect rect = mAccumulatedInvalidRect; + mAccumulatedInvalidRect.SetEmpty(); + + // Fix up old invalidations that might have been made when our + // surface was a different size + IntSize surfaceSize = mCurrentSurface->GetSize(); + rect.IntersectRect(rect, + nsIntRect(0, 0, surfaceSize.width, surfaceSize.height)); + + if (!ReadbackDifferenceRect(rect)) { + // We couldn't read back the pixels that differ between the + // current surface and last, so we have to invalidate the + // entire window. + rect.SetRect(0, 0, mWindow.width, mWindow.height); + } + + bool haveTransparentPixels = + gfxContentType::COLOR_ALPHA == mCurrentSurface->GetContentType(); + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Painting%s on surface " + "", + this, haveTransparentPixels ? " with alpha" : "", rect.x, rect.y, + rect.width, rect.height, mCurrentSurface->GetSize().width, + mCurrentSurface->GetSize().height)); + + if (CanPaintOnBackground()) { + PLUGIN_LOG_DEBUG((" (on background)")); + // Source the background pixels ... + { + RefPtr surface = + mHelperSurface ? mHelperSurface : mCurrentSurface; + RefPtr dt = CreateDrawTargetForSurface(surface); + RefPtr backgroundSurface = + gfxPlatform::GetSourceSurfaceForSurface(dt, mBackground); + dt->CopySurface(backgroundSurface, rect, rect.TopLeft()); + } + // ... and hand off to the plugin + // BEWARE: mBackground may die during this call + PaintRectToSurface(rect, mCurrentSurface, DeviceColor()); + } else if (!temporarilyMakeVisible && mDoAlphaExtraction) { + // We don't want to pay the expense of alpha extraction for + // phony paints. + PLUGIN_LOG_DEBUG((" (with alpha recovery)")); + PaintRectWithAlphaExtraction(rect, mCurrentSurface); + } else { + PLUGIN_LOG_DEBUG((" (onto opaque surface)")); + + // If we're on a platform that needs helper surfaces for + // plugins, and we're forcing a throwaway paint of a + // wmode=transparent plugin, then make sure to use the helper + // surface here. + RefPtr target = (temporarilyMakeVisible && mHelperSurface) + ? mHelperSurface + : mCurrentSurface; + + PaintRectToSurface(rect, target, DeviceColor()); + } + mHasPainted = true; + + if (temporarilyMakeVisible) { + mWindow.clipRect.right = mWindow.clipRect.bottom = 0; + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Undoing temporary clipping w=, clip=", + this, mWindow.x, mWindow.y, mWindow.width, mWindow.height, + mWindow.clipRect.left, mWindow.clipRect.top, mWindow.clipRect.right, + mWindow.clipRect.bottom)); + + if (mPluginIface->setwindow) { + mPluginIface->setwindow(&mData, &mWindow); + } + + // Skip forwarding the results of the phony paint to the + // browser. We may have painted a transparent plugin using + // the opaque-plugin path, which can result in wrong pixels. + // We also don't want to pay the expense of forwarding the + // surface for plugins that might really be invisible. + mAccumulatedInvalidRect.SetRect(0, 0, mWindow.width, mWindow.height); + return true; + } + + NPRect r = {(uint16_t)rect.y, (uint16_t)rect.x, (uint16_t)rect.YMost(), + (uint16_t)rect.XMost()}; + SurfaceDescriptor currSurf; +#ifdef MOZ_X11 + if (mCurrentSurface->GetType() == gfxSurfaceType::Xlib) { + gfxXlibSurface* xsurf = static_cast(mCurrentSurface.get()); + currSurf = SurfaceDescriptorX11(xsurf); + // Need to sync all pending x-paint requests + // before giving drawable to another process + XSync(mWsInfo.display, X11False); + } else +#endif +#ifdef XP_WIN + if (SharedDIBSurface::IsSharedDIBSurface(mCurrentSurface)) { + SharedDIBSurface* s = static_cast(mCurrentSurface.get()); + if (!mCurrentSurfaceActor) { + base::SharedMemoryHandle handle = nullptr; + s->ShareToProcess(OtherPid(), &handle); + + mCurrentSurfaceActor = SendPPluginSurfaceConstructor( + handle, mCurrentSurface->GetSize(), haveTransparentPixels); + } + currSurf = mCurrentSurfaceActor; + s->Flush(); + } else +#endif + if (gfxSharedImageSurface::IsSharedImage(mCurrentSurface)) { + currSurf = std::move( + static_cast(mCurrentSurface.get())->GetShmem()); + } else { + MOZ_CRASH("Surface type is not remotable"); + return false; + } + + // Unused, except to possibly return a shmem to us + SurfaceDescriptor returnSurf; + + if (!SendShow(r, currSurf, &returnSurf)) { + return false; + } + + SwapSurfaces(); + mSurfaceDifferenceRect = rect; + return true; +} + +bool PluginInstanceChild::ReadbackDifferenceRect(const nsIntRect& rect) { + if (!mBackSurface) return false; + + // We can read safely from XSurface,SharedDIBSurface and Unsafe + // SharedMemory, because PluginHost is not able to modify that surface +#if defined(MOZ_X11) + if (mBackSurface->GetType() != gfxSurfaceType::Xlib && + !gfxSharedImageSurface::IsSharedImage(mBackSurface)) + return false; +#elif defined(XP_WIN) + if (!SharedDIBSurface::IsSharedDIBSurface(mBackSurface)) return false; +#endif + +#if defined(MOZ_X11) || defined(XP_WIN) + if (mCurrentSurface->GetContentType() != mBackSurface->GetContentType()) + return false; + + if (mSurfaceDifferenceRect.IsEmpty()) return true; + + PLUGIN_LOG_DEBUG( + ("[InstanceChild][%p] Reading back part of ", this, + mSurfaceDifferenceRect.x, mSurfaceDifferenceRect.y, + mSurfaceDifferenceRect.width, mSurfaceDifferenceRect.height)); + + // Read back previous content + RefPtr dt = CreateDrawTargetForSurface(mCurrentSurface); + RefPtr source = + gfxPlatform::GetSourceSurfaceForSurface(dt, mBackSurface); + // Subtract from mSurfaceDifferenceRect area which is overlapping with rect + nsIntRegion result; + result.Sub(mSurfaceDifferenceRect, nsIntRegion(rect)); + for (auto iter = result.RectIter(); !iter.Done(); iter.Next()) { + const nsIntRect& r = iter.Get(); + dt->CopySurface(source, r, r.TopLeft()); + } + + return true; +#else + return false; +#endif +} + +void PluginInstanceChild::InvalidateRectDelayed(void) { + if (!mCurrentInvalidateTask) { + return; + } + + mCurrentInvalidateTask = nullptr; + + // When this method is run asynchronously, we can end up switching to + // direct drawing before while we wait to run. In that case, bail. + if (IsUsingDirectDrawing()) { + return; + } + + if (mAccumulatedInvalidRect.IsEmpty()) { + return; + } + + if (!ShowPluginFrame()) { + AsyncShowPluginFrame(); + } +} + +void PluginInstanceChild::AsyncShowPluginFrame(void) { + if (mCurrentInvalidateTask) { + return; + } + + // When the plugin is using direct surfaces to draw, it is not driving + // paints via paint events - it will drive painting via its own events + // and/or DidComposite callbacks. + if (IsUsingDirectDrawing()) { + return; + } + + mCurrentInvalidateTask = NewNonOwningCancelableRunnableMethod( + "plugins::PluginInstanceChild::InvalidateRectDelayed", this, + &PluginInstanceChild::InvalidateRectDelayed); + RefPtr addrefedTask = mCurrentInvalidateTask; + MessageLoop::current()->PostTask(addrefedTask.forget()); +} + +void PluginInstanceChild::InvalidateRect(NPRect* aInvalidRect) { + NS_ASSERTION(aInvalidRect, "Null pointer!"); + +#ifdef OS_WIN + // Invalidate and draw locally for windowed plugins. + if (mWindow.type == NPWindowTypeWindow) { + NS_ASSERTION(IsWindow(mPluginWindowHWND), "Bad window?!"); + RECT rect = {aInvalidRect->left, aInvalidRect->top, aInvalidRect->right, + aInvalidRect->bottom}; + ::InvalidateRect(mPluginWindowHWND, &rect, FALSE); + return; + } +#endif + + if (IsUsingDirectDrawing()) { + NS_ASSERTION(false, + "Should not call InvalidateRect() in direct surface mode!"); + return; + } + + if (mLayersRendering) { + nsIntRect r(aInvalidRect->left, aInvalidRect->top, + aInvalidRect->right - aInvalidRect->left, + aInvalidRect->bottom - aInvalidRect->top); + + mAccumulatedInvalidRect.UnionRect(r, mAccumulatedInvalidRect); + // If we are able to paint and invalidate sent, then reset + // accumulated rectangle + AsyncShowPluginFrame(); + return; + } + + // If we were going to use layers rendering but it's not set up + // yet, and the plugin happens to call this first, we'll forward + // the invalidation to the browser. It's unclear whether + // non-layers plugins need this rect forwarded when their window + // width or height is 0, which it would be for layers plugins + // before their first SetWindow(). + SendNPN_InvalidateRect(*aInvalidRect); +} + +mozilla::ipc::IPCResult PluginInstanceChild::RecvUpdateBackground( + const SurfaceDescriptor& aBackground, const nsIntRect& aRect) { + MOZ_ASSERT(mIsTransparent, "Only transparent plugins use backgrounds"); + + if (!mBackground) { + // XXX refactor me + switch (aBackground.type()) { +#ifdef MOZ_X11 + case SurfaceDescriptor::TSurfaceDescriptorX11: { + mBackground = aBackground.get_SurfaceDescriptorX11().OpenForeign(); + break; + } +#endif + case SurfaceDescriptor::TShmem: { + mBackground = gfxSharedImageSurface::Open(aBackground.get_Shmem()); + break; + } + default: + MOZ_CRASH("Unexpected background surface descriptor"); + } + + if (!mBackground) { + return IPC_FAIL_NO_REASON(this); + } + + IntSize bgSize = mBackground->GetSize(); + mAccumulatedInvalidRect.UnionRect( + mAccumulatedInvalidRect, nsIntRect(0, 0, bgSize.width, bgSize.height)); + AsyncShowPluginFrame(); + return IPC_OK(); + } + + // XXX refactor me + mAccumulatedInvalidRect.UnionRect(aRect, mAccumulatedInvalidRect); + + // This must be asynchronous, because we may be nested within RPC messages + // which do not expect to receiving paint events. + AsyncShowPluginFrame(); + + return IPC_OK(); +} + +PPluginBackgroundDestroyerChild* +PluginInstanceChild::AllocPPluginBackgroundDestroyerChild() { + return new PluginBackgroundDestroyerChild(); +} + +mozilla::ipc::IPCResult +PluginInstanceChild::RecvPPluginBackgroundDestroyerConstructor( + PPluginBackgroundDestroyerChild* aActor) { + // Our background changed, so we have to invalidate the area + // painted with the old background. If the background was + // destroyed because we have a new background, then we expect to + // be notified of that "soon", before processing the asynchronous + // invalidation here. If we're *not* getting a new background, + // our current front surface is stale and we want to repaint + // "soon" so that we can hand the browser back a surface with + // alpha values. (We should be notified of that invalidation soon + // too, but we don't assume that here.) + if (mBackground) { + IntSize bgsize = mBackground->GetSize(); + mAccumulatedInvalidRect.UnionRect( + nsIntRect(0, 0, bgsize.width, bgsize.height), mAccumulatedInvalidRect); + + // NB: we don't have to XSync here because only ShowPluginFrame() + // uses mBackground, and it always XSyncs after finishing. + mBackground = nullptr; + AsyncShowPluginFrame(); + } + + if (!PPluginBackgroundDestroyerChild::Send__delete__(aActor)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool PluginInstanceChild::DeallocPPluginBackgroundDestroyerChild( + PPluginBackgroundDestroyerChild* aActor) { + delete aActor; + return true; +} + +uint32_t PluginInstanceChild::ScheduleTimer(uint32_t interval, bool repeat, + TimerFunc func) { + auto* t = new ChildTimer(this, interval, repeat, func); + if (0 == t->ID()) { + delete t; + return 0; + } + + mTimers.AppendElement(t); + return t->ID(); +} + +void PluginInstanceChild::UnscheduleTimer(uint32_t id) { + if (0 == id) return; + + mTimers.RemoveElement(id, ChildTimer::IDComparator()); +} + +void PluginInstanceChild::SwapSurfaces() { + RefPtr tmpsurf = mCurrentSurface; +#ifdef XP_WIN + PPluginSurfaceChild* tmpactor = mCurrentSurfaceActor; +#endif + + mCurrentSurface = mBackSurface; +#ifdef XP_WIN + mCurrentSurfaceActor = mBackSurfaceActor; +#endif + + mBackSurface = tmpsurf; +#ifdef XP_WIN + mBackSurfaceActor = tmpactor; +#endif + +#ifdef MOZ_WIDGET_COCOA + mDoubleBufferCARenderer.SwapSurfaces(); + + // Outdated back surface... not usable anymore due to changed plugin size. + // Dropping obsolete surface + if (mDoubleBufferCARenderer.HasFrontSurface() && + mDoubleBufferCARenderer.HasBackSurface() && + (mDoubleBufferCARenderer.GetFrontSurfaceWidth() != + mDoubleBufferCARenderer.GetBackSurfaceWidth() || + mDoubleBufferCARenderer.GetFrontSurfaceHeight() != + mDoubleBufferCARenderer.GetBackSurfaceHeight() || + mDoubleBufferCARenderer.GetFrontSurfaceContentsScaleFactor() != + mDoubleBufferCARenderer.GetBackSurfaceContentsScaleFactor())) { + mDoubleBufferCARenderer.ClearFrontSurface(); + } +#else + if (mCurrentSurface && mBackSurface && + (mCurrentSurface->GetSize() != mBackSurface->GetSize() || + mCurrentSurface->GetContentType() != mBackSurface->GetContentType())) { + ClearCurrentSurface(); + } +#endif +} + +void PluginInstanceChild::ClearCurrentSurface() { + mCurrentSurface = nullptr; +#ifdef MOZ_WIDGET_COCOA + if (mDoubleBufferCARenderer.HasFrontSurface()) { + mDoubleBufferCARenderer.ClearFrontSurface(); + } +#endif +#ifdef XP_WIN + if (mCurrentSurfaceActor) { + PPluginSurfaceChild::Send__delete__(mCurrentSurfaceActor); + mCurrentSurfaceActor = nullptr; + } +#endif + mHelperSurface = nullptr; +} + +void PluginInstanceChild::ClearAllSurfaces() { + if (mBackSurface) { + // Get last surface back, and drop it + SurfaceDescriptor temp = null_t(); + NPRect r = {0, 0, 1, 1}; + SendShow(r, temp, &temp); + } + + if (gfxSharedImageSurface::IsSharedImage(mCurrentSurface)) + DeallocShmem( + static_cast(mCurrentSurface.get())->GetShmem()); + if (gfxSharedImageSurface::IsSharedImage(mBackSurface)) + DeallocShmem( + static_cast(mBackSurface.get())->GetShmem()); + mCurrentSurface = nullptr; + mBackSurface = nullptr; + +#ifdef XP_WIN + if (mCurrentSurfaceActor) { + PPluginSurfaceChild::Send__delete__(mCurrentSurfaceActor); + mCurrentSurfaceActor = nullptr; + } + if (mBackSurfaceActor) { + PPluginSurfaceChild::Send__delete__(mBackSurfaceActor); + mBackSurfaceActor = nullptr; + } +#endif + +#ifdef MOZ_WIDGET_COCOA + if (mDoubleBufferCARenderer.HasBackSurface()) { + // Get last surface back, and drop it + SurfaceDescriptor temp = null_t(); + NPRect r = {0, 0, 1, 1}; + SendShow(r, temp, &temp); + } + + if (mCGLayer) { + mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(mCGLayer); + mCGLayer = nullptr; + } + + mDoubleBufferCARenderer.ClearFrontSurface(); + mDoubleBufferCARenderer.ClearBackSurface(); +#endif +} + +static void InvalidateObjects(nsTHashtable& aEntries) { + for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) { + DeletingObjectEntry* e = iter.Get(); + NPObject* o = e->GetKey(); + if (!e->mDeleted && o->_class && o->_class->invalidate) { + o->_class->invalidate(o); + } + } +} + +static void DeleteObjects(nsTHashtable& aEntries) { + for (auto iter = aEntries.Iter(); !iter.Done(); iter.Next()) { + DeletingObjectEntry* e = iter.Get(); + NPObject* o = e->GetKey(); + if (!e->mDeleted) { + e->mDeleted = true; + +#ifdef NS_BUILD_REFCNT_LOGGING + { + int32_t refcnt = o->referenceCount; + while (refcnt) { + --refcnt; + NS_LOG_RELEASE(o, refcnt, "NPObject"); + } + } +#endif + + PluginModuleChild::DeallocNPObject(o); + } + } +} + +void PluginInstanceChild::Destroy() { + if (mDestroyed) { + return; + } + if (mStackDepth != 0) { + MOZ_CRASH("Destroying plugin instance on the stack."); + } + mDestroyed = true; + +#if defined(OS_WIN) + SetProp(mPluginWindowHWND, kPluginIgnoreSubclassProperty, (HANDLE)1); +#endif + + nsTArray streams; + ManagedPBrowserStreamChild(streams); + + // First make sure none of these streams become deleted + streams.RemoveElementsBy([](const auto& stream) { + return !static_cast(stream)->InstanceDying(); + }); + for (uint32_t i = 0; i < streams.Length(); ++i) + static_cast(streams[i])->FinishDelivery(); + + mTimers.Clear(); + + // NPP_Destroy() should be a synchronization point for plugin threads + // calling NPN_AsyncCall: after this function returns, they are no longer + // allowed to make async calls on this instance. + static_cast(Manager())->NPP_Destroy(this); + mData.ndata = 0; + + if (mCurrentInvalidateTask) { + mCurrentInvalidateTask->Cancel(); + mCurrentInvalidateTask = nullptr; + } + if (mCurrentAsyncSetWindowTask) { + mCurrentAsyncSetWindowTask->Cancel(); + mCurrentAsyncSetWindowTask = nullptr; + } + { + MutexAutoLock autoLock(mAsyncInvalidateMutex); + if (mAsyncInvalidateTask) { + mAsyncInvalidateTask->Cancel(); + mAsyncInvalidateTask = nullptr; + } + } + + ClearAllSurfaces(); + mDirectBitmaps.Clear(); + + mDeletingHash = MakeUnique>(); + PluginScriptableObjectChild::NotifyOfInstanceShutdown(this); + + InvalidateObjects(*mDeletingHash); + DeleteObjects(*mDeletingHash); + + // Null out our cached actors as they should have been killed in the + // PluginInstanceDestroyed call above. + mCachedWindowActor = nullptr; + mCachedElementActor = nullptr; + +#if defined(OS_WIN) + DestroyWinlessPopupSurrogate(); + UnhookWinlessFlashThrottle(); + DestroyPluginWindow(); + + for (uint32_t i = 0; i < mPendingFlashThrottleMsgs.Length(); ++i) { + mPendingFlashThrottleMsgs[i]->Cancel(); + } + mPendingFlashThrottleMsgs.Clear(); +#endif +} + +mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_Destroy( + NPError* aResult) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + *aResult = NPERR_NO_ERROR; + + Destroy(); + + return IPC_OK(); +} + +void PluginInstanceChild::ActorDestroy(ActorDestroyReason why) { +#ifdef XP_WIN + // ClearAllSurfaces() should not try to send anything after ActorDestroy. + mCurrentSurfaceActor = nullptr; + mBackSurfaceActor = nullptr; +#endif + + Destroy(); +} diff --git a/dom/plugins/ipc/PluginInstanceChild.h b/dom/plugins/ipc/PluginInstanceChild.h new file mode 100644 index 0000000000..479c060f91 --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceChild.h @@ -0,0 +1,613 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginInstanceChild_h +#define dom_plugins_PluginInstanceChild_h 1 + +#include "mozilla/EventForwards.h" +#include "mozilla/plugins/PPluginInstanceChild.h" +#include "mozilla/plugins/PluginScriptableObjectChild.h" +#include "mozilla/plugins/StreamNotifyChild.h" +#include "mozilla/plugins/PPluginSurfaceChild.h" +#include "mozilla/ipc/CrossProcessMutex.h" +#include "nsRefPtrHashtable.h" +#if defined(OS_WIN) +# include "mozilla/gfx/SharedDIBWin.h" +#elif defined(MOZ_WIDGET_COCOA) +# include "PluginUtilsOSX.h" +# include "mozilla/gfx/QuartzSupport.h" +# include "base/timer.h" + +#endif + +#include "npfunctions.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" +#include "ChildTimer.h" +#include "nsRect.h" +#include "nsTHashtable.h" +#include "mozilla/PaintTracker.h" +#include "mozilla/gfx/Types.h" + +#include + +class gfxASurface; + +namespace mozilla { +namespace plugins { + +class PBrowserStreamChild; +class BrowserStreamChild; +class StreamNotifyChild; + +class PluginInstanceChild : public PPluginInstanceChild { + friend class BrowserStreamChild; + friend class PluginStreamChild; + friend class StreamNotifyChild; + friend class PluginScriptableObjectChild; + friend class PPluginInstanceChild; + +#ifdef OS_WIN + friend LRESULT CALLBACK PluginWindowProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK PluginWindowProcInternal(HWND hWnd, UINT message, + WPARAM wParam, + LPARAM lParam); +#endif + + protected: + mozilla::ipc::IPCResult AnswerCreateChildPluginWindow( + NativeWindowHandle* aChildPluginWindow); + + mozilla::ipc::IPCResult RecvCreateChildPopupSurrogate( + const NativeWindowHandle& aNetscapeWindow); + + mozilla::ipc::IPCResult AnswerNPP_SetWindow(const NPRemoteWindow& window); + + mozilla::ipc::IPCResult AnswerNPP_GetValue_NPPVpluginWantsAllNetworkStreams( + bool* wantsAllStreams, NPError* rv); + mozilla::ipc::IPCResult AnswerNPP_GetValue_NPPVpluginScriptableNPObject( + PPluginScriptableObjectChild** value, NPError* result); + mozilla::ipc::IPCResult + AnswerNPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId(nsCString* aPlugId, + NPError* aResult); + mozilla::ipc::IPCResult AnswerNPP_SetValue_NPNVprivateModeBool( + const bool& value, NPError* result); + mozilla::ipc::IPCResult AnswerNPP_SetValue_NPNVmuteAudioBool( + const bool& value, NPError* result); + mozilla::ipc::IPCResult AnswerNPP_SetValue_NPNVCSSZoomFactor( + const double& value, NPError* result); + + mozilla::ipc::IPCResult AnswerNPP_HandleEvent(const NPRemoteEvent& event, + int16_t* handled); + mozilla::ipc::IPCResult AnswerNPP_HandleEvent_Shmem( + const NPRemoteEvent& event, Shmem&& mem, int16_t* handled, Shmem* rtnmem); + mozilla::ipc::IPCResult AnswerNPP_HandleEvent_IOSurface( + const NPRemoteEvent& event, const uint32_t& surface, int16_t* handled); + + // Async rendering + mozilla::ipc::IPCResult RecvAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow); + + virtual void DoAsyncSetWindow(const gfxSurfaceType& aSurfaceType, + const NPRemoteWindow& aWindow, bool aIsAsync); + + PPluginSurfaceChild* AllocPPluginSurfaceChild( + const WindowsSharedMemoryHandle&, const gfx::IntSize&, const bool&) { + return new PPluginSurfaceChild(); + } + + bool DeallocPPluginSurfaceChild(PPluginSurfaceChild* s) { + delete s; + return true; + } + + mozilla::ipc::IPCResult AnswerPaint(const NPRemoteEvent& event, + int16_t* handled) { + PaintTracker pt; + if (!AnswerNPP_HandleEvent(event, handled)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); + } + + mozilla::ipc::IPCResult RecvWindowPosChanged(const NPRemoteEvent& event); + + mozilla::ipc::IPCResult RecvContentsScaleFactorChanged( + const double& aContentsScaleFactor); + + mozilla::ipc::IPCResult AnswerNPP_Destroy(NPError* result); + + PPluginScriptableObjectChild* AllocPPluginScriptableObjectChild(); + + bool DeallocPPluginScriptableObjectChild( + PPluginScriptableObjectChild* aObject); + + virtual mozilla::ipc::IPCResult RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectChild* aActor) override; + + virtual mozilla::ipc::IPCResult RecvPBrowserStreamConstructor( + PBrowserStreamChild* aActor, const nsCString& aURL, + const uint32_t& aLength, const uint32_t& aLastmodified, + PStreamNotifyChild* aNotifyData, const nsCString& aHeaders) override; + + mozilla::ipc::IPCResult AnswerNPP_NewStream(PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, NPError* rv, + uint16_t* stype); + + PBrowserStreamChild* AllocPBrowserStreamChild(const nsCString& url, + const uint32_t& length, + const uint32_t& lastmodified, + PStreamNotifyChild* notifyData, + const nsCString& headers); + + bool DeallocPBrowserStreamChild(PBrowserStreamChild* stream); + + PStreamNotifyChild* AllocPStreamNotifyChild( + const nsCString& url, const nsCString& target, const bool& post, + const nsCString& buffer, const bool& file, NPError* result); + + bool DeallocPStreamNotifyChild(PStreamNotifyChild* notifyData); + + mozilla::ipc::IPCResult AnswerSetPluginFocus(); + + mozilla::ipc::IPCResult AnswerUpdateWindow(); + + mozilla::ipc::IPCResult RecvNPP_DidComposite(); + + public: + PluginInstanceChild(const NPPluginFuncs* aPluginIface, + const nsCString& aMimeType, + const nsTArray& aNames, + const nsTArray& aValues); + + virtual ~PluginInstanceChild(); + + NPError DoNPP_New(); + + // Common sync+async implementation of NPP_NewStream + NPError DoNPP_NewStream(BrowserStreamChild* actor, const nsCString& mimeType, + const bool& seekable, uint16_t* stype); + + bool Initialize(); + + NPP GetNPP() { return &mData; } + + NPError NPN_GetValue(NPNVariable aVariable, void* aValue); + + NPError NPN_SetValue(NPPVariable aVariable, void* aValue); + + PluginScriptableObjectChild* GetActorForNPObject(NPObject* aObject); + + NPError NPN_NewStream(NPMIMEType aMIMEType, const char* aWindow, + NPStream** aStream); + + void InvalidateRect(NPRect* aInvalidRect); + +#ifdef MOZ_WIDGET_COCOA + void Invalidate(); +#endif // definied(MOZ_WIDGET_COCOA) + + uint32_t ScheduleTimer(uint32_t interval, bool repeat, TimerFunc func); + void UnscheduleTimer(uint32_t id); + + int GetQuirks(); + + void NPN_URLRedirectResponse(void* notifyData, NPBool allow); + + NPError NPN_InitAsyncSurface(NPSize* size, NPImageFormat format, + void* initData, NPAsyncSurface* surface); + NPError NPN_FinalizeAsyncSurface(NPAsyncSurface* surface); + + void NPN_SetCurrentAsyncSurface(NPAsyncSurface* surface, NPRect* changed); + + void DoAsyncRedraw(); + + mozilla::ipc::IPCResult RecvHandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, const bool& aIsConsumed); + +#if defined(XP_WIN) + NPError DefaultAudioDeviceChanged(NPAudioDeviceChangeDetails& details); + NPError AudioDeviceStateChanged(NPAudioDeviceStateChanged& aDeviceState); +#endif + + private: + friend class PluginModuleChild; + + NPError InternalGetNPObjectForValue(NPNVariable aValue, NPObject** aObject); + + bool IsUsingDirectDrawing(); + + mozilla::ipc::IPCResult RecvUpdateBackground( + const SurfaceDescriptor& aBackground, const nsIntRect& aRect); + + PPluginBackgroundDestroyerChild* AllocPPluginBackgroundDestroyerChild(); + + mozilla::ipc::IPCResult RecvPPluginBackgroundDestroyerConstructor( + PPluginBackgroundDestroyerChild* aActor) override; + + bool DeallocPPluginBackgroundDestroyerChild( + PPluginBackgroundDestroyerChild* aActor); + +#if defined(OS_WIN) + static bool RegisterWindowClass(); + bool CreatePluginWindow(); + void DestroyPluginWindow(); + void SizePluginWindow(int width, int height); + int16_t WinlessHandleEvent(NPEvent& event); + void CreateWinlessPopupSurrogate(); + void DestroyWinlessPopupSurrogate(); + void InitPopupMenuHook(); + void SetupFlashMsgThrottle(); + void UnhookWinlessFlashThrottle(); + void HookSetWindowLongPtr(); + void InitImm32Hook(); + static inline bool SetWindowLongHookCheck(HWND hWnd, int nIndex, + LONG_PTR newLong); + void FlashThrottleMessage(HWND, UINT, WPARAM, LPARAM, bool); + static LRESULT CALLBACK DummyWindowProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK PluginWindowProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam); + static BOOL WINAPI TrackPopupHookProc(HMENU hMenu, UINT uFlags, int x, int y, + int nReserved, HWND hWnd, + CONST RECT* prcRect); + static BOOL CALLBACK EnumThreadWindowsCallback(HWND hWnd, LPARAM aParam); + static LRESULT CALLBACK WinlessHiddenFlashWndProc(HWND hWnd, UINT message, + WPARAM wParam, + LPARAM lParam); +# ifdef _WIN64 + static LONG_PTR WINAPI SetWindowLongPtrAHook(HWND hWnd, int nIndex, + LONG_PTR newLong); + static LONG_PTR WINAPI SetWindowLongPtrWHook(HWND hWnd, int nIndex, + LONG_PTR newLong); + +# else + static LONG WINAPI SetWindowLongAHook(HWND hWnd, int nIndex, LONG newLong); + static LONG WINAPI SetWindowLongWHook(HWND hWnd, int nIndex, LONG newLong); +# endif + + static HIMC WINAPI ImmGetContextProc(HWND aWND); + static LONG WINAPI ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex, + LPVOID aBuf, DWORD aLen); + static BOOL WINAPI ImmSetCandidateWindowProc(HIMC hIMC, + LPCANDIDATEFORM plCandidate); + static BOOL WINAPI ImmNotifyIME(HIMC aIMC, DWORD aAction, DWORD aIndex, + DWORD aValue); + static BOOL WINAPI ImmAssociateContextExProc(HWND hWnd, HIMC aIMC, + DWORD dwFlags); + + class FlashThrottleMsg : public CancelableRunnable { + public: + FlashThrottleMsg(PluginInstanceChild* aInstance, HWND aWnd, UINT aMsg, + WPARAM aWParam, LPARAM aLParam, bool isWindowed) + : CancelableRunnable("FlashThrottleMsg"), + mInstance(aInstance), + mWnd(aWnd), + mMsg(aMsg), + mWParam(aWParam), + mLParam(aLParam), + mWindowed(isWindowed) {} + + NS_IMETHOD Run() override; + nsresult Cancel() override; + + WNDPROC GetProc(); + HWND GetWnd() { return mWnd; } + UINT GetMsg() { return mMsg; } + WPARAM GetWParam() { return mWParam; } + LPARAM GetLParam() { return mLParam; } + + private: + PluginInstanceChild* mInstance; + HWND mWnd; + UINT mMsg; + WPARAM mWParam; + LPARAM mLParam; + bool mWindowed; + }; + + bool ShouldPostKeyMessage(UINT message, WPARAM wParam, LPARAM lParam); + bool MaybePostKeyMessage(UINT message, WPARAM wParam, LPARAM lParam); +#endif // #if defined(OS_WIN) + const NPPluginFuncs* mPluginIface; + nsCString mMimeType; + nsTArray mNames; + nsTArray mValues; + NPP_t mData; + NPWindow mWindow; +#if defined(XP_DARWIN) || defined(XP_WIN) + double mContentsScaleFactor; +#endif + double mCSSZoomFactor; + uint32_t mPostingKeyEvents; + uint32_t mPostingKeyEventsOutdated; + int16_t mDrawingModel; + + NPAsyncSurface* mCurrentDirectSurface; + + // The surface hashtables below serve a few purposes. They let us verify + // and retain extra information about plugin surfaces, and they let us + // free shared memory that the plugin might forget to release. + struct DirectBitmap { + DirectBitmap(PluginInstanceChild* aOwner, const Shmem& shmem, + const gfx::IntSize& size, uint32_t stride, + SurfaceFormat format); + + private: + ~DirectBitmap(); + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DirectBitmap); + + PluginInstanceChild* mOwner; + Shmem mShmem; + gfx::SurfaceFormat mFormat; + gfx::IntSize mSize; + uint32_t mStride; + }; + nsRefPtrHashtable, DirectBitmap> mDirectBitmaps; + +#if defined(XP_WIN) + nsDataHashtable, WindowsHandle> mDxgiSurfaces; +#endif + + mozilla::Mutex mAsyncInvalidateMutex; + CancelableRunnable* mAsyncInvalidateTask; + + // Cached scriptable actors to avoid IPC churn + PluginScriptableObjectChild* mCachedWindowActor; + PluginScriptableObjectChild* mCachedElementActor; + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + NPSetWindowCallbackStruct mWsInfo; +#elif defined(OS_WIN) + HWND mPluginWindowHWND; + WNDPROC mPluginWndProc; + HWND mPluginParentHWND; + int mNestedEventLevelDepth; + HWND mCachedWinlessPluginHWND; + HWND mWinlessPopupSurrogateHWND; + nsIntPoint mPluginSize; + WNDPROC mWinlessThrottleOldWndProc; + HWND mWinlessHiddenMsgHWND; +#endif + +#if defined(OS_WIN) + nsTArray mPendingFlashThrottleMsgs; +#endif + nsTArray > mTimers; + + /** + * During destruction we enumerate all remaining scriptable objects and + * invalidate/delete them. Enumeration can re-enter, so maintain a + * hash separate from PluginModuleChild.mObjectMap. + */ + UniquePtr > mDeletingHash; + +#if defined(MOZ_WIDGET_COCOA) + private: +# if defined(__i386__) + NPEventModel mEventModel; +# endif + CGColorSpaceRef mShColorSpace; + CGContextRef mShContext; + RefPtr mCARenderer; + void* mCGLayer; + + // Core Animation drawing model requires a refresh timer. + uint32_t mCARefreshTimer; + + public: + const NPCocoaEvent* getCurrentEvent() { return mCurrentEvent; } + + bool CGDraw(CGContextRef ref, nsIntRect aUpdateRect); + +# if defined(__i386__) + NPEventModel EventModel() { return mEventModel; } +# endif + + private: + const NPCocoaEvent* mCurrentEvent; +#endif + + bool CanPaintOnBackground(); + + bool IsVisible() { +#ifdef XP_MACOSX + return mWindow.clipRect.top != mWindow.clipRect.bottom && + mWindow.clipRect.left != mWindow.clipRect.right; +#else + return mWindow.clipRect.top != 0 || mWindow.clipRect.left != 0 || + mWindow.clipRect.bottom != 0 || mWindow.clipRect.right != 0; +#endif + } + + // ShowPluginFrame - in general does four things: + // 1) Create mCurrentSurface optimized for rendering to parent process + // 2) Updated mCurrentSurface to be a complete copy of mBackSurface + // 3) Draw the invalidated plugin area into mCurrentSurface + // 4) Send it to parent process. + bool ShowPluginFrame(void); + + // If we can read back safely from mBackSurface, copy + // mSurfaceDifferenceRect from mBackSurface to mFrontSurface. + // @return Whether the back surface could be read. + bool ReadbackDifferenceRect(const nsIntRect& rect); + + // Post ShowPluginFrame task + void AsyncShowPluginFrame(void); + + // In the PaintRect functions, aSurface is the size of the full plugin + // window. Each PaintRect function renders into the subrectangle aRect of + // aSurface (possibly more if we're working around a Flash bug). + + // Paint plugin content rectangle to surface with bg color filling + void PaintRectToSurface(const nsIntRect& aRect, gfxASurface* aSurface, + const gfx::DeviceColor& aColor); + + // Render plugin content to surface using + // white/black image alpha extraction algorithm + void PaintRectWithAlphaExtraction(const nsIntRect& aRect, + gfxASurface* aSurface); + + // Call plugin NPAPI function to render plugin content to surface + // @param - aSurface - should be compatible with current platform plugin + // rendering + // @return - FALSE if plugin not painted to surface + void PaintRectToPlatformSurface(const nsIntRect& aRect, + gfxASurface* aSurface); + + // Update NPWindow platform attributes and call plugin "setwindow" + // @param - aForceSetWindow - call setwindow even if platform attributes are + // the same + void UpdateWindowAttributes(bool aForceSetWindow = false); + + // Create optimized mCurrentSurface for parent process rendering + // @return FALSE if optimized surface not created + bool CreateOptSurface(void); + + // Create mHelperSurface if mCurrentSurface non compatible with plugins + // @return TRUE if helper surface created successfully, or not needed + bool MaybeCreatePlatformHelperSurface(void); + + // Make sure that we have surface for rendering + bool EnsureCurrentBuffer(void); + + // Helper function for delayed InvalidateRect call + // non null mCurrentInvalidateTask will call this function + void InvalidateRectDelayed(void); + + // Clear mCurrentSurface/mCurrentSurfaceActor/mHelperSurface + void ClearCurrentSurface(); + + // Swap mCurrentSurface/mBackSurface and their associated actors + void SwapSurfaces(); + + // Clear all surfaces in response to NPP_Destroy + void ClearAllSurfaces(); + + void Destroy(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + // Set as true when SetupLayer called + // and go with different path in InvalidateRect function + bool mLayersRendering; + + // Current surface available for rendering + RefPtr mCurrentSurface; + + // Back surface, just keeping reference to + // surface which is on ParentProcess side + RefPtr mBackSurface; + +#ifdef XP_MACOSX + // Current IOSurface available for rendering + // We can't use thebes gfxASurface like other platforms. + PluginUtilsOSX::nsDoubleBufferCARenderer mDoubleBufferCARenderer; +#endif + + // (Not to be confused with mBackSurface). This is a recent copy + // of the opaque pixels under our object frame, if + // |mIsTransparent|. We ask the plugin render directly onto a + // copy of the background pixels if available, and fall back on + // alpha recovery otherwise. + RefPtr mBackground; + +#ifdef XP_WIN + // These actors mirror mCurrentSurface/mBackSurface + PPluginSurfaceChild* mCurrentSurfaceActor; + PPluginSurfaceChild* mBackSurfaceActor; +#endif + + // Accumulated invalidate rect, while back buffer is not accessible, + // in plugin coordinates. + nsIntRect mAccumulatedInvalidRect; + + // Plugin only call SetTransparent + // and does not remember their transparent state + // and p->getvalue return always false + bool mIsTransparent; + + // Surface type optimized of parent process + gfxSurfaceType mSurfaceType; + + // Keep InvalidateRect task pointer to be able Cancel it on Destroy + RefPtr mCurrentInvalidateTask; + + // Keep AsyncSetWindow task pointer to be able to Cancel it on Destroy + RefPtr mCurrentAsyncSetWindowTask; + + // True while plugin-child in plugin call + // Use to prevent plugin paint re-enter + bool mPendingPluginCall; + + // On some platforms, plugins may not support rendering to a surface with + // alpha, or not support rendering to an image surface. + // In those cases we need to draw to a temporary platform surface; we cache + // that surface here. + RefPtr mHelperSurface; + + // true when plugin does not support painting to ARGB32 + // surface this is false if plugin supports + // NPPVpluginTransparentAlphaBool (which is not part of + // NPAPI yet) + bool mDoAlphaExtraction; + + // true when the plugin has painted at least once. We use this to ensure + // that we ask a plugin to paint at least once even if it's invisible; + // some plugin (instances) rely on this in order to work properly. + bool mHasPainted; + + // Cached rectangle rendered to previous surface(mBackSurface) + // Used for reading back to current surface and syncing data, + // in plugin coordinates. + nsIntRect mSurfaceDifferenceRect; + + // Has this instance been destroyed, either by ActorDestroy or NPP_Destroy? + bool mDestroyed; + +#ifdef XP_WIN + // WM_*CHAR messages are never consumed by chrome process's widget. + // So, if preceding keydown or keyup event is consumed by reserved + // shortcut key in the chrome process, we shouldn't send the following + // WM_*CHAR messages to the plugin. + bool mLastKeyEventConsumed; + + // Store the last IME state by ImmAssociateContextEx. This will reset by + // WM_KILLFOCUS; + bool mLastEnableIMEState; +#endif // #ifdef XP_WIN + + // While IME in the process has composition, this is set to true. + // Otherwise, false. + static bool sIsIMEComposing; + + // A counter is incremented by AutoStackHelper to indicate that there is an + // active plugin call which should be preventing shutdown. + public: + class AutoStackHelper { + public: + explicit AutoStackHelper(PluginInstanceChild* instance) + : mInstance(instance) { + ++mInstance->mStackDepth; + } + ~AutoStackHelper() { --mInstance->mStackDepth; } + + private: + PluginInstanceChild* const mInstance; + }; + + private: + int32_t mStackDepth; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // ifndef dom_plugins_PluginInstanceChild_h diff --git a/dom/plugins/ipc/PluginInstanceParent.cpp b/dom/plugins/ipc/PluginInstanceParent.cpp new file mode 100644 index 0000000000..6e29171d91 --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -0,0 +1,2326 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" +#include // for intptr_t + +#include "mozilla/BasicEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ToString.h" +#include "mozilla/dom/Element.h" +#include "PluginInstanceParent.h" +#include "BrowserStreamParent.h" +#include "PluginBackgroundDestroyer.h" +#include "PluginModuleParent.h" +#include "StreamNotifyParent.h" +#include "npfunctions.h" +#include "gfxASurface.h" +#include "gfxContext.h" +#include "gfxPlatform.h" +#include "gfxSharedImageSurface.h" +#include "nsNetUtil.h" +#include "nsNPAPIPluginInstance.h" +#include "nsPluginInstanceOwner.h" +#include "nsFocusManager.h" +#ifdef MOZ_X11 +# include "gfxXlibSurface.h" +#endif +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "Layers.h" +#include "ImageContainer.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "mozilla/layers/TextureWrapperImage.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/layers/ImageBridgeChild.h" +#if defined(XP_WIN) +# include "mozilla/layers/D3D11ShareHandleImage.h" +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/TextureD3D11.h" +#endif + +#ifdef XP_MACOSX +# include "MacIOSurfaceImage.h" +#endif + +#if defined(OS_WIN) +# include +# include "gfxWindowsPlatform.h" +# include "mozilla/plugins/PluginSurfaceParent.h" +# include "nsClassHashtable.h" +# include "nsHashKeys.h" +# include "nsIWidget.h" +# include "nsPluginNativeWindow.h" +# include "PluginQuirks.h" +# include "mozilla/layers/CompositorBridgeChild.h" +# include "GPUVideoImage.h" +# include "mozilla/layers/SynchronousTask.h" +extern const wchar_t* kFlashFullscreenClass; +#elif defined(MOZ_WIDGET_GTK) +# include "mozilla/dom/ContentChild.h" +# include +#elif defined(XP_MACOSX) +# include +#endif // defined(XP_MACOSX) + +using namespace mozilla::plugins; +using namespace mozilla::layers; +using namespace mozilla::gl; + +void StreamNotifyParent::ActorDestroy(ActorDestroyReason aWhy) { + // Implement me! Bug 1005162 +} + +mozilla::ipc::IPCResult StreamNotifyParent::RecvRedirectNotifyResponse( + const bool& allow) { + PluginInstanceParent* instance = + static_cast(Manager()); + instance->mNPNIface->urlredirectresponse(instance->mNPP, this, + static_cast(allow)); + return IPC_OK(); +} + +#if defined(XP_WIN) +namespace mozilla { +namespace plugins { +/** + * e10s specific, used in cross referencing hwnds with plugin instances so we + * can access methods here from PluginWidgetChild. + */ +static nsClassHashtable* + sPluginInstanceList; + +// static +PluginInstanceParent* PluginInstanceParent::LookupPluginInstanceByID( + uintptr_t aId) { + MOZ_ASSERT(NS_IsMainThread()); + if (sPluginInstanceList) { + return sPluginInstanceList->Get((void*)aId); + } + return nullptr; +} +} // namespace plugins +} // namespace mozilla +#endif + +PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent, NPP npp, + const nsCString& aMimeType, + const NPNetscapeFuncs* npniface) + : mParent(parent), + mNPP(npp), + mNPNIface(npniface), + mWindowType(NPWindowTypeWindow), + mDrawingModel(kDefaultDrawingModel), + mLastRecordedDrawingModel(-1), + mFrameID(0) +#if defined(OS_WIN) + , + mPluginHWND(nullptr), + mChildPluginHWND(nullptr), + mChildPluginsParentHWND(nullptr), + mPluginWndProc(nullptr) +#endif // defined(XP_WIN) +#if defined(XP_MACOSX) + , + mShWidth(0), + mShHeight(0), + mShColorSpace(nullptr) +#endif +{ +#if defined(OS_WIN) + if (!sPluginInstanceList) { + sPluginInstanceList = + new nsClassHashtable(); + } +#endif +} + +PluginInstanceParent::~PluginInstanceParent() { + if (mNPP) mNPP->pdata = nullptr; + +#if defined(OS_WIN) + NS_ASSERTION(!(mPluginHWND || mPluginWndProc), + "Subclass was not reset correctly before the dtor was reached!"); +#endif +#if defined(MOZ_WIDGET_COCOA) + if (mShWidth != 0 && mShHeight != 0) { + DeallocShmem(mShSurface); + } + if (mShColorSpace) ::CGColorSpaceRelease(mShColorSpace); +#endif +} + +bool PluginInstanceParent::InitMetadata(const nsACString& aMimeType, + const nsACString& aSrcAttribute) { + if (aSrcAttribute.IsEmpty()) { + return false; + } + // Ensure that the src attribute is absolute + RefPtr owner = GetOwner(); + if (!owner) { + return false; + } + return NS_SUCCEEDED( + NS_MakeAbsoluteURI(mSrcAttribute, aSrcAttribute, owner->GetBaseURI())); +} + +void PluginInstanceParent::ActorDestroy(ActorDestroyReason why) { +#if defined(OS_WIN) + if (why == AbnormalShutdown) { + // If the plugin process crashes, this is the only + // chance we get to destroy resources. + UnsubclassPluginWindow(); + } +#endif + if (mFrontSurface) { + mFrontSurface = nullptr; + if (mImageContainer) { + mImageContainer->ClearAllImages(); + } +#ifdef MOZ_X11 + FinishX(DefaultXDisplay()); +#endif + } + if (IsUsingDirectDrawing() && mImageContainer) { + mImageContainer->ClearAllImages(); + } +} + +NPError PluginInstanceParent::Destroy() { + NPError retval; + if (!CallNPP_Destroy(&retval)) { + retval = NPERR_GENERIC_ERROR; + } + +#if defined(OS_WIN) + UnsubclassPluginWindow(); +#endif + + return retval; +} + +bool PluginInstanceParent::IsUsingDirectDrawing() { + return IsDrawingModelDirect(mDrawingModel); +} + +PBrowserStreamParent* PluginInstanceParent::AllocPBrowserStreamParent( + const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, + PStreamNotifyParent* notifyData, const nsCString& headers) { + MOZ_CRASH("Not reachable"); + return nullptr; +} + +bool PluginInstanceParent::DeallocPBrowserStreamParent( + PBrowserStreamParent* stream) { + delete stream; + return true; +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_NPNVnetscapeWindow( + NativeWindowHandle* value, NPError* result) { +#ifdef XP_WIN + HWND id; +#elif defined(MOZ_X11) + XID id; +#elif defined(XP_DARWIN) + intptr_t id; +#elif defined(ANDROID) || defined(MOZ_WAYLAND) + // TODO: Need impl + int id; +#else +# warning Implement me +#endif + + *result = mNPNIface->getvalue(mNPP, NPNVnetscapeWindow, &id); + *value = id; + return IPC_OK(); +} + +bool PluginInstanceParent::InternalGetValueForNPObject( + NPNVariable aVariable, PPluginScriptableObjectParent** aValue, + NPError* aResult) { + NPObject* npobject; + NPError result = mNPNIface->getvalue(mNPP, aVariable, (void*)&npobject); + if (result == NPERR_NO_ERROR) { + NS_ASSERTION(npobject, "Shouldn't return null and NPERR_NO_ERROR!"); + + PluginScriptableObjectParent* actor = GetActorForNPObject(npobject); + mNPNIface->releaseobject(npobject); + if (actor) { + *aValue = actor; + *aResult = NPERR_NO_ERROR; + return true; + } + + NS_ERROR("Failed to get actor!"); + result = NPERR_GENERIC_ERROR; + } + + *aValue = nullptr; + *aResult = result; + return true; +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_NPNVWindowNPObject( + PPluginScriptableObjectParent** aValue, NPError* aResult) { + if (!InternalGetValueForNPObject(NPNVWindowNPObject, aValue, aResult)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_NPNVPluginElementNPObject( + PPluginScriptableObjectParent** aValue, NPError* aResult) { + if (!InternalGetValueForNPObject(NPNVPluginElementNPObject, aValue, + aResult)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_NPNVprivateModeBool(bool* value, + NPError* result) { + NPBool v; + *result = mNPNIface->getvalue(mNPP, NPNVprivateModeBool, &v); + *value = v; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_DrawingModelSupport( + const NPNVariable& model, bool* value) { + *value = false; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_NPNVdocumentOrigin(nsCString* value, + NPError* result) { + void* v = nullptr; + *result = mNPNIface->getvalue(mNPP, NPNVdocumentOrigin, &v); + if (*result == NPERR_NO_ERROR && v) { + value->Adopt(static_cast(v)); + } + return IPC_OK(); +} + +static inline bool AllowDirectBitmapSurfaceDrawing() { + if (!mozilla::StaticPrefs::dom_ipc_plugins_asyncdrawing_enabled()) { + return false; + } + return gfxPlatform::GetPlatform()->SupportsPluginDirectBitmapDrawing(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_SupportsAsyncBitmapSurface( + bool* value) { + *value = AllowDirectBitmapSurfaceDrawing(); + return IPC_OK(); +} + +/* static */ +bool PluginInstanceParent::SupportsPluginDirectDXGISurfaceDrawing() { + bool value = false; +#if defined(XP_WIN) + // When WebRender does not use ANGLE, DXGISurface could not be used. + bool useAsyncDXGISurface = + StaticPrefs::dom_ipc_plugins_allow_dxgi_surface() && + !(gfx::gfxVars::UseWebRender() && !gfx::gfxVars::UseWebRenderANGLE()); + if (useAsyncDXGISurface) { + auto cbc = CompositorBridgeChild::Get(); + if (cbc) { + cbc->SendSupportsAsyncDXGISurface(&value); + } + } +#endif + return value; +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_GetValue_PreferredDXGIAdapter( + DxgiAdapterDesc* aOutDesc) { + PodZero(aOutDesc); +#if defined(XP_WIN) + auto cbc = CompositorBridgeChild::Get(); + if (cbc) { + cbc->SendPreferredDXGIAdapter(aOutDesc); + } +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginWindow(const bool& windowed, + NPError* result) { + // Yes, we are passing a boolean as a void*. We have to cast to intptr_t + // first to avoid gcc warnings about casting to a pointer from a + // non-pointer-sized integer. + *result = mNPNIface->setvalue(mNPP, NPPVpluginWindowBool, + (void*)(intptr_t)windowed); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginTransparent( + const bool& transparent, NPError* result) { + *result = mNPNIface->setvalue(mNPP, NPPVpluginTransparentBool, + (void*)(intptr_t)transparent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginUsesDOMForCursor( + const bool& useDOMForCursor, NPError* result) { + *result = mNPNIface->setvalue(mNPP, NPPVpluginUsesDOMForCursorBool, + (void*)(intptr_t)useDOMForCursor); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginDrawingModel( + const int& drawingModel, NPError* result) { + bool allowed = false; + + switch (drawingModel) { +#if defined(XP_MACOSX) + case NPDrawingModelCoreAnimation: + case NPDrawingModelInvalidatingCoreAnimation: + case NPDrawingModelOpenGL: + case NPDrawingModelCoreGraphics: + allowed = true; + break; +#elif defined(XP_WIN) + case NPDrawingModelSyncWin: + allowed = true; + break; + case NPDrawingModelAsyncWindowsDXGISurface: + allowed = SupportsPluginDirectDXGISurfaceDrawing(); + break; +#elif defined(MOZ_X11) + case NPDrawingModelSyncX: + allowed = true; + break; +#endif + case NPDrawingModelAsyncBitmapSurface: + allowed = AllowDirectBitmapSurfaceDrawing(); + break; + default: + allowed = false; + break; + } + + if (!allowed) { + *result = NPERR_GENERIC_ERROR; + return IPC_OK(); + } + + mDrawingModel = drawingModel; + + int requestModel = drawingModel; + +#ifdef XP_MACOSX + if (drawingModel == NPDrawingModelCoreAnimation || + drawingModel == NPDrawingModelInvalidatingCoreAnimation) { + // We need to request CoreGraphics otherwise + // the nsPluginFrame will try to draw a CALayer + // that can not be shared across process. + requestModel = NPDrawingModelCoreGraphics; + } +#endif + + *result = mNPNIface->setvalue(mNPP, NPPVpluginDrawingModel, + (void*)(intptr_t)requestModel); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginEventModel( + const int& eventModel, NPError* result) { +#ifdef XP_MACOSX + *result = mNPNIface->setvalue(mNPP, NPPVpluginEventModel, + (void*)(intptr_t)eventModel); + return IPC_OK(); +#else + *result = NPERR_GENERIC_ERROR; + return IPC_OK(); +#endif +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginIsPlayingAudio( + const bool& isAudioPlaying, NPError* result) { + *result = mNPNIface->setvalue(mNPP, NPPVpluginIsPlayingAudio, + (void*)(intptr_t)isAudioPlaying); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerNPN_GetURL( + const nsCString& url, const nsCString& target, NPError* result) { + *result = mNPNIface->geturl(mNPP, NullableStringGet(url), + NullableStringGet(target)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerNPN_PostURL( + const nsCString& url, const nsCString& target, const nsCString& buffer, + const bool& file, NPError* result) { + *result = mNPNIface->posturl(mNPP, url.get(), NullableStringGet(target), + buffer.Length(), buffer.get(), file); + return IPC_OK(); +} + +PStreamNotifyParent* PluginInstanceParent::AllocPStreamNotifyParent( + const nsCString& url, const nsCString& target, const bool& post, + const nsCString& buffer, const bool& file, NPError* result) { + return new StreamNotifyParent(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerPStreamNotifyConstructor( + PStreamNotifyParent* actor, const nsCString& url, const nsCString& target, + const bool& post, const nsCString& buffer, const bool& file, + NPError* result) { + bool streamDestroyed = false; + static_cast(actor)->SetDestructionFlag(&streamDestroyed); + + if (!post) { + *result = mNPNIface->geturlnotify(mNPP, NullableStringGet(url), + NullableStringGet(target), actor); + } else { + *result = mNPNIface->posturlnotify( + mNPP, NullableStringGet(url), NullableStringGet(target), + buffer.Length(), NullableStringGet(buffer), file, actor); + } + + if (streamDestroyed) { + // If the stream was destroyed, we must return an error code in the + // constructor. + *result = NPERR_GENERIC_ERROR; + } else { + static_cast(actor)->ClearDestructionFlag(); + if (*result != NPERR_NO_ERROR) { + if (!PStreamNotifyParent::Send__delete__(actor, NPERR_GENERIC_ERROR)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); + } + } + + return IPC_OK(); +} + +bool PluginInstanceParent::DeallocPStreamNotifyParent( + PStreamNotifyParent* notifyData) { + delete notifyData; + return true; +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvNPN_InvalidateRect( + const NPRect& rect) { + mNPNIface->invalidaterect(mNPP, const_cast(&rect)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvRevokeCurrentDirectSurface() { + ImageContainer* container = GetImageContainer(); + if (!container) { + return IPC_OK(); + } + + container->ClearAllImages(); + + PLUGIN_LOG_DEBUG((" (RecvRevokeCurrentDirectSurface)")); + return IPC_OK(); +} + +#if defined(XP_WIN) +// Uses the ImageBridge to perform IGPUVideoSurfaceManager operations +// in the GPU process. +class AsyncPluginSurfaceManager : public IGPUVideoSurfaceManager { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPluginSurfaceManager, override) + + already_AddRefed Readback( + const SurfaceDescriptorGPUVideo& aSD) override { + SurfaceDescriptorPlugin pluginSD = aSD; + if (!InImageBridgeChildThread()) { + SynchronousTask task("AsyncPluginSurfaceManager readback sync"); + RefPtr result; + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch( + NewRunnableFunction("AsyncPluginSurfaceManager readback", + &DoSyncReadback, &pluginSD, &result, &task)); + task.Wait(); + return result.forget(); + } + + return DoReadback(pluginSD); + } + + void DeallocateSurfaceDescriptor( + const SurfaceDescriptorGPUVideo& aSD) override { + SurfaceDescriptorPlugin pluginSD = aSD; + if (!InImageBridgeChildThread()) { + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch( + NewRunnableFunction("AsyncPluginSurfaceManager dealloc", &DoDealloc, + &pluginSD)); + return; + } + + return DoDealloc(&pluginSD); + } + + // Set of display surfaces for which the related plugin surface has been + // freed. They are freed when the AsyncPluginSurfaceManager is told it is + // safe. + static HashSet sOrphanedDisplaySurfaces; + + private: + ~AsyncPluginSurfaceManager() {} + + struct SurfaceDescriptorUserData { + explicit SurfaceDescriptorUserData(layers::SurfaceDescriptor& aSD) + : mSD(aSD) {} + + ~SurfaceDescriptorUserData() { + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + DestroySurfaceDescriptor(ibc, &mSD); + } + + layers::SurfaceDescriptor mSD; + }; + + static void DeleteSurfaceDescriptorUserData(void* aClosure) { + SurfaceDescriptorUserData* sd = + reinterpret_cast(aClosure); + delete sd; + } + + static already_AddRefed DoReadback( + const SurfaceDescriptorPlugin& aSD) { + MOZ_ASSERT(InImageBridgeChildThread()); + + RefPtr source; + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return nullptr; + } + + layers::SurfaceDescriptor dataSD = null_t(); + ibc->SendReadbackAsyncPluginSurface(aSD, &dataSD); + if (!IsSurfaceDescriptorValid(dataSD)) { + NS_WARNING("Bad SurfaceDescriptor received in Readback"); + return nullptr; + } + + source = GetSurfaceForDescriptor(dataSD); + if (!source) { + DestroySurfaceDescriptor(ibc, &dataSD); + NS_WARNING("Failed to map SurfaceDescriptor in Readback"); + return nullptr; + } + + static UserDataKey sSurfaceDescriptor; + source->AddUserData(&sSurfaceDescriptor, + new SurfaceDescriptorUserData(dataSD), + DeleteSurfaceDescriptorUserData); + + return source.forget(); + } + + static void DoSyncReadback(const SurfaceDescriptorPlugin* aSD, + RefPtr* aResult, + SynchronousTask* aTask) { + AutoCompleteTask act(aTask); + *aResult = DoReadback(*aSD); + } + + static void DoDealloc(const SurfaceDescriptorPlugin* aSD) { + MOZ_ASSERT(InImageBridgeChildThread()); + + // If the plugin already Finalized and freed its surface then, since the + // compositor is now also done with the display surface, we can free + // it too. + WindowsHandle handle = aSD->displaySurf().handle(); + auto surfIt = sOrphanedDisplaySurfaces.lookup(handle); + if (!surfIt) { + // We wil continue to use the surfaces with future GPUVideoImages. + return; + } + + sOrphanedDisplaySurfaces.remove(surfIt); + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + ibc->SendRemoveAsyncPluginSurface(*aSD, true); + } +}; + +/* static */ HashSet + AsyncPluginSurfaceManager::sOrphanedDisplaySurfaces; + +void InitDXGISurface(const gfx::SurfaceFormat& aFormat, + const gfx::IntSize& aSize, + SurfaceDescriptorPlugin* aSDPlugin, + SynchronousTask* aTask) { + MOZ_ASSERT(InImageBridgeChildThread()); + + AutoCompleteTask act(aTask); + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + + layers::SurfaceDescriptorPlugin sd; + if (!ibc->SendMakeAsyncPluginSurfaces(aFormat, aSize, &sd)) { + return; + } + *aSDPlugin = sd; +} + +void FinalizeDXGISurface(const SurfaceDescriptorPlugin& aSD) { + MOZ_ASSERT(InImageBridgeChildThread()); + + Unused << AsyncPluginSurfaceManager::sOrphanedDisplaySurfaces.put( + aSD.displaySurf().handle()); + + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + ibc->SendRemoveAsyncPluginSurface(aSD, false); +} + +void CopyDXGISurface(const SurfaceDescriptorPlugin& aSD, + SynchronousTask* aTask) { + MOZ_ASSERT(InImageBridgeChildThread()); + + AutoCompleteTask act(aTask); + auto ibc = ImageBridgeChild::GetSingleton(); + if (!ibc) { + return; + } + ibc->SendUpdateAsyncPluginSurface(aSD); +} + +#endif // defined(XP_WIN) + +mozilla::ipc::IPCResult PluginInstanceParent::RecvInitDXGISurface( + const gfx::SurfaceFormat& format, const gfx::IntSize& size, + WindowsHandle* outHandle, NPError* outError) { + *outHandle = 0; + *outError = NPERR_GENERIC_ERROR; + +#if defined(XP_WIN) + MOZ_ASSERT(NS_IsMainThread()); + if (format != SurfaceFormat::B8G8R8A8 && format != SurfaceFormat::B8G8R8X8) { + *outError = NPERR_INVALID_PARAM; + return IPC_OK(); + } + if (size.width <= 0 || size.height <= 0) { + *outError = NPERR_INVALID_PARAM; + return IPC_OK(); + } + + if (!ImageBridgeChild::GetSingleton()) { + return IPC_OK(); + } + + // Ask the ImageBridge thread to generate two SurfaceDescriptorPlugins -- + // one for the GPU process to display and one for the Plugin process to + // render to. + SurfaceDescriptorPlugin sd; + SynchronousTask task("SendMakeAsyncPluginSurfaces sync"); + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch( + NewRunnableFunction("SendingMakeAsyncPluginSurfaces", &InitDXGISurface, + format, size, &sd, &task)); + task.Wait(); + + if (!sd.id()) { + NS_WARNING("SendMakeAsyncPluginSurfaces failed"); + return IPC_OK(); + } + + WindowsHandle pluginSurfHandle = sd.pluginSurf().handle(); + bool ok = mAsyncSurfaceMap.put(pluginSurfHandle, AsyncSurfaceInfo{sd, size}); + if (!ok) { + return IPC_OK(); + } + + *outHandle = pluginSurfHandle; + *outError = NPERR_NO_ERROR; +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvFinalizeDXGISurface( + const WindowsHandle& pluginSurfHandle) { +#if defined(XP_WIN) + MOZ_ASSERT(NS_IsMainThread()); + + if (!ImageBridgeChild::GetSingleton()) { + return IPC_OK(); + } + + auto asiIt = mAsyncSurfaceMap.lookup(pluginSurfHandle); + if (!asiIt) { + NS_WARNING("Plugin surface did not exist to finalize"); + return IPC_OK(); + } + + AsyncSurfaceInfo& asi = asiIt->value(); + + // Release the plugin surface but keep the display surface since it may + // still be displayed. Also let the display surface know that it should + // not receive further requests to copy from the plugin surface. + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch(NewRunnableFunction( + "SendingRemoveAsyncPluginSurface", &FinalizeDXGISurface, asi.mSD)); + + mAsyncSurfaceMap.remove(asiIt); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvShowDirectBitmap( + Shmem&& buffer, const SurfaceFormat& format, const uint32_t& stride, + const IntSize& size, const IntRect& dirty) { + // Validate format. + if (format != SurfaceFormat::B8G8R8A8 && format != SurfaceFormat::B8G8R8X8) { + MOZ_ASSERT_UNREACHABLE("bad format type"); + return IPC_FAIL_NO_REASON(this); + } + if (size.width <= 0 || size.height <= 0) { + MOZ_ASSERT_UNREACHABLE("bad image size"); + return IPC_FAIL_NO_REASON(this); + } + if (mDrawingModel != NPDrawingModelAsyncBitmapSurface) { + MOZ_ASSERT_UNREACHABLE("plugin did not set a bitmap drawing model"); + return IPC_FAIL_NO_REASON(this); + } + + // Validate buffer and size. + CheckedInt nbytes = + CheckedInt(uint32_t(size.height)) * stride; + if (!nbytes.isValid() || nbytes.value() != buffer.Size()) { + MOZ_ASSERT_UNREACHABLE("bad shmem size"); + return IPC_FAIL_NO_REASON(this); + } + + ImageContainer* container = GetImageContainer(); + if (!container) { + return IPC_FAIL_NO_REASON(this); + } + + RefPtr source = + gfx::Factory::CreateWrappingDataSourceSurface(buffer.get(), + stride, size, format); + if (!source) { + return IPC_FAIL_NO_REASON(this); + } + + // Allocate a texture for the compositor. + RefPtr allocator = + mParent->EnsureTextureAllocatorForDirectBitmap(); + RefPtr texture = allocator->CreateOrRecycle( + format, size, BackendSelector::Content, TextureFlags::NO_FLAGS, + TextureAllocationFlags(ALLOC_FOR_OUT_OF_BAND_CONTENT | + ALLOC_UPDATE_FROM_SURFACE)); + if (!texture) { + NS_WARNING("Could not allocate a TextureClient for plugin!"); + return IPC_FAIL_NO_REASON(this); + } + + // Upload the plugin buffer. + { + TextureClientAutoLock autoLock(texture, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return IPC_FAIL_NO_REASON(this); + } + texture->UpdateFromSurface(source); + } + + // Wrap the texture in an image and ship it off. + RefPtr image = + new TextureWrapperImage(texture, gfx::IntRect(gfx::IntPoint(0, 0), size)); + SetCurrentImage(image); + + PLUGIN_LOG_DEBUG( + (" (RecvShowDirectBitmap received shmem=%p stride=%d size=%s dirty=%s)", + buffer.get(), stride, ToString(size).c_str(), + ToString(dirty).c_str())); + return IPC_OK(); +} + +void PluginInstanceParent::SetCurrentImage(Image* aImage) { + MOZ_ASSERT(IsUsingDirectDrawing()); + ImageContainer::NonOwningImage holder(aImage); + holder.mFrameID = ++mFrameID; + + AutoTArray imageList; + imageList.AppendElement(holder); + mImageContainer->SetCurrentImages(imageList); + + // Invalidate our area in the page so the image gets flushed. + gfx::IntRect rect = aImage->GetPictureRect(); + NPRect nprect = {uint16_t(rect.x), uint16_t(rect.y), uint16_t(rect.width), + uint16_t(rect.height)}; + RecvNPN_InvalidateRect(nprect); + + RecordDrawingModel(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvShowDirectDXGISurface( + const WindowsHandle& pluginSurfHandle, const gfx::IntRect& dirty) { +#if defined(XP_WIN) + MOZ_ASSERT(NS_IsMainThread()); + + if (!ImageBridgeChild::GetSingleton()) { + return IPC_OK(); + } + + auto asiIt = mAsyncSurfaceMap.lookup(pluginSurfHandle); + if (!asiIt) { + NS_WARNING("Plugin surface did not exist to finalize"); + return IPC_OK(); + } + + AsyncSurfaceInfo& asi = asiIt->value(); + + // Tell the ImageBridge to copy from the plugin surface to the display surface + SynchronousTask task("SendUpdateAsyncPluginSurface sync"); + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch(NewRunnableFunction( + "SendingUpdateAsyncPluginSurface", &CopyDXGISurface, asi.mSD, &task)); + task.Wait(); + + // Make sure we have an ImageContainer for SetCurrentImage. + ImageContainer* container = GetImageContainer(); + if (!container) { + return IPC_OK(); + } + + SetCurrentImage( + new GPUVideoImage(new AsyncPluginSurfaceManager(), asi.mSD, asi.mSize)); + + PLUGIN_LOG_DEBUG((" (RecvShowDirectDXGISurface received handle=%p rect=%s)", + reinterpret_cast(pluginSurfHandle), + ToString(dirty).c_str())); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvShow( + const NPRect& updatedRect, const SurfaceDescriptor& newSurface, + SurfaceDescriptor* prevSurface) { + PLUGIN_LOG_DEBUG(("[InstanceParent][%p] RecvShow for ", + this, updatedRect.left, updatedRect.top, + updatedRect.right - updatedRect.left, + updatedRect.bottom - updatedRect.top)); + + MOZ_ASSERT(!IsUsingDirectDrawing()); + + // XXXjwatt rewrite to use Moz2D + RefPtr surface; + if (newSurface.type() == SurfaceDescriptor::TShmem) { + if (!newSurface.get_Shmem().IsReadable()) { + NS_WARNING("back surface not readable"); + return IPC_FAIL_NO_REASON(this); + } + surface = gfxSharedImageSurface::Open(newSurface.get_Shmem()); + } +#ifdef XP_MACOSX + else if (newSurface.type() == SurfaceDescriptor::TIOSurfaceDescriptor) { + IOSurfaceDescriptor iodesc = newSurface.get_IOSurfaceDescriptor(); + + RefPtr newIOSurface = MacIOSurface::LookupSurface( + iodesc.surfaceId(), iodesc.contentsScaleFactor()); + + if (!newIOSurface) { + NS_WARNING("Got bad IOSurfaceDescriptor in RecvShow"); + return IPC_FAIL_NO_REASON(this); + } + + if (mFrontIOSurface) + *prevSurface = + IOSurfaceDescriptor(mFrontIOSurface->GetIOSurfaceID(), + mFrontIOSurface->GetContentsScaleFactor()); + else + *prevSurface = null_t(); + + mFrontIOSurface = newIOSurface; + + RecvNPN_InvalidateRect(updatedRect); + + PLUGIN_LOG_DEBUG( + (" (RecvShow invalidated for surface %p)", mFrontSurface.get())); + + return IPC_OK(); + } +#endif +#ifdef MOZ_X11 + else if (newSurface.type() == SurfaceDescriptor::TSurfaceDescriptorX11) { + surface = newSurface.get_SurfaceDescriptorX11().OpenForeign(); + } +#endif +#ifdef XP_WIN + else if (newSurface.type() == SurfaceDescriptor::TPPluginSurfaceParent) { + PluginSurfaceParent* s = static_cast( + newSurface.get_PPluginSurfaceParent()); + surface = s->Surface(); + } +#endif + + if (mFrontSurface) { + // This is the "old front buffer" we're about to hand back to + // the plugin. We might still have drawing operations + // referencing it. +#ifdef MOZ_X11 + if (mFrontSurface->GetType() == gfxSurfaceType::Xlib) { + // Finish with the surface and XSync here to ensure the server has + // finished operations on the surface before the plugin starts + // scribbling on it again, or worse, destroys it. + mFrontSurface->Finish(); + FinishX(DefaultXDisplay()); + } else +#endif + { + mFrontSurface->Flush(); + } + } + + if (mFrontSurface && gfxSharedImageSurface::IsSharedImage(mFrontSurface)) + *prevSurface = std::move( + static_cast(mFrontSurface.get())->GetShmem()); + else + *prevSurface = null_t(); + + if (surface) { + // Notify the cairo backend that this surface has changed behind + // its back. + gfxRect ur(updatedRect.left, updatedRect.top, + updatedRect.right - updatedRect.left, + updatedRect.bottom - updatedRect.top); + surface->MarkDirty(ur); + + bool isPlugin = true; + RefPtr sourceSurface = + gfxPlatform::GetSourceSurfaceForSurface(nullptr, surface, isPlugin); + RefPtr image = + new SourceSurfaceImage(surface->GetSize(), sourceSurface); + + AutoTArray imageList; + imageList.AppendElement(ImageContainer::NonOwningImage(image)); + + ImageContainer* container = GetImageContainer(); + container->SetCurrentImages(imageList); + } else if (mImageContainer) { + mImageContainer->ClearAllImages(); + } + + mFrontSurface = surface; + RecvNPN_InvalidateRect(updatedRect); + + PLUGIN_LOG_DEBUG( + (" (RecvShow invalidated for surface %p)", mFrontSurface.get())); + + RecordDrawingModel(); + return IPC_OK(); +} + +nsresult PluginInstanceParent::AsyncSetWindow(NPWindow* aWindow) { + NPRemoteWindow window; + mWindowType = aWindow->type; + window.window = reinterpret_cast(aWindow->window); + window.x = aWindow->x; + window.y = aWindow->y; + window.width = aWindow->width; + window.height = aWindow->height; + window.clipRect = aWindow->clipRect; + window.type = aWindow->type; +#if defined(XP_MACOSX) || defined(XP_WIN) + double scaleFactor = 1.0; + mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &scaleFactor); + window.contentsScaleFactor = scaleFactor; +#endif + +#if defined(OS_WIN) + MaybeCreateChildPopupSurrogate(); +#endif + +#if defined(OS_WIN) + // Windows async surfaces must be Win32. In particular, it is incompatible + // with in-memory surface types. + gfxSurfaceType surfType = gfxSurfaceType::Win32; +#else + gfxSurfaceType surfType = + gfxPlatform::GetPlatform()->ScreenReferenceSurface()->GetType(); +#endif + + if (surfType == (gfxSurfaceType)-1) { + return NS_ERROR_FAILURE; + } + + if (!SendAsyncSetWindow(surfType, window)) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult PluginInstanceParent::GetImageContainer(ImageContainer** aContainer) { + if (IsUsingDirectDrawing()) { + // Use the image container created by the most recent direct surface + // call, if any. We don't create one if no surfaces were presented + // yet. + ImageContainer* container = mImageContainer; + NS_IF_ADDREF(container); + *aContainer = container; + return NS_OK; + } + +#ifdef XP_MACOSX + MacIOSurface* ioSurface = nullptr; + + if (mFrontIOSurface) { + ioSurface = mFrontIOSurface; + } else if (mIOSurface) { + ioSurface = mIOSurface; + } + + if (!mFrontSurface && !ioSurface) +#else + if (!mFrontSurface) +#endif + return NS_ERROR_NOT_AVAILABLE; + + ImageContainer* container = GetImageContainer(); + + if (!container) { + return NS_ERROR_FAILURE; + } + +#ifdef XP_MACOSX + if (ioSurface) { + RefPtr image = new MacIOSurfaceImage(ioSurface); + container->SetCurrentImageInTransaction(image); + + NS_IF_ADDREF(container); + *aContainer = container; + return NS_OK; + } +#endif + + NS_IF_ADDREF(container); + *aContainer = container; + return NS_OK; +} + +nsresult PluginInstanceParent::GetImageSize(nsIntSize* aSize) { + if (IsUsingDirectDrawing()) { + if (!mImageContainer) { + return NS_ERROR_NOT_AVAILABLE; + } + *aSize = mImageContainer->GetCurrentSize(); + return NS_OK; + } + + if (mFrontSurface) { + mozilla::gfx::IntSize size = mFrontSurface->GetSize(); + *aSize = nsIntSize(size.width, size.height); + return NS_OK; + } + +#ifdef XP_MACOSX + if (mFrontIOSurface) { + *aSize = + nsIntSize(mFrontIOSurface->GetWidth(), mFrontIOSurface->GetHeight()); + return NS_OK; + } else if (mIOSurface) { + *aSize = nsIntSize(mIOSurface->GetWidth(), mIOSurface->GetHeight()); + return NS_OK; + } +#endif + + return NS_ERROR_NOT_AVAILABLE; +} + +void PluginInstanceParent::DidComposite() { + if (!IsUsingDirectDrawing()) { + return; + } + Unused << SendNPP_DidComposite(); +} + +#ifdef XP_MACOSX +nsresult PluginInstanceParent::IsRemoteDrawingCoreAnimation(bool* aDrawing) { + *aDrawing = (NPDrawingModelCoreAnimation == (NPDrawingModel)mDrawingModel || + NPDrawingModelInvalidatingCoreAnimation == + (NPDrawingModel)mDrawingModel); + return NS_OK; +} +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +nsresult PluginInstanceParent::ContentsScaleFactorChanged( + double aContentsScaleFactor) { + bool rv = SendContentsScaleFactorChanged(aContentsScaleFactor); + return rv ? NS_OK : NS_ERROR_FAILURE; +} +#endif // #ifdef XP_MACOSX + +nsresult PluginInstanceParent::SetBackgroundUnknown() { + PLUGIN_LOG_DEBUG(("[InstanceParent][%p] SetBackgroundUnknown", this)); + + if (mBackground) { + DestroyBackground(); + MOZ_ASSERT(!mBackground, "Background not destroyed"); + } + + return NS_OK; +} + +nsresult PluginInstanceParent::BeginUpdateBackground(const nsIntRect& aRect, + DrawTarget** aDrawTarget) { + PLUGIN_LOG_DEBUG( + ("[InstanceParent][%p] BeginUpdateBackground for ", + this, aRect.x, aRect.y, aRect.width, aRect.height)); + + if (!mBackground) { + // XXX if we failed to create a background surface on one + // update, there's no guarantee that later updates will be for + // the entire background area until successful. We might want + // to fix that eventually. + MOZ_ASSERT(aRect.TopLeft() == nsIntPoint(0, 0), + "Expecting rect for whole frame"); + if (!CreateBackground(aRect.Size())) { + *aDrawTarget = nullptr; + return NS_OK; + } + } + + mozilla::gfx::IntSize sz = mBackground->GetSize(); +#ifdef DEBUG + MOZ_ASSERT(nsIntRect(0, 0, sz.width, sz.height).Contains(aRect), + "Update outside of background area"); +#endif + + RefPtr dt = gfxPlatform::CreateDrawTargetForSurface( + mBackground, gfx::IntSize(sz.width, sz.height)); + dt.forget(aDrawTarget); + + return NS_OK; +} + +nsresult PluginInstanceParent::EndUpdateBackground(const nsIntRect& aRect) { + PLUGIN_LOG_DEBUG( + ("[InstanceParent][%p] EndUpdateBackground for ", + this, aRect.x, aRect.y, aRect.width, aRect.height)); + +#ifdef MOZ_X11 + // Have to XSync here to avoid the plugin trying to draw with this + // surface racing with its creation in the X server. We also want + // to avoid the plugin drawing onto stale pixels, then handing us + // back a front surface from those pixels that we might + // recomposite for "a while" until the next update. This XSync + // still doesn't guarantee that the plugin draws onto a consistent + // view of its background, but it does mean that the plugin is + // drawing onto pixels no older than those in the latest + // EndUpdateBackground(). + XSync(DefaultXDisplay(), X11False); +#endif + + Unused << SendUpdateBackground(BackgroundDescriptor(), aRect); + + return NS_OK; +} + +#if defined(XP_WIN) +nsresult PluginInstanceParent::SetScrollCaptureId(uint64_t aScrollCaptureId) { + if (aScrollCaptureId == ImageContainer::sInvalidAsyncContainerId) { + return NS_ERROR_FAILURE; + } + + mImageContainer = new ImageContainer(CompositableHandle(aScrollCaptureId)); + return NS_OK; +} + +nsresult PluginInstanceParent::GetScrollCaptureContainer( + ImageContainer** aContainer) { + if (!aContainer || !mImageContainer) { + return NS_ERROR_FAILURE; + } + + RefPtr container = GetImageContainer(); + container.forget(aContainer); + + return NS_OK; +} +#endif // XP_WIN + +bool PluginInstanceParent::CreateBackground(const nsIntSize& aSize) { + MOZ_ASSERT(!mBackground, "Already have a background"); + + // XXX refactor me + +#if defined(MOZ_X11) + Screen* screen = DefaultScreenOfDisplay(DefaultXDisplay()); + Visual* visual = DefaultVisualOfScreen(screen); + mBackground = gfxXlibSurface::Create( + screen, visual, mozilla::gfx::IntSize(aSize.width, aSize.height)); + return !!mBackground; + +#elif defined(XP_WIN) + // We have chosen to create an unsafe surface in which the plugin + // can read from the region while we're writing to it. + mBackground = gfxSharedImageSurface::CreateUnsafe( + this, mozilla::gfx::IntSize(aSize.width, aSize.height), + mozilla::gfx::SurfaceFormat::X8R8G8B8_UINT32); + return !!mBackground; +#else + return false; +#endif +} + +void PluginInstanceParent::DestroyBackground() { + if (!mBackground) { + return; + } + + // Relinquish ownership of |mBackground| to its destroyer + PPluginBackgroundDestroyerParent* pbd = + new PluginBackgroundDestroyerParent(mBackground); + mBackground = nullptr; + + // If this fails, there's no problem: |bd| will be destroyed along + // with the old background surface. + Unused << SendPPluginBackgroundDestroyerConstructor(pbd); +} + +mozilla::plugins::SurfaceDescriptor +PluginInstanceParent::BackgroundDescriptor() { + MOZ_ASSERT(mBackground, "Need a background here"); + + // XXX refactor me + +#ifdef MOZ_X11 + gfxXlibSurface* xsurf = static_cast(mBackground.get()); + return SurfaceDescriptorX11(xsurf); +#endif + +#ifdef XP_WIN + MOZ_ASSERT(gfxSharedImageSurface::IsSharedImage(mBackground), + "Expected shared image surface"); + gfxSharedImageSurface* shmem = + static_cast(mBackground.get()); + return mozilla::plugins::SurfaceDescriptor(std::move(shmem->GetShmem())); +#endif + + // If this is ever used, which it shouldn't be, it will trigger a + // hard assertion in IPDL-generated code. + return mozilla::plugins::SurfaceDescriptor(); +} + +ImageContainer* PluginInstanceParent::GetImageContainer() { + if (mImageContainer) { + return mImageContainer; + } + + if (IsUsingDirectDrawing()) { + mImageContainer = + LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS); + } else { + mImageContainer = LayerManager::CreateImageContainer(); + } + return mImageContainer; +} + +PPluginBackgroundDestroyerParent* +PluginInstanceParent::AllocPPluginBackgroundDestroyerParent() { + MOZ_CRASH("'Power-user' ctor is used exclusively"); + return nullptr; +} + +bool PluginInstanceParent::DeallocPPluginBackgroundDestroyerParent( + PPluginBackgroundDestroyerParent* aActor) { + delete aActor; + return true; +} + +NPError PluginInstanceParent::NPP_SetWindow(const NPWindow* aWindow) { + PLUGIN_LOG_DEBUG(("%s (aWindow=%p)", FULLFUNCTION, (void*)aWindow)); + + NS_ENSURE_TRUE(aWindow, NPERR_GENERIC_ERROR); + + NPRemoteWindow window; + mWindowType = aWindow->type; + +#if defined(OS_WIN) + // On windowless controls, reset the shared memory surface as needed. + if (mWindowType == NPWindowTypeDrawable) { + MaybeCreateChildPopupSurrogate(); + } else { + SubclassPluginWindow(reinterpret_cast(aWindow->window)); + + window.window = reinterpret_cast(aWindow->window); + window.x = aWindow->x; + window.y = aWindow->y; + window.width = aWindow->width; + window.height = aWindow->height; + window.type = aWindow->type; + + // On Windows we need to create and set the parent before we set the + // window on the plugin, or keyboard interaction will not work. + if (!MaybeCreateAndParentChildPluginWindow()) { + return NPERR_GENERIC_ERROR; + } + } +#else + window.window = reinterpret_cast(aWindow->window); + window.x = aWindow->x; + window.y = aWindow->y; + window.width = aWindow->width; + window.height = aWindow->height; + window.clipRect = aWindow->clipRect; // MacOS specific + window.type = aWindow->type; +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + double floatScaleFactor = 1.0; + mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &floatScaleFactor); + window.contentsScaleFactor = floatScaleFactor; +#endif +#if defined(XP_MACOSX) + int scaleFactor = ceil(floatScaleFactor); + if (mShWidth != window.width * scaleFactor || + mShHeight != window.height * scaleFactor) { + if (mDrawingModel == NPDrawingModelCoreAnimation || + mDrawingModel == NPDrawingModelInvalidatingCoreAnimation) { + mIOSurface = MacIOSurface::CreateIOSurface(window.width, window.height, + floatScaleFactor); + } else if (uint32_t(mShWidth * mShHeight) != + window.width * scaleFactor * window.height * scaleFactor) { + if (mShWidth != 0 && mShHeight != 0) { + DeallocShmem(mShSurface); + mShWidth = 0; + mShHeight = 0; + } + + if (window.width != 0 && window.height != 0) { + if (!AllocShmem( + window.width * scaleFactor * window.height * 4 * scaleFactor, + SharedMemory::TYPE_BASIC, &mShSurface)) { + PLUGIN_LOG_DEBUG(("Shared memory could not be allocated.")); + return NPERR_GENERIC_ERROR; + } + } + } + mShWidth = window.width * scaleFactor; + mShHeight = window.height * scaleFactor; + } +#endif + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + const NPSetWindowCallbackStruct* ws_info = + static_cast(aWindow->ws_info); + window.visualID = ws_info->visual ? ws_info->visual->visualid : 0; + window.colormap = ws_info->colormap; +#endif + + if (!CallNPP_SetWindow(window)) { + return NPERR_GENERIC_ERROR; + } + + RecordDrawingModel(); + return NPERR_NO_ERROR; +} + +NPError PluginInstanceParent::NPP_GetValue(NPPVariable aVariable, + void* _retval) { + switch (aVariable) { + case NPPVpluginWantsAllNetworkStreams: { + bool wantsAllStreams; + NPError rv; + + if (!CallNPP_GetValue_NPPVpluginWantsAllNetworkStreams(&wantsAllStreams, + &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + (*(NPBool*)_retval) = wantsAllStreams; + return NPERR_NO_ERROR; + } + + case NPPVpluginScriptableNPObject: { + PPluginScriptableObjectParent* actor; + NPError rv; + if (!CallNPP_GetValue_NPPVpluginScriptableNPObject(&actor, &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + if (!actor) { + NS_ERROR("NPPVpluginScriptableNPObject succeeded but null."); + return NPERR_GENERIC_ERROR; + } + + const NPNetscapeFuncs* npn = mParent->GetNetscapeFuncs(); + if (!npn) { + NS_WARNING("No netscape functions?!"); + return NPERR_GENERIC_ERROR; + } + + NPObject* object = + static_cast(actor)->GetObject(true); + NS_ASSERTION(object, "This shouldn't ever be null!"); + + (*(NPObject**)_retval) = npn->retainobject(object); + return NPERR_NO_ERROR; + } + +#ifdef MOZ_ACCESSIBILITY_ATK + case NPPVpluginNativeAccessibleAtkPlugId: { + nsCString plugId; + NPError rv; + if (!CallNPP_GetValue_NPPVpluginNativeAccessibleAtkPlugId(&plugId, &rv)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR != rv) { + return rv; + } + + (*(nsCString*)_retval) = plugId; + return NPERR_NO_ERROR; + } +#endif + + default: + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceParent::NPP_GetValue: Unhandled NPPVariable " + "%i (%s)", + (int)aVariable, NPPVariableToString(aVariable))); + return NPERR_GENERIC_ERROR; + } +} + +NPError PluginInstanceParent::NPP_SetValue(NPNVariable variable, void* value) { + NPError result; + switch (variable) { + case NPNVprivateModeBool: + if (!CallNPP_SetValue_NPNVprivateModeBool(*static_cast(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + case NPNVmuteAudioBool: + if (!CallNPP_SetValue_NPNVmuteAudioBool(*static_cast(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + case NPNVCSSZoomFactor: + if (!CallNPP_SetValue_NPNVCSSZoomFactor(*static_cast(value), + &result)) + return NPERR_GENERIC_ERROR; + + return result; + + default: + NS_ERROR("Unhandled NPNVariable in NPP_SetValue"); + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("In PluginInstanceParent::NPP_SetValue: Unhandled NPNVariable " + "%i (%s)", + (int)variable, NPNVariableToString(variable))); + return NPERR_GENERIC_ERROR; + } +} + +void PluginInstanceParent::NPP_URLRedirectNotify(const char* url, + int32_t status, + void* notifyData) { + if (!notifyData) return; + + PStreamNotifyParent* streamNotify = + static_cast(notifyData); + Unused << streamNotify->SendRedirectNotify(NullableString(url), status); +} + +int16_t PluginInstanceParent::NPP_HandleEvent(void* event) { + PLUGIN_LOG_DEBUG_FUNCTION; + +#if defined(XP_MACOSX) + NPCocoaEvent* npevent = reinterpret_cast(event); +#else + NPEvent* npevent = reinterpret_cast(event); +#endif + NPRemoteEvent npremoteevent; + npremoteevent.event = *npevent; +#if defined(XP_MACOSX) || defined(XP_WIN) + double scaleFactor = 1.0; + mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &scaleFactor); + npremoteevent.contentsScaleFactor = scaleFactor; +#endif + int16_t handled = 0; + +#if defined(OS_WIN) + if (mWindowType == NPWindowTypeDrawable) { + switch (npevent->event) { + case WM_KILLFOCUS: { + // When the user selects fullscreen mode in Flash video players, + // WM_KILLFOCUS will be delayed by deferred event processing: + // WM_LBUTTONUP results in a call to CreateWindow within Flash, + // which fires WM_KILLFOCUS. Delayed delivery causes Flash to + // misinterpret the event, dropping back out of fullscreen. Trap + // this event and drop it. + // mPluginHWND is always NULL for non-windowed plugins. + if (mPluginHWND) { + wchar_t szClass[26]; + HWND hwnd = GetForegroundWindow(); + if (hwnd && hwnd != mPluginHWND && + GetClassNameW(hwnd, szClass, + sizeof(szClass) / sizeof(char16_t)) && + !wcscmp(szClass, kFlashFullscreenClass)) { + return 0; + } + } + } break; + + case WM_WINDOWPOSCHANGED: { + // We send this in nsPluginFrame just before painting + return SendWindowPosChanged(npremoteevent); + } + + case WM_IME_STARTCOMPOSITION: + case WM_IME_COMPOSITION: + case WM_IME_ENDCOMPOSITION: + if (!(mParent->GetQuirks() & QUIRK_WINLESS_HOOK_IME)) { + // IME message will be posted on allowed plugins only such as + // Flash. Because if we cannot know that plugin can handle + // IME correctly. + return 0; + } + break; + } + } +#endif + +#if defined(MOZ_X11) + switch (npevent->type) { + case GraphicsExpose: + PLUGIN_LOG_DEBUG((" schlepping drawable 0x%lx across the pipe\n", + npevent->xgraphicsexpose.drawable)); + // Make sure the X server has created the Drawable and completes any + // drawing before the plugin draws on top. + // + // XSync() waits for the X server to complete. Really this parent + // process does not need to wait; the child is the process that needs + // to wait. A possibly-slightly-better alternative would be to send + // an X event to the child that the child would wait for. + FinishX(DefaultXDisplay()); + + return CallPaint(npremoteevent, &handled) ? handled : 0; + + case ButtonPress: + // Release any active pointer grab so that the plugin X client can + // grab the pointer if it wishes. + Display* dpy = DefaultXDisplay(); +# ifdef MOZ_WIDGET_GTK + // GDK attempts to (asynchronously) track whether there is an active + // grab so ungrab through GDK. + // + // This call needs to occur in the same process that receives the event in + // the first place (chrome process) + if (XRE_IsContentProcess()) { + dom::ContentChild* cp = dom::ContentChild::GetSingleton(); + cp->SendUngrabPointer(npevent->xbutton.time); + } else { + gdk_pointer_ungrab(npevent->xbutton.time); + } +# else + XUngrabPointer(dpy, npevent->xbutton.time); +# endif + // Wait for the ungrab to complete. + XSync(dpy, X11False); + break; + } +#endif + +#ifdef XP_MACOSX + if (npevent->type == NPCocoaEventDrawRect) { + if (mDrawingModel == NPDrawingModelCoreAnimation || + mDrawingModel == NPDrawingModelInvalidatingCoreAnimation) { + if (!mIOSurface) { + NS_ERROR("No IOSurface allocated."); + return false; + } + if (!CallNPP_HandleEvent_IOSurface( + npremoteevent, mIOSurface->GetIOSurfaceID(), &handled)) + return false; // no good way to handle errors here... + + CGContextRef cgContext = npevent->data.draw.context; + if (!mShColorSpace) { + mShColorSpace = CreateSystemColorSpace(); + } + if (!mShColorSpace) { + PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace.")); + return false; + } + if (cgContext) { + nsCARenderer::DrawSurfaceToCGContext( + cgContext, mIOSurface, mShColorSpace, npevent->data.draw.x, + npevent->data.draw.y, npevent->data.draw.width, + npevent->data.draw.height); + } + return true; + } else if (mFrontIOSurface) { + CGContextRef cgContext = npevent->data.draw.context; + if (!mShColorSpace) { + mShColorSpace = CreateSystemColorSpace(); + } + if (!mShColorSpace) { + PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace.")); + return false; + } + if (cgContext) { + nsCARenderer::DrawSurfaceToCGContext( + cgContext, mFrontIOSurface, mShColorSpace, npevent->data.draw.x, + npevent->data.draw.y, npevent->data.draw.width, + npevent->data.draw.height); + } + return true; + } else { + if (mShWidth == 0 && mShHeight == 0) { + PLUGIN_LOG_DEBUG(("NPCocoaEventDrawRect on window of size 0.")); + return false; + } + if (!mShSurface.IsReadable()) { + PLUGIN_LOG_DEBUG(("Shmem is not readable.")); + return false; + } + + if (!CallNPP_HandleEvent_Shmem(npremoteevent, std::move(mShSurface), + &handled, &mShSurface)) + return false; // no good way to handle errors here... + + if (!mShSurface.IsReadable()) { + PLUGIN_LOG_DEBUG( + ("Shmem not returned. Either the plugin crashed " + "or we have a bug.")); + return false; + } + + char* shContextByte = mShSurface.get(); + + if (!mShColorSpace) { + mShColorSpace = CreateSystemColorSpace(); + } + if (!mShColorSpace) { + PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace.")); + return false; + } + CGContextRef shContext = ::CGBitmapContextCreate( + shContextByte, mShWidth, mShHeight, 8, mShWidth * 4, mShColorSpace, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host); + if (!shContext) { + PLUGIN_LOG_DEBUG(("Could not allocate CGBitmapContext.")); + return false; + } + + CGImageRef shImage = ::CGBitmapContextCreateImage(shContext); + if (shImage) { + CGContextRef cgContext = npevent->data.draw.context; + + ::CGContextDrawImage(cgContext, CGRectMake(0, 0, mShWidth, mShHeight), + shImage); + ::CGImageRelease(shImage); + } else { + ::CGContextRelease(shContext); + return false; + } + ::CGContextRelease(shContext); + return true; + } + } +#endif + + if (!CallNPP_HandleEvent(npremoteevent, &handled)) + return 0; // no good way to handle errors here... + + return handled; +} + +NPError PluginInstanceParent::NPP_NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype) { + PLUGIN_LOG_DEBUG(("%s (type=%s, stream=%p, seekable=%i)", FULLFUNCTION, + (char*)type, (void*)stream, (int)seekable)); + + BrowserStreamParent* bs = new BrowserStreamParent(this, stream); + + if (!SendPBrowserStreamConstructor( + bs, NullableString(stream->url), stream->end, stream->lastmodified, + static_cast(stream->notifyData), + NullableString(stream->headers))) { + return NPERR_GENERIC_ERROR; + } + + NPError err = NPERR_NO_ERROR; + bs->SetAlive(); + if (!CallNPP_NewStream(bs, NullableString(type), seekable, &err, stype)) { + err = NPERR_GENERIC_ERROR; + } + if (NPERR_NO_ERROR != err) { + Unused << PBrowserStreamParent::Send__delete__(bs); + } + + return err; +} + +NPError PluginInstanceParent::NPP_DestroyStream(NPStream* stream, + NPReason reason) { + PLUGIN_LOG_DEBUG( + ("%s (stream=%p, reason=%i)", FULLFUNCTION, (void*)stream, (int)reason)); + + AStream* s = static_cast(stream->pdata); + if (!s) { + // The stream has already been deleted by other means. + // With async plugin init this could happen if async NPP_NewStream + // returns an error code. + return NPERR_NO_ERROR; + } + MOZ_ASSERT(s->IsBrowserStream()); + BrowserStreamParent* sp = static_cast(s); + if (sp->mNPP != this) MOZ_CRASH("Mismatched plugin data"); + sp->NPP_DestroyStream(reason); + return NPERR_NO_ERROR; +} + +void PluginInstanceParent::NPP_Print(NPPrint* platformPrint) { + // TODO: implement me + NS_ERROR("Not implemented"); +} + +PPluginScriptableObjectParent* +PluginInstanceParent::AllocPPluginScriptableObjectParent() { + return new PluginScriptableObjectParent(Proxy); +} + +bool PluginInstanceParent::DeallocPPluginScriptableObjectParent( + PPluginScriptableObjectParent* aObject) { + PluginScriptableObjectParent* actor = + static_cast(aObject); + + NPObject* object = actor->GetObject(false); + if (object) { + NS_ASSERTION(mScriptableObjects.Get(object, nullptr), + "NPObject not in the hash!"); + mScriptableObjects.Remove(object); + } +#ifdef DEBUG + else { + for (auto iter = mScriptableObjects.Iter(); !iter.Done(); iter.Next()) { + NS_ASSERTION(actor != iter.UserData(), + "Actor in the hash with a null NPObject!"); + } + } +#endif + + delete actor; + return true; +} + +mozilla::ipc::IPCResult +PluginInstanceParent::RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectParent* aActor) { + // This is only called in response to the child process requesting the + // creation of an actor. This actor will represent an NPObject that is + // created by the plugin and returned to the browser. + PluginScriptableObjectParent* actor = + static_cast(aActor); + NS_ASSERTION(!actor->GetObject(false), "Actor already has an object?!"); + + actor->InitializeProxy(); + NS_ASSERTION(actor->GetObject(false), "Actor should have an object!"); + + return IPC_OK(); +} + +void PluginInstanceParent::NPP_URLNotify(const char* url, NPReason reason, + void* notifyData) { + PLUGIN_LOG_DEBUG( + ("%s (%s, %i, %p)", FULLFUNCTION, url, (int)reason, notifyData)); + + PStreamNotifyParent* streamNotify = + static_cast(notifyData); + Unused << PStreamNotifyParent::Send__delete__(streamNotify, reason); +} + +bool PluginInstanceParent::RegisterNPObjectForActor( + NPObject* aObject, PluginScriptableObjectParent* aActor) { + NS_ASSERTION(aObject && aActor, "Null pointers!"); + NS_ASSERTION(!mScriptableObjects.Get(aObject, nullptr), "Duplicate entry!"); + mScriptableObjects.Put(aObject, aActor); + return true; +} + +void PluginInstanceParent::UnregisterNPObject(NPObject* aObject) { + NS_ASSERTION(aObject, "Null pointer!"); + NS_ASSERTION(mScriptableObjects.Get(aObject, nullptr), "Unknown entry!"); + mScriptableObjects.Remove(aObject); +} + +PluginScriptableObjectParent* PluginInstanceParent::GetActorForNPObject( + NPObject* aObject) { + NS_ASSERTION(aObject, "Null pointer!"); + + if (aObject->_class == PluginScriptableObjectParent::GetClass()) { + // One of ours! + ParentNPObject* object = static_cast(aObject); + NS_ASSERTION(object->parent, "Null actor!"); + return object->parent; + } + + PluginScriptableObjectParent* actor; + if (mScriptableObjects.Get(aObject, &actor)) { + return actor; + } + + actor = new PluginScriptableObjectParent(LocalObject); + if (!SendPPluginScriptableObjectConstructor(actor)) { + NS_WARNING("Failed to send constructor message!"); + return nullptr; + } + + actor->InitializeLocal(aObject); + return actor; +} + +PPluginSurfaceParent* PluginInstanceParent::AllocPPluginSurfaceParent( + const WindowsSharedMemoryHandle& handle, const mozilla::gfx::IntSize& size, + const bool& transparent) { +#ifdef XP_WIN + return new PluginSurfaceParent(handle, size, transparent); +#else + NS_ERROR("This shouldn't be called!"); + return nullptr; +#endif +} + +bool PluginInstanceParent::DeallocPPluginSurfaceParent( + PPluginSurfaceParent* s) { +#ifdef XP_WIN + delete s; + return true; +#else + return false; +#endif +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerNPN_PushPopupsEnabledState( + const bool& aState) { + mNPNIface->pushpopupsenabledstate(mNPP, aState ? 1 : 0); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginInstanceParent::AnswerNPN_PopPopupsEnabledState() { + mNPNIface->poppopupsenabledstate(mNPP); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerNPN_GetValueForURL( + const NPNURLVariable& variable, const nsCString& url, nsCString* value, + NPError* result) { + char* v; + uint32_t len; + + *result = mNPNIface->getvalueforurl(mNPP, (NPNURLVariable)variable, url.get(), + &v, &len); + if (NPERR_NO_ERROR == *result) value->Adopt(v, len); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerNPN_SetValueForURL( + const NPNURLVariable& variable, const nsCString& url, + const nsCString& value, NPError* result) { + *result = mNPNIface->setvalueforurl(mNPP, (NPNURLVariable)variable, url.get(), + value.get(), value.Length()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerNPN_ConvertPoint( + const double& sourceX, const bool& ignoreDestX, const double& sourceY, + const bool& ignoreDestY, const NPCoordinateSpace& sourceSpace, + const NPCoordinateSpace& destSpace, double* destX, double* destY, + bool* result) { + *result = mNPNIface->convertpoint(mNPP, sourceX, sourceY, sourceSpace, + ignoreDestX ? nullptr : destX, + ignoreDestY ? nullptr : destY, destSpace); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvRedrawPlugin() { + nsNPAPIPluginInstance* inst = + static_cast(mNPP->ndata); + if (!inst) { + return IPC_FAIL_NO_REASON(this); + } + + inst->RedrawPlugin(); + return IPC_OK(); +} + +nsPluginInstanceOwner* PluginInstanceParent::GetOwner() { + nsNPAPIPluginInstance* inst = + static_cast(mNPP->ndata); + if (!inst) { + return nullptr; + } + return inst->GetOwner(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvSetNetscapeWindowAsParent( + const NativeWindowHandle& childWindow) { +#if defined(XP_WIN) + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner || NS_FAILED(owner->SetNetscapeWindowAsParent(childWindow))) { + NS_WARNING("Failed to set Netscape window as parent."); + } + + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("RecvSetNetscapeWindowAsParent not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +#if defined(OS_WIN) + +/* + plugin focus changes between processes + + focus from dom -> child: + Focus manager calls on widget to set the focus on the window. + We pick up the resulting wm_setfocus event here, and forward + that over ipc to the child which calls set focus on itself. + + focus from child -> focus manager: + Child picks up the local wm_setfocus and sends it via ipc over + here. We then post a custom event to widget/windows/nswindow + which fires off a gui event letting the browser know. +*/ + +static const wchar_t kPluginInstanceParentProperty[] = + L"PluginInstanceParentProperty"; + +// static +LRESULT CALLBACK PluginInstanceParent::PluginWindowHookProc(HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) { + PluginInstanceParent* self = reinterpret_cast( + ::GetPropW(hWnd, kPluginInstanceParentProperty)); + if (!self) { + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::PluginWindowHookProc null this ptr!"); + return DefWindowProc(hWnd, message, wParam, lParam); + } + + NS_ASSERTION(self->mPluginHWND == hWnd, "Wrong window!"); + + switch (message) { + case WM_SETFOCUS: + // Let the child plugin window know it should take focus. + Unused << self->CallSetPluginFocus(); + break; + + case WM_CLOSE: + self->UnsubclassPluginWindow(); + break; + } + + if (self->mPluginWndProc == PluginWindowHookProc) { + MOZ_ASSERT_UNREACHABLE( + "PluginWindowHookProc invoking mPluginWndProc w/" + "mPluginWndProc == PluginWindowHookProc????"); + return DefWindowProc(hWnd, message, wParam, lParam); + } + return ::CallWindowProc(self->mPluginWndProc, hWnd, message, wParam, lParam); +} + +void PluginInstanceParent::SubclassPluginWindow(HWND aWnd) { + if ((aWnd && mPluginHWND == aWnd) || (!aWnd && mPluginHWND)) { + return; + } + + if (XRE_IsContentProcess()) { + if (!aWnd) { + NS_WARNING( + "PluginInstanceParent::SubclassPluginWindow unexpected null window"); + return; + } + mPluginHWND = aWnd; // now a remote window, we can't subclass this + mPluginWndProc = nullptr; + // Note sPluginInstanceList wil delete 'this' if we do not remove + // it on shutdown. + sPluginInstanceList->Put((void*)mPluginHWND, this); + return; + } + + NS_ASSERTION( + !(mPluginHWND && aWnd != mPluginHWND), + "PluginInstanceParent::SubclassPluginWindow hwnd is not our window!"); + + mPluginHWND = aWnd; + mPluginWndProc = (WNDPROC)::SetWindowLongPtrA( + mPluginHWND, GWLP_WNDPROC, + reinterpret_cast(PluginWindowHookProc)); + DebugOnly bRes = + ::SetPropW(mPluginHWND, kPluginInstanceParentProperty, this); + NS_ASSERTION( + mPluginWndProc, + "PluginInstanceParent::SubclassPluginWindow failed to set subclass!"); + NS_ASSERTION( + bRes, "PluginInstanceParent::SubclassPluginWindow failed to set prop!"); +} + +void PluginInstanceParent::UnsubclassPluginWindow() { + if (XRE_IsContentProcess()) { + if (mPluginHWND) { + // Remove 'this' from the plugin list safely + mozilla::UniquePtr tmp; + MOZ_ASSERT(sPluginInstanceList); + sPluginInstanceList->Remove((void*)mPluginHWND, &tmp); + mozilla::Unused << tmp.release(); + if (!sPluginInstanceList->Count()) { + delete sPluginInstanceList; + sPluginInstanceList = nullptr; + } + } + mPluginHWND = nullptr; + return; + } + + if (mPluginHWND && mPluginWndProc) { + ::SetWindowLongPtrA(mPluginHWND, GWLP_WNDPROC, + reinterpret_cast(mPluginWndProc)); + + ::RemovePropW(mPluginHWND, kPluginInstanceParentProperty); + + mPluginWndProc = nullptr; + mPluginHWND = nullptr; + } +} + +/* windowless drawing helpers */ + +/* + * Origin info: + * + * windowless, offscreen: + * + * WM_WINDOWPOSCHANGED: origin is relative to container + * setwindow: origin is 0,0 + * WM_PAINT: origin is 0,0 + * + * windowless, native: + * + * WM_WINDOWPOSCHANGED: origin is relative to container + * setwindow: origin is relative to container + * WM_PAINT: origin is relative to container + * + * PluginInstanceParent: + * + * painting: mPluginPort (nsIntRect, saved in SetWindow) + */ + +bool PluginInstanceParent::MaybeCreateAndParentChildPluginWindow() { + // On Windows we need to create and set the parent before we set the + // window on the plugin, or keyboard interaction will not work. + if (!mChildPluginHWND) { + if (!CallCreateChildPluginWindow(&mChildPluginHWND) || !mChildPluginHWND) { + return false; + } + } + + // It's not clear if the parent window would ever change, but when this + // was done in the NPAPI child it used to allow for this. + if (mPluginHWND == mChildPluginsParentHWND) { + return true; + } + + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner) { + // We can't reparent without an owner, the plugin is probably shutting + // down, just return true to allow any calls to continue. + return true; + } + + // Note that this call will probably cause a sync native message to the + // process that owns the child window. + owner->SetWidgetWindowAsParent(mChildPluginHWND); + mChildPluginsParentHWND = mPluginHWND; + return true; +} + +void PluginInstanceParent::MaybeCreateChildPopupSurrogate() { + // Already created or not required for this plugin. + if (mChildPluginHWND || mWindowType != NPWindowTypeDrawable || + !(mParent->GetQuirks() & QUIRK_WINLESS_TRACKPOPUP_HOOK)) { + return; + } + + // We need to pass the netscape window down to be cached as part of the call + // to create the surrogate, because the reparenting of the surrogate in the + // main process can cause sync Windows messages to the plugin process, which + // then cause sync messages from the plugin child for the netscape window + // which causes a deadlock. + NativeWindowHandle netscapeWindow; + NPError result = + mNPNIface->getvalue(mNPP, NPNVnetscapeWindow, &netscapeWindow); + if (NPERR_NO_ERROR != result) { + NS_WARNING("Can't get netscape window to pass to plugin child."); + return; + } + + if (!SendCreateChildPopupSurrogate(netscapeWindow)) { + NS_WARNING("Failed to create popup surrogate in child."); + } +} + +#endif // defined(OS_WIN) + +mozilla::ipc::IPCResult PluginInstanceParent::AnswerPluginFocusChange( + const bool& gotFocus) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + // Currently only in use on windows - an event we receive from the child + // when it's plugin window (or one of it's children) receives keyboard + // focus. We detect this and forward a notification here so we can update + // focus. +#if defined(OS_WIN) + if (gotFocus) { + nsPluginInstanceOwner* owner = GetOwner(); + if (owner) { + RefPtr fm = nsFocusManager::GetFocusManager(); + RefPtr element; + owner->GetDOMElement(getter_AddRefs(element)); + if (fm && element) { + fm->SetFocus(element, 0); + } + } + } + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("AnswerPluginFocusChange not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +PluginInstanceParent* PluginInstanceParent::Cast(NPP aInstance) { + auto ip = static_cast(aInstance->pdata); + + // If the plugin crashed and the PluginInstanceParent was deleted, + // aInstance->pdata will be nullptr. + if (!ip) { + return nullptr; + } + + if (aInstance != ip->mNPP) { + MOZ_CRASH("Corrupted plugin data."); + } + + return ip; +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvGetCompositionString( + const uint32_t& aIndex, nsTArray* aDist, int32_t* aLength) { +#if defined(OS_WIN) + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner) { + *aLength = IMM_ERROR_GENERAL; + return IPC_OK(); + } + + if (!owner->GetCompositionString(aIndex, aDist, aLength)) { + *aLength = IMM_ERROR_NODATA; + } +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvRequestCommitOrCancel( + const bool& aCommitted) { +#if defined(OS_WIN) + nsPluginInstanceOwner* owner = GetOwner(); + if (owner) { + owner->RequestCommitOrCancel(aCommitted); + } +#endif + return IPC_OK(); +} + +nsresult PluginInstanceParent::HandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, bool aIsConsumed) { + if (NS_WARN_IF( + !SendHandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +mozilla::ipc::IPCResult PluginInstanceParent::RecvOnWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData) { + nsPluginInstanceOwner* owner = GetOwner(); + if (NS_WARN_IF(!owner)) { + // Notifies the plugin process of the key event being not consumed + // by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return IPC_OK(); + } + owner->OnWindowedPluginKeyEvent(aKeyEventData); + return IPC_OK(); +} + +void PluginInstanceParent::RecordDrawingModel() { + int mode = -1; + switch (mWindowType) { + case NPWindowTypeWindow: + // We use 0=windowed since there is no specific NPDrawingModel value. + mode = 0; + break; + case NPWindowTypeDrawable: + mode = mDrawingModel + 1; + break; + default: + MOZ_ASSERT_UNREACHABLE("bad window type"); + return; + } + + if (mode == mLastRecordedDrawingModel) { + return; + } + MOZ_ASSERT(mode >= 0); + + Telemetry::Accumulate(Telemetry::PLUGIN_DRAWING_MODEL, mode); + mLastRecordedDrawingModel = mode; +} diff --git a/dom/plugins/ipc/PluginInstanceParent.h b/dom/plugins/ipc/PluginInstanceParent.h new file mode 100644 index 0000000000..bef637b21f --- /dev/null +++ b/dom/plugins/ipc/PluginInstanceParent.h @@ -0,0 +1,403 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginInstanceParent_h +#define dom_plugins_PluginInstanceParent_h 1 + +#include "mozilla/plugins/PPluginInstanceParent.h" +#include "mozilla/plugins/PluginScriptableObjectParent.h" +#if defined(OS_WIN) +# include "mozilla/gfx/SharedDIBWin.h" +# include +# include "nsRefPtrHashtable.h" +# include "mozilla/layers/LayersSurfaces.h" +#elif defined(MOZ_WIDGET_COCOA) +# include "mozilla/gfx/QuartzSupport.h" +#endif + +#include "npfunctions.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsRect.h" + +#include "mozilla/Unused.h" +#include "mozilla/EventForwards.h" + +class gfxASurface; +class gfxContext; +class nsPluginInstanceOwner; + +namespace mozilla { +namespace layers { +class Image; +class ImageContainer; +class TextureClientRecycleAllocator; +} // namespace layers +namespace plugins { + +class PBrowserStreamParent; +class PluginModuleParent; +class D3D11SurfaceHolder; + +class PluginInstanceParent : public PPluginInstanceParent { + friend class PluginModuleParent; + friend class BrowserStreamParent; + friend class StreamNotifyParent; + friend class PPluginInstanceParent; + +#if defined(XP_WIN) + public: + /** + * Helper method for looking up instances based on a supplied id. + */ + static PluginInstanceParent* LookupPluginInstanceByID(uintptr_t aId); +#endif // defined(XP_WIN) + + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + PluginInstanceParent(PluginModuleParent* parent, NPP npp, + const nsCString& mimeType, + const NPNetscapeFuncs* npniface); + + virtual ~PluginInstanceParent(); + + bool InitMetadata(const nsACString& aMimeType, + const nsACString& aSrcAttribute); + NPError Destroy(); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + PPluginScriptableObjectParent* AllocPPluginScriptableObjectParent(); + + virtual mozilla::ipc::IPCResult RecvPPluginScriptableObjectConstructor( + PPluginScriptableObjectParent* aActor) override; + + bool DeallocPPluginScriptableObjectParent( + PPluginScriptableObjectParent* aObject); + PBrowserStreamParent* AllocPBrowserStreamParent( + const nsCString& url, const uint32_t& length, + const uint32_t& lastmodified, PStreamNotifyParent* notifyData, + const nsCString& headers); + bool DeallocPBrowserStreamParent(PBrowserStreamParent* stream); + + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVnetscapeWindow( + NativeWindowHandle* value, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVWindowNPObject( + PPluginScriptableObjectParent** value, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVPluginElementNPObject( + PPluginScriptableObjectParent** value, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVprivateModeBool( + bool* value, NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_GetValue_DrawingModelSupport( + const NPNVariable& model, bool* value); + + mozilla::ipc::IPCResult AnswerNPN_GetValue_NPNVdocumentOrigin( + nsCString* value, NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_GetValue_SupportsAsyncBitmapSurface( + bool* value); + + static bool SupportsPluginDirectDXGISurfaceDrawing(); + mozilla::ipc::IPCResult AnswerNPN_GetValue_SupportsAsyncDXGISurface( + bool* value) { + *value = SupportsPluginDirectDXGISurfaceDrawing(); + return IPC_OK(); + } + + mozilla::ipc::IPCResult AnswerNPN_GetValue_PreferredDXGIAdapter( + DxgiAdapterDesc* desc); + + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginWindow( + const bool& windowed, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginTransparent( + const bool& transparent, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginUsesDOMForCursor( + const bool& useDOMForCursor, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginDrawingModel( + const int& drawingModel, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginEventModel( + const int& eventModel, NPError* result); + mozilla::ipc::IPCResult AnswerNPN_SetValue_NPPVpluginIsPlayingAudio( + const bool& isAudioPlaying, NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_GetURL(const nsCString& url, + const nsCString& target, + NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_PostURL(const nsCString& url, + const nsCString& target, + const nsCString& buffer, + const bool& file, NPError* result); + + PStreamNotifyParent* AllocPStreamNotifyParent( + const nsCString& url, const nsCString& target, const bool& post, + const nsCString& buffer, const bool& file, NPError* result); + + virtual mozilla::ipc::IPCResult AnswerPStreamNotifyConstructor( + PStreamNotifyParent* actor, const nsCString& url, const nsCString& target, + const bool& post, const nsCString& buffer, const bool& file, + NPError* result) override; + + bool DeallocPStreamNotifyParent(PStreamNotifyParent* notifyData); + + mozilla::ipc::IPCResult RecvNPN_InvalidateRect(const NPRect& rect); + + mozilla::ipc::IPCResult RecvRevokeCurrentDirectSurface(); + + /** + * Windows async plugin rendering uses DXGI surface objects, entirely + * maintained by the compositor process, to back the rendering of plugins. + * The expected mechanics are: + * - The PluginInstanceChild (PIC) in the plugin process sends + * InitDXGISurface to us, the content process' PluginInstanceParent (PIP). + * - The PIP uses the ImageBridge to tell the compositor to create 2 + * surfaces -- one to be written to by the plugin process and another + * to be displayed by the compositor. The PIP returns the plugin + * surface to the PIC. + * - The PIC repeatedly issues ShowDirectDXGISurface calls to tell the + * PIP to blit the plugin surface to the display surface. These + * requests are forwarded to the compositor via the ImageBridge. + * - The PIC sends FinalizeDXGISurface tio the PIP when it no longer needs + * the surface. The PIP then tells the compositor to destroy the + * plugin surface. After this, the PIP will tell the compositor to + * also destroy the display surface as soon as the ImageBridge says it + * no longer needs it. + */ + mozilla::ipc::IPCResult RecvInitDXGISurface(const gfx::SurfaceFormat& format, + const gfx::IntSize& size, + WindowsHandle* outHandle, + NPError* outError); + mozilla::ipc::IPCResult RecvShowDirectDXGISurface(const WindowsHandle& handle, + const gfx::IntRect& rect); + + mozilla::ipc::IPCResult RecvFinalizeDXGISurface(const WindowsHandle& handle); + + // -- + + mozilla::ipc::IPCResult RecvShowDirectBitmap(Shmem&& buffer, + const gfx::SurfaceFormat& format, + const uint32_t& stride, + const gfx::IntSize& size, + const gfx::IntRect& dirty); + + mozilla::ipc::IPCResult RecvShow(const NPRect& updatedRect, + const SurfaceDescriptor& newSurface, + SurfaceDescriptor* prevSurface); + + PPluginSurfaceParent* AllocPPluginSurfaceParent( + const WindowsSharedMemoryHandle& handle, + const mozilla::gfx::IntSize& size, const bool& transparent); + + bool DeallocPPluginSurfaceParent(PPluginSurfaceParent* s); + + mozilla::ipc::IPCResult AnswerNPN_PushPopupsEnabledState(const bool& aState); + + mozilla::ipc::IPCResult AnswerNPN_PopPopupsEnabledState(); + + mozilla::ipc::IPCResult AnswerNPN_GetValueForURL( + const NPNURLVariable& variable, const nsCString& url, nsCString* value, + NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_SetValueForURL( + const NPNURLVariable& variable, const nsCString& url, + const nsCString& value, NPError* result); + + mozilla::ipc::IPCResult AnswerNPN_ConvertPoint( + const double& sourceX, const bool& ignoreDestX, const double& sourceY, + const bool& ignoreDestY, const NPCoordinateSpace& sourceSpace, + const NPCoordinateSpace& destSpace, double* destX, double* destY, + bool* result); + + mozilla::ipc::IPCResult RecvRedrawPlugin(); + + mozilla::ipc::IPCResult RecvSetNetscapeWindowAsParent( + const NativeWindowHandle& childWindow); + + NPError NPP_SetWindow(const NPWindow* aWindow); + + NPError NPP_GetValue(NPPVariable variable, void* retval); + NPError NPP_SetValue(NPNVariable variable, void* value); + + void NPP_URLRedirectNotify(const char* url, int32_t status, void* notifyData); + + NPError NPP_NewStream(NPMIMEType type, NPStream* stream, NPBool seekable, + uint16_t* stype); + NPError NPP_DestroyStream(NPStream* stream, NPReason reason); + + void NPP_Print(NPPrint* platformPrint); + + int16_t NPP_HandleEvent(void* event); + + void NPP_URLNotify(const char* url, NPReason reason, void* notifyData); + + PluginModuleParent* Module() { return mParent; } + + const NPNetscapeFuncs* GetNPNIface() { return mNPNIface; } + + bool RegisterNPObjectForActor(NPObject* aObject, + PluginScriptableObjectParent* aActor); + + void UnregisterNPObject(NPObject* aObject); + + PluginScriptableObjectParent* GetActorForNPObject(NPObject* aObject); + + NPP GetNPP() { return mNPP; } + + void GetSrcAttribute(nsACString& aOutput) const { aOutput = mSrcAttribute; } + + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult AnswerPluginFocusChange( + const bool& gotFocus); + + nsresult AsyncSetWindow(NPWindow* window); + nsresult GetImageContainer(mozilla::layers::ImageContainer** aContainer); + nsresult GetImageSize(nsIntSize* aSize); +#ifdef XP_MACOSX + nsresult IsRemoteDrawingCoreAnimation(bool* aDrawing); +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + nsresult ContentsScaleFactorChanged(double aContentsScaleFactor); +#endif + nsresult SetBackgroundUnknown(); + nsresult BeginUpdateBackground(const nsIntRect& aRect, + DrawTarget** aDrawTarget); + nsresult EndUpdateBackground(const nsIntRect& aRect); +#if defined(XP_WIN) + nsresult SetScrollCaptureId(uint64_t aScrollCaptureId); + nsresult GetScrollCaptureContainer( + mozilla::layers::ImageContainer** aContainer); +#endif + void DidComposite(); + + bool IsUsingDirectDrawing(); + + static PluginInstanceParent* Cast(NPP instance); + + // for IME hook + mozilla::ipc::IPCResult RecvGetCompositionString(const uint32_t& aIndex, + nsTArray* aBuffer, + int32_t* aLength); + mozilla::ipc::IPCResult RecvRequestCommitOrCancel(const bool& aCommitted); + + // for reserved shortcut key handling with windowed plugin on Windows + nsresult HandledWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, bool aIsConsumed); + mozilla::ipc::IPCResult RecvOnWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData); + + private: + // Create an appropriate platform surface for a background of size + // |aSize|. Return true if successful. + bool CreateBackground(const nsIntSize& aSize); + void DestroyBackground(); + SurfaceDescriptor BackgroundDescriptor() /*const*/; + + typedef mozilla::layers::ImageContainer ImageContainer; + ImageContainer* GetImageContainer(); + + PPluginBackgroundDestroyerParent* AllocPPluginBackgroundDestroyerParent(); + + bool DeallocPPluginBackgroundDestroyerParent( + PPluginBackgroundDestroyerParent* aActor); + + bool InternalGetValueForNPObject(NPNVariable aVariable, + PPluginScriptableObjectParent** aValue, + NPError* aResult); + + nsPluginInstanceOwner* GetOwner(); + + void SetCurrentImage(layers::Image* aImage); + + // Update Telemetry with the current drawing model. + void RecordDrawingModel(); + + private: + PluginModuleParent* mParent; + NPP mNPP; + const NPNetscapeFuncs* mNPNIface; + nsCString mSrcAttribute; + NPWindowType mWindowType; + int16_t mDrawingModel; + + // Since plugins may request different drawing models to find a compatible + // one, we only record the drawing model after a SetWindow call and if the + // drawing model has changed. + int mLastRecordedDrawingModel; + + nsDataHashtable, PluginScriptableObjectParent*> + mScriptableObjects; + + // This is used to tell the compositor that it should invalidate the + // ImageLayer. + uint32_t mFrameID; + +#if defined(XP_WIN) + // Note: DXGI 1.1 surface handles are global across all processes, and are not + // marshaled. As long as we haven't freed a texture its handle should be valid + // as a unique cross-process identifier for the texture. + nsRefPtrHashtable, D3D11SurfaceHolder> mD3D11Surfaces; +#endif + +#if defined(OS_WIN) + private: + // Used in handling parent/child forwarding of events. + static LRESULT CALLBACK PluginWindowHookProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam); + void SubclassPluginWindow(HWND aWnd); + void UnsubclassPluginWindow(); + + bool MaybeCreateAndParentChildPluginWindow(); + void MaybeCreateChildPopupSurrogate(); + + private: + nsIntRect mPluginPort; + nsIntRect mSharedSize; + HWND mPluginHWND; + // This is used for the normal child plugin HWND for windowed plugins and, + // if needed, also the child popup surrogate HWND for windowless plugins. + HWND mChildPluginHWND; + HWND mChildPluginsParentHWND; + WNDPROC mPluginWndProc; + + struct AsyncSurfaceInfo { + layers::SurfaceDescriptorPlugin mSD; + gfx::IntSize mSize; + }; + // Key is plugin surface's texture's handle + HashMap mAsyncSurfaceMap; +#endif // defined(OS_WIN) + +#if defined(MOZ_WIDGET_COCOA) + private: + Shmem mShSurface; + uint16_t mShWidth; + uint16_t mShHeight; + CGColorSpaceRef mShColorSpace; + RefPtr mIOSurface; + RefPtr mFrontIOSurface; +#endif // definied(MOZ_WIDGET_COCOA) + + // ObjectFrame layer wrapper + RefPtr mFrontSurface; + // For windowless+transparent instances, this surface contains a + // "pretty recent" copy of the pixels under its frame. + // On the plugin side, we use this surface to avoid doing alpha + // recovery when possible. This surface is created and owned by + // the browser, but a "read-only" reference is sent to the plugin. + // + // We have explicitly chosen not to provide any guarantees about + // the consistency of the pixels in |mBackground|. A plugin may + // be able to observe partial updates to the background. + RefPtr mBackground; + + RefPtr mImageContainer; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // ifndef dom_plugins_PluginInstanceParent_h diff --git a/dom/plugins/ipc/PluginInterposeOSX.h b/dom/plugins/ipc/PluginInterposeOSX.h new file mode 100644 index 0000000000..2f10dbd6c7 --- /dev/null +++ b/dom/plugins/ipc/PluginInterposeOSX.h @@ -0,0 +1,137 @@ +// vim:set ts=2 sts=2 sw=2 et cin: +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H +#define DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H + +#include "base/basictypes.h" +#include "nsPoint.h" +#include "npapi.h" + +// Make this includable from non-Objective-C code. +#ifndef __OBJC__ +class NSCursor; +#else +# import +#endif + +// The header file QuickdrawAPI.h is missing on OS X 10.7 and up (though the +// QuickDraw APIs defined in it are still present) -- so we need to supply the +// relevant parts of its contents here. It's likely that Apple will eventually +// remove the APIs themselves (probably in OS X 10.8), so we need to make them +// weak imports, and test for their presence before using them. +#if !defined(__QUICKDRAWAPI__) + +typedef short Bits16[16]; +struct Cursor { + Bits16 data; + Bits16 mask; + Point hotSpot; +}; +typedef struct Cursor Cursor; + +#endif /* __QUICKDRAWAPI__ */ + +namespace mac_plugin_interposing { + +// Class used to serialize NSCursor objects over IPC between processes. +class NSCursorInfo { + public: + enum Type { + TypeCustom, + TypeArrow, + TypeClosedHand, + TypeContextualMenu, // Only supported on OS X 10.6 and up + TypeCrosshair, + TypeDisappearingItem, + TypeDragCopy, // Only supported on OS X 10.6 and up + TypeDragLink, // Only supported on OS X 10.6 and up + TypeIBeam, + TypeNotAllowed, // Only supported on OS X 10.6 and up + TypeOpenHand, + TypePointingHand, + TypeResizeDown, + TypeResizeLeft, + TypeResizeLeftRight, + TypeResizeRight, + TypeResizeUp, + TypeResizeUpDown, + TypeTransparent // Special type + }; + + NSCursorInfo(); + explicit NSCursorInfo(NSCursor* aCursor); + explicit NSCursorInfo(const Cursor* aCursor); + ~NSCursorInfo(); + + NSCursor* GetNSCursor() const; + Type GetType() const; + const char* GetTypeName() const; + nsPoint GetHotSpot() const; + uint8_t* GetCustomImageData() const; + uint32_t GetCustomImageDataLength() const; + + void SetType(Type aType); + void SetHotSpot(nsPoint aHotSpot); + void SetCustomImageData(uint8_t* aData, uint32_t aDataLength); + + static bool GetNativeCursorsSupported(); + + private: + NSCursor* GetTransparentCursor() const; + + Type mType; + // The hot spot's coordinate system is the cursor's coordinate system, and + // has an upper-left origin (in both Cocoa and pre-Cocoa systems). + nsPoint mHotSpot; + uint8_t* mCustomImageData; + uint32_t mCustomImageDataLength; + static int32_t mNativeCursorsSupported; +}; + +namespace parent { + +void OnPluginShowWindow(uint32_t window_id, CGRect window_bounds, bool modal); +void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid); +void OnSetCursor(const NSCursorInfo& cursorInfo); +void OnShowCursor(bool show); +void OnPushCursor(const NSCursorInfo& cursorInfo); +void OnPopCursor(); + +} // namespace parent + +namespace child { + +void SetUpCocoaInterposing(); + +} // namespace child + +} // namespace mac_plugin_interposing + +#endif /* DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H */ diff --git a/dom/plugins/ipc/PluginInterposeOSX.mm b/dom/plugins/ipc/PluginInterposeOSX.mm new file mode 100644 index 0000000000..3f9f3ff747 --- /dev/null +++ b/dom/plugins/ipc/PluginInterposeOSX.mm @@ -0,0 +1,1040 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/basictypes.h" +#include "nsCocoaUtils.h" +#include "PluginModuleChild.h" +#include "nsDebug.h" +#include "PluginInterposeOSX.h" +#include +#import +#import +#import + +using namespace mozilla::plugins; + +namespace mac_plugin_interposing { + +int32_t NSCursorInfo::mNativeCursorsSupported = -1; + +// This constructor may be called from the browser process or the plugin +// process. +NSCursorInfo::NSCursorInfo() + : mType(TypeArrow), + mHotSpot(nsPoint(0, 0)), + mCustomImageData(NULL), + mCustomImageDataLength(0) {} + +NSCursorInfo::NSCursorInfo(NSCursor* aCursor) + : mType(TypeArrow), mHotSpot(nsPoint(0, 0)), mCustomImageData(NULL), mCustomImageDataLength(0) { + // This constructor is only ever called from the plugin process, so the + // following is safe. + if (!GetNativeCursorsSupported()) { + return; + } + + NSPoint hotSpotCocoa = [aCursor hotSpot]; + mHotSpot = nsPoint(hotSpotCocoa.x, hotSpotCocoa.y); + + Class nsCursorClass = [NSCursor class]; + if ([aCursor isEqual:[NSCursor arrowCursor]]) { + mType = TypeArrow; + } else if ([aCursor isEqual:[NSCursor closedHandCursor]]) { + mType = TypeClosedHand; + } else if ([aCursor isEqual:[NSCursor crosshairCursor]]) { + mType = TypeCrosshair; + } else if ([aCursor isEqual:[NSCursor disappearingItemCursor]]) { + mType = TypeDisappearingItem; + } else if ([aCursor isEqual:[NSCursor IBeamCursor]]) { + mType = TypeIBeam; + } else if ([aCursor isEqual:[NSCursor openHandCursor]]) { + mType = TypeOpenHand; + } else if ([aCursor isEqual:[NSCursor pointingHandCursor]]) { + mType = TypePointingHand; + } else if ([aCursor isEqual:[NSCursor resizeDownCursor]]) { + mType = TypeResizeDown; + } else if ([aCursor isEqual:[NSCursor resizeLeftCursor]]) { + mType = TypeResizeLeft; + } else if ([aCursor isEqual:[NSCursor resizeLeftRightCursor]]) { + mType = TypeResizeLeftRight; + } else if ([aCursor isEqual:[NSCursor resizeRightCursor]]) { + mType = TypeResizeRight; + } else if ([aCursor isEqual:[NSCursor resizeUpCursor]]) { + mType = TypeResizeUp; + } else if ([aCursor isEqual:[NSCursor resizeUpDownCursor]]) { + mType = TypeResizeUpDown; + // The following cursor types are only supported on OS X 10.6 and up. + } else if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(contextualMenuCursor)]]) { + mType = TypeContextualMenu; + } else if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(dragCopyCursor)]]) { + mType = TypeDragCopy; + } else if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)] && + [aCursor isEqual:[nsCursorClass performSelector:@selector(dragLinkCursor)]]) { + mType = TypeDragLink; + } else if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)] && + [aCursor + isEqual:[nsCursorClass performSelector:@selector(operationNotAllowedCursor)]]) { + mType = TypeNotAllowed; + } else { + NSImage* image = [aCursor image]; + NSArray* reps = image ? [image representations] : nil; + NSUInteger repsCount = reps ? [reps count] : 0; + if (!repsCount) { + // If we have a custom cursor with no image representations, assume we + // need a transparent cursor. + mType = TypeTransparent; + } else { + CGImageRef cgImage = nil; + // XXX We don't know how to deal with a cursor that doesn't have a + // bitmap image representation. For now we fall back to an arrow + // cursor. + for (NSUInteger i = 0; i < repsCount; ++i) { + id rep = [reps objectAtIndex:i]; + if ([rep isKindOfClass:[NSBitmapImageRep class]]) { + cgImage = [(NSBitmapImageRep*)rep CGImage]; + break; + } + } + if (cgImage) { + CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); + if (data) { + CGImageDestinationRef dest = + ::CGImageDestinationCreateWithData(data, kUTTypePNG, 1, NULL); + if (dest) { + ::CGImageDestinationAddImage(dest, cgImage, NULL); + if (::CGImageDestinationFinalize(dest)) { + uint32_t dataLength = (uint32_t)::CFDataGetLength(data); + mCustomImageData = (uint8_t*)moz_xmalloc(dataLength); + ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData); + mCustomImageDataLength = dataLength; + mType = TypeCustom; + } + ::CFRelease(dest); + } + ::CFRelease(data); + } + } + if (!mCustomImageData) { + mType = TypeArrow; + } + } + } +} + +NSCursorInfo::NSCursorInfo(const Cursor* aCursor) + : mType(TypeArrow), mHotSpot(nsPoint(0, 0)), mCustomImageData(NULL), mCustomImageDataLength(0) { + // This constructor is only ever called from the plugin process, so the + // following is safe. + if (!GetNativeCursorsSupported()) { + return; + } + + mHotSpot = nsPoint(aCursor->hotSpot.h, aCursor->hotSpot.v); + + int width = 16, height = 16; + int bytesPerPixel = 4; + int rowBytes = width * bytesPerPixel; + int bitmapSize = height * rowBytes; + + bool isTransparent = true; + + uint8_t* bitmap = (uint8_t*)moz_xmalloc(bitmapSize); + // The way we create 'bitmap' is largely "borrowed" from Chrome's + // WebCursor::InitFromCursor(). + for (int y = 0; y < height; ++y) { + unsigned short data = aCursor->data[y]; + unsigned short mask = aCursor->mask[y]; + // Change 'data' and 'mask' from big-endian to little-endian, but output + // big-endian data below. + data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF); + mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF); + // It'd be nice to use a gray-scale bitmap. But + // CGBitmapContextCreateImage() (used below) won't work with one that also + // has alpha values. + for (int x = 0; x < width; ++x) { + int offset = (y * rowBytes) + (x * bytesPerPixel); + // Color value + if (data & 0x8000) { + bitmap[offset] = 0x0; + bitmap[offset + 1] = 0x0; + bitmap[offset + 2] = 0x0; + } else { + bitmap[offset] = 0xFF; + bitmap[offset + 1] = 0xFF; + bitmap[offset + 2] = 0xFF; + } + // Mask value + if (mask & 0x8000) { + bitmap[offset + 3] = 0xFF; + isTransparent = false; + } else { + bitmap[offset + 3] = 0x0; + } + data <<= 1; + mask <<= 1; + } + } + + if (isTransparent) { + // If aCursor is transparent, we don't need to serialize custom cursor + // data over IPC. + mType = TypeTransparent; + } else { + CGColorSpaceRef color = ::CGColorSpaceCreateDeviceRGB(); + if (color) { + CGContextRef context = + ::CGBitmapContextCreate(bitmap, width, height, 8, rowBytes, color, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + if (context) { + CGImageRef image = ::CGBitmapContextCreateImage(context); + if (image) { + ::CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0); + if (data) { + CGImageDestinationRef dest = + ::CGImageDestinationCreateWithData(data, kUTTypePNG, 1, NULL); + if (dest) { + ::CGImageDestinationAddImage(dest, image, NULL); + if (::CGImageDestinationFinalize(dest)) { + uint32_t dataLength = (uint32_t)::CFDataGetLength(data); + mCustomImageData = (uint8_t*)moz_xmalloc(dataLength); + ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData); + mCustomImageDataLength = dataLength; + mType = TypeCustom; + } + ::CFRelease(dest); + } + ::CFRelease(data); + } + ::CGImageRelease(image); + } + ::CGContextRelease(context); + } + ::CGColorSpaceRelease(color); + } + } + + free(bitmap); +} + +NSCursorInfo::~NSCursorInfo() { + if (mCustomImageData) { + free(mCustomImageData); + } +} + +NSCursor* NSCursorInfo::GetNSCursor() const { + NSCursor* retval = nil; + + Class nsCursorClass = [NSCursor class]; + switch (mType) { + case TypeArrow: + retval = [NSCursor arrowCursor]; + break; + case TypeClosedHand: + retval = [NSCursor closedHandCursor]; + break; + case TypeCrosshair: + retval = [NSCursor crosshairCursor]; + break; + case TypeDisappearingItem: + retval = [NSCursor disappearingItemCursor]; + break; + case TypeIBeam: + retval = [NSCursor IBeamCursor]; + break; + case TypeOpenHand: + retval = [NSCursor openHandCursor]; + break; + case TypePointingHand: + retval = [NSCursor pointingHandCursor]; + break; + case TypeResizeDown: + retval = [NSCursor resizeDownCursor]; + break; + case TypeResizeLeft: + retval = [NSCursor resizeLeftCursor]; + break; + case TypeResizeLeftRight: + retval = [NSCursor resizeLeftRightCursor]; + break; + case TypeResizeRight: + retval = [NSCursor resizeRightCursor]; + break; + case TypeResizeUp: + retval = [NSCursor resizeUpCursor]; + break; + case TypeResizeUpDown: + retval = [NSCursor resizeUpDownCursor]; + break; + // The following four cursor types are only supported on OS X 10.6 and up. + case TypeContextualMenu: { + if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)]) { + retval = [nsCursorClass performSelector:@selector(contextualMenuCursor)]; + } + break; + } + case TypeDragCopy: { + if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)]) { + retval = [nsCursorClass performSelector:@selector(dragCopyCursor)]; + } + break; + } + case TypeDragLink: { + if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)]) { + retval = [nsCursorClass performSelector:@selector(dragLinkCursor)]; + } + break; + } + case TypeNotAllowed: { + if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)]) { + retval = [nsCursorClass performSelector:@selector(operationNotAllowedCursor)]; + } + break; + } + case TypeTransparent: + retval = GetTransparentCursor(); + break; + default: + break; + } + + if (!retval && mCustomImageData && mCustomImageDataLength) { + CGDataProviderRef provider = ::CGDataProviderCreateWithData(NULL, (const void*)mCustomImageData, + mCustomImageDataLength, NULL); + if (provider) { + CGImageRef cgImage = + ::CGImageCreateWithPNGDataProvider(provider, NULL, false, kCGRenderingIntentDefault); + if (cgImage) { + NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; + if (rep) { + NSImage* image = [[NSImage alloc] init]; + if (image) { + [image addRepresentation:rep]; + retval = + [[[NSCursor alloc] initWithImage:image + hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] autorelease]; + [image release]; + } + [rep release]; + } + ::CGImageRelease(cgImage); + } + ::CFRelease(provider); + } + } + + // Fall back to an arrow cursor if need be. + if (!retval) { + retval = [NSCursor arrowCursor]; + } + + return retval; +} + +// Get a transparent cursor with the appropriate hot spot. We need one if +// (for example) we have a custom cursor with no image data. +NSCursor* NSCursorInfo::GetTransparentCursor() const { + NSCursor* retval = nil; + + int width = 16, height = 16; + int bytesPerPixel = 2; + int rowBytes = width * bytesPerPixel; + int dataSize = height * rowBytes; + + uint8_t* data = (uint8_t*)moz_xmalloc(dataSize); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + int offset = (y * rowBytes) + (x * bytesPerPixel); + data[offset] = 0x7E; // Arbitrary gray-scale value + data[offset + 1] = 0; // Alpha value to make us transparent + } + } + + NSBitmapImageRep* imageRep = + [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil + pixelsWide:width + pixelsHigh:height + bitsPerSample:8 + samplesPerPixel:2 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedWhiteColorSpace + bytesPerRow:rowBytes + bitsPerPixel:16] autorelease]; + if (imageRep) { + uint8_t* repDataPtr = [imageRep bitmapData]; + if (repDataPtr) { + memcpy(repDataPtr, data, dataSize); + NSImage* image = [[[NSImage alloc] initWithSize:NSMakeSize(width, height)] autorelease]; + if (image) { + [image addRepresentation:imageRep]; + retval = [[[NSCursor alloc] initWithImage:image + hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)] autorelease]; + } + } + } + + free(data); + + // Fall back to an arrow cursor if (for some reason) the above code failed. + if (!retval) { + retval = [NSCursor arrowCursor]; + } + + return retval; +} + +NSCursorInfo::Type NSCursorInfo::GetType() const { return mType; } + +const char* NSCursorInfo::GetTypeName() const { + switch (mType) { + case TypeCustom: + return "TypeCustom"; + case TypeArrow: + return "TypeArrow"; + case TypeClosedHand: + return "TypeClosedHand"; + case TypeContextualMenu: + return "TypeContextualMenu"; + case TypeCrosshair: + return "TypeCrosshair"; + case TypeDisappearingItem: + return "TypeDisappearingItem"; + case TypeDragCopy: + return "TypeDragCopy"; + case TypeDragLink: + return "TypeDragLink"; + case TypeIBeam: + return "TypeIBeam"; + case TypeNotAllowed: + return "TypeNotAllowed"; + case TypeOpenHand: + return "TypeOpenHand"; + case TypePointingHand: + return "TypePointingHand"; + case TypeResizeDown: + return "TypeResizeDown"; + case TypeResizeLeft: + return "TypeResizeLeft"; + case TypeResizeLeftRight: + return "TypeResizeLeftRight"; + case TypeResizeRight: + return "TypeResizeRight"; + case TypeResizeUp: + return "TypeResizeUp"; + case TypeResizeUpDown: + return "TypeResizeUpDown"; + case TypeTransparent: + return "TypeTransparent"; + default: + break; + } + return "TypeUnknown"; +} + +nsPoint NSCursorInfo::GetHotSpot() const { return mHotSpot; } + +uint8_t* NSCursorInfo::GetCustomImageData() const { return mCustomImageData; } + +uint32_t NSCursorInfo::GetCustomImageDataLength() const { return mCustomImageDataLength; } + +void NSCursorInfo::SetType(Type aType) { mType = aType; } + +void NSCursorInfo::SetHotSpot(nsPoint aHotSpot) { mHotSpot = aHotSpot; } + +void NSCursorInfo::SetCustomImageData(uint8_t* aData, uint32_t aDataLength) { + if (mCustomImageData) { + free(mCustomImageData); + } + if (aDataLength) { + mCustomImageData = (uint8_t*)moz_xmalloc(aDataLength); + memcpy(mCustomImageData, aData, aDataLength); + } else { + mCustomImageData = NULL; + } + mCustomImageDataLength = aDataLength; +} + +// This should never be called from the browser process -- only from the +// plugin process. +bool NSCursorInfo::GetNativeCursorsSupported() { + if (mNativeCursorsSupported == -1) { + ENSURE_PLUGIN_THREAD(false); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + bool result = pmc->GetNativeCursorsSupported(); + if (result) { + mNativeCursorsSupported = 1; + } else { + mNativeCursorsSupported = 0; + } + } + } + return (mNativeCursorsSupported == 1); +} + +} // namespace mac_plugin_interposing + +namespace mac_plugin_interposing { +namespace parent { + +// Tracks plugin windows currently visible. +std::set plugin_visible_windows_set_; +// Tracks full screen windows currently visible. +std::set plugin_fullscreen_windows_set_; +// Tracks modal windows currently visible. +std::set plugin_modal_windows_set_; + +void OnPluginShowWindow(uint32_t window_id, CGRect window_bounds, bool modal) { + plugin_visible_windows_set_.insert(window_id); + + if (modal) plugin_modal_windows_set_.insert(window_id); + + CGRect main_display_bounds = ::CGDisplayBounds(CGMainDisplayID()); + + if (CGRectEqualToRect(window_bounds, main_display_bounds) && + (plugin_fullscreen_windows_set_.find(window_id) == plugin_fullscreen_windows_set_.end())) { + plugin_fullscreen_windows_set_.insert(window_id); + + nsCocoaUtils::HideOSChromeOnScreen(true); + } +} + +static void ActivateProcess(pid_t pid) { + ProcessSerialNumber process; + OSStatus status = ::GetProcessForPID(pid, &process); + + if (status == noErr) { + SetFrontProcess(&process); + } else { + NS_WARNING("Unable to get process for pid."); + } +} + +// Must be called on the UI thread. +// If plugin_pid is -1, the browser will be the active process on return, +// otherwise that process will be given focus back before this function returns. +static void ReleasePluginFullScreen(pid_t plugin_pid) { + // Releasing full screen only works if we are the frontmost process; grab + // focus, but give it back to the plugin process if requested. + ActivateProcess(base::GetCurrentProcId()); + + nsCocoaUtils::HideOSChromeOnScreen(false); + + if (plugin_pid != -1) { + ActivateProcess(plugin_pid); + } +} + +void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid) { + bool had_windows = !plugin_visible_windows_set_.empty(); + plugin_visible_windows_set_.erase(window_id); + bool browser_needs_activation = had_windows && plugin_visible_windows_set_.empty(); + + plugin_modal_windows_set_.erase(window_id); + if (plugin_fullscreen_windows_set_.find(window_id) != plugin_fullscreen_windows_set_.end()) { + plugin_fullscreen_windows_set_.erase(window_id); + pid_t plugin_pid = browser_needs_activation ? -1 : aPluginPid; + browser_needs_activation = false; + ReleasePluginFullScreen(plugin_pid); + } + + if (browser_needs_activation) { + ActivateProcess(getpid()); + } +} + +void OnSetCursor(const NSCursorInfo& cursorInfo) { + NSCursor* aCursor = cursorInfo.GetNSCursor(); + if (aCursor) { + [aCursor set]; + } +} + +void OnShowCursor(bool show) { + if (show) { + [NSCursor unhide]; + } else { + [NSCursor hide]; + } +} + +void OnPushCursor(const NSCursorInfo& cursorInfo) { + NSCursor* aCursor = cursorInfo.GetNSCursor(); + if (aCursor) { + [aCursor push]; + } +} + +void OnPopCursor() { [NSCursor pop]; } + +} // namespace parent +} // namespace mac_plugin_interposing + +namespace mac_plugin_interposing { +namespace child { + +// TODO(stuartmorgan): Make this an IPC to order the plugin process above the +// browser process only if the browser is current frontmost. +void FocusPluginProcess() { + ProcessSerialNumber this_process, front_process; + if ((GetCurrentProcess(&this_process) != noErr) || (GetFrontProcess(&front_process) != noErr)) { + return; + } + + Boolean matched = false; + if ((SameProcess(&this_process, &front_process, &matched) == noErr) && !matched) { + SetFrontProcess(&this_process); + } +} + +void NotifyBrowserOfPluginShowWindow(uint32_t window_id, CGRect bounds, bool modal) { + ENSURE_PLUGIN_THREAD_VOID(); + + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) pmc->PluginShowWindow(window_id, modal, bounds); +} + +void NotifyBrowserOfPluginHideWindow(uint32_t window_id, CGRect bounds) { + ENSURE_PLUGIN_THREAD_VOID(); + + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) pmc->PluginHideWindow(window_id); +} + +void NotifyBrowserOfSetCursor(NSCursorInfo& aCursorInfo) { + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->SetCursor(aCursorInfo); + } +} + +void NotifyBrowserOfShowCursor(bool show) { + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->ShowCursor(show); + } +} + +void NotifyBrowserOfPushCursor(NSCursorInfo& aCursorInfo) { + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->PushCursor(aCursorInfo); + } +} + +void NotifyBrowserOfPopCursor() { + ENSURE_PLUGIN_THREAD_VOID(); + PluginModuleChild* pmc = PluginModuleChild::GetChrome(); + if (pmc) { + pmc->PopCursor(); + } +} + +struct WindowInfo { + uint32_t window_id; + CGRect bounds; + explicit WindowInfo(NSWindow* aWindow) { + NSInteger window_num = [aWindow windowNumber]; + window_id = window_num > 0 ? window_num : 0; + bounds = NSRectToCGRect([aWindow frame]); + } +}; + +static void OnPluginWindowClosed(const WindowInfo& window_info) { + if (window_info.window_id == 0) return; + mac_plugin_interposing::child::NotifyBrowserOfPluginHideWindow(window_info.window_id, + window_info.bounds); +} + +static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) { + // The window id is 0 if it has never been shown (including while it is the + // process of being shown for the first time); when that happens, we'll catch + // it in _setWindowNumber instead. + static BOOL s_pending_display_is_modal = NO; + if (window_info.window_id == 0) { + if (is_modal) s_pending_display_is_modal = YES; + return; + } + if (s_pending_display_is_modal) { + is_modal = YES; + s_pending_display_is_modal = NO; + } + mac_plugin_interposing::child::NotifyBrowserOfPluginShowWindow(window_info.window_id, + window_info.bounds, is_modal); +} + +static BOOL OnSetCursor(NSCursorInfo& aInfo) { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfSetCursor(aInfo); + return YES; + } + return NO; +} + +static BOOL OnHideCursor() { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfShowCursor(NO); + return YES; + } + return NO; +} + +static BOOL OnUnhideCursor() { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfShowCursor(YES); + return YES; + } + return NO; +} + +static BOOL OnPushCursor(NSCursorInfo& aInfo) { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfPushCursor(aInfo); + return YES; + } + return NO; +} + +static BOOL OnPopCursor() { + if (NSCursorInfo::GetNativeCursorsSupported()) { + NotifyBrowserOfPopCursor(); + return YES; + } + return NO; +} + +} // namespace child +} // namespace mac_plugin_interposing + +using namespace mac_plugin_interposing::child; + +@interface NSWindow (PluginInterposing) +- (void)pluginInterpose_orderOut:(id)sender; +- (void)pluginInterpose_orderFront:(id)sender; +- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender; +- (void)pluginInterpose_setWindowNumber:(NSInteger)num; +@end + +@implementation NSWindow (PluginInterposing) + +- (void)pluginInterpose_orderOut:(id)sender { + WindowInfo window_info(self); + [self pluginInterpose_orderOut:sender]; + OnPluginWindowClosed(window_info); +} + +- (void)pluginInterpose_orderFront:(id)sender { + mac_plugin_interposing::child::FocusPluginProcess(); + [self pluginInterpose_orderFront:sender]; + OnPluginWindowShown(WindowInfo(self), NO); +} + +- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender { + mac_plugin_interposing::child::FocusPluginProcess(); + [self pluginInterpose_makeKeyAndOrderFront:sender]; + OnPluginWindowShown(WindowInfo(self), NO); +} + +- (void)pluginInterpose_setWindowNumber:(NSInteger)num { + if (num > 0) mac_plugin_interposing::child::FocusPluginProcess(); + [self pluginInterpose_setWindowNumber:num]; + if (num > 0) OnPluginWindowShown(WindowInfo(self), NO); +} + +@end + +@interface NSApplication (PluginInterposing) +- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window; +@end + +@implementation NSApplication (PluginInterposing) + +- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window { + mac_plugin_interposing::child::FocusPluginProcess(); + // This is out-of-order relative to the other calls, but runModalForWindow: + // won't return until the window closes, and the order only matters for + // full-screen windows. + OnPluginWindowShown(WindowInfo(window), YES); + return [self pluginInterpose_runModalForWindow:window]; +} + +@end + +// Hook commands to manipulate the current cursor, so that they can be passed +// from the child process to the parent process. These commands have no +// effect unless they're performed in the parent process. +@interface NSCursor (PluginInterposing) +- (void)pluginInterpose_set; +- (void)pluginInterpose_push; +- (void)pluginInterpose_pop; ++ (NSCursor*)pluginInterpose_currentCursor; ++ (void)pluginInterpose_hide; ++ (void)pluginInterpose_unhide; ++ (void)pluginInterpose_pop; +@end + +// Cache the results of [NSCursor set], [NSCursor push] and [NSCursor pop]. +// The last element is always the current cursor. +static NSMutableArray* gCursorStack = nil; + +static BOOL initCursorStack() { + if (!gCursorStack) { + gCursorStack = [[NSMutableArray arrayWithCapacity:5] retain]; + } + return (gCursorStack != NULL); +} + +static NSCursor* currentCursorFromCache() { + if (!initCursorStack()) return nil; + return (NSCursor*)[gCursorStack lastObject]; +} + +static void setCursorInCache(NSCursor* aCursor) { + if (!initCursorStack() || !aCursor) return; + NSUInteger count = [gCursorStack count]; + if (count) { + [gCursorStack replaceObjectAtIndex:count - 1 withObject:aCursor]; + } else { + [gCursorStack addObject:aCursor]; + } +} + +static void pushCursorInCache(NSCursor* aCursor) { + if (!initCursorStack() || !aCursor) return; + [gCursorStack addObject:aCursor]; +} + +static void popCursorInCache() { + if (!initCursorStack()) return; + // Apple's doc on the +[NSCursor pop] method says: "If the current cursor + // is the only cursor on the stack, this method does nothing." + if ([gCursorStack count] > 1) { + [gCursorStack removeLastObject]; + } +} + +@implementation NSCursor (PluginInterposing) + +- (void)pluginInterpose_set { + NSCursorInfo info(self); + OnSetCursor(info); + setCursorInCache(self); + [self pluginInterpose_set]; +} + +- (void)pluginInterpose_push { + NSCursorInfo info(self); + OnPushCursor(info); + pushCursorInCache(self); + [self pluginInterpose_push]; +} + +- (void)pluginInterpose_pop { + OnPopCursor(); + popCursorInCache(); + [self pluginInterpose_pop]; +} + +// The currentCursor method always returns nil when running in a background +// process. But this may confuse plugins (notably Flash, see bug 621117). So +// if we get a nil return from the "call to super", we return a cursor that's +// been cached by previous calls to set or push. According to Apple's docs, +// currentCursor "only returns the cursor set by your application using +// NSCursor methods". So we don't need to worry about changes to the cursor +// made by other methods like SetThemeCursor(). ++ (NSCursor*)pluginInterpose_currentCursor { + NSCursor* retval = [self pluginInterpose_currentCursor]; + if (!retval) { + retval = currentCursorFromCache(); + } + return retval; +} + ++ (void)pluginInterpose_hide { + OnHideCursor(); + [self pluginInterpose_hide]; +} + ++ (void)pluginInterpose_unhide { + OnUnhideCursor(); + [self pluginInterpose_unhide]; +} + ++ (void)pluginInterpose_pop { + OnPopCursor(); + popCursorInCache(); + [self pluginInterpose_pop]; +} + +@end + +static void ExchangeMethods(Class target_class, BOOL class_method, SEL original, SEL replacement) { + Method m1; + Method m2; + if (class_method) { + m1 = class_getClassMethod(target_class, original); + m2 = class_getClassMethod(target_class, replacement); + } else { + m1 = class_getInstanceMethod(target_class, original); + m2 = class_getInstanceMethod(target_class, replacement); + } + + if (m1 == m2) return; + + if (m1 && m2) + method_exchangeImplementations(m1, m2); + else + MOZ_ASSERT_UNREACHABLE("Cocoa swizzling failed"); +} + +namespace mac_plugin_interposing { +namespace child { + +void SetUpCocoaInterposing() { + Class nswindow_class = [NSWindow class]; + ExchangeMethods(nswindow_class, NO, @selector(orderOut:), @selector(pluginInterpose_orderOut:)); + ExchangeMethods(nswindow_class, NO, @selector(orderFront:), + @selector(pluginInterpose_orderFront:)); + ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:), + @selector(pluginInterpose_makeKeyAndOrderFront:)); + ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:), + @selector(pluginInterpose_setWindowNumber:)); + + ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:), + @selector(pluginInterpose_runModalForWindow:)); + + Class nscursor_class = [NSCursor class]; + ExchangeMethods(nscursor_class, NO, @selector(set), @selector(pluginInterpose_set)); + ExchangeMethods(nscursor_class, NO, @selector(push), @selector(pluginInterpose_push)); + ExchangeMethods(nscursor_class, NO, @selector(pop), @selector(pluginInterpose_pop)); + ExchangeMethods(nscursor_class, YES, @selector(currentCursor), + @selector(pluginInterpose_currentCursor)); + ExchangeMethods(nscursor_class, YES, @selector(hide), @selector(pluginInterpose_hide)); + ExchangeMethods(nscursor_class, YES, @selector(unhide), @selector(pluginInterpose_unhide)); + ExchangeMethods(nscursor_class, YES, @selector(pop), @selector(pluginInterpose_pop)); +} + +} // namespace child +} // namespace mac_plugin_interposing + +// Called from plugin_child_interpose.mm, which hooks calls to +// SetCursor() (the QuickDraw call) from the plugin child process. +extern "C" NS_VISIBILITY_DEFAULT BOOL +mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor) { + NSCursorInfo info(cursor); + return OnSetCursor(info); +} + +// Called from plugin_child_interpose.mm, which hooks calls to +// SetThemeCursor() (the Appearance Manager call) from the plugin child +// process. +extern "C" NS_VISIBILITY_DEFAULT BOOL +mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor) { + NSCursorInfo info; + switch (cursor) { + case kThemeArrowCursor: + info.SetType(NSCursorInfo::TypeArrow); + break; + case kThemeCopyArrowCursor: + info.SetType(NSCursorInfo::TypeDragCopy); + break; + case kThemeAliasArrowCursor: + info.SetType(NSCursorInfo::TypeDragLink); + break; + case kThemeContextualMenuArrowCursor: + info.SetType(NSCursorInfo::TypeContextualMenu); + break; + case kThemeIBeamCursor: + info.SetType(NSCursorInfo::TypeIBeam); + break; + case kThemeCrossCursor: + case kThemePlusCursor: + info.SetType(NSCursorInfo::TypeCrosshair); + break; + case kThemeWatchCursor: + case kThemeSpinningCursor: + info.SetType(NSCursorInfo::TypeArrow); + break; + case kThemeClosedHandCursor: + info.SetType(NSCursorInfo::TypeClosedHand); + break; + case kThemeOpenHandCursor: + info.SetType(NSCursorInfo::TypeOpenHand); + break; + case kThemePointingHandCursor: + case kThemeCountingUpHandCursor: + case kThemeCountingDownHandCursor: + case kThemeCountingUpAndDownHandCursor: + info.SetType(NSCursorInfo::TypePointingHand); + break; + case kThemeResizeLeftCursor: + info.SetType(NSCursorInfo::TypeResizeLeft); + break; + case kThemeResizeRightCursor: + info.SetType(NSCursorInfo::TypeResizeRight); + break; + case kThemeResizeLeftRightCursor: + info.SetType(NSCursorInfo::TypeResizeLeftRight); + break; + case kThemeNotAllowedCursor: + info.SetType(NSCursorInfo::TypeNotAllowed); + break; + case kThemeResizeUpCursor: + info.SetType(NSCursorInfo::TypeResizeUp); + break; + case kThemeResizeDownCursor: + info.SetType(NSCursorInfo::TypeResizeDown); + break; + case kThemeResizeUpDownCursor: + info.SetType(NSCursorInfo::TypeResizeUpDown); + break; + case kThemePoofCursor: + info.SetType(NSCursorInfo::TypeDisappearingItem); + break; + default: + info.SetType(NSCursorInfo::TypeArrow); + break; + } + return OnSetCursor(info); +} + +extern "C" NS_VISIBILITY_DEFAULT BOOL mac_plugin_interposing_child_OnHideCursor() { + return OnHideCursor(); +} + +extern "C" NS_VISIBILITY_DEFAULT BOOL mac_plugin_interposing_child_OnShowCursor() { + return OnUnhideCursor(); +} diff --git a/dom/plugins/ipc/PluginLibrary.h b/dom/plugins/ipc/PluginLibrary.h new file mode 100644 index 0000000000..ce8cc3af17 --- /dev/null +++ b/dom/plugins/ipc/PluginLibrary.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_PluginLibrary_h +#define mozilla_PluginLibrary_h 1 + +#include "prlink.h" +#include "npapi.h" +#include "npfunctions.h" +#include "nscore.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nsError.h" +#include "mozilla/EventForwards.h" +#include "nsSize.h" +#include "nsRect.h" + +class nsNPAPIPlugin; + +namespace mozilla { +namespace gfx { +class DrawTarget; +} +namespace layers { +class Image; +class ImageContainer; +} // namespace layers +} // namespace mozilla + +class nsIClearSiteDataCallback; + +#define nsIGetSitesWithDataCallback_CID \ + { \ + 0xd0028b83, 0xfdf9, 0x4c53, { \ + 0xb7, 0xbb, 0x47, 0x46, 0x0f, 0x6b, 0x83, 0x6c \ + } \ + } +class nsIGetSitesWithDataCallback : public nsISupports { + public: + NS_IMETHOD SitesWithData(nsTArray& result) = 0; + NS_DECLARE_STATIC_IID_ACCESSOR(nsIGetSitesWithDataCallback_CID) +}; +NS_DEFINE_STATIC_IID_ACCESSOR(nsIGetSitesWithDataCallback, + nsIGetSitesWithDataCallback_CID) + +namespace mozilla { + +class PluginLibrary { + public: + typedef mozilla::gfx::DrawTarget DrawTarget; + + virtual ~PluginLibrary() = default; + + /** + * Inform this library about the nsNPAPIPlugin which owns it. This + * object will hold a weak pointer to the plugin. + */ + virtual void SetPlugin(nsNPAPIPlugin* plugin) = 0; + + virtual bool HasRequiredFunctions() = 0; + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, + NPError* error) = 0; +#else + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) = 0; +#endif + virtual nsresult NP_Shutdown(NPError* error) = 0; + virtual nsresult NP_GetMIMEDescription(const char** mimeDesc) = 0; + virtual nsresult NP_GetValue(void* future, NPPVariable aVariable, + void* aValue, NPError* error) = 0; +#if defined(XP_WIN) || defined(XP_MACOSX) + virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) = 0; +#endif + virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance, int16_t argc, + char* argn[], char* argv[], NPSavedData* saved, + NPError* error) = 0; + + virtual nsresult NPP_ClearSiteData( + const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr callback) = 0; + virtual nsresult NPP_GetSitesWithData( + nsCOMPtr callback) = 0; + + virtual nsresult AsyncSetWindow(NPP instance, NPWindow* window) = 0; + virtual nsresult GetImageContainer( + NPP instance, mozilla::layers::ImageContainer** aContainer) = 0; + virtual nsresult GetImageSize(NPP instance, nsIntSize* aSize) = 0; + virtual void DidComposite(NPP instance) = 0; + virtual bool IsOOP() = 0; +#if defined(XP_MACOSX) + virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, + bool* aDrawing) = 0; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + virtual nsresult ContentsScaleFactorChanged(NPP instance, + double aContentsScaleFactor) = 0; +#endif +#if defined(XP_WIN) + virtual nsresult GetScrollCaptureContainer( + NPP aInstance, mozilla::layers::ImageContainer** aContainer) = 0; +#endif + virtual nsresult HandledWindowedPluginKeyEvent( + NPP aInstance, const mozilla::NativeEventData& aNativeKeyData, + bool aIsCOnsumed) = 0; + + /** + * The next three methods are the third leg in the trip to + * PluginInstanceParent. They approximately follow the ReadbackSink + * API. + */ + virtual nsresult SetBackgroundUnknown(NPP instance) = 0; + virtual nsresult BeginUpdateBackground(NPP instance, const nsIntRect&, + DrawTarget**) = 0; + virtual nsresult EndUpdateBackground(NPP instance, const nsIntRect&) = 0; + virtual nsresult GetRunID(uint32_t* aRunID) = 0; + virtual void SetHasLocalInstance() = 0; +}; + +} // namespace mozilla + +#endif // ifndef mozilla_PluginLibrary_h diff --git a/dom/plugins/ipc/PluginMessageUtils.cpp b/dom/plugins/ipc/PluginMessageUtils.cpp new file mode 100644 index 0000000000..4d639b6f39 --- /dev/null +++ b/dom/plugins/ipc/PluginMessageUtils.cpp @@ -0,0 +1,141 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8; -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginMessageUtils.h" +#include "nsThreadUtils.h" + +#include "PluginInstanceParent.h" +#include "PluginInstanceChild.h" +#include "PluginScriptableObjectParent.h" +#include "PluginScriptableObjectChild.h" + +using std::string; + +using mozilla::ipc::MessageChannel; + +namespace { + +class DeferNPObjectReleaseRunnable : public mozilla::Runnable { + public: + DeferNPObjectReleaseRunnable(const NPNetscapeFuncs* f, NPObject* o) + : Runnable("DeferNPObjectReleaseRunnable"), mFuncs(f), mObject(o) { + NS_ASSERTION(o, "no release null objects"); + } + + NS_IMETHOD Run() override; + + private: + const NPNetscapeFuncs* mFuncs; + NPObject* mObject; +}; + +NS_IMETHODIMP +DeferNPObjectReleaseRunnable::Run() { + mFuncs->releaseobject(mObject); + return NS_OK; +} + +} // namespace + +namespace mozilla::plugins { + +NPRemoteWindow::NPRemoteWindow() + : window(0), + x(0), + y(0), + width(0), + height(0), + type(NPWindowTypeDrawable) +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + , + visualID(0), + colormap(0) +#endif /* XP_UNIX */ +#if defined(XP_MACOSX) + , + contentsScaleFactor(1.0) +#endif +{ + clipRect.top = 0; + clipRect.left = 0; + clipRect.bottom = 0; + clipRect.right = 0; +} + +ipc::RacyInterruptPolicy MediateRace(const MessageChannel::MessageInfo& parent, + const MessageChannel::MessageInfo& child) { + switch (parent.type()) { + case PPluginInstance::Msg_Paint__ID: + case PPluginInstance::Msg_NPP_SetWindow__ID: + case PPluginInstance::Msg_NPP_HandleEvent_Shmem__ID: + case PPluginInstance::Msg_NPP_HandleEvent_IOSurface__ID: + // our code relies on the frame list not changing during paints and + // reflows + return ipc::RIPParentWins; + + default: + return ipc::RIPChildWins; + } +} + +#if defined(OS_LINUX) || defined(OS_SOLARIS) +static string ReplaceAll(const string& haystack, const string& needle, + const string& with) { + string munged = haystack; + string::size_type i = 0; + + while (string::npos != (i = munged.find(needle, i))) { + munged.replace(i, needle.length(), with); + i += with.length(); + } + + return munged; +} +#endif + +string MungePluginDsoPath(const string& path) { +#if defined(OS_LINUX) || defined(OS_SOLARIS) + // https://bugzilla.mozilla.org/show_bug.cgi?id=519601 + return ReplaceAll(path, "netscape", "netsc@pe"); +#else + return path; +#endif +} + +string UnmungePluginDsoPath(const string& munged) { +#if defined(OS_LINUX) || defined(OS_SOLARIS) + return ReplaceAll(munged, "netsc@pe", "netscape"); +#else + return munged; +#endif +} + +LogModule* GetPluginLog() { + static LazyLogModule sLog("IPCPlugins"); + return sLog; +} + +void DeferNPObjectLastRelease(const NPNetscapeFuncs* f, NPObject* o) { + if (!o) return; + + if (o->referenceCount > 1) { + f->releaseobject(o); + return; + } + + NS_DispatchToCurrentThread(new DeferNPObjectReleaseRunnable(f, o)); +} + +void DeferNPVariantLastRelease(const NPNetscapeFuncs* f, NPVariant* v) { + if (!NPVARIANT_IS_OBJECT(*v)) { + f->releasevariantvalue(v); + return; + } + DeferNPObjectLastRelease(f, v->value.objectValue); + VOID_TO_NPVARIANT(*v); +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/PluginMessageUtils.h b/dom/plugins/ipc/PluginMessageUtils.h new file mode 100644 index 0000000000..c21aa10966 --- /dev/null +++ b/dom/plugins/ipc/PluginMessageUtils.h @@ -0,0 +1,662 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_PLUGINS_PLUGINMESSAGEUTILS_H +#define DOM_PLUGINS_PLUGINMESSAGEUTILS_H + +#include "ipc/EnumSerializer.h" +#include "base/message_loop.h" +#include "base/shared_memory.h" + +#include "mozilla/ipc/CrossProcessMutex.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/UniquePtr.h" +#include "gfxipc/SurfaceDescriptor.h" + +#include "npapi.h" +#include "npruntime.h" +#include "npfunctions.h" +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/Logging.h" +#include "nsHashKeys.h" + +#ifdef XP_MACOSX +# include "PluginInterposeOSX.h" +#else +namespace mac_plugin_interposing { +class NSCursorInfo {}; +} // namespace mac_plugin_interposing +#endif +using mac_plugin_interposing::NSCursorInfo; + +namespace mozilla { +namespace plugins { + +using layers::SurfaceDescriptorX11; + +enum ScriptableObjectType { LocalObject, Proxy }; + +mozilla::ipc::RacyInterruptPolicy MediateRace( + const mozilla::ipc::MessageChannel::MessageInfo& parent, + const mozilla::ipc::MessageChannel::MessageInfo& child); + +std::string MungePluginDsoPath(const std::string& path); +std::string UnmungePluginDsoPath(const std::string& munged); + +extern mozilla::LogModule* GetPluginLog(); + +#if defined(_MSC_VER) +# define FULLFUNCTION __FUNCSIG__ +#elif defined(__GNUC__) +# define FULLFUNCTION __PRETTY_FUNCTION__ +#else +# define FULLFUNCTION __FUNCTION__ +#endif + +#define PLUGIN_LOG_DEBUG(args) \ + MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, args) +#define PLUGIN_LOG_DEBUG_FUNCTION \ + MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, ("%s", FULLFUNCTION)) +#define PLUGIN_LOG_DEBUG_METHOD \ + MOZ_LOG(GetPluginLog(), mozilla::LogLevel::Debug, \ + ("%s [%p]", FULLFUNCTION, (void*)this)) + +/** + * This is NPByteRange without the linked list. + */ +struct IPCByteRange { + int32_t offset; + uint32_t length; +}; + +typedef nsTArray IPCByteRanges; + +typedef nsCString Buffer; + +struct NPRemoteWindow { + NPRemoteWindow(); + uint64_t window; + int32_t x; + int32_t y; + uint32_t width; + uint32_t height; + NPRect clipRect; + NPWindowType type; +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + VisualID visualID; + Colormap colormap; +#endif /* XP_UNIX */ +#if defined(XP_MACOSX) || defined(XP_WIN) + double contentsScaleFactor; +#endif +}; + +// This struct is like NPAudioDeviceChangeDetails, only it uses a +// std::wstring instead of a const wchar_t* for the defaultDevice. +// This gives us the necessary memory-ownership semantics without +// requiring C++ objects in npapi.h. +struct NPAudioDeviceChangeDetailsIPC { + int32_t flow; + int32_t role; + std::wstring defaultDevice; +}; + +struct NPAudioDeviceStateChangedIPC { + std::wstring device; + uint32_t state; +}; + +#ifdef XP_WIN +typedef HWND NativeWindowHandle; +#elif defined(MOZ_X11) +typedef XID NativeWindowHandle; +#elif defined(XP_DARWIN) || defined(ANDROID) || defined(MOZ_WAYLAND) +typedef intptr_t NativeWindowHandle; // never actually used, will always be 0 +#else +# error Need NativeWindowHandle for this platform +#endif + +#ifdef XP_WIN +typedef base::SharedMemoryHandle WindowsSharedMemoryHandle; +typedef HANDLE DXGISharedSurfaceHandle; +#else // XP_WIN +typedef mozilla::null_t WindowsSharedMemoryHandle; +typedef mozilla::null_t DXGISharedSurfaceHandle; +#endif + +// XXX maybe not the best place for these. better one? + +#define VARSTR(v_) \ + case v_: \ + return #v_ +inline const char* NPPVariableToString(NPPVariable aVar) { + switch (aVar) { + VARSTR(NPPVpluginNameString); + VARSTR(NPPVpluginDescriptionString); + VARSTR(NPPVpluginWindowBool); + VARSTR(NPPVpluginTransparentBool); + VARSTR(NPPVjavaClass); + VARSTR(NPPVpluginWindowSize); + VARSTR(NPPVpluginTimerInterval); + + VARSTR(NPPVpluginScriptableInstance); + VARSTR(NPPVpluginScriptableIID); + + VARSTR(NPPVjavascriptPushCallerBool); + + VARSTR(NPPVpluginKeepLibraryInMemory); + VARSTR(NPPVpluginNeedsXEmbed); + + VARSTR(NPPVpluginScriptableNPObject); + + VARSTR(NPPVformValue); + + VARSTR(NPPVpluginUrlRequestsDisplayedBool); + + VARSTR(NPPVpluginWantsAllNetworkStreams); + +#ifdef XP_MACOSX + VARSTR(NPPVpluginDrawingModel); + VARSTR(NPPVpluginEventModel); +#endif + +#ifdef XP_WIN + VARSTR(NPPVpluginRequiresAudioDeviceChanges); +#endif + + default: + return "???"; + } +} + +inline const char* NPNVariableToString(NPNVariable aVar) { + switch (aVar) { + VARSTR(NPNVxDisplay); + VARSTR(NPNVxtAppContext); + VARSTR(NPNVnetscapeWindow); + VARSTR(NPNVjavascriptEnabledBool); + VARSTR(NPNVasdEnabledBool); + VARSTR(NPNVisOfflineBool); + + VARSTR(NPNVserviceManager); + VARSTR(NPNVDOMElement); + VARSTR(NPNVDOMWindow); + VARSTR(NPNVToolkit); + VARSTR(NPNVSupportsXEmbedBool); + + VARSTR(NPNVWindowNPObject); + + VARSTR(NPNVPluginElementNPObject); + + VARSTR(NPNVSupportsWindowless); + + VARSTR(NPNVprivateModeBool); + VARSTR(NPNVdocumentOrigin); + +#ifdef XP_WIN + VARSTR(NPNVaudioDeviceChangeDetails); +#endif + + default: + return "???"; + } +} +#undef VARSTR + +inline bool IsPluginThread() { + MessageLoop* loop = MessageLoop::current(); + if (!loop) return false; + return (loop->type() == MessageLoop::TYPE_UI); +} + +inline void AssertPluginThread() { + MOZ_RELEASE_ASSERT(IsPluginThread(), + "Should be on the plugin's main thread!"); +} + +#define ENSURE_PLUGIN_THREAD(retval) \ + PR_BEGIN_MACRO \ + if (!IsPluginThread()) { \ + NS_WARNING("Not running on the plugin's main thread!"); \ + return (retval); \ + } \ + PR_END_MACRO + +#define ENSURE_PLUGIN_THREAD_VOID() \ + PR_BEGIN_MACRO \ + if (!IsPluginThread()) { \ + NS_WARNING("Not running on the plugin's main thread!"); \ + return; \ + } \ + PR_END_MACRO + +void DeferNPObjectLastRelease(const NPNetscapeFuncs* f, NPObject* o); +void DeferNPVariantLastRelease(const NPNetscapeFuncs* f, NPVariant* v); + +inline bool IsDrawingModelDirect(int16_t aModel) { + return aModel == NPDrawingModelAsyncBitmapSurface +#if defined(XP_WIN) + || aModel == NPDrawingModelAsyncWindowsDXGISurface +#endif + ; +} + +// in NPAPI, char* == nullptr is sometimes meaningful. the following is +// helper code for dealing with nullable nsCString's +inline nsCString NullableString(const char* aString) { + if (!aString) { + return VoidCString(); + } + return nsCString(aString); +} + +inline const char* NullableStringGet(const nsCString& str) { + if (str.IsVoid()) return nullptr; + + return str.get(); +} + +struct DeletingObjectEntry : public nsPtrHashKey { + explicit DeletingObjectEntry(const NPObject* key) + : nsPtrHashKey(key), mDeleted(false) {} + + bool mDeleted; +}; + +} /* namespace plugins */ + +} /* namespace mozilla */ + +namespace IPC { + +template <> +struct ParamTraits { + typedef NPRect paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.top); + WriteParam(aMsg, aParam.left); + WriteParam(aMsg, aParam.bottom); + WriteParam(aMsg, aParam.right); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint16_t top, left, bottom, right; + if (ReadParam(aMsg, aIter, &top) && ReadParam(aMsg, aIter, &left) && + ReadParam(aMsg, aIter, &bottom) && ReadParam(aMsg, aIter, &right)) { + aResult->top = top; + aResult->left = left; + aResult->bottom = bottom; + aResult->right = right; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%u, %u, %u, %u]", aParam.top, aParam.left, + aParam.bottom, aParam.right)); + } +}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + NPWindowType, NPWindowType::NPWindowTypeWindow, + NPWindowType::NPWindowTypeDrawable> {}; + +template <> +struct ParamTraits { + typedef mozilla::plugins::NPRemoteWindow paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteUInt64(aParam.window); + WriteParam(aMsg, aParam.x); + WriteParam(aMsg, aParam.y); + WriteParam(aMsg, aParam.width); + WriteParam(aMsg, aParam.height); + WriteParam(aMsg, aParam.clipRect); + WriteParam(aMsg, aParam.type); +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + aMsg->WriteULong(aParam.visualID); + aMsg->WriteULong(aParam.colormap); +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + aMsg->WriteDouble(aParam.contentsScaleFactor); +#endif + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint64_t window; + int32_t x, y; + uint32_t width, height; + NPRect clipRect; + NPWindowType type; + if (!(aMsg->ReadUInt64(aIter, &window) && ReadParam(aMsg, aIter, &x) && + ReadParam(aMsg, aIter, &y) && ReadParam(aMsg, aIter, &width) && + ReadParam(aMsg, aIter, &height) && + ReadParam(aMsg, aIter, &clipRect) && ReadParam(aMsg, aIter, &type))) + return false; + +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + unsigned long visualID; + unsigned long colormap; + if (!(aMsg->ReadULong(aIter, &visualID) && + aMsg->ReadULong(aIter, &colormap))) + return false; +#endif + +#if defined(XP_MACOSX) || defined(XP_WIN) + double contentsScaleFactor; + if (!aMsg->ReadDouble(aIter, &contentsScaleFactor)) return false; +#endif + + aResult->window = window; + aResult->x = x; + aResult->y = y; + aResult->width = width; + aResult->height = height; + aResult->clipRect = clipRect; + aResult->type = type; +#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX) + aResult->visualID = visualID; + aResult->colormap = colormap; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + aResult->contentsScaleFactor = contentsScaleFactor; +#endif + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%u, %d, %d, %u, %u, %d", + (unsigned long)aParam.window, aParam.x, aParam.y, + aParam.width, aParam.height, (long)aParam.type)); + } +}; + +#ifdef XP_MACOSX +template <> +struct ParamTraits { + // Empty string writes a length of 0 and no buffer. + // We don't write a nullptr terminating character in buffers. + static void Write(Message* aMsg, NPNSString* aParam) { + CFStringRef cfString = (CFStringRef)aParam; + + // Write true if we have a string, false represents nullptr. + aMsg->WriteBool(!!cfString); + if (!cfString) { + return; + } + + long length = ::CFStringGetLength(cfString); + WriteParam(aMsg, length); + if (length == 0) { + return; + } + + // Attempt to get characters without any allocation/conversion. + if (::CFStringGetCharactersPtr(cfString)) { + aMsg->WriteBytes(::CFStringGetCharactersPtr(cfString), + length * sizeof(UniChar)); + } else { + UniChar* buffer = (UniChar*)moz_xmalloc(length * sizeof(UniChar)); + ::CFStringGetCharacters(cfString, ::CFRangeMake(0, length), buffer); + aMsg->WriteBytes(buffer, length * sizeof(UniChar)); + free(buffer); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + NPNSString** aResult) { + bool haveString = false; + if (!aMsg->ReadBool(aIter, &haveString)) { + return false; + } + if (!haveString) { + *aResult = nullptr; + return true; + } + + long length; + if (!ReadParam(aMsg, aIter, &length)) { + return false; + } + + // Avoid integer multiplication overflow. + if (length > INT_MAX / static_cast(sizeof(UniChar))) { + return false; + } + + auto chars = mozilla::MakeUnique(length); + if (length != 0) { + if (!aMsg->ReadBytesInto(aIter, chars.get(), length * sizeof(UniChar))) { + return false; + } + } + + *aResult = (NPNSString*)::CFStringCreateWithBytes( + kCFAllocatorDefault, (UInt8*)chars.get(), length * sizeof(UniChar), + kCFStringEncodingUTF16, false); + if (!*aResult) { + return false; + } + + return true; + } +}; +#endif + +#ifdef XP_MACOSX +template <> +struct ParamTraits { + typedef NSCursorInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + NSCursorInfo::Type type = aParam.GetType(); + + aMsg->WriteInt(type); + + nsPoint hotSpot = aParam.GetHotSpot(); + WriteParam(aMsg, hotSpot.x); + WriteParam(aMsg, hotSpot.y); + + uint32_t dataLength = aParam.GetCustomImageDataLength(); + WriteParam(aMsg, dataLength); + if (dataLength == 0) { + return; + } + + uint8_t* buffer = (uint8_t*)moz_xmalloc(dataLength); + memcpy(buffer, aParam.GetCustomImageData(), dataLength); + aMsg->WriteBytes(buffer, dataLength); + free(buffer); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + NSCursorInfo::Type type; + if (!aMsg->ReadInt(aIter, (int*)&type)) { + return false; + } + + nscoord hotSpotX, hotSpotY; + if (!ReadParam(aMsg, aIter, &hotSpotX) || + !ReadParam(aMsg, aIter, &hotSpotY)) { + return false; + } + + uint32_t dataLength; + if (!ReadParam(aMsg, aIter, &dataLength)) { + return false; + } + + auto data = mozilla::MakeUnique(dataLength); + if (dataLength != 0) { + if (!aMsg->ReadBytesInto(aIter, data.get(), dataLength)) { + return false; + } + } + + aResult->SetType(type); + aResult->SetHotSpot(nsPoint(hotSpotX, hotSpotY)); + aResult->SetCustomImageData(data.get(), dataLength); + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + const char* typeName = aParam.GetTypeName(); + nsPoint hotSpot = aParam.GetHotSpot(); + int hotSpotX, hotSpotY; +# ifdef NS_COORD_IS_FLOAT + hotSpotX = rint(hotSpot.x); + hotSpotY = rint(hotSpot.y); +# else + hotSpotX = hotSpot.x; + hotSpotY = hotSpot.y; +# endif + uint32_t dataLength = aParam.GetCustomImageDataLength(); + uint8_t* data = aParam.GetCustomImageData(); + + aLog->append(StringPrintf(L"[%s, (%i %i), %u, %p]", typeName, hotSpotX, + hotSpotY, dataLength, data)); + } +}; +#else +template <> +struct ParamTraits { + typedef NSCursorInfo paramType; + static void Write(Message* aMsg, const paramType& aParam) { + MOZ_CRASH("NSCursorInfo isn't meaningful on this platform"); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + MOZ_CRASH("NSCursorInfo isn't meaningful on this platform"); + return false; + } +}; +#endif // #ifdef XP_MACOSX + +template <> +struct ParamTraits { + typedef mozilla::plugins::IPCByteRange paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.offset); + WriteParam(aMsg, aParam.length); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + paramType p; + if (ReadParam(aMsg, aIter, &p.offset) && + ReadParam(aMsg, aIter, &p.length)) { + *aResult = p; + return true; + } + return false; + } +}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +// The only accepted value is NPNURLVariable::NPNURLVProxy +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + NPCoordinateSpace, NPCoordinateSpace::NPCoordinateSpacePlugin, + NPCoordinateSpace::NPCoordinateSpaceFlippedScreen> {}; + +template <> +struct ParamTraits { + typedef mozilla::plugins::NPAudioDeviceChangeDetailsIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.flow); + WriteParam(aMsg, aParam.role); + WriteParam(aMsg, aParam.defaultDevice); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + int32_t flow, role; + std::wstring defaultDevice; + if (ReadParam(aMsg, aIter, &flow) && ReadParam(aMsg, aIter, &role) && + ReadParam(aMsg, aIter, &defaultDevice)) { + aResult->flow = flow; + aResult->role = role; + aResult->defaultDevice = defaultDevice; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%d, %d, %S]", aParam.flow, aParam.role, + aParam.defaultDevice.c_str())); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::plugins::NPAudioDeviceStateChangedIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.device); + WriteParam(aMsg, aParam.state); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + int32_t state; + std::wstring device; + if (ReadParam(aMsg, aIter, &device) && ReadParam(aMsg, aIter, &state)) { + aResult->device = device; + aResult->state = state; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"[%S,%d]", aParam.device.c_str(), aParam.state)); + } +}; +} /* namespace IPC */ + +// Serializing NPEvents is completely platform-specific and can be rather +// intricate depending on the platform. So for readability we split it +// into separate files and have the only macro crud live here. +// +// NB: these guards are based on those where struct NPEvent is defined +// in npapi.h. They should be kept in sync. +#if defined(XP_MACOSX) +# include "mozilla/plugins/NPEventOSX.h" +#elif defined(XP_WIN) +# include "mozilla/plugins/NPEventWindows.h" +#elif defined(ANDROID) +# include "mozilla/plugins/NPEventAndroid.h" +#elif defined(XP_UNIX) +# include "mozilla/plugins/NPEventUnix.h" +#else +# error Unsupported platform +#endif + +#endif /* DOM_PLUGINS_PLUGINMESSAGEUTILS_H */ diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp new file mode 100644 index 0000000000..81eb8467d4 --- /dev/null +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -0,0 +1,1984 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=4 et : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/plugins/PluginModuleChild.h" + +/* This must occur *after* plugins/PluginModuleChild.h to avoid typedefs + * conflicts. */ +#include "mozilla/ArrayUtils.h" + +#include "mozilla/ipc/MessageChannel.h" + +#ifdef MOZ_WIDGET_GTK +# include +# include +#endif + +#include "nsIFile.h" + +#include "pratom.h" +#include "nsDebug.h" +#include "nsCOMPtr.h" +#include "nsPluginsDir.h" +#include "nsXULAppAPI.h" + +#ifdef MOZ_X11 +# include "nsX11ErrorHandler.h" +# include "mozilla/X11Util.h" +#endif + +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/plugins/PluginInstanceChild.h" +#include "mozilla/plugins/StreamNotifyChild.h" +#include "mozilla/plugins/BrowserStreamChild.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" + +#include "nsNPAPIPlugin.h" +#include "FunctionHook.h" +#include "FunctionBrokerChild.h" + +#ifdef XP_WIN +# include "mozilla/widget/AudioSession.h" +# include +#endif + +#ifdef MOZ_WIDGET_COCOA +# include "PluginInterposeOSX.h" +# include "PluginUtilsOSX.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "ChildProfilerController.h" +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +using namespace mozilla; +using namespace mozilla::ipc; +using namespace mozilla::plugins; +using namespace mozilla::widget; + +#if defined(XP_WIN) +const wchar_t* kFlashFullscreenClass = L"ShockwaveFlashFullScreen"; +# if defined(MOZ_SANDBOX) +std::wstring sRoamingPath; +# endif +#endif + +namespace { +// see PluginModuleChild::GetChrome() +PluginModuleChild* gChromeInstance = nullptr; +} // namespace + +#ifdef XP_WIN +// Used with fix for flash fullscreen window loosing focus. +static bool gDelayFlashFocusReplyUntilEval = false; +#endif + +/* static */ +bool PluginModuleChild::CreateForContentProcess( + Endpoint&& aEndpoint) { + auto* child = new PluginModuleChild(false); + return child->InitForContent(std::move(aEndpoint)); +} + +PluginModuleChild::PluginModuleChild(bool aIsChrome) + : mLibrary(0), + mPluginFilename(""), + mQuirks(QUIRKS_NOT_INITIALIZED), + mIsChrome(aIsChrome), + mHasShutdown(false), + mShutdownFunc(0), + mInitializeFunc(0) +#if defined(OS_WIN) || defined(OS_MACOSX) + , + mGetEntryPointsFunc(0) +#elif defined(MOZ_WIDGET_GTK) + , + mNestedLoopTimerId(0) +#endif +#ifdef OS_WIN + , + mNestedEventHook(nullptr), + mGlobalCallWndProcHook(nullptr), + mAsyncRenderSupport(false) +#endif +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + , + mFlashSandboxLevel(0), + mEnableFlashSandboxLogging(false) +#endif +{ + memset(&mFunctions, 0, sizeof(mFunctions)); + if (mIsChrome) { + MOZ_ASSERT(!gChromeInstance); + gChromeInstance = this; + } + +#ifdef XP_MACOSX + if (aIsChrome) { + mac_plugin_interposing::child::SetUpCocoaInterposing(); + } +#endif +} + +PluginModuleChild::~PluginModuleChild() { + if (mIsChrome) { + MOZ_ASSERT(gChromeInstance == this); + + // We don't unload the plugin library in case it uses atexit handlers or + // other similar hooks. + + DeinitGraphics(); + PluginScriptableObjectChild::ClearIdentifiers(); + + gChromeInstance = nullptr; + } +} + +// static +PluginModuleChild* PluginModuleChild::GetChrome() { + // A special PluginModuleChild instance that talks to the chrome process + // during startup and shutdown. Synchronous messages to or from this actor + // should be avoided because they may lead to hangs. + MOZ_ASSERT(gChromeInstance); + return gChromeInstance; +} + +void PluginModuleChild::CommonInit() { + PLUGIN_LOG_DEBUG_METHOD; + + // Request Windows message deferral behavior on our channel. This + // applies to the top level and all sub plugin protocols since they + // all share the same channel. + // Bug 1090573 - Don't do this for connections to content processes. + GetIPCChannel()->SetChannelFlags( + MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); + + memset((void*)&mFunctions, 0, sizeof(mFunctions)); + mFunctions.size = sizeof(mFunctions); + mFunctions.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; +} + +bool PluginModuleChild::InitForContent( + Endpoint&& aEndpoint) { + CommonInit(); + + if (!aEndpoint.Bind(this)) { + return false; + } + + mLibrary = GetChrome()->mLibrary; + mFunctions = GetChrome()->mFunctions; + + return true; +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvInitProfiler( + Endpoint&& aEndpoint) { +#ifdef MOZ_GECKO_PROFILER + mProfilerController = ChildProfilerController::Create(std::move(aEndpoint)); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvDisableFlashProtectedMode() { + MOZ_ASSERT(mIsChrome); +#ifdef XP_WIN + FunctionHook::HookProtectedMode(); +#else + MOZ_ASSERT(false, "Should not be called"); +#endif + return IPC_OK(); +} + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +void PluginModuleChild::EnableFlashSandbox(int aLevel, + bool aShouldEnableLogging) { + mFlashSandboxLevel = aLevel; + mEnableFlashSandboxLogging = aShouldEnableLogging; +} +#endif + +#if defined(OS_WIN) && defined(MOZ_SANDBOX) +/* static */ +void PluginModuleChild::SetFlashRoamingPath(const std::wstring& aRoamingPath) { + MOZ_ASSERT(sRoamingPath.empty()); + sRoamingPath = aRoamingPath; +} + +/* static */ std::wstring PluginModuleChild::GetFlashRoamingPath() { + return sRoamingPath; +} +#endif + +bool PluginModuleChild::InitForChrome(const std::string& aPluginFilename, + base::ProcessId aParentPid, + MessageLoop* aIOLoop, + UniquePtr aChannel) { + NS_ASSERTION(aChannel, "need a channel"); + +#if defined(OS_WIN) && defined(MOZ_SANDBOX) + MOZ_ASSERT(!sRoamingPath.empty(), + "Should have already called SetFlashRoamingPath"); +#endif + + if (!InitGraphics()) return false; + + mPluginFilename = aPluginFilename.c_str(); + nsCOMPtr localFile; + NS_NewLocalFile(NS_ConvertUTF8toUTF16(mPluginFilename), true, + getter_AddRefs(localFile)); + + if (!localFile) return false; + + bool exists; + localFile->Exists(&exists); + NS_ASSERTION(exists, "plugin file ain't there"); + + nsPluginFile pluginFile(localFile); + + nsPluginInfo info = nsPluginInfo(); + if (NS_FAILED(pluginFile.GetPluginInfo(info, &mLibrary))) { + return false; + } + +#if defined(XP_WIN) + // XXX quirks isn't initialized yet + mAsyncRenderSupport = info.fSupportsAsyncRender; +#endif +#if defined(MOZ_X11) + constexpr auto flash10Head = "Shockwave Flash 10."_ns; + if (StringBeginsWith(nsDependentCString(info.fDescription), flash10Head)) { + AddQuirk(QUIRK_FLASH_EXPOSE_COORD_TRANSLATION); + } +#endif +#if defined(XP_MACOSX) + const char* namePrefix = "Plugin Content"; + char nameBuffer[80]; + SprintfLiteral(nameBuffer, "%s (%s)", namePrefix, info.fName); + mozilla::plugins::PluginUtilsOSX::SetProcessName(nameBuffer); +#endif + pluginFile.FreePluginInfo(info); +#if defined(MOZ_X11) || defined(XP_MACOSX) + if (!mLibrary) +#endif + { + nsresult rv = pluginFile.LoadPlugin(&mLibrary); + if (NS_FAILED(rv)) return false; + } + NS_ASSERTION(mLibrary, "couldn't open shared object"); + + CommonInit(); + + if (!Open(std::move(aChannel), aParentPid, aIOLoop)) { + return false; + } + + GetIPCChannel()->SetAbortOnError(true); + +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + mShutdownFunc = + (NP_PLUGINSHUTDOWN)PR_FindFunctionSymbol(mLibrary, "NP_Shutdown"); + + // create the new plugin handler + + mInitializeFunc = + (NP_PLUGINUNIXINIT)PR_FindFunctionSymbol(mLibrary, "NP_Initialize"); + NS_ASSERTION(mInitializeFunc, "couldn't find NP_Initialize()"); + +#elif defined(OS_WIN) || defined(OS_MACOSX) + mShutdownFunc = + (NP_PLUGINSHUTDOWN)PR_FindFunctionSymbol(mLibrary, "NP_Shutdown"); + + mGetEntryPointsFunc = + (NP_GETENTRYPOINTS)PR_FindSymbol(mLibrary, "NP_GetEntryPoints"); + NS_ENSURE_TRUE(mGetEntryPointsFunc, false); + + mInitializeFunc = + (NP_PLUGININIT)PR_FindFunctionSymbol(mLibrary, "NP_Initialize"); + NS_ENSURE_TRUE(mInitializeFunc, false); +#else + +# error Please copy the initialization code from nsNPAPIPlugin.cpp + +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (mFlashSandboxLevel > 0) { + MacSandboxInfo flashSandboxInfo; + flashSandboxInfo.type = MacSandboxType_Flash; + flashSandboxInfo.pluginBinaryPath = aPluginFilename; + flashSandboxInfo.level = mFlashSandboxLevel; + flashSandboxInfo.shouldLog = mEnableFlashSandboxLogging; + + std::string sbError; + if (!mozilla::StartMacSandbox(flashSandboxInfo, sbError)) { + fprintf(stderr, "Failed to start sandbox:\n%s\n", sbError.c_str()); + return false; + } + } +#endif + + return true; +} + +#if defined(MOZ_WIDGET_GTK) + +typedef void (*GObjectDisposeFn)(GObject*); +typedef gboolean (*GtkWidgetScrollEventFn)(GtkWidget*, GdkEventScroll*); +typedef void (*GtkPlugEmbeddedFn)(GtkPlug*); + +static GObjectDisposeFn real_gtk_plug_dispose; +static GtkPlugEmbeddedFn real_gtk_plug_embedded; + +static void undo_bogus_unref(gpointer data, GObject* object, + gboolean is_last_ref) { + if (!is_last_ref) // recursion in g_object_ref + return; + + g_object_ref(object); +} + +static void wrap_gtk_plug_dispose(GObject* object) { + // Work around Flash Player bug described in bug 538914. + // + // This function is called during gtk_widget_destroy and/or before + // the object's last reference is removed. A reference to the + // object is held during the call so the ref count should not drop + // to zero. However, Flash Player tries to destroy the GtkPlug + // using g_object_unref instead of gtk_widget_destroy. The + // reference that Flash is removing actually belongs to the + // GtkPlug. During real_gtk_plug_dispose, the GtkPlug removes its + // reference. + // + // A toggle ref is added to prevent premature deletion of the object + // caused by Flash Player's extra unref, and to detect when there are + // unexpectedly no other references. + g_object_add_toggle_ref(object, undo_bogus_unref, nullptr); + (*real_gtk_plug_dispose)(object); + g_object_remove_toggle_ref(object, undo_bogus_unref, nullptr); +} + +static gboolean gtk_plug_scroll_event(GtkWidget* widget, + GdkEventScroll* gdk_event) { + if (!gtk_widget_is_toplevel(widget)) // in same process as its GtkSocket + return FALSE; // event not handled; propagate to GtkSocket + + GdkWindow* socket_window = gtk_plug_get_socket_window(GTK_PLUG(widget)); + if (!socket_window) return FALSE; + + // Propagate the event to the embedder. + GdkScreen* screen = gdk_window_get_screen(socket_window); + GdkWindow* plug_window = gtk_widget_get_window(widget); + GdkWindow* event_window = gdk_event->window; + gint x = gdk_event->x; + gint y = gdk_event->y; + unsigned int button; + unsigned int button_mask = 0; + XEvent xevent; + Display* dpy = GDK_WINDOW_XDISPLAY(socket_window); + + /* Translate the event coordinates to the plug window, + * which should be aligned with the socket window. + */ + while (event_window != plug_window) { + gint dx, dy; + + gdk_window_get_position(event_window, &dx, &dy); + x += dx; + y += dy; + + event_window = gdk_window_get_parent(event_window); + if (!event_window) return FALSE; + } + + switch (gdk_event->direction) { + case GDK_SCROLL_UP: + button = 4; + button_mask = Button4Mask; + break; + case GDK_SCROLL_DOWN: + button = 5; + button_mask = Button5Mask; + break; + case GDK_SCROLL_LEFT: + button = 6; + break; + case GDK_SCROLL_RIGHT: + button = 7; + break; + default: + return FALSE; // unknown GdkScrollDirection + } + + memset(&xevent, 0, sizeof(xevent)); + xevent.xbutton.type = ButtonPress; + xevent.xbutton.window = gdk_x11_window_get_xid(socket_window); + xevent.xbutton.root = + gdk_x11_window_get_xid(gdk_screen_get_root_window(screen)); + xevent.xbutton.subwindow = gdk_x11_window_get_xid(plug_window); + xevent.xbutton.time = gdk_event->time; + xevent.xbutton.x = x; + xevent.xbutton.y = y; + xevent.xbutton.x_root = gdk_event->x_root; + xevent.xbutton.y_root = gdk_event->y_root; + xevent.xbutton.state = gdk_event->state; + xevent.xbutton.button = button; + xevent.xbutton.same_screen = X11True; + + gdk_error_trap_push(); + + XSendEvent(dpy, xevent.xbutton.window, X11True, ButtonPressMask, &xevent); + + xevent.xbutton.type = ButtonRelease; + xevent.xbutton.state |= button_mask; + XSendEvent(dpy, xevent.xbutton.window, X11True, ButtonReleaseMask, &xevent); + + gdk_display_sync(gdk_screen_get_display(screen)); + gdk_error_trap_pop(); + + return TRUE; // event handled +} + +static void wrap_gtk_plug_embedded(GtkPlug* plug) { + GdkWindow* socket_window = gtk_plug_get_socket_window(plug); + if (socket_window) { + if (gtk_check_version(2, 18, 7) != nullptr // older + && g_object_get_data(G_OBJECT(socket_window), + "moz-existed-before-set-window")) { + // Add missing reference for + // https://bugzilla.gnome.org/show_bug.cgi?id=607061 + g_object_ref(socket_window); + } + + // Ensure the window exists to make this GtkPlug behave like an + // in-process GtkPlug for Flash Player. (Bugs 561308 and 539138). + gtk_widget_realize(GTK_WIDGET(plug)); + } + + if (*real_gtk_plug_embedded) { + (*real_gtk_plug_embedded)(plug); + } +} + +// +// The next four constants are knobs that can be tuned. They trade +// off potential UI lag from delayed event processing with CPU time. +// +static const gint kNestedLoopDetectorPriority = G_PRIORITY_HIGH_IDLE; +// 90ms so that we can hopefully break livelocks before the user +// notices UI lag (100ms) +static const guint kNestedLoopDetectorIntervalMs = 90; + +static const gint kBrowserEventPriority = G_PRIORITY_HIGH_IDLE; +static const guint kBrowserEventIntervalMs = 10; + +// static +gboolean PluginModuleChild::DetectNestedEventLoop(gpointer data) { + PluginModuleChild* pmc = static_cast(data); + + MOZ_ASSERT(0 != pmc->mNestedLoopTimerId, "callback after descheduling"); + MOZ_ASSERT(pmc->mTopLoopDepth < g_main_depth(), + "not canceled before returning to main event loop!"); + + PLUGIN_LOG_DEBUG(("Detected nested glib event loop")); + + // just detected a nested loop; start a timer that will + // periodically rpc-call back into the browser and process some + // events + pmc->mNestedLoopTimerId = g_timeout_add_full( + kBrowserEventPriority, kBrowserEventIntervalMs, + PluginModuleChild::ProcessBrowserEvents, data, nullptr); + // cancel the nested-loop detection timer + return FALSE; +} + +// static +gboolean PluginModuleChild::ProcessBrowserEvents(gpointer data) { + PluginModuleChild* pmc = static_cast(data); + + MOZ_ASSERT(pmc->mTopLoopDepth < g_main_depth(), + "not canceled before returning to main event loop!"); + + pmc->CallProcessSomeEvents(); + + return TRUE; +} + +void PluginModuleChild::EnteredCxxStack() { + MOZ_ASSERT(0 == mNestedLoopTimerId, "previous timer not descheduled"); + + mNestedLoopTimerId = g_timeout_add_full( + kNestedLoopDetectorPriority, kNestedLoopDetectorIntervalMs, + PluginModuleChild::DetectNestedEventLoop, this, nullptr); + +# ifdef DEBUG + mTopLoopDepth = g_main_depth(); +# endif +} + +void PluginModuleChild::ExitedCxxStack() { + MOZ_ASSERT(0 < mNestedLoopTimerId, "nested loop timeout not scheduled"); + + g_source_remove(mNestedLoopTimerId); + mNestedLoopTimerId = 0; +} + +#endif + +mozilla::ipc::IPCResult PluginModuleChild::RecvSetParentHangTimeout( + const uint32_t& aSeconds) { +#ifdef XP_WIN + SetReplyTimeoutMs(((aSeconds > 0) ? (1000 * aSeconds) : 0)); +#endif + return IPC_OK(); +} + +bool PluginModuleChild::ShouldContinueFromReplyTimeout() { +#ifdef XP_WIN + MOZ_CRASH("terminating child process"); +#endif + return true; +} + +bool PluginModuleChild::InitGraphics() { +#if defined(MOZ_WIDGET_GTK) + // Work around plugins that don't interact well with GDK + // client-side windows. + PR_SetEnv("GDK_NATIVE_WINDOWS=1"); + + gtk_init(0, 0); + + // GtkPlug is a static class so will leak anyway but this ref makes sure. + gpointer gtk_plug_class = g_type_class_ref(GTK_TYPE_PLUG); + + // The dispose method is a good place to hook into the destruction process + // because the reference count should be 1 the last time dispose is + // called. (Toggle references wouldn't detect if the reference count + // might be higher.) + GObjectDisposeFn* dispose = &G_OBJECT_CLASS(gtk_plug_class)->dispose; + MOZ_ASSERT(*dispose != wrap_gtk_plug_dispose, "InitGraphics called twice"); + real_gtk_plug_dispose = *dispose; + *dispose = wrap_gtk_plug_dispose; + + // If we ever stop setting GDK_NATIVE_WINDOWS, we'll also need to + // gtk_widget_add_events GDK_SCROLL_MASK or GDK client-side windows will + // not tell us about the scroll events that it intercepts. With native + // windows, this is called when GDK intercepts the events; if GDK doesn't + // intercept the events, then the X server will instead send them directly + // to an ancestor (embedder) window. + GtkWidgetScrollEventFn* scroll_event = + >K_WIDGET_CLASS(gtk_plug_class)->scroll_event; + if (!*scroll_event) { + *scroll_event = gtk_plug_scroll_event; + } + + GtkPlugEmbeddedFn* embedded = >K_PLUG_CLASS(gtk_plug_class)->embedded; + real_gtk_plug_embedded = *embedded; + *embedded = wrap_gtk_plug_embedded; + +#else + // may not be necessary on all platforms +#endif +#ifdef MOZ_X11 + // Do this after initializing GDK, or GDK will install its own handler. + InstallX11ErrorHandler(); +#endif + return true; +} + +void PluginModuleChild::DeinitGraphics() { +#if defined(MOZ_X11) && defined(NS_FREE_PERMANENT_DATA) + // We free some data off of XDisplay close hooks, ensure they're + // run. Closing the display is pretty scary, so we only do it to + // silence leak checkers. + XCloseDisplay(DefaultXDisplay()); +#endif +} + +NPError PluginModuleChild::NP_Shutdown() { + AssertPluginThread(); + MOZ_ASSERT(mIsChrome); + + if (mHasShutdown) { + return NPERR_NO_ERROR; + } + +#if defined XP_WIN + mozilla::widget::StopAudioSession(); +#endif + + // the PluginModuleParent shuts down this process after this interrupt + // call pops off its stack + + NPError rv = mShutdownFunc ? mShutdownFunc() : NPERR_NO_ERROR; + + // weakly guard against re-entry after NP_Shutdown + memset(&mFunctions, 0, sizeof(mFunctions)); + +#ifdef OS_WIN + ResetEventHooks(); +#endif + + GetIPCChannel()->SetAbortOnError(false); + + mHasShutdown = true; + + return rv; +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerNP_Shutdown(NPError* rv) { + *rv = NP_Shutdown(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerOptionalFunctionsSupported( + bool* aURLRedirectNotify, bool* aClearSiteData, bool* aGetSitesWithData) { + *aURLRedirectNotify = !!mFunctions.urlredirectnotify; + *aClearSiteData = !!mFunctions.clearsitedata; + *aGetSitesWithData = !!mFunctions.getsiteswithdata; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvNPP_ClearSiteData( + const nsCString& aSite, const uint64_t& aFlags, const uint64_t& aMaxAge, + const uint64_t& aCallbackId) { + NPError result = + mFunctions.clearsitedata(NullableStringGet(aSite), aFlags, aMaxAge); + SendReturnClearSiteData(result, aCallbackId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvNPP_GetSitesWithData( + const uint64_t& aCallbackId) { + char** result = mFunctions.getsiteswithdata(); + nsTArray array; + if (!result) { + SendReturnSitesWithData(array, aCallbackId); + return IPC_OK(); + } + char** iterator = result; + while (*iterator) { + array.AppendElement(*iterator); + free(*iterator); + ++iterator; + } + SendReturnSitesWithData(array, aCallbackId); + free(result); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvSetAudioSessionData( + const nsID& aId, const nsString& aDisplayName, const nsString& aIconPath) { +#if !defined XP_WIN + MOZ_CRASH("Not Reached!"); +#else + nsresult rv = + mozilla::widget::RecvAudioSessionData(aId, aDisplayName, aIconPath); + NS_ENSURE_SUCCESS(rv, IPC_OK()); // Bail early if this fails + + // Ignore failures here; we can't really do anything about them + mozilla::widget::StartAudioSession(); + return IPC_OK(); +#endif +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvInitPluginModuleChild( + Endpoint&& aEndpoint) { + if (!CreateForContentProcess(std::move(aEndpoint))) { + return IPC_FAIL(this, "CreateForContentProcess failed"); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvInitPluginFunctionBroker( + Endpoint&& aEndpoint) { +#if defined(XP_WIN) + MOZ_ASSERT(mIsChrome); + if (!FunctionBrokerChild::Initialize(std::move(aEndpoint))) { + return IPC_FAIL( + this, "InitPluginFunctionBroker failed to initialize broker child."); + } + return IPC_OK(); +#else + return IPC_FAIL(this, + "InitPluginFunctionBroker not supported on this platform."); +#endif +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerInitCrashReporter( + mozilla::dom::NativeThreadId* aOutId) { + CrashReporterClient::InitSingleton(); + *aOutId = CrashReporter::CurrentThreadId(); + + return IPC_OK(); +} + +void PluginModuleChild::ActorDestroy(ActorDestroyReason why) { +#ifdef MOZ_GECKO_PROFILER + if (mProfilerController) { + mProfilerController->Shutdown(); + mProfilerController = nullptr; + } +#endif + + if (!mIsChrome) { + PluginModuleChild* chromeInstance = PluginModuleChild::GetChrome(); + if (chromeInstance) { + chromeInstance->SendNotifyContentModuleDestroyed(); + } + + // Destroy ourselves once we finish other teardown activities. + RefPtr> task = + new DeleteTask(this); + MessageLoop::current()->PostTask(task.forget()); + return; + } + + if (AbnormalShutdown == why) { + NS_WARNING("shutting down early because of crash!"); + ProcessChild::QuickExit(); + } + + if (!mHasShutdown) { + MOZ_ASSERT(gChromeInstance == this); + NP_Shutdown(); + } + +#if defined(XP_WIN) + FunctionBrokerChild::Destroy(); + FunctionHook::ClearDllInterceptorCache(); +#endif + + // doesn't matter why we're being destroyed; it's up to us to + // initiate (clean) shutdown + CrashReporterClient::DestroySingleton(); + + XRE_ShutdownChildProcess(); +} + +void PluginModuleChild::CleanUp() {} + +const char* PluginModuleChild::GetUserAgent() { + return NullableStringGet(Settings().userAgent()); +} + +//----------------------------------------------------------------------------- +// FIXME/cjones: just getting this out of the way for the moment ... + +namespace mozilla::plugins::child { + +static NPError _requestread(NPStream* pstream, NPByteRange* rangeList); + +static NPError _geturlnotify(NPP aNPP, const char* relativeURL, + const char* target, void* notifyData); + +static NPError _getvalue(NPP aNPP, NPNVariable variable, void* r_value); + +static NPError _setvalue(NPP aNPP, NPPVariable variable, void* r_value); + +static NPError _geturl(NPP aNPP, const char* relativeURL, const char* target); + +static NPError _posturlnotify(NPP aNPP, const char* relativeURL, + const char* target, uint32_t len, const char* buf, + NPBool file, void* notifyData); + +static NPError _posturl(NPP aNPP, const char* relativeURL, const char* target, + uint32_t len, const char* buf, NPBool file); + +static void _status(NPP aNPP, const char* message); + +static void _memfree(void* ptr); + +static uint32_t _memflush(uint32_t size); + +static void _reloadplugins(NPBool reloadPages); + +static void _invalidaterect(NPP aNPP, NPRect* invalidRect); + +static void _invalidateregion(NPP aNPP, NPRegion invalidRegion); + +static void _forceredraw(NPP aNPP); + +static const char* _useragent(NPP aNPP); + +static void* _memalloc(uint32_t size); + +// Deprecated entry points for the old Java plugin. +static void* /* OJI type: JRIEnv* */ +_getjavaenv(void); + +// Deprecated entry points for the old Java plugin. +static void* /* OJI type: jref */ +_getjavapeer(NPP aNPP); + +static bool _invoke(NPP aNPP, NPObject* npobj, NPIdentifier method, + const NPVariant* args, uint32_t argCount, + NPVariant* result); + +static bool _invokedefault(NPP aNPP, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +static bool _evaluate(NPP aNPP, NPObject* npobj, NPString* script, + NPVariant* result); + +static bool _getproperty(NPP aNPP, NPObject* npobj, NPIdentifier property, + NPVariant* result); + +static bool _setproperty(NPP aNPP, NPObject* npobj, NPIdentifier property, + const NPVariant* value); + +static bool _removeproperty(NPP aNPP, NPObject* npobj, NPIdentifier property); + +static bool _hasproperty(NPP aNPP, NPObject* npobj, NPIdentifier propertyName); + +static bool _hasmethod(NPP aNPP, NPObject* npobj, NPIdentifier methodName); + +static bool _enumerate(NPP aNPP, NPObject* npobj, NPIdentifier** identifier, + uint32_t* count); + +static bool _construct(NPP aNPP, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +static void _releasevariantvalue(NPVariant* variant); + +static void _setexception(NPObject* npobj, const NPUTF8* message); + +static void _pushpopupsenabledstate(NPP aNPP, NPBool enabled); + +static void _poppopupsenabledstate(NPP aNPP); + +static NPError _getvalueforurl(NPP npp, NPNURLVariable variable, + const char* url, char** value, uint32_t* len); + +static NPError _setvalueforurl(NPP npp, NPNURLVariable variable, + const char* url, const char* value, + uint32_t len); + +static uint32_t _scheduletimer(NPP instance, uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)); + +static void _unscheduletimer(NPP instance, uint32_t timerID); + +static NPError _popupcontextmenu(NPP instance, NPMenu* menu); + +static NPBool _convertpoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace); + +static void _urlredirectresponse(NPP instance, void* notifyData, NPBool allow); + +static NPError _initasyncsurface(NPP instance, NPSize* size, + NPImageFormat format, void* initData, + NPAsyncSurface* surface); + +static NPError _finalizeasyncsurface(NPP instance, NPAsyncSurface* surface); + +static void _setcurrentasyncsurface(NPP instance, NPAsyncSurface* surface, + NPRect* changed); + +} // namespace mozilla::plugins::child + +const NPNetscapeFuncs PluginModuleChild::sBrowserFuncs = { + sizeof(sBrowserFuncs), + (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR, + mozilla::plugins::child::_geturl, + mozilla::plugins::child::_posturl, + mozilla::plugins::child::_requestread, + nullptr, // _newstream, unimplemented + nullptr, // _write, unimplemented + nullptr, // _destroystream, unimplemented + mozilla::plugins::child::_status, + mozilla::plugins::child::_useragent, + mozilla::plugins::child::_memalloc, + mozilla::plugins::child::_memfree, + mozilla::plugins::child::_memflush, + mozilla::plugins::child::_reloadplugins, + mozilla::plugins::child::_getjavaenv, + mozilla::plugins::child::_getjavapeer, + mozilla::plugins::child::_geturlnotify, + mozilla::plugins::child::_posturlnotify, + mozilla::plugins::child::_getvalue, + mozilla::plugins::child::_setvalue, + mozilla::plugins::child::_invalidaterect, + mozilla::plugins::child::_invalidateregion, + mozilla::plugins::child::_forceredraw, + PluginModuleChild::NPN_GetStringIdentifier, + PluginModuleChild::NPN_GetStringIdentifiers, + PluginModuleChild::NPN_GetIntIdentifier, + PluginModuleChild::NPN_IdentifierIsString, + PluginModuleChild::NPN_UTF8FromIdentifier, + PluginModuleChild::NPN_IntFromIdentifier, + PluginModuleChild::NPN_CreateObject, + PluginModuleChild::NPN_RetainObject, + PluginModuleChild::NPN_ReleaseObject, + mozilla::plugins::child::_invoke, + mozilla::plugins::child::_invokedefault, + mozilla::plugins::child::_evaluate, + mozilla::plugins::child::_getproperty, + mozilla::plugins::child::_setproperty, + mozilla::plugins::child::_removeproperty, + mozilla::plugins::child::_hasproperty, + mozilla::plugins::child::_hasmethod, + mozilla::plugins::child::_releasevariantvalue, + mozilla::plugins::child::_setexception, + mozilla::plugins::child::_pushpopupsenabledstate, + mozilla::plugins::child::_poppopupsenabledstate, + mozilla::plugins::child::_enumerate, + nullptr, // pluginthreadasynccall, not used + mozilla::plugins::child::_construct, + mozilla::plugins::child::_getvalueforurl, + mozilla::plugins::child::_setvalueforurl, + nullptr, // NPN GetAuthenticationInfo, not supported + mozilla::plugins::child::_scheduletimer, + mozilla::plugins::child::_unscheduletimer, + mozilla::plugins::child::_popupcontextmenu, + mozilla::plugins::child::_convertpoint, + nullptr, // handleevent, unimplemented + nullptr, // unfocusinstance, unimplemented + mozilla::plugins::child::_urlredirectresponse, + mozilla::plugins::child::_initasyncsurface, + mozilla::plugins::child::_finalizeasyncsurface, + mozilla::plugins::child::_setcurrentasyncsurface, +}; + +PluginInstanceChild* InstCast(NPP aNPP) { + MOZ_ASSERT(!!(aNPP->ndata), "nil instance"); + return static_cast(aNPP->ndata); +} + +namespace mozilla::plugins::child { + +NPError _requestread(NPStream* aStream, NPByteRange* aRangeList) { + return NPERR_STREAM_NOT_SEEKABLE; +} + +NPError _geturlnotify(NPP aNPP, const char* aRelativeURL, const char* aTarget, + void* aNotifyData) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + if (!aNPP) // nullptr check for nspluginwrapper (bug 561690) + return NPERR_INVALID_INSTANCE_ERROR; + + nsCString url = NullableString(aRelativeURL); + auto* sn = new StreamNotifyChild(url); + + NPError err; + if (!InstCast(aNPP)->CallPStreamNotifyConstructor( + sn, url, NullableString(aTarget), false, nsCString(), false, &err)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR == err) { + // If NPN_PostURLNotify fails, the parent will immediately send us + // a PStreamNotifyDestructor, which should not call NPP_URLNotify. + sn->SetValid(aNotifyData); + } + + return err; +} + +NPError _getvalue(NPP aNPP, NPNVariable aVariable, void* aValue) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + switch (aVariable) { + // Copied from nsNPAPIPlugin.cpp + case NPNVToolkit: +#if defined(MOZ_WIDGET_GTK) + *static_cast(aValue) = NPNVGtk2; + return NPERR_NO_ERROR; +#endif + return NPERR_GENERIC_ERROR; + + case NPNVjavascriptEnabledBool: + *(NPBool*)aValue = + PluginModuleChild::GetChrome()->Settings().javascriptEnabled(); + return NPERR_NO_ERROR; + case NPNVasdEnabledBool: + *(NPBool*)aValue = + PluginModuleChild::GetChrome()->Settings().asdEnabled(); + return NPERR_NO_ERROR; + case NPNVisOfflineBool: + *(NPBool*)aValue = PluginModuleChild::GetChrome()->Settings().isOffline(); + return NPERR_NO_ERROR; + case NPNVSupportsXEmbedBool: + // We don't support windowed xembed any more. But we still deliver + // events based on X/GTK, not Xt, so we continue to return true + // (and Flash requires that we return true). + *(NPBool*)aValue = true; + return NPERR_NO_ERROR; + case NPNVSupportsWindowless: + *(NPBool*)aValue = true; + return NPERR_NO_ERROR; +#if defined(MOZ_WIDGET_GTK) + case NPNVxDisplay: { + if (!aNPP) { + return NPERR_INVALID_INSTANCE_ERROR; + } + return InstCast(aNPP)->NPN_GetValue(aVariable, aValue); + } + case NPNVxtAppContext: + return NPERR_GENERIC_ERROR; +#endif + default: { + if (aNPP) { + return InstCast(aNPP)->NPN_GetValue(aVariable, aValue); + } + + NS_WARNING("Null NPP!"); + return NPERR_INVALID_INSTANCE_ERROR; + } + } + + MOZ_ASSERT_UNREACHABLE("Shouldn't get here!"); + return NPERR_GENERIC_ERROR; +} + +NPError _setvalue(NPP aNPP, NPPVariable aVariable, void* aValue) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + return InstCast(aNPP)->NPN_SetValue(aVariable, aValue); +} + +NPError _geturl(NPP aNPP, const char* aRelativeURL, const char* aTarget) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + NPError err; + InstCast(aNPP)->CallNPN_GetURL(NullableString(aRelativeURL), + NullableString(aTarget), &err); + return err; +} + +NPError _posturlnotify(NPP aNPP, const char* aRelativeURL, const char* aTarget, + uint32_t aLength, const char* aBuffer, NPBool aIsFile, + void* aNotifyData) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + if (!aBuffer) return NPERR_INVALID_PARAM; + + if (aIsFile) { + PLUGIN_LOG_DEBUG( + ("NPN_PostURLNotify with file=true is no longer supported")); + return NPERR_GENERIC_ERROR; + } + + nsCString url = NullableString(aRelativeURL); + auto* sn = new StreamNotifyChild(url); + + NPError err; + if (!InstCast(aNPP)->CallPStreamNotifyConstructor( + sn, url, NullableString(aTarget), true, nsCString(aBuffer, aLength), + aIsFile, &err)) { + return NPERR_GENERIC_ERROR; + } + + if (NPERR_NO_ERROR == err) { + // If NPN_PostURLNotify fails, the parent will immediately send us + // a PStreamNotifyDestructor, which should not call NPP_URLNotify. + sn->SetValid(aNotifyData); + } + + return err; +} + +NPError _posturl(NPP aNPP, const char* aRelativeURL, const char* aTarget, + uint32_t aLength, const char* aBuffer, NPBool aIsFile) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM); + + if (aIsFile) { + PLUGIN_LOG_DEBUG(("NPN_PostURL with file=true is no longer supported")); + return NPERR_GENERIC_ERROR; + } + NPError err; + // FIXME what should happen when |aBuffer| is null? + InstCast(aNPP)->CallNPN_PostURL( + NullableString(aRelativeURL), NullableString(aTarget), + nsDependentCString(aBuffer, aLength), aIsFile, &err); + return err; +} + +void _status(NPP aNPP, const char* aMessage) { + // NPN_Status is no longer supported. +} + +void _memfree(void* aPtr) { + PLUGIN_LOG_DEBUG_FUNCTION; + free(aPtr); +} + +uint32_t _memflush(uint32_t aSize) { return 0; } + +void _reloadplugins(NPBool aReloadPages) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + // Send the reload message to all modules. Chrome will need to reload from + // disk and content will need to request a new list of plugin tags from + // chrome. + PluginModuleChild::GetChrome()->SendNPN_ReloadPlugins(!!aReloadPages); +} + +void _invalidaterect(NPP aNPP, NPRect* aInvalidRect) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + // nullptr check for nspluginwrapper (bug 548434) + if (aNPP) { + InstCast(aNPP)->InvalidateRect(aInvalidRect); + } +} + +void _invalidateregion(NPP aNPP, NPRegion aInvalidRegion) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + NS_WARNING("Not yet implemented!"); +} + +void _forceredraw(NPP aNPP) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + // We ignore calls to NPN_ForceRedraw. Such calls should + // never be necessary. +} + +const char* _useragent(NPP aNPP) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(nullptr); + return PluginModuleChild::GetChrome()->GetUserAgent(); +} + +void* _memalloc(uint32_t aSize) { + PLUGIN_LOG_DEBUG_FUNCTION; + return moz_xmalloc(aSize); +} + +// Deprecated entry points for the old Java plugin. +void* /* OJI type: JRIEnv* */ +_getjavaenv(void) { + PLUGIN_LOG_DEBUG_FUNCTION; + return 0; +} + +void* /* OJI type: jref */ +_getjavapeer(NPP aNPP) { + PLUGIN_LOG_DEBUG_FUNCTION; + return 0; +} + +bool _invoke(NPP aNPP, NPObject* aNPObj, NPIdentifier aMethod, + const NPVariant* aArgs, uint32_t aArgCount, NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->invoke) + return false; + + return aNPObj->_class->invoke(aNPObj, aMethod, aArgs, aArgCount, aResult); +} + +bool _invokedefault(NPP aNPP, NPObject* aNPObj, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->invokeDefault) + return false; + + return aNPObj->_class->invokeDefault(aNPObj, aArgs, aArgCount, aResult); +} + +bool _evaluate(NPP aNPP, NPObject* aObject, NPString* aScript, + NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!(aNPP && aObject && aScript && aResult)) { + NS_ERROR("Bad arguments!"); + return false; + } + + PluginScriptableObjectChild* actor = + InstCast(aNPP)->GetActorForNPObject(aObject); + if (!actor) { + NS_ERROR("Failed to create actor?!"); + return false; + } + +#ifdef XP_WIN + if (gDelayFlashFocusReplyUntilEval) { + ReplyMessage(0); + gDelayFlashFocusReplyUntilEval = false; + } +#endif + + return actor->Evaluate(aScript, aResult); +} + +bool _getproperty(NPP aNPP, NPObject* aNPObj, NPIdentifier aPropertyName, + NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->getProperty) + return false; + + return aNPObj->_class->getProperty(aNPObj, aPropertyName, aResult); +} + +bool _setproperty(NPP aNPP, NPObject* aNPObj, NPIdentifier aPropertyName, + const NPVariant* aValue) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->setProperty) + return false; + + return aNPObj->_class->setProperty(aNPObj, aPropertyName, aValue); +} + +bool _removeproperty(NPP aNPP, NPObject* aNPObj, NPIdentifier aPropertyName) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->removeProperty) + return false; + + return aNPObj->_class->removeProperty(aNPObj, aPropertyName); +} + +bool _hasproperty(NPP aNPP, NPObject* aNPObj, NPIdentifier aPropertyName) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->hasProperty) + return false; + + return aNPObj->_class->hasProperty(aNPObj, aPropertyName); +} + +bool _hasmethod(NPP aNPP, NPObject* aNPObj, NPIdentifier aMethodName) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || !aNPObj->_class->hasMethod) + return false; + + return aNPObj->_class->hasMethod(aNPObj, aMethodName); +} + +bool _enumerate(NPP aNPP, NPObject* aNPObj, NPIdentifier** aIdentifiers, + uint32_t* aCount) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class) return false; + + if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(aNPObj->_class) || + !aNPObj->_class->enumerate) { + *aIdentifiers = 0; + *aCount = 0; + return true; + } + + return aNPObj->_class->enumerate(aNPObj, aIdentifiers, aCount); +} + +bool _construct(NPP aNPP, NPObject* aNPObj, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(false); + + if (!aNPP || !aNPObj || !aNPObj->_class || + !NP_CLASS_STRUCT_VERSION_HAS_CTOR(aNPObj->_class) || + !aNPObj->_class->construct) { + return false; + } + + return aNPObj->_class->construct(aNPObj, aArgs, aArgCount, aResult); +} + +void _releasevariantvalue(NPVariant* aVariant) { + PLUGIN_LOG_DEBUG_FUNCTION; + // Only assert plugin thread here for consistency with in-process plugins. + AssertPluginThread(); + + if (NPVARIANT_IS_STRING(*aVariant)) { + NPString str = NPVARIANT_TO_STRING(*aVariant); + free(const_cast(str.UTF8Characters)); + } else if (NPVARIANT_IS_OBJECT(*aVariant)) { + NPObject* object = NPVARIANT_TO_OBJECT(*aVariant); + if (object) { + PluginModuleChild::NPN_ReleaseObject(object); + } + } + VOID_TO_NPVARIANT(*aVariant); +} + +void _setexception(NPObject* aNPObj, const NPUTF8* aMessage) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + // Do nothing. We no longer support this API. +} + +void _pushpopupsenabledstate(NPP aNPP, NPBool aEnabled) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + InstCast(aNPP)->CallNPN_PushPopupsEnabledState(aEnabled ? true : false); +} + +void _poppopupsenabledstate(NPP aNPP) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD_VOID(); + + InstCast(aNPP)->CallNPN_PopPopupsEnabledState(); +} + +NPError _getvalueforurl(NPP npp, NPNURLVariable variable, const char* url, + char** value, uint32_t* len) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + if (!url) return NPERR_INVALID_URL; + + if (!npp || !value || !len) return NPERR_INVALID_PARAM; + + if (variable == NPNURLVProxy) { + nsCString v; + NPError result; + InstCast(npp)->CallNPN_GetValueForURL(variable, nsCString(url), &v, + &result); + if (NPERR_NO_ERROR == result) { + *value = ToNewCString(v); + *len = v.Length(); + } + return result; + } + + return NPERR_INVALID_PARAM; +} + +NPError _setvalueforurl(NPP npp, NPNURLVariable variable, const char* url, + const char* value, uint32_t len) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + if (!value) return NPERR_INVALID_PARAM; + + if (!url) return NPERR_INVALID_URL; + + if (variable == NPNURLVProxy) { + NPError result; + InstCast(npp)->CallNPN_SetValueForURL( + variable, nsCString(url), nsDependentCString(value, len), &result); + return result; + } + + return NPERR_INVALID_PARAM; +} + +uint32_t _scheduletimer(NPP npp, uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + return InstCast(npp)->ScheduleTimer(interval, repeat, timerFunc); +} + +void _unscheduletimer(NPP npp, uint32_t timerID) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + InstCast(npp)->UnscheduleTimer(timerID); +} + +#ifdef OS_MACOSX +static void ProcessBrowserEvents(void* pluginModule) { + PluginModuleChild* pmc = static_cast(pluginModule); + + if (!pmc) return; + + pmc->CallProcessSomeEvents(); +} +#endif + +NPError _popupcontextmenu(NPP instance, NPMenu* menu) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + +#ifdef MOZ_WIDGET_COCOA + double pluginX, pluginY; + double screenX, screenY; + + const NPCocoaEvent* currentEvent = InstCast(instance)->getCurrentEvent(); + if (!currentEvent) { + return NPERR_GENERIC_ERROR; + } + + // Ensure that the events has an x/y value. + if (currentEvent->type != NPCocoaEventMouseDown && + currentEvent->type != NPCocoaEventMouseUp && + currentEvent->type != NPCocoaEventMouseMoved && + currentEvent->type != NPCocoaEventMouseEntered && + currentEvent->type != NPCocoaEventMouseExited && + currentEvent->type != NPCocoaEventMouseDragged) { + return NPERR_GENERIC_ERROR; + } + + pluginX = currentEvent->data.mouse.pluginX; + pluginY = currentEvent->data.mouse.pluginY; + + if ((pluginX < 0.0) || (pluginY < 0.0)) return NPERR_GENERIC_ERROR; + + NPBool success = + _convertpoint(instance, pluginX, pluginY, NPCoordinateSpacePlugin, + &screenX, &screenY, NPCoordinateSpaceScreen); + + if (success) { + return mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu( + menu, screenX, screenY, InstCast(instance)->Manager(), + ProcessBrowserEvents); + } else { + NS_WARNING("Convertpoint failed, could not created contextmenu."); + return NPERR_GENERIC_ERROR; + } + +#else + NS_WARNING("Not supported on this platform!"); + return NPERR_GENERIC_ERROR; +#endif +} + +NPBool _convertpoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace) { + PLUGIN_LOG_DEBUG_FUNCTION; + if (!IsPluginThread()) { + NS_WARNING("Not running on the plugin's main thread!"); + return false; + } + + double rDestX = 0; + bool ignoreDestX = !destX; + double rDestY = 0; + bool ignoreDestY = !destY; + bool result = false; + InstCast(instance)->CallNPN_ConvertPoint(sourceX, ignoreDestX, sourceY, + ignoreDestY, sourceSpace, destSpace, + &rDestX, &rDestY, &result); + if (result) { + if (destX) *destX = rDestX; + if (destY) *destY = rDestY; + } + + return result; +} + +void _urlredirectresponse(NPP instance, void* notifyData, NPBool allow) { + InstCast(instance)->NPN_URLRedirectResponse(notifyData, allow); +} + +NPError _initasyncsurface(NPP instance, NPSize* size, NPImageFormat format, + void* initData, NPAsyncSurface* surface) { + return InstCast(instance)->NPN_InitAsyncSurface(size, format, initData, + surface); +} + +NPError _finalizeasyncsurface(NPP instance, NPAsyncSurface* surface) { + return InstCast(instance)->NPN_FinalizeAsyncSurface(surface); +} + +void _setcurrentasyncsurface(NPP instance, NPAsyncSurface* surface, + NPRect* changed) { + InstCast(instance)->NPN_SetCurrentAsyncSurface(surface, changed); +} + +} // namespace mozilla::plugins::child + +//----------------------------------------------------------------------------- + +mozilla::ipc::IPCResult PluginModuleChild::RecvSettingChanged( + const PluginSettings& aSettings) { + mCachedSettings = aSettings; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerNP_GetEntryPoints( + NPError* _retval) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + MOZ_ASSERT(mIsChrome); + +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + return IPC_OK(); +#elif defined(OS_WIN) || defined(OS_MACOSX) + *_retval = mGetEntryPointsFunc(&mFunctions); + return IPC_OK(); +#else +# error Please implement me for your platform +#endif +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerNP_Initialize( + const PluginSettings& aSettings, NPError* rv) { + *rv = DoNP_Initialize(aSettings); + return IPC_OK(); +} + +NPError PluginModuleChild::DoNP_Initialize(const PluginSettings& aSettings) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + MOZ_ASSERT(mIsChrome); + + mCachedSettings = aSettings; + +#ifdef OS_WIN + SetEventHooks(); +#endif + +#ifdef MOZ_X11 +# ifdef MOZ_WIDGET_GTK + if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) { + // We don't support NPAPI plugins on Wayland. + return NPERR_GENERIC_ERROR; + } +# endif + // Send the parent our X socket to act as a proxy reference for our X + // resources. + int xSocketFd = ConnectionNumber(DefaultXDisplay()); + SendBackUpXResources(FileDescriptor(xSocketFd)); +#endif + + NPError result; +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + result = mInitializeFunc(&sBrowserFuncs, &mFunctions); +#elif defined(OS_WIN) || defined(OS_MACOSX) + result = mInitializeFunc(&sBrowserFuncs); +#else +# error Please implement me for your platform +#endif + + return result; +} + +PPluginInstanceChild* PluginModuleChild::AllocPPluginInstanceChild( + const nsCString& aMimeType, const nsTArray& aNames, + const nsTArray& aValues) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + + // In e10s, gChromeInstance hands out quirks to instances, but never + // allocates an instance on its own. Make sure it gets the latest copy + // of quirks once we have them. Also note, with process-per-tab, we may + // have multiple PluginModuleChilds in the same plugin process, so only + // initialize this once in gChromeInstance, which is a singleton. + GetChrome()->InitQuirksModes(aMimeType); + mQuirks = GetChrome()->mQuirks; + +#ifdef XP_WIN + FunctionHook::HookFunctions(mQuirks); +#endif + + return new PluginInstanceChild(&mFunctions, aMimeType, aNames, aValues); +} + +void PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) { + if (mQuirks != QUIRKS_NOT_INITIALIZED) { + return; + } + + mQuirks = GetQuirksFromMimeTypeAndFilename(aMimeType, mPluginFilename); +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerModuleSupportsAsyncRender( + bool* aResult) { +#if defined(XP_WIN) + *aResult = gChromeInstance->mAsyncRenderSupport; + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE("Shouldn't get here!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleChild::RecvPPluginInstanceConstructor( + PPluginInstanceChild* aActor, const nsCString& aMimeType, + nsTArray&& aNames, nsTArray&& aValues) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + + NS_ASSERTION(aActor, "Null actor!"); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleChild::AnswerSyncNPP_New( + PPluginInstanceChild* aActor, NPError* rv) { + PLUGIN_LOG_DEBUG_METHOD; + PluginInstanceChild* childInstance = + reinterpret_cast(aActor); + AssertPluginThread(); + *rv = childInstance->DoNPP_New(); + return IPC_OK(); +} + +bool PluginModuleChild::DeallocPPluginInstanceChild( + PPluginInstanceChild* aActor) { + PLUGIN_LOG_DEBUG_METHOD; + AssertPluginThread(); + + delete aActor; + + return true; +} + +NPObject* PluginModuleChild::NPN_CreateObject(NPP aNPP, NPClass* aClass) { + PLUGIN_LOG_DEBUG_FUNCTION; + ENSURE_PLUGIN_THREAD(nullptr); + + PluginInstanceChild* i = InstCast(aNPP); + if (i->mDeletingHash) { + NS_ERROR("Plugin used NPP after NPP_Destroy"); + return nullptr; + } + + NPObject* newObject; + if (aClass && aClass->allocate) { + newObject = aClass->allocate(aNPP, aClass); + } else { + newObject = reinterpret_cast(child::_memalloc(sizeof(NPObject))); + } + + if (newObject) { + newObject->_class = aClass; + newObject->referenceCount = 1; + NS_LOG_ADDREF(newObject, 1, "NPObject", sizeof(NPObject)); + } + + PluginScriptableObjectChild::RegisterObject(newObject, i); + + return newObject; +} + +NPObject* PluginModuleChild::NPN_RetainObject(NPObject* aNPObj) { + AssertPluginThread(); + + if (NS_WARN_IF(!aNPObj)) { + return nullptr; + } + +#ifdef NS_BUILD_REFCNT_LOGGING + int32_t refCnt = +#endif + PR_ATOMIC_INCREMENT((int32_t*)&aNPObj->referenceCount); + NS_LOG_ADDREF(aNPObj, refCnt, "NPObject", sizeof(NPObject)); + + return aNPObj; +} + +void PluginModuleChild::NPN_ReleaseObject(NPObject* aNPObj) { + AssertPluginThread(); + + PluginInstanceChild* instance = + PluginScriptableObjectChild::GetInstanceForNPObject(aNPObj); + if (!instance) { + // The PluginInstanceChild was destroyed + return; + } + + DeletingObjectEntry* doe = nullptr; + if (instance->mDeletingHash) { + doe = instance->mDeletingHash->GetEntry(aNPObj); + if (!doe) { + NS_ERROR( + "An object for a destroyed instance isn't in the instance deletion " + "hash"); + return; + } + if (doe->mDeleted) return; + } + + int32_t refCnt = PR_ATOMIC_DECREMENT((int32_t*)&aNPObj->referenceCount); + NS_LOG_RELEASE(aNPObj, refCnt, "NPObject"); + + if (refCnt == 0) { + DeallocNPObject(aNPObj); + if (doe) doe->mDeleted = true; + } +} + +void PluginModuleChild::DeallocNPObject(NPObject* aNPObj) { + if (aNPObj->_class && aNPObj->_class->deallocate) { + aNPObj->_class->deallocate(aNPObj); + } else { + child::_memfree(aNPObj); + } + + PluginScriptableObjectChild* actor = + PluginScriptableObjectChild::GetActorForNPObject(aNPObj); + if (actor) actor->NPObjectDestroyed(); + + PluginScriptableObjectChild::UnregisterObject(aNPObj); +} + +NPIdentifier PluginModuleChild::NPN_GetStringIdentifier(const NPUTF8* aName) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + if (!aName) return 0; + + nsDependentCString name(aName); + PluginIdentifier ident(name); + PluginScriptableObjectChild::StackIdentifier stackID(ident); + stackID.MakePermanent(); + return stackID.ToNPIdentifier(); +} + +void PluginModuleChild::NPN_GetStringIdentifiers(const NPUTF8** aNames, + int32_t aNameCount, + NPIdentifier* aIdentifiers) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + if (!(aNames && aNameCount > 0 && aIdentifiers)) { + MOZ_CRASH("Bad input! Headed for a crash!"); + } + + for (int32_t index = 0; index < aNameCount; ++index) { + if (!aNames[index]) { + aIdentifiers[index] = 0; + continue; + } + nsDependentCString name(aNames[index]); + PluginIdentifier ident(name); + PluginScriptableObjectChild::StackIdentifier stackID(ident); + stackID.MakePermanent(); + aIdentifiers[index] = stackID.ToNPIdentifier(); + } +} + +bool PluginModuleChild::NPN_IdentifierIsString(NPIdentifier aIdentifier) { + PLUGIN_LOG_DEBUG_FUNCTION; + + PluginScriptableObjectChild::StackIdentifier stack(aIdentifier); + return stack.IsString(); +} + +NPIdentifier PluginModuleChild::NPN_GetIntIdentifier(int32_t aIntId) { + PLUGIN_LOG_DEBUG_FUNCTION; + AssertPluginThread(); + + PluginIdentifier ident(aIntId); + PluginScriptableObjectChild::StackIdentifier stackID(ident); + stackID.MakePermanent(); + return stackID.ToNPIdentifier(); +} + +NPUTF8* PluginModuleChild::NPN_UTF8FromIdentifier(NPIdentifier aIdentifier) { + PLUGIN_LOG_DEBUG_FUNCTION; + + PluginScriptableObjectChild::StackIdentifier stackID(aIdentifier); + if (stackID.IsString()) { + return ToNewCString(stackID.GetString()); + } + return nullptr; +} + +int32_t PluginModuleChild::NPN_IntFromIdentifier(NPIdentifier aIdentifier) { + PLUGIN_LOG_DEBUG_FUNCTION; + + PluginScriptableObjectChild::StackIdentifier stackID(aIdentifier); + if (!stackID.IsString()) { + return stackID.GetInt(); + } + return INT32_MIN; +} + +#ifdef OS_WIN +void PluginModuleChild::EnteredCall() { mIncallPumpingStack.AppendElement(); } + +void PluginModuleChild::ExitedCall() { + NS_ASSERTION(mIncallPumpingStack.Length(), "mismatched entered/exited"); + const IncallFrame& f = mIncallPumpingStack.LastElement(); + if (f._spinning) + MessageLoop::current()->SetNestableTasksAllowed( + f._savedNestableTasksAllowed); + + // XXX Is RemoveLastElement intentionally called only after calling + // SetNestableTasksAllowed? Otherwise, PopLastElement could be used above. + mIncallPumpingStack.RemoveLastElement(); +} + +LRESULT CALLBACK PluginModuleChild::CallWindowProcHook(int nCode, WPARAM wParam, + LPARAM lParam) { + // Trap and reply to anything we recognize as the source of a + // potential send message deadlock. + if (nCode >= 0 && + (InSendMessageEx(nullptr) & (ISMEX_REPLIED | ISMEX_SEND)) == ISMEX_SEND) { + CWPSTRUCT* pCwp = reinterpret_cast(lParam); + if (pCwp->message == WM_KILLFOCUS) { + // Fix for flash fullscreen window loosing focus. On single + // core systems, sync killfocus events need to be handled + // after the flash fullscreen window procedure processes this + // message, otherwise fullscreen focus will not work correctly. + wchar_t szClass[26]; + if (GetClassNameW(pCwp->hwnd, szClass, + sizeof(szClass) / sizeof(char16_t)) && + !wcscmp(szClass, kFlashFullscreenClass)) { + gDelayFlashFocusReplyUntilEval = true; + } + } + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +LRESULT CALLBACK PluginModuleChild::NestedInputEventHook(int nCode, + WPARAM wParam, + LPARAM lParam) { + PluginModuleChild* self = GetChrome(); + uint32_t len = self->mIncallPumpingStack.Length(); + if (nCode >= 0 && len && !self->mIncallPumpingStack[len - 1]._spinning) { + MessageLoop* loop = MessageLoop::current(); + self->SendProcessNativeEventsInInterruptCall(); + IncallFrame& f = self->mIncallPumpingStack[len - 1]; + f._spinning = true; + f._savedNestableTasksAllowed = loop->NestableTasksAllowed(); + loop->SetNestableTasksAllowed(true); + loop->set_os_modal_loop(true); + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +void PluginModuleChild::SetEventHooks() { + NS_ASSERTION( + !mNestedEventHook, + "mNestedEventHook already setup in call to SetNestedInputEventHook?"); + NS_ASSERTION( + !mGlobalCallWndProcHook, + "mGlobalCallWndProcHook already setup in call to CallWindowProcHook?"); + + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + // WH_MSGFILTER event hook for detecting modal loops in the child. + mNestedEventHook = SetWindowsHookEx(WH_MSGFILTER, NestedInputEventHook, + nullptr, GetCurrentThreadId()); + + // WH_CALLWNDPROC event hook for trapping sync messages sent from + // parent that can cause deadlocks. + mGlobalCallWndProcHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcHook, + nullptr, GetCurrentThreadId()); +} + +void PluginModuleChild::ResetEventHooks() { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + if (mNestedEventHook) UnhookWindowsHookEx(mNestedEventHook); + mNestedEventHook = nullptr; + if (mGlobalCallWndProcHook) UnhookWindowsHookEx(mGlobalCallWndProcHook); + mGlobalCallWndProcHook = nullptr; +} +#endif + +mozilla::ipc::IPCResult +PluginModuleChild::RecvProcessNativeEventsInInterruptCall() { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(OS_WIN) + ProcessNativeEventsInInterruptCall(); + return IPC_OK(); +#else + MOZ_CRASH( + "PluginModuleChild::RecvProcessNativeEventsInInterruptCall not " + "implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +#ifdef MOZ_WIDGET_COCOA +void PluginModuleChild::ProcessNativeEvents() { CallProcessSomeEvents(); } +#endif + +NPError PluginModuleChild::PluginRequiresAudioDeviceChanges( + PluginInstanceChild* aInstance, NPBool aShouldRegister) { +#ifdef XP_WIN + // Maintain a set of PluginInstanceChildren that we need to tell when the + // default audio device has changed. + NPError rv = NPERR_NO_ERROR; + if (aShouldRegister) { + if (mAudioNotificationSet.IsEmpty()) { + // We are registering the first plugin. Notify the PluginModuleParent + // that it needs to start sending us audio device notifications. + if (!CallNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + aShouldRegister, &rv)) { + return NPERR_GENERIC_ERROR; + } + } + if (rv == NPERR_NO_ERROR) { + mAudioNotificationSet.PutEntry(aInstance); + } + } else if (!mAudioNotificationSet.IsEmpty()) { + mAudioNotificationSet.RemoveEntry(aInstance); + if (mAudioNotificationSet.IsEmpty()) { + // We released the last plugin. Unregister from the PluginModuleParent. + if (!CallNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + aShouldRegister, &rv)) { + return NPERR_GENERIC_ERROR; + } + } + } + return rv; +#else + MOZ_CRASH( + "PluginRequiresAudioDeviceChanges is not available on this platform."); +#endif // XP_WIN +} + +mozilla::ipc::IPCResult +PluginModuleChild::RecvNPP_SetValue_NPNVaudioDeviceChangeDetails( + const NPAudioDeviceChangeDetailsIPC& detailsIPC) { +#if defined(XP_WIN) + NPAudioDeviceChangeDetails details; + details.flow = detailsIPC.flow; + details.role = detailsIPC.role; + details.defaultDevice = detailsIPC.defaultDevice.c_str(); + for (auto iter = mAudioNotificationSet.ConstIter(); !iter.Done(); + iter.Next()) { + PluginInstanceChild* pluginInst = iter.Get()->GetKey(); + pluginInst->DefaultAudioDeviceChanged(details); + } + return IPC_OK(); +#else + MOZ_CRASH( + "NPP_SetValue_NPNVaudioDeviceChangeDetails is a Windows-only message"); +#endif +} + +mozilla::ipc::IPCResult +PluginModuleChild::RecvNPP_SetValue_NPNVaudioDeviceStateChanged( + const NPAudioDeviceStateChangedIPC& aDeviceStateIPC) { +#if defined(XP_WIN) + NPAudioDeviceStateChanged stateChange; + stateChange.newState = aDeviceStateIPC.state; + stateChange.device = aDeviceStateIPC.device.c_str(); + for (auto iter = mAudioNotificationSet.ConstIter(); !iter.Done(); + iter.Next()) { + PluginInstanceChild* pluginInst = iter.Get()->GetKey(); + pluginInst->AudioDeviceStateChanged(stateChange); + } + return IPC_OK(); +#else + MOZ_CRASH("NPP_SetValue_NPNVaudioDeviceRemoved is a Windows-only message"); +#endif +} diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h new file mode 100644 index 0000000000..31d4eafb8f --- /dev/null +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -0,0 +1,345 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginModuleChild_h +#define dom_plugins_PluginModuleChild_h 1 + +#include "mozilla/Attributes.h" + +#include +#include + +#include "base/basictypes.h" + +#include "prlink.h" + +#include "npapi.h" +#include "npfunctions.h" + +#include "nsDataHashtable.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" + +#ifdef MOZ_WIDGET_COCOA +# include "PluginInterposeOSX.h" +#endif + +#include "mozilla/plugins/PPluginModuleChild.h" +#include "mozilla/plugins/PluginInstanceChild.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/plugins/PluginQuirks.h" + +#if defined(MOZ_WIDGET_GTK) +# include +#endif + +namespace mozilla { + +class ChildProfilerController; + +namespace plugins { + +class PluginInstanceChild; + +class PluginModuleChild : public PPluginModuleChild { + friend class PPluginModuleChild; + + protected: + virtual mozilla::ipc::RacyInterruptPolicy MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) override { + return MediateRace(parent, child); + } + + virtual bool ShouldContinueFromReplyTimeout() override; + + mozilla::ipc::IPCResult RecvSettingChanged(const PluginSettings& aSettings); + + // Implement the PPluginModuleChild interface + mozilla::ipc::IPCResult RecvInitProfiler( + Endpoint&& aEndpoint); + mozilla::ipc::IPCResult RecvDisableFlashProtectedMode(); + mozilla::ipc::IPCResult AnswerNP_GetEntryPoints(NPError* rv); + mozilla::ipc::IPCResult AnswerNP_Initialize(const PluginSettings& aSettings, + NPError* rv); + mozilla::ipc::IPCResult AnswerSyncNPP_New(PPluginInstanceChild* aActor, + NPError* rv); + + mozilla::ipc::IPCResult RecvInitPluginModuleChild( + Endpoint&& endpoint); + + mozilla::ipc::IPCResult RecvInitPluginFunctionBroker( + Endpoint&& endpoint); + + PPluginInstanceChild* AllocPPluginInstanceChild( + const nsCString& aMimeType, const nsTArray& aNames, + const nsTArray& aValues); + + bool DeallocPPluginInstanceChild(PPluginInstanceChild* aActor); + + mozilla::ipc::IPCResult RecvPPluginInstanceConstructor( + PPluginInstanceChild* aActor, const nsCString& aMimeType, + nsTArray&& aNames, nsTArray&& aValues) override; + mozilla::ipc::IPCResult AnswerNP_Shutdown(NPError* rv); + + mozilla::ipc::IPCResult AnswerOptionalFunctionsSupported( + bool* aURLRedirectNotify, bool* aClearSiteData, bool* aGetSitesWithData); + + mozilla::ipc::IPCResult RecvNPP_ClearSiteData(const nsCString& aSite, + const uint64_t& aFlags, + const uint64_t& aMaxAge, + const uint64_t& aCallbackId); + + mozilla::ipc::IPCResult RecvNPP_GetSitesWithData(const uint64_t& aCallbackId); + + mozilla::ipc::IPCResult RecvSetAudioSessionData(const nsID& aId, + const nsString& aDisplayName, + const nsString& aIconPath); + + mozilla::ipc::IPCResult RecvSetParentHangTimeout(const uint32_t& aSeconds); + + mozilla::ipc::IPCResult AnswerInitCrashReporter( + mozilla::dom::NativeThreadId* aId); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + mozilla::ipc::IPCResult RecvProcessNativeEventsInInterruptCall(); + + mozilla::ipc::IPCResult AnswerModuleSupportsAsyncRender(bool* aResult); + + public: + explicit PluginModuleChild(bool aIsChrome); + virtual ~PluginModuleChild(); + + void CommonInit(); + +#if defined(OS_WIN) && defined(MOZ_SANDBOX) + // Path to the roaming Flash Player folder. This is used to restore some + // behavior blocked by the sandbox. + static void SetFlashRoamingPath(const std::wstring& aRoamingPath); + static std::wstring GetFlashRoamingPath(); +#endif + + // aPluginFilename is UTF8, not native-charset! + bool InitForChrome(const std::string& aPluginFilename, + base::ProcessId aParentPid, MessageLoop* aIOLoop, + UniquePtr aChannel); + + bool InitForContent(Endpoint&& aEndpoint); + + static bool CreateForContentProcess(Endpoint&& aEndpoint); + + void CleanUp(); + + NPError NP_Shutdown(); + + const char* GetUserAgent(); + + static const NPNetscapeFuncs sBrowserFuncs; + + static PluginModuleChild* GetChrome(); + + /** + * The child implementation of NPN_CreateObject. + */ + static NPObject* NPN_CreateObject(NPP aNPP, NPClass* aClass); + /** + * The child implementation of NPN_RetainObject. + */ + static NPObject* NPN_RetainObject(NPObject* aNPObj); + /** + * The child implementation of NPN_ReleaseObject. + */ + static void NPN_ReleaseObject(NPObject* aNPObj); + + /** + * The child implementations of NPIdentifier-related functions. + */ + static NPIdentifier NPN_GetStringIdentifier(const NPUTF8* aName); + static void NPN_GetStringIdentifiers(const NPUTF8** aNames, + int32_t aNameCount, + NPIdentifier* aIdentifiers); + static NPIdentifier NPN_GetIntIdentifier(int32_t aIntId); + static bool NPN_IdentifierIsString(NPIdentifier aIdentifier); + static NPUTF8* NPN_UTF8FromIdentifier(NPIdentifier aIdentifier); + static int32_t NPN_IntFromIdentifier(NPIdentifier aIdentifier); + +#ifdef MOZ_WIDGET_COCOA + void ProcessNativeEvents(); + + void PluginShowWindow(uint32_t window_id, bool modal, CGRect r) { + SendPluginShowWindow(window_id, modal, r.origin.x, r.origin.y, r.size.width, + r.size.height); + } + + void PluginHideWindow(uint32_t window_id) { SendPluginHideWindow(window_id); } + + void SetCursor(NSCursorInfo& cursorInfo) { SendSetCursor(cursorInfo); } + + void ShowCursor(bool show) { SendShowCursor(show); } + + void PushCursor(NSCursorInfo& cursorInfo) { SendPushCursor(cursorInfo); } + + void PopCursor() { SendPopCursor(); } + + bool GetNativeCursorsSupported() { + return Settings().nativeCursorsSupported(); + } +#endif + + int GetQuirks() { return mQuirks; } + + const PluginSettings& Settings() const { return mCachedSettings; } + + NPError PluginRequiresAudioDeviceChanges(PluginInstanceChild* aInstance, + NPBool aShouldRegister); + mozilla::ipc::IPCResult RecvNPP_SetValue_NPNVaudioDeviceChangeDetails( + const NPAudioDeviceChangeDetailsIPC& detailsIPC); + mozilla::ipc::IPCResult RecvNPP_SetValue_NPNVaudioDeviceStateChanged( + const NPAudioDeviceStateChangedIPC& aDeviceStateIPC); + + private: + NPError DoNP_Initialize(const PluginSettings& aSettings); + void AddQuirk(PluginQuirks quirk) { + if (mQuirks == QUIRKS_NOT_INITIALIZED) mQuirks = 0; + mQuirks |= quirk; + } + void InitQuirksModes(const nsCString& aMimeType); + bool InitGraphics(); + void DeinitGraphics(); + +#if defined(MOZ_WIDGET_GTK) + static gboolean DetectNestedEventLoop(gpointer data); + static gboolean ProcessBrowserEvents(gpointer data); + + virtual void EnteredCxxStack() override; + virtual void ExitedCxxStack() override; +#endif + + PRLibrary* mLibrary; + nsCString mPluginFilename; // UTF8 + int mQuirks; + + bool mIsChrome; + bool mHasShutdown; // true if NP_Shutdown has run + +#ifdef MOZ_GECKO_PROFILER + RefPtr mProfilerController; +#endif + + // we get this from the plugin + NP_PLUGINSHUTDOWN mShutdownFunc; +#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + NP_PLUGINUNIXINIT mInitializeFunc; +#elif defined(OS_WIN) || defined(OS_MACOSX) + NP_PLUGININIT mInitializeFunc; + NP_GETENTRYPOINTS mGetEntryPointsFunc; +#endif + + NPPluginFuncs mFunctions; + + PluginSettings mCachedSettings; + +#if defined(MOZ_WIDGET_GTK) + // If a plugin spins a nested glib event loop in response to a + // synchronous IPC message from the browser, the loop might break + // only after the browser responds to a request sent by the + // plugin. This can happen if a plugin uses gtk's synchronous + // copy/paste, for example. But because the browser is blocked on + // a condvar, it can't respond to the request. This situation + // isn't technically a deadlock, but the symptoms are basically + // the same from the user's perspective. + // + // We take two steps to prevent this + // + // (1) Detect nested event loops spun by the plugin. This is + // done by scheduling a glib timer event in the plugin + // process whenever the browser might block on the plugin. + // If the plugin indeed spins a nested loop, this timer event + // will fire "soon" thereafter. + // + // (2) When a nested loop is detected, deschedule the + // nested-loop-detection timer and in its place, schedule + // another timer that periodically calls back into the + // browser and spins a mini event loop. This mini event loop + // processes a handful of pending native events. + // + // Because only timer (1) or (2) (or neither) may be active at any + // point in time, we use the same member variable + // |mNestedLoopTimerId| to refer to both. + // + // When the browser no longer might be blocked on a plugin's IPC + // response, we deschedule whichever of (1) or (2) is active. + guint mNestedLoopTimerId; +# ifdef DEBUG + // Depth of the stack of calls to g_main_context_dispatch before any + // nested loops are run. This is 1 when IPC calls are dispatched from + // g_main_context_iteration, or 0 when dispatched directly from + // MessagePumpForUI. + int mTopLoopDepth; +# endif +#endif + +#if defined(XP_WIN) + typedef nsTHashtable> PluginInstanceSet; + // Set of plugins that have registered to be notified when the audio device + // changes. + PluginInstanceSet mAudioNotificationSet; +#endif + + public: // called by PluginInstanceChild + /** + * Dealloc an NPObject after last-release or when the associated instance + * is destroyed. This function will remove the object from mObjectMap. + */ + static void DeallocNPObject(NPObject* o); + + NPError NPP_Destroy(PluginInstanceChild* instance) { + return mFunctions.destroy(instance->GetNPP(), 0); + } + +#if defined(OS_MACOSX) && defined(MOZ_SANDBOX) + void EnableFlashSandbox(int aLevel, bool aShouldEnableLogging); +#endif + + private: +#if defined(OS_MACOSX) && defined(MOZ_SANDBOX) + int mFlashSandboxLevel; + bool mEnableFlashSandboxLogging; +#endif + +#if defined(OS_WIN) + virtual void EnteredCall() override; + virtual void ExitedCall() override; + + // Entered/ExitedCall notifications keep track of whether the plugin has + // entered a nested event loop within this interrupt call. + struct IncallFrame { + IncallFrame() : _spinning(false), _savedNestableTasksAllowed(false) {} + + bool _spinning; + bool _savedNestableTasksAllowed; + }; + + AutoTArray mIncallPumpingStack; + + static LRESULT CALLBACK NestedInputEventHook(int code, WPARAM wParam, + LPARAM lParam); + static LRESULT CALLBACK CallWindowProcHook(int code, WPARAM wParam, + LPARAM lParam); + void SetEventHooks(); + void ResetEventHooks(); + HHOOK mNestedEventHook; + HHOOK mGlobalCallWndProcHook; + + public: + bool mAsyncRenderSupport; +#endif +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif // ifndef dom_plugins_PluginModuleChild_h diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp new file mode 100644 index 0000000000..6743ed1893 --- /dev/null +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -0,0 +1,2541 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/plugins/PluginModuleParent.h" + +#include "base/process_util.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/Document.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/CrashReporterHost.h" +#include "mozilla/dom/Element.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/plugins/BrowserStreamParent.h" +#include "mozilla/plugins/PluginBridge.h" +#include "mozilla/plugins/PluginInstanceParent.h" +#include "mozilla/plugins/PPluginBackgroundDestroyerParent.h" +#include "mozilla/plugins/PStreamNotifyParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/UniquePtr.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsICrashService.h" +#include "nsIObserverService.h" +#include "nsNPAPIPlugin.h" +#include "nsPluginInstanceOwner.h" +#include "nsPrintfCString.h" +#include "prsystem.h" +#include "prclist.h" +#include "PluginQuirks.h" +#include "gfxPlatform.h" +#include "GeckoProfiler.h" +#include "nsPluginTags.h" +#include "nsUnicharUtils.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" + +#ifdef XP_WIN +# include "mozilla/plugins/PluginSurfaceParent.h" +# include "mozilla/widget/AudioSession.h" +# include "PluginHangUIParent.h" +# include "FunctionBrokerParent.h" +# include "PluginUtilsWin.h" +#endif + +#ifdef MOZ_WIDGET_GTK +# include +#elif XP_MACOSX +# include "PluginInterposeOSX.h" +# include "PluginUtilsOSX.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "ProfilerParent.h" +#endif + +using base::KillProcess; + +using mozilla::PluginLibrary; +using mozilla::ipc::GeckoChildProcessHost; +using mozilla::ipc::MessageChannel; + +using namespace mozilla; +using namespace mozilla::plugins; +using namespace mozilla::plugins::parent; + +using namespace CrashReporter; + +static const char kContentTimeoutPref[] = "dom.ipc.plugins.contentTimeoutSecs"; +static const char kChildTimeoutPref[] = "dom.ipc.plugins.timeoutSecs"; +static const char kParentTimeoutPref[] = "dom.ipc.plugins.parentTimeoutSecs"; +static const char kLaunchTimeoutPref[] = + "dom.ipc.plugins.processLaunchTimeoutSecs"; +#ifdef XP_WIN +static const char kHangUITimeoutPref[] = "dom.ipc.plugins.hangUITimeoutSecs"; +static const char kHangUIMinDisplayPref[] = + "dom.ipc.plugins.hangUIMinDisplaySecs"; +# define CHILD_TIMEOUT_PREF kHangUITimeoutPref +#else +# define CHILD_TIMEOUT_PREF kChildTimeoutPref +#endif + +bool mozilla::plugins::SetupBridge( + uint32_t aPluginId, dom::ContentParent* aContentParent, nsresult* rv, + uint32_t* runID, ipc::Endpoint* aEndpoint) { + AUTO_PROFILER_LABEL("plugins::SetupBridge", OTHER); + if (NS_WARN_IF(!rv) || NS_WARN_IF(!runID)) { + return false; + } + + RefPtr host = nsPluginHost::GetInst(); + RefPtr plugin; + *rv = host->GetPluginForContentProcess(aPluginId, getter_AddRefs(plugin)); + if (NS_FAILED(*rv)) { + return true; + } + PluginModuleChromeParent* chromeParent = + static_cast(plugin->GetLibrary()); + *rv = chromeParent->GetRunID(runID); + if (NS_FAILED(*rv)) { + return true; + } + + ipc::Endpoint parent; + ipc::Endpoint child; + + *rv = PPluginModule::CreateEndpoints( + aContentParent->OtherPid(), chromeParent->OtherPid(), &parent, &child); + if (NS_FAILED(*rv)) { + return true; + } + + *aEndpoint = std::move(parent); + + if (!chromeParent->SendInitPluginModuleChild(std::move(child))) { + *rv = NS_ERROR_BRIDGE_OPEN_CHILD; + return true; + } + + return true; +} + +#ifdef MOZ_CRASHREPORTER_INJECTOR + +/** + * Use for executing CreateToolhelp32Snapshot off main thread + */ +class mozilla::plugins::FinishInjectorInitTask + : public mozilla::CancelableRunnable { + public: + FinishInjectorInitTask() + : CancelableRunnable("FinishInjectorInitTask"), + mMutex("FlashInjectorInitTask::mMutex"), + mParent(nullptr), + mMainThreadMsgLoop(MessageLoop::current()) { + MOZ_ASSERT(NS_IsMainThread()); + } + + void Init(PluginModuleChromeParent* aParent) { + MOZ_ASSERT(aParent); + mParent = aParent; + } + + void PostToMainThread() { + RefPtr self = this; + mSnapshot.own(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); + { // Scope for lock + mozilla::MutexAutoLock lock(mMutex); + if (mMainThreadMsgLoop) { + mMainThreadMsgLoop->PostTask(self.forget()); + } + } + } + + NS_IMETHOD Run() override { + mParent->DoInjection(mSnapshot); + // We don't need to hold this lock during DoInjection, but we do need + // to obtain it before returning from Run() to ensure that + // PostToMainThread has completed before we return. + mozilla::MutexAutoLock lock(mMutex); + return NS_OK; + } + + nsresult Cancel() override { + mozilla::MutexAutoLock lock(mMutex); + mMainThreadMsgLoop = nullptr; + return NS_OK; + } + + private: + mozilla::Mutex mMutex; + nsAutoHandle mSnapshot; + PluginModuleChromeParent* mParent; + MessageLoop* mMainThreadMsgLoop; +}; + +#endif // MOZ_CRASHREPORTER_INJECTOR + +namespace { + +/** + * Objects of this class remain linked until an error occurs in the + * plugin initialization sequence. + */ +class PluginModuleMapping : public PRCList { + public: + explicit PluginModuleMapping(uint32_t aPluginId) + : mPluginId(aPluginId), + mProcessIdValid(false), + mProcessId(0), + mModule(nullptr), + mChannelOpened(false) { + MOZ_COUNT_CTOR(PluginModuleMapping); + PR_INIT_CLIST(this); + PR_APPEND_LINK(this, &sModuleListHead); + } + + ~PluginModuleMapping() { + PR_REMOVE_LINK(this); + MOZ_COUNT_DTOR(PluginModuleMapping); + } + + bool IsChannelOpened() const { return mChannelOpened; } + + void SetChannelOpened() { mChannelOpened = true; } + + PluginModuleContentParent* GetModule() { + if (!mModule) { + mModule = new PluginModuleContentParent(); + } + return mModule; + } + + static PluginModuleMapping* AssociateWithProcessId( + uint32_t aPluginId, base::ProcessId aProcessId) { + PluginModuleMapping* mapping = + static_cast(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + mapping->AssociateWithProcessId(aProcessId); + return mapping; + } + mapping = static_cast(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static PluginModuleMapping* Resolve(base::ProcessId aProcessId) { + PluginModuleMapping* mapping = nullptr; + + if (sIsLoadModuleOnStack) { + // Special case: If loading synchronously, we just need to access + // the tail entry of the list. + mapping = + static_cast(PR_LIST_TAIL(&sModuleListHead)); + MOZ_ASSERT(mapping); + return mapping; + } + + mapping = static_cast(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mProcessIdValid && mapping->mProcessId == aProcessId) { + return mapping; + } + mapping = static_cast(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static PluginModuleMapping* FindModuleByPluginId(uint32_t aPluginId) { + PluginModuleMapping* mapping = + static_cast(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + return mapping; + } + mapping = static_cast(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + class MOZ_RAII NotifyLoadingModule { + public: + explicit NotifyLoadingModule() { + PluginModuleMapping::sIsLoadModuleOnStack = true; + } + + ~NotifyLoadingModule() { + PluginModuleMapping::sIsLoadModuleOnStack = false; + } + + private: + }; + + private: + void AssociateWithProcessId(base::ProcessId aProcessId) { + MOZ_ASSERT(!mProcessIdValid); + mProcessId = aProcessId; + mProcessIdValid = true; + } + + uint32_t mPluginId; + bool mProcessIdValid; + base::ProcessId mProcessId; + PluginModuleContentParent* mModule; + bool mChannelOpened; + + friend class NotifyLoadingModule; + + static PRCList sModuleListHead; + static bool sIsLoadModuleOnStack; +}; + +PRCList PluginModuleMapping::sModuleListHead = + PR_INIT_STATIC_CLIST(&PluginModuleMapping::sModuleListHead); + +bool PluginModuleMapping::sIsLoadModuleOnStack = false; + +} // namespace + +static PluginModuleChromeParent* PluginModuleChromeParentForId( + const uint32_t aPluginId) { + MOZ_ASSERT(XRE_IsParentProcess()); + + RefPtr host = nsPluginHost::GetInst(); + nsPluginTag* pluginTag = host->PluginWithId(aPluginId); + if (!pluginTag || !pluginTag->mPlugin) { + return nullptr; + } + RefPtr plugin = pluginTag->mPlugin; + + return static_cast(plugin->GetLibrary()); +} + +void mozilla::plugins::TakeFullMinidump(uint32_t aPluginId, + base::ProcessId aContentProcessId, + const nsAString& aBrowserDumpId, + nsString& aDumpId) { + PluginModuleChromeParent* chromeParent = + PluginModuleChromeParentForId(aPluginId); + + if (chromeParent) { + chromeParent->TakeFullMinidump(aContentProcessId, aBrowserDumpId, aDumpId); + } +} + +void mozilla::plugins::TerminatePlugin(uint32_t aPluginId, + base::ProcessId aContentProcessId, + const nsCString& aMonitorDescription, + const nsAString& aDumpId) { + PluginModuleChromeParent* chromeParent = + PluginModuleChromeParentForId(aPluginId); + + if (chromeParent) { + chromeParent->TerminateChildProcess(MessageLoop::current(), + aContentProcessId, aMonitorDescription, + aDumpId); + } +} + +/* static */ +PluginLibrary* PluginModuleContentParent::LoadModule(uint32_t aPluginId, + nsPluginTag* aPluginTag) { + PluginModuleMapping::NotifyLoadingModule loadingModule; + UniquePtr mapping(new PluginModuleMapping(aPluginId)); + + MOZ_ASSERT(XRE_IsContentProcess()); + + /* + * We send a LoadPlugin message to the chrome process using an intr + * message. Before it sends its response, it sends a message to create + * PluginModuleParent instance. That message is handled by + * PluginModuleContentParent::Initialize, which saves the instance in + * its module mapping. We fetch it from there after LoadPlugin finishes. + */ + dom::ContentChild* cp = dom::ContentChild::GetSingleton(); + nsresult rv; + uint32_t runID; + Endpoint endpoint; + if (!cp->SendLoadPlugin(aPluginId, &rv, &runID, &endpoint) || NS_FAILED(rv)) { + return nullptr; + } + Initialize(std::move(endpoint)); + + PluginModuleContentParent* parent = mapping->GetModule(); + MOZ_ASSERT(parent); + + if (!mapping->IsChannelOpened()) { + // mapping is linked into PluginModuleMapping::sModuleListHead and is + // needed later, so since this function is returning successfully we + // forget it here. + Unused << mapping.release(); + } + + parent->mPluginId = aPluginId; + parent->mRunID = runID; + + return parent; +} + +/* static */ +void PluginModuleContentParent::Initialize( + Endpoint&& aEndpoint) { + UniquePtr moduleMapping( + PluginModuleMapping::Resolve(aEndpoint.OtherPid())); + MOZ_ASSERT(moduleMapping); + PluginModuleContentParent* parent = moduleMapping->GetModule(); + MOZ_ASSERT(parent); + + DebugOnly ok = aEndpoint.Bind(parent); + MOZ_ASSERT(ok); + + moduleMapping->SetChannelOpened(); + + if (XRE_UseNativeEventProcessing()) { + // If we're processing native events in our message pump, request Windows + // message deferral behavior on our channel. This applies to the top level + // and all sub plugin protocols since they all share the same channel. + parent->GetIPCChannel()->SetChannelFlags( + MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); + } + + TimeoutChanged(kContentTimeoutPref, parent); + + // moduleMapping is linked into PluginModuleMapping::sModuleListHead and is + // needed later, so since this function is returning successfully we + // forget it here. + Unused << moduleMapping.release(); +} + +// static +PluginLibrary* PluginModuleChromeParent::LoadModule(const char* aFilePath, + uint32_t aPluginId, + nsPluginTag* aPluginTag) { + PLUGIN_LOG_DEBUG_FUNCTION; + + UniquePtr parent(new PluginModuleChromeParent( + aFilePath, aPluginId, aPluginTag->mSandboxLevel)); + UniquePtr onLaunchedRunnable( + new LaunchedTask(parent.get())); + bool launched = parent->mSubprocess->Launch( + std::move(onLaunchedRunnable), aPluginTag->mSandboxLevel, + aPluginTag->mIsSandboxLoggingEnabled); + if (!launched) { + // We never reached open + parent->mShutdown = true; + return nullptr; + } + parent->mIsFlashPlugin = aPluginTag->mIsFlashPlugin; + uint32_t blocklistState; + nsresult rv = aPluginTag->GetBlocklistState(&blocklistState); + parent->mIsBlocklisted = NS_FAILED(rv) || blocklistState != 0; + int32_t launchTimeoutSecs = Preferences::GetInt(kLaunchTimeoutPref, 0); + if (!parent->mSubprocess->WaitUntilConnected(launchTimeoutSecs * 1000)) { + parent->mShutdown = true; + return nullptr; + } + +#if defined(XP_WIN) + Endpoint brokerParentEnd; + Endpoint brokerChildEnd; + rv = PFunctionBroker::CreateEndpoints(base::GetCurrentProcId(), + parent->OtherPid(), &brokerParentEnd, + &brokerChildEnd); + if (NS_FAILED(rv)) { + parent->mShutdown = true; + return nullptr; + } + + parent->mBrokerParent = + FunctionBrokerParent::Create(std::move(brokerParentEnd)); + if (parent->mBrokerParent) { + Unused << parent->SendInitPluginFunctionBroker(std::move(brokerChildEnd)); + } +#endif + return parent.release(); +} + +static const char* gCallbackPrefs[] = { + kChildTimeoutPref, + kParentTimeoutPref, +#ifdef XP_WIN + kHangUITimeoutPref, + kHangUIMinDisplayPref, +#endif + nullptr, +}; + +void PluginModuleChromeParent::OnProcessLaunched(const bool aSucceeded) { + if (!aSucceeded) { + mShutdown = true; + OnInitFailure(); + return; + } + // We may have already been initialized by another call that was waiting + // for process connect. If so, this function doesn't need to run. + if (mShutdown) { + return; + } + + Open(mSubprocess->TakeChannel(), + base::GetProcId(mSubprocess->GetChildProcessHandle())); + + // Request Windows message deferral behavior on our channel. This + // applies to the top level and all sub plugin protocols since they + // all share the same channel. + GetIPCChannel()->SetChannelFlags( + MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); + + TimeoutChanged(CHILD_TIMEOUT_PREF, this); + + Preferences::RegisterCallbacks(TimeoutChanged, gCallbackPrefs, + static_cast(this)); + + RegisterSettingsCallbacks(); + + // If this fails, we're having IPC troubles, and we're doomed anyways. + if (!InitCrashReporter()) { + mShutdown = true; + Close(); + OnInitFailure(); + return; + } + +#if defined(XP_WIN) && defined(_X86_) + // Protected mode only applies to Windows and only to x86. + if (!mIsBlocklisted && mIsFlashPlugin && + (Preferences::GetBool("dom.ipc.plugins.flash.disable-protected-mode", + false) || + mSandboxLevel >= 2)) { + Unused << SendDisableFlashProtectedMode(); + } +#endif + +#ifdef MOZ_GECKO_PROFILER + Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid())); +#endif +} + +bool PluginModuleChromeParent::InitCrashReporter() { + NativeThreadId threadId; + if (!CallInitCrashReporter(&threadId)) { + return false; + } + + { + mozilla::MutexAutoLock lock(mCrashReporterMutex); + mCrashReporter = + MakeUnique(GeckoProcessType_Plugin, threadId); + } + + return true; +} + +PluginModuleParent::PluginModuleParent(bool aIsChrome) + : mQuirks(QUIRKS_NOT_INITIALIZED), + mIsChrome(aIsChrome), + mShutdown(false), + mHadLocalInstance(false), + mClearSiteDataSupported(false), + mGetSitesWithDataSupported(false), + mNPNIface(nullptr), + mNPPIface(nullptr), + mPlugin(nullptr), + mTaskFactory(this), + mSandboxLevel(0), + mIsFlashPlugin(false), + mRunID(0), + mCrashReporterMutex("PluginModuleChromeParent::mCrashReporterMutex") {} + +PluginModuleParent::~PluginModuleParent() { + if (!OkToCleanup()) { + MOZ_CRASH("unsafe destruction"); + } + + if (!mShutdown) { + NS_WARNING("Plugin host deleted the module without shutting down."); + NPError err; + NP_Shutdown(&err); + } +} + +PluginModuleContentParent::PluginModuleContentParent() + : PluginModuleParent(false), mPluginId(0) { + Preferences::RegisterCallback(TimeoutChanged, kContentTimeoutPref, + static_cast(this)); +} + +PluginModuleContentParent::~PluginModuleContentParent() { + Preferences::UnregisterCallback(TimeoutChanged, kContentTimeoutPref, + static_cast(this)); +} + +PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, + uint32_t aPluginId, + int32_t aSandboxLevel) + : PluginModuleParent(true), + mSubprocess(new PluginProcessParent(aFilePath)), + mPluginId(aPluginId), + mChromeTaskFactory(this), + mHangAnnotationFlags(0) +#ifdef XP_WIN + , + mPluginCpuUsageOnHang(), + mHangUIParent(nullptr), + mHangUIEnabled(true), + mIsTimerReset(true), + mBrokerParent(nullptr) +#endif +#ifdef MOZ_CRASHREPORTER_INJECTOR + , + mFlashProcess1(0), + mFlashProcess2(0), + mFinishInitTask(nullptr) +#endif + , + mIsBlocklisted(false), + mIsCleaningFromTimeout(false) { + NS_ASSERTION(mSubprocess, "Out of memory!"); + mSandboxLevel = aSandboxLevel; + mRunID = GeckoChildProcessHost::GetUniqueID(); + + mozilla::BackgroundHangMonitor::RegisterAnnotator(*this); +} + +PluginModuleChromeParent::~PluginModuleChromeParent() { + if (!OkToCleanup()) { + MOZ_CRASH("unsafe destruction"); + } + +#ifdef XP_WIN + // If we registered for audio notifications, stop. + mozilla::plugins::PluginUtilsWin::RegisterForAudioDeviceChanges(this, false); +#endif + + if (!mShutdown) { + NS_WARNING("Plugin host deleted the module without shutting down."); + NPError err; + NP_Shutdown(&err); + } + + NS_ASSERTION(mShutdown, "NP_Shutdown didn't"); + + if (mSubprocess) { + mSubprocess->Destroy(); + mSubprocess = nullptr; + } + +#ifdef MOZ_CRASHREPORTER_INJECTOR + if (mFlashProcess1) UnregisterInjectorCallback(mFlashProcess1); + if (mFlashProcess2) UnregisterInjectorCallback(mFlashProcess2); + if (mFinishInitTask) { + // mFinishInitTask will be deleted by the main thread message_loop + mFinishInitTask->Cancel(); + } +#endif + + UnregisterSettingsCallbacks(); + + Preferences::UnregisterCallbacks(TimeoutChanged, gCallbackPrefs, + static_cast(this)); + +#ifdef XP_WIN + if (mHangUIParent) { + delete mHangUIParent; + mHangUIParent = nullptr; + } +#endif + + mozilla::BackgroundHangMonitor::UnregisterAnnotator(*this); +} + +void PluginModuleChromeParent::AddCrashAnnotations() { + // mCrashReporterMutex is already held by the caller + mCrashReporterMutex.AssertCurrentThreadOwns(); + + typedef nsDependentCString cstring; + + // Get the plugin filename, try to get just the file leafname + const std::string& pluginFile = mSubprocess->GetPluginFilePath(); + size_t filePos = pluginFile.rfind(FILE_PATH_SEPARATOR); + if (filePos == std::string::npos) + filePos = 0; + else + filePos++; + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginFilename, + cstring(pluginFile.substr(filePos).c_str())); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginName, + mPluginName); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginVersion, + mPluginVersion); + + if (mCrashReporter) { +#ifdef XP_WIN + if (mPluginCpuUsageOnHang.Length() > 0) { + nsCString cpuUsageStr; + cpuUsageStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[0] * 100) / 100); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::PluginCpuUsage, + cpuUsageStr); + +# ifdef MOZ_CRASHREPORTER_INJECTOR + for (uint32_t i = 1; i < mPluginCpuUsageOnHang.Length(); ++i) { + nsCString tempStr; + tempStr.AppendFloat(std::ceil(mPluginCpuUsageOnHang[i] * 100) / 100); + // HACK: There can only be at most two flash processes hence + // the hardcoded annotations + CrashReporter::Annotation annotation = + (i == 1) ? CrashReporter::Annotation::CpuUsageFlashProcess1 + : CrashReporter::Annotation::CpuUsageFlashProcess2; + mCrashReporter->AddAnnotation(annotation, tempStr); + } +# endif + } +#endif + } +} + +void PluginModuleParent::SetChildTimeout(const int32_t aChildTimeout) { + int32_t timeoutMs = + (aChildTimeout > 0) ? (1000 * aChildTimeout) : MessageChannel::kNoTimeout; + SetReplyTimeoutMs(timeoutMs); +} + +void PluginModuleParent::TimeoutChanged(const char* aPref, void* aModule) { + auto module = static_cast(aModule); + + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); +#ifndef XP_WIN + if (!strcmp(aPref, kChildTimeoutPref)) { + MOZ_ASSERT(module->IsChrome()); + // The timeout value used by the parent for children + int32_t timeoutSecs = Preferences::GetInt(kChildTimeoutPref, 0); + module->SetChildTimeout(timeoutSecs); +#else + if (!strcmp(aPref, kChildTimeoutPref) || + !strcmp(aPref, kHangUIMinDisplayPref) || + !strcmp(aPref, kHangUITimeoutPref)) { + MOZ_ASSERT(module->IsChrome()); + static_cast(module)->EvaluateHangUIState(true); +#endif // XP_WIN + } else if (!strcmp(aPref, kParentTimeoutPref)) { + // The timeout value used by the child for its parent + MOZ_ASSERT(module->IsChrome()); + int32_t timeoutSecs = Preferences::GetInt(kParentTimeoutPref, 0); + Unused << static_cast(module) + ->SendSetParentHangTimeout(timeoutSecs); + } else if (!strcmp(aPref, kContentTimeoutPref)) { + MOZ_ASSERT(!module->IsChrome()); + int32_t timeoutSecs = Preferences::GetInt(kContentTimeoutPref, 0); + module->SetChildTimeout(timeoutSecs); + } +} + +void PluginModuleChromeParent::CleanupFromTimeout(const bool aFromHangUI) { + if (mShutdown) { + return; + } + + if (!OkToCleanup()) { + // there's still plugin code on the C++ stack, try again + MessageLoop::current()->PostDelayedTask( + mChromeTaskFactory.NewRunnableMethod( + &PluginModuleChromeParent::CleanupFromTimeout, aFromHangUI), + 10); + return; + } + + // Avoid recursively calling this method. MessageChannel::Close() can + // cause this task to be re-launched. + if (mIsCleaningFromTimeout) { + return; + } + + AutoRestore resetCleaningFlag(mIsCleaningFromTimeout); + mIsCleaningFromTimeout = true; + + /* If the plugin container was terminated by the Plugin Hang UI, + then either the I/O thread detects a channel error, or the + main thread must set the error (whomever gets there first). + OTOH, if we terminate and return false from + ShouldContinueFromReplyTimeout, then the channel state has + already been set to ChannelTimeout and we should call the + regular Close function. */ + if (aFromHangUI) { + GetIPCChannel()->CloseWithError(); + } else { + Close(); + } +} + +#ifdef XP_WIN +namespace { + +uint64_t FileTimeToUTC(const FILETIME& ftime) { + ULARGE_INTEGER li; + li.LowPart = ftime.dwLowDateTime; + li.HighPart = ftime.dwHighDateTime; + return li.QuadPart; +} + +struct CpuUsageSamples { + uint64_t sampleTimes[2]; + uint64_t cpuTimes[2]; +}; + +bool GetProcessCpuUsage(const nsTArray& processHandles, + nsTArray& cpuUsage) { + nsTArray samples(processHandles.Length()); + FILETIME creationTime, exitTime, kernelTime, userTime, currentTime; + BOOL res; + + for (uint32_t i = 0; i < processHandles.Length(); ++i) { + ::GetSystemTimeAsFileTime(¤tTime); + res = ::GetProcessTimes(processHandles[i], &creationTime, &exitTime, + &kernelTime, &userTime); + if (!res) { + NS_WARNING("failed to get process times"); + return false; + } + + CpuUsageSamples s; + s.sampleTimes[0] = FileTimeToUTC(currentTime); + s.cpuTimes[0] = FileTimeToUTC(kernelTime) + FileTimeToUTC(userTime); + samples.AppendElement(s); + } + + // we already hung for a while, a little bit longer won't matter + ::Sleep(50); + + const int32_t numberOfProcessors = PR_GetNumberOfProcessors(); + + for (uint32_t i = 0; i < processHandles.Length(); ++i) { + ::GetSystemTimeAsFileTime(¤tTime); + res = ::GetProcessTimes(processHandles[i], &creationTime, &exitTime, + &kernelTime, &userTime); + if (!res) { + NS_WARNING("failed to get process times"); + return false; + } + + samples[i].sampleTimes[1] = FileTimeToUTC(currentTime); + samples[i].cpuTimes[1] = + FileTimeToUTC(kernelTime) + FileTimeToUTC(userTime); + + const uint64_t deltaSampleTime = + samples[i].sampleTimes[1] - samples[i].sampleTimes[0]; + const uint64_t deltaCpuTime = + samples[i].cpuTimes[1] - samples[i].cpuTimes[0]; + const float usage = + 100.f * (float(deltaCpuTime) / deltaSampleTime) / numberOfProcessors; + cpuUsage.AppendElement(usage); + } + + return true; +} + +} // namespace + +#endif // #ifdef XP_WIN + +/** + * This function converts the topmost routing id on the call stack (as recorded + * by the MessageChannel) into a pointer to a IProtocol object. + */ +mozilla::ipc::IProtocol* PluginModuleChromeParent::GetInvokingProtocol() { + int32_t routingId = GetIPCChannel()->GetTopmostMessageRoutingId(); + // Nothing being routed. No protocol. Just return nullptr. + if (routingId == MSG_ROUTING_NONE) { + return nullptr; + } + // If routingId is MSG_ROUTING_CONTROL then we're dealing with control + // messages that were initiated by the topmost managing protocol, ie. this. + if (routingId == MSG_ROUTING_CONTROL) { + return this; + } + // Otherwise we can look up the protocol object by the routing id. + mozilla::ipc::IProtocol* protocol = Lookup(routingId); + return protocol; +} + +/** + * This function examines the IProtocol object parameter and converts it into + * the PluginInstanceParent object that is associated with that protocol, if + * any. Since PluginInstanceParent manages subprotocols, this function needs + * to determine whether |aProtocol| is a subprotocol, and if so it needs to + * obtain the protocol's manager. + * + * This function needs to be updated if the subprotocols are modified in + * PPluginInstance.ipdl. + */ +PluginInstanceParent* PluginModuleChromeParent::GetManagingInstance( + mozilla::ipc::IProtocol* aProtocol) { + MOZ_ASSERT(aProtocol); + mozilla::ipc::IProtocol* listener = aProtocol; + switch (listener->GetProtocolId()) { + case PPluginInstanceMsgStart: + // In this case, aProtocol is the instance itself. Just cast it. + return static_cast(aProtocol); + case PPluginBackgroundDestroyerMsgStart: { + PPluginBackgroundDestroyerParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PPluginScriptableObjectMsgStart: { + PPluginScriptableObjectParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PBrowserStreamMsgStart: { + PBrowserStreamParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } + case PStreamNotifyMsgStart: { + PStreamNotifyParent* actor = static_cast(aProtocol); + return static_cast(actor->Manager()); + } +#ifdef XP_WIN + case PPluginSurfaceMsgStart: { + PPluginSurfaceParent* actor = + static_cast(aProtocol); + return static_cast(actor->Manager()); + } +#endif + default: + return nullptr; + } +} + +void PluginModuleChromeParent::EnteredCxxStack() { + mHangAnnotationFlags |= kInPluginCall; +} + +void PluginModuleChromeParent::ExitedCxxStack() { + mHangAnnotationFlags = 0; +#ifdef XP_WIN + FinishHangUI(); +#endif +} + +/** + * This function is always called by the BackgroundHangMonitor thread. + */ +void PluginModuleChromeParent::AnnotateHang( + mozilla::BackgroundHangAnnotations& aAnnotations) { + uint32_t flags = mHangAnnotationFlags; + if (flags) { + /* We don't actually annotate anything specifically for kInPluginCall; + we use it to determine whether to annotate other things. It will + be pretty obvious from the hang stack that we're in a plugin + call when the hang occurred. */ + if (flags & kHangUIShown) { + aAnnotations.AddAnnotation(u"HangUIShown"_ns, true); + } + if (flags & kHangUIContinued) { + aAnnotations.AddAnnotation(u"HangUIContinued"_ns, true); + } + if (flags & kHangUIDontShow) { + aAnnotations.AddAnnotation(u"HangUIDontShow"_ns, true); + } + aAnnotations.AddAnnotation(u"pluginName"_ns, mPluginName); + aAnnotations.AddAnnotation(u"pluginVersion"_ns, mPluginVersion); + } +} + +static bool CreatePluginMinidump(base::ProcessId processId, + ThreadId childThread, nsIFile* parentMinidump, + const nsACString& name) { + mozilla::ipc::ScopedProcessHandle handle; + if (processId == 0 || + !base::OpenPrivilegedProcessHandle(processId, &handle.rwget())) { + return false; + } + return CreateAdditionalChildMinidump(handle, 0, parentMinidump, name); +} + +bool PluginModuleChromeParent::ShouldContinueFromReplyTimeout() { + if (mIsFlashPlugin) { + MessageLoop::current()->PostTask(mTaskFactory.NewRunnableMethod( + &PluginModuleChromeParent::NotifyFlashHang)); + } + +#ifdef XP_WIN + if (LaunchHangUI()) { + return true; + } + // If LaunchHangUI returned false then we should proceed with the + // original plugin hang behaviour and kill the plugin container. + FinishHangUI(); +#endif // XP_WIN + + TerminateChildProcess(MessageLoop::current(), mozilla::ipc::kInvalidProcessId, + "ModalHangUI"_ns, u""_ns); + GetIPCChannel()->CloseWithTimeout(); + return false; +} + +bool PluginModuleContentParent::ShouldContinueFromReplyTimeout() { + RefPtr monitor = ProcessHangMonitor::Get(); + if (!monitor) { + return true; + } + monitor->NotifyPluginHang(mPluginId); + return true; +} + +void PluginModuleContentParent::OnExitedSyncSend() { + ProcessHangMonitor::ClearHang(); +} + +void PluginModuleChromeParent::TakeFullMinidump(base::ProcessId aContentPid, + const nsAString& aBrowserDumpId, + nsString& aDumpId) { + mozilla::MutexAutoLock lock(mCrashReporterMutex); + + if (!mCrashReporter) { + return; + } + + bool reportsReady = false; + + // Check to see if we already have a browser dump id - with e10s plugin + // hangs we take this earlier (see ProcessHangMonitor) from a background + // thread. We do this before we message the main thread about the hang + // since the posted message will trash our browser stack state. + nsCOMPtr browserDumpFile; + if (CrashReporter::GetMinidumpForID(aBrowserDumpId, + getter_AddRefs(browserDumpFile))) { + // We have a single browser report, generate a new plugin process parent + // report and pair it up with the browser report handed in. + reportsReady = mCrashReporter->GenerateMinidumpAndPair( + this, browserDumpFile, "browser"_ns); + + if (!reportsReady) { + browserDumpFile = nullptr; + CrashReporter::DeleteMinidumpFilesForID(aBrowserDumpId); + } + } + + // Generate crash report including plugin and browser process minidumps. + // The plugin process is the parent report with additional dumps including + // the browser process, content process when running under e10s, and + // various flash subprocesses if we're the flash module. + if (!reportsReady) { + reportsReady = mCrashReporter->GenerateMinidumpAndPair( + this, + nullptr, // Pair with a dump of this process and thread. + "browser"_ns); + } + + if (reportsReady) { + aDumpId = mCrashReporter->MinidumpID(); + PLUGIN_LOG_DEBUG(("generated paired browser/plugin minidumps: %s)", + NS_ConvertUTF16toUTF8(aDumpId).get())); + nsAutoCString additionalDumps("browser"); + nsCOMPtr pluginDumpFile; + if (GetMinidumpForID(aDumpId, getter_AddRefs(pluginDumpFile))) { +#ifdef MOZ_CRASHREPORTER_INJECTOR + // If we have handles to the flash sandbox processes on Windows, + // include those minidumps as well. + if (CreatePluginMinidump(mFlashProcess1, 0, pluginDumpFile, + "flash1"_ns)) { + additionalDumps.AppendLiteral(",flash1"); + } + if (CreatePluginMinidump(mFlashProcess2, 0, pluginDumpFile, + "flash2"_ns)) { + additionalDumps.AppendLiteral(",flash2"); + } +#endif // MOZ_CRASHREPORTER_INJECTOR + if (aContentPid != mozilla::ipc::kInvalidProcessId) { + // Include the content process minidump + if (CreatePluginMinidump(aContentPid, 0, pluginDumpFile, + "content"_ns)) { + additionalDumps.AppendLiteral(",content"); + } + } + } + mCrashReporter->AddAnnotation(Annotation::additional_minidumps, + additionalDumps); + } else { + NS_WARNING("failed to capture paired minidumps from hang"); + } +} + +void PluginModuleChromeParent::TerminateChildProcess( + MessageLoop* aMsgLoop, base::ProcessId aContentPid, + const nsCString& aMonitorDescription, const nsAString& aDumpId) { + // Start by taking a full minidump if necessary, this is done early + // because it also needs to lock the mCrashReporterMutex and Mutex doesn't + // support recursive locking. + nsAutoString dumpId; + if (aDumpId.IsEmpty()) { + TakeFullMinidump(aContentPid, u""_ns, dumpId); + } + + mozilla::MutexAutoLock lock(mCrashReporterMutex); + if (!mCrashReporter) { + // If mCrashReporter is null then the hang has ended, the plugin module + // is shutting down. There's nothing to do here. + return; + } + mCrashReporter->AddAnnotation(Annotation::PluginHang, true); + mCrashReporter->AddAnnotation(Annotation::HangMonitorDescription, + aMonitorDescription); +#ifdef XP_WIN + if (mHangUIParent) { + unsigned int hangUIDuration = mHangUIParent->LastShowDurationMs(); + if (hangUIDuration) { + mCrashReporter->AddAnnotation(Annotation::PluginHangUIDuration, + hangUIDuration); + } + } +#endif // XP_WIN + + mozilla::ipc::ScopedProcessHandle geckoChildProcess; + bool childOpened = + base::OpenProcessHandle(OtherPid(), &geckoChildProcess.rwget()); + +#ifdef XP_WIN + // collect cpu usage for plugin processes + + nsTArray processHandles; + + if (childOpened) { + processHandles.AppendElement(geckoChildProcess); + } + +# ifdef MOZ_CRASHREPORTER_INJECTOR + mozilla::ipc::ScopedProcessHandle flashBrokerProcess; + if (mFlashProcess1 && + base::OpenProcessHandle(mFlashProcess1, &flashBrokerProcess.rwget())) { + processHandles.AppendElement(flashBrokerProcess); + } + mozilla::ipc::ScopedProcessHandle flashSandboxProcess; + if (mFlashProcess2 && + base::OpenProcessHandle(mFlashProcess2, &flashSandboxProcess.rwget())) { + processHandles.AppendElement(flashSandboxProcess); + } +# endif + + if (!GetProcessCpuUsage(processHandles, mPluginCpuUsageOnHang)) { + mPluginCpuUsageOnHang.Clear(); + } +#endif + + // this must run before the error notification from the channel, + // or not at all + bool isFromHangUI = aMsgLoop != MessageLoop::current(); + aMsgLoop->PostTask(mChromeTaskFactory.NewRunnableMethod( + &PluginModuleChromeParent::CleanupFromTimeout, isFromHangUI)); + + if (!childOpened || !KillProcess(geckoChildProcess, 1, false)) { + NS_WARNING("failed to kill subprocess!"); + } +} + +bool PluginModuleParent::GetPluginDetails() { + RefPtr host = nsPluginHost::GetInst(); + if (!host) { + return false; + } + nsPluginTag* pluginTag = host->TagForPlugin(mPlugin); + if (!pluginTag) { + return false; + } + mPluginName = pluginTag->Name(); + mPluginVersion = pluginTag->Version(); + mPluginFilename = pluginTag->FileName(); + mIsFlashPlugin = pluginTag->mIsFlashPlugin; + mSandboxLevel = pluginTag->mSandboxLevel; + return true; +} + +void PluginModuleParent::InitQuirksModes(const nsCString& aMimeType) { + if (mQuirks != QUIRKS_NOT_INITIALIZED) { + return; + } + + mQuirks = GetQuirksFromMimeTypeAndFilename(aMimeType, mPluginFilename); +} + +#ifdef XP_WIN +void PluginModuleChromeParent::EvaluateHangUIState(const bool aReset) { + int32_t minDispSecs = Preferences::GetInt(kHangUIMinDisplayPref, 10); + int32_t autoStopSecs = Preferences::GetInt(kChildTimeoutPref, 0); + int32_t timeoutSecs = 0; + if (autoStopSecs > 0 && autoStopSecs < minDispSecs) { + /* If we're going to automatically terminate the plugin within a + time frame shorter than minDispSecs, there's no point in + showing the hang UI; it would just flash briefly on the screen. */ + mHangUIEnabled = false; + } else { + timeoutSecs = Preferences::GetInt(kHangUITimeoutPref, 0); + mHangUIEnabled = timeoutSecs > 0; + } + if (mHangUIEnabled) { + if (aReset) { + mIsTimerReset = true; + SetChildTimeout(timeoutSecs); + return; + } else if (mIsTimerReset) { + /* The Hang UI is being shown, so now we're setting the + timeout to kChildTimeoutPref while we wait for a user + response. ShouldContinueFromReplyTimeout will fire + after (reply timeout / 2) seconds, which is not what + we want. Doubling the timeout value here so that we get + the right result. */ + autoStopSecs *= 2; + } + } + mIsTimerReset = false; + SetChildTimeout(autoStopSecs); +} + +bool PluginModuleChromeParent::LaunchHangUI() { + if (!mHangUIEnabled) { + return false; + } + if (mHangUIParent) { + if (mHangUIParent->IsShowing()) { + // We've already shown the UI but the timeout has expired again. + return false; + } + if (mHangUIParent->DontShowAgain()) { + mHangAnnotationFlags |= kHangUIDontShow; + bool wasLastHangStopped = mHangUIParent->WasLastHangStopped(); + if (!wasLastHangStopped) { + mHangAnnotationFlags |= kHangUIContinued; + } + return !wasLastHangStopped; + } + delete mHangUIParent; + mHangUIParent = nullptr; + } + mHangUIParent = + new PluginHangUIParent(this, Preferences::GetInt(kHangUITimeoutPref, 0), + Preferences::GetInt(kChildTimeoutPref, 0)); + bool retval = mHangUIParent->Init(NS_ConvertUTF8toUTF16(mPluginName)); + if (retval) { + mHangAnnotationFlags |= kHangUIShown; + /* Once the UI is shown we switch the timeout over to use + kChildTimeoutPref, allowing us to terminate a hung plugin + after kChildTimeoutPref seconds if the user doesn't respond to + the hang UI. */ + EvaluateHangUIState(false); + } + return retval; +} + +void PluginModuleChromeParent::FinishHangUI() { + if (mHangUIEnabled && mHangUIParent) { + bool needsCancel = mHangUIParent->IsShowing(); + // If we're still showing, send a Cancel notification + if (needsCancel) { + mHangUIParent->Cancel(); + } + /* If we cancelled the UI or if the user issued a response, + we need to reset the child process timeout. */ + if (needsCancel || (!mIsTimerReset && mHangUIParent->WasShown())) { + /* We changed the timeout to kChildTimeoutPref when the plugin hang + UI was displayed. Now that we're finishing the UI, we need to + switch it back to kHangUITimeoutPref. */ + EvaluateHangUIState(true); + } + } +} + +void PluginModuleChromeParent::OnHangUIContinue() { + mHangAnnotationFlags |= kHangUIContinued; +} +#endif // XP_WIN + +#ifdef MOZ_CRASHREPORTER_INJECTOR +static void RemoveMinidump(nsIFile* minidump) { + if (!minidump) return; + + minidump->Remove(false); + nsCOMPtr extraFile; + if (GetExtraFileForMinidump(minidump, getter_AddRefs(extraFile))) { + extraFile->Remove(true); + } +} +#endif // MOZ_CRASHREPORTER_INJECTOR + +void PluginModuleChromeParent::ProcessFirstMinidump() { + mozilla::MutexAutoLock lock(mCrashReporterMutex); + + if (!mCrashReporter) { + HandleOrphanedMinidump(); + return; + } + + AddCrashAnnotations(); + + if (mCrashReporter->HasMinidump()) { + // A minidump may be set in TerminateChildProcess, which means the + // process hang monitor has already collected a 3-way browser, plugin, + // content crash report. If so, update the existing report with our + // annotations and finalize it. If not, fall through for standard + // plugin crash report handling. + mCrashReporter->FinalizeCrashReport(); + return; + } + + AnnotationTable annotations; + uint32_t sequence = UINT32_MAX; + nsAutoCString flashProcessType; + RefPtr dumpFile = + mCrashReporter->TakeCrashedChildMinidump(OtherPid(), &sequence); + +#ifdef MOZ_CRASHREPORTER_INJECTOR + nsCOMPtr childDumpFile; + uint32_t childSequence; + + if (mFlashProcess1 && + TakeMinidumpForChild(mFlashProcess1, getter_AddRefs(childDumpFile), + annotations, &childSequence)) { + if (childSequence < sequence && + mCrashReporter->AdoptMinidump(childDumpFile, annotations)) { + RemoveMinidump(dumpFile); + dumpFile = childDumpFile; + sequence = childSequence; + flashProcessType.AssignLiteral("Broker"); + } else { + RemoveMinidump(childDumpFile); + } + } + if (mFlashProcess2 && + TakeMinidumpForChild(mFlashProcess2, getter_AddRefs(childDumpFile), + annotations, &childSequence)) { + if (childSequence < sequence && + mCrashReporter->AdoptMinidump(childDumpFile, annotations)) { + RemoveMinidump(dumpFile); + dumpFile = childDumpFile; + sequence = childSequence; + flashProcessType.AssignLiteral("Sandbox"); + } else { + RemoveMinidump(childDumpFile); + } + } +#endif + + if (!dumpFile) { + NS_WARNING( + "[PluginModuleParent::ActorDestroy] abnormal shutdown without " + "minidump!"); + return; + } + + PLUGIN_LOG_DEBUG(("got child minidump: %s", + NS_ConvertUTF16toUTF8(mCrashReporter->MinidumpID()).get())); + + if (!flashProcessType.IsEmpty()) { + mCrashReporter->AddAnnotation(Annotation::FlashProcessDump, + flashProcessType); + } + mCrashReporter->FinalizeCrashReport(); +} + +void PluginModuleChromeParent::HandleOrphanedMinidump() { + if (CrashReporter::FinalizeOrphanedMinidump( + OtherPid(), GeckoProcessType_Plugin, &mOrphanedDumpId)) { + ipc::CrashReporterHost::RecordCrash(GeckoProcessType_Plugin, + nsICrashService::CRASH_TYPE_CRASH, + mOrphanedDumpId); + } else { + NS_WARNING(nsPrintfCString("plugin process pid = %d crashed without " + "leaving a minidump behind", + OtherPid()) + .get()); + } +} + +void PluginModuleParent::ActorDestroy(ActorDestroyReason why) { + switch (why) { + case AbnormalShutdown: { + mShutdown = true; + // Defer the PluginCrashed method so that we don't re-enter + // and potentially modify the actor child list while enumerating it. + if (mPlugin) + MessageLoop::current()->PostTask(mTaskFactory.NewRunnableMethod( + &PluginModuleParent::NotifyPluginCrashed)); + break; + } + case NormalShutdown: + mShutdown = true; + break; + + default: + MOZ_CRASH("Unexpected shutdown reason for toplevel actor."); + } +} + +nsresult PluginModuleParent::GetRunID(uint32_t* aRunID) { + if (NS_WARN_IF(!aRunID)) { + return NS_ERROR_INVALID_POINTER; + } + *aRunID = mRunID; + return NS_OK; +} + +void PluginModuleChromeParent::ActorDestroy(ActorDestroyReason why) { + if (why == AbnormalShutdown) { + ProcessFirstMinidump(); + Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, "plugin"_ns, 1); + } + + // We can't broadcast settings changes anymore. + UnregisterSettingsCallbacks(); + +#if defined(XP_WIN) + if (mBrokerParent) { + FunctionBrokerParent::Destroy(mBrokerParent); + mBrokerParent = nullptr; + } +#endif + + PluginModuleParent::ActorDestroy(why); +} + +void PluginModuleParent::NotifyFlashHang() { + nsCOMPtr obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "flash-plugin-hang", nullptr); + } +} + +void PluginModuleParent::NotifyPluginCrashed() { + if (!OkToCleanup()) { + // there's still plugin code on the C++ stack. try again + MessageLoop::current()->PostDelayedTask( + mTaskFactory.NewRunnableMethod( + &PluginModuleParent::NotifyPluginCrashed), + 10); + return; + } + + if (!mPlugin) { + return; + } + + nsString dumpID; + nsCString additionalMinidumps; + + if (mCrashReporter && mCrashReporter->HasMinidump()) { + dumpID = mCrashReporter->MinidumpID(); + additionalMinidumps = mCrashReporter->AdditionalMinidumps(); + } else { + dumpID = mOrphanedDumpId; + } + + mPlugin->PluginCrashed(dumpID, additionalMinidumps); +} + +PPluginInstanceParent* PluginModuleParent::AllocPPluginInstanceParent( + const nsCString& aMimeType, const nsTArray& aNames, + const nsTArray& aValues) { + NS_ERROR("Not reachable!"); + return nullptr; +} + +bool PluginModuleParent::DeallocPPluginInstanceParent( + PPluginInstanceParent* aActor) { + PLUGIN_LOG_DEBUG_METHOD; + delete aActor; + return true; +} + +void PluginModuleParent::SetPluginFuncs(NPPluginFuncs* aFuncs) { + MOZ_ASSERT(aFuncs); + + aFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + aFuncs->javaClass = nullptr; + + // Gecko should always call these functions through a PluginLibrary object. + aFuncs->newp = nullptr; + aFuncs->clearsitedata = nullptr; + aFuncs->getsiteswithdata = nullptr; + + aFuncs->destroy = NPP_Destroy; + aFuncs->setwindow = NPP_SetWindow; + aFuncs->newstream = NPP_NewStream; + aFuncs->destroystream = NPP_DestroyStream; + aFuncs->writeready = NPP_WriteReady; + aFuncs->write = NPP_Write; + aFuncs->print = NPP_Print; + aFuncs->event = NPP_HandleEvent; + aFuncs->urlnotify = NPP_URLNotify; + aFuncs->getvalue = NPP_GetValue; + aFuncs->setvalue = NPP_SetValue; + aFuncs->gotfocus = nullptr; + aFuncs->lostfocus = nullptr; + aFuncs->urlredirectnotify = nullptr; + + // Provide 'NPP_URLRedirectNotify', 'NPP_ClearSiteData', and + // 'NPP_GetSitesWithData' functionality if it is supported by the plugin. + bool urlRedirectSupported = false; + Unused << CallOptionalFunctionsSupported(&urlRedirectSupported, + &mClearSiteDataSupported, + &mGetSitesWithDataSupported); + if (urlRedirectSupported) { + aFuncs->urlredirectnotify = NPP_URLRedirectNotify; + } +} + +NPError PluginModuleParent::NPP_Destroy(NPP instance, NPSavedData** saved) { + // FIXME/cjones: + // (1) send a "destroy" message to the child + // (2) the child shuts down its instance + // (3) remove both parent and child IDs from map + // (4) free parent + + PLUGIN_LOG_DEBUG_FUNCTION; + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + if (!pip) return NPERR_NO_ERROR; + + NPError retval = pip->Destroy(); + instance->pdata = nullptr; + + Unused << PluginInstanceParent::Send__delete__(pip); + return retval; +} + +NPError PluginModuleParent::NPP_NewStream(NPP instance, NPMIMEType type, + NPStream* stream, NPBool seekable, + uint16_t* stype) { + AUTO_PROFILER_LABEL("PluginModuleParent::NPP_NewStream", OTHER); + + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_NewStream(type, stream, seekable, stype) + : NPERR_GENERIC_ERROR; +} + +NPError PluginModuleParent::NPP_SetWindow(NPP instance, NPWindow* window) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_SetWindow(window) : NPERR_GENERIC_ERROR; +} + +NPError PluginModuleParent::NPP_DestroyStream(NPP instance, NPStream* stream, + NPReason reason) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_DestroyStream(stream, reason) : NPERR_GENERIC_ERROR; +} + +int32_t PluginModuleParent::NPP_WriteReady(NPP instance, NPStream* stream) { + BrowserStreamParent* s = StreamCast(instance, stream); + return s ? s->WriteReady() : -1; +} + +int32_t PluginModuleParent::NPP_Write(NPP instance, NPStream* stream, + int32_t offset, int32_t len, + void* buffer) { + BrowserStreamParent* s = StreamCast(instance, stream); + if (!s) return -1; + + return s->Write(offset, len, buffer); +} + +void PluginModuleParent::NPP_Print(NPP instance, NPPrint* platformPrint) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_Print(platformPrint) : (void)0; +} + +int16_t PluginModuleParent::NPP_HandleEvent(NPP instance, void* event) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_HandleEvent(event) : NPERR_GENERIC_ERROR; +} + +void PluginModuleParent::NPP_URLNotify(NPP instance, const char* url, + NPReason reason, void* notifyData) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_URLNotify(url, reason, notifyData) : (void)0; +} + +NPError PluginModuleParent::NPP_GetValue(NPP instance, NPPVariable variable, + void* ret_value) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_GetValue(variable, ret_value) : NPERR_GENERIC_ERROR; +} + +NPError PluginModuleParent::NPP_SetValue(NPP instance, NPNVariable variable, + void* value) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_SetValue(variable, value) : NPERR_GENERIC_ERROR; +} + +mozilla::ipc::IPCResult PluginModuleChromeParent:: + AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, NPError* result) { +#ifdef XP_WIN + *result = NPERR_NO_ERROR; + nsresult err = + mozilla::plugins::PluginUtilsWin::RegisterForAudioDeviceChanges( + this, shouldRegister); + if (err != NS_OK) { + *result = NPERR_GENERIC_ERROR; + } + return IPC_OK(); +#else + MOZ_CRASH( + "NPPVpluginRequiresAudioDeviceChanges is not valid on this platform."); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvBackUpXResources( + const FileDescriptor& aXSocketFd) { +#ifndef MOZ_X11 + MOZ_CRASH("This message only makes sense on X11 platforms"); +#else + MOZ_ASSERT(0 > mPluginXSocketFdDup.get(), "Already backed up X resources??"); + if (aXSocketFd.IsValid()) { + auto rawFD = aXSocketFd.ClonePlatformHandle(); + mPluginXSocketFdDup.reset(rawFD.release()); + } +#endif + return IPC_OK(); +} + +void PluginModuleParent::NPP_URLRedirectNotify(NPP instance, const char* url, + int32_t status, + void* notifyData) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->NPP_URLRedirectNotify(url, status, notifyData) : (void)0; +} + +BrowserStreamParent* PluginModuleParent::StreamCast(NPP instance, NPStream* s) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + if (!pip) { + return nullptr; + } + + BrowserStreamParent* sp = + static_cast(static_cast(s->pdata)); + if (sp && (sp->mNPP != pip || s != sp->mStream)) { + MOZ_CRASH("Corrupted plugin stream data."); + } + return sp; +} + +bool PluginModuleParent::HasRequiredFunctions() { return true; } + +nsresult PluginModuleParent::AsyncSetWindow(NPP instance, NPWindow* window) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->AsyncSetWindow(window) : NS_ERROR_FAILURE; +} + +nsresult PluginModuleParent::GetImageContainer( + NPP instance, mozilla::layers::ImageContainer** aContainer) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->GetImageContainer(aContainer) : NS_ERROR_FAILURE; +} + +nsresult PluginModuleParent::GetImageSize(NPP instance, nsIntSize* aSize) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->GetImageSize(aSize) : NS_ERROR_FAILURE; +} + +void PluginModuleParent::DidComposite(NPP aInstance) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(aInstance); + return pip ? pip->DidComposite() : (void)0; +} + +nsresult PluginModuleParent::SetBackgroundUnknown(NPP instance) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->SetBackgroundUnknown() : NS_ERROR_FAILURE; +} + +nsresult PluginModuleParent::BeginUpdateBackground(NPP instance, + const nsIntRect& aRect, + DrawTarget** aDrawTarget) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->BeginUpdateBackground(aRect, aDrawTarget) + : NS_ERROR_FAILURE; +} + +nsresult PluginModuleParent::EndUpdateBackground(NPP instance, + const nsIntRect& aRect) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->EndUpdateBackground(aRect) : NS_ERROR_FAILURE; +} + +#if defined(XP_WIN) +nsresult PluginModuleParent::GetScrollCaptureContainer( + NPP aInstance, mozilla::layers::ImageContainer** aContainer) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(aInstance); + return pip ? pip->GetScrollCaptureContainer(aContainer) : NS_ERROR_FAILURE; +} +#endif + +nsresult PluginModuleParent::HandledWindowedPluginKeyEvent( + NPP aInstance, const NativeEventData& aNativeKeyData, bool aIsConsumed) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(aInstance); + return pip ? pip->HandledWindowedPluginKeyEvent(aNativeKeyData, aIsConsumed) + : NS_ERROR_FAILURE; +} + +void PluginModuleParent::OnInitFailure() { + if (GetIPCChannel()->CanSend()) { + Close(); + } + + mShutdown = true; +} + +class PluginOfflineObserver final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit PluginOfflineObserver(PluginModuleChromeParent* pmp) : mPmp(pmp) {} + + private: + ~PluginOfflineObserver() = default; + PluginModuleChromeParent* mPmp; +}; + +NS_IMPL_ISUPPORTS(PluginOfflineObserver, nsIObserver) + +NS_IMETHODIMP +PluginOfflineObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(!strcmp(aTopic, "ipc:network:set-offline")); + mPmp->CachedSettingChanged(); + return NS_OK; +} + +void PluginModuleChromeParent::RegisterSettingsCallbacks() { + Preferences::RegisterCallback(CachedSettingChanged, "javascript.enabled", + this); + Preferences::RegisterCallback(CachedSettingChanged, + "dom.ipc.plugins.nativeCursorSupport", this); + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + mPluginOfflineObserver = new PluginOfflineObserver(this); + observerService->AddObserver(mPluginOfflineObserver, + "ipc:network:set-offline", false); + } +} + +void PluginModuleChromeParent::UnregisterSettingsCallbacks() { + Preferences::UnregisterCallback(CachedSettingChanged, "javascript.enabled", + this); + Preferences::UnregisterCallback(CachedSettingChanged, + "dom.ipc.plugins.nativeCursorSupport", this); + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(mPluginOfflineObserver, + "ipc:network:set-offline"); + mPluginOfflineObserver = nullptr; + } +} + +bool PluginModuleParent::GetSetting(NPNVariable aVariable) { + NPBool boolVal = false; + mozilla::plugins::parent::_getvalue(nullptr, aVariable, &boolVal); + return boolVal; +} + +void PluginModuleParent::GetSettings(PluginSettings* aSettings) { + aSettings->javascriptEnabled() = GetSetting(NPNVjavascriptEnabledBool); + aSettings->asdEnabled() = GetSetting(NPNVasdEnabledBool); + aSettings->isOffline() = GetSetting(NPNVisOfflineBool); + aSettings->supportsXembed() = GetSetting(NPNVSupportsXEmbedBool); + aSettings->supportsWindowless() = GetSetting(NPNVSupportsWindowless); + aSettings->userAgent() = NullableString(mNPNIface->uagent(nullptr)); + +#if defined(XP_MACOSX) + aSettings->nativeCursorsSupported() = + Preferences::GetBool("dom.ipc.plugins.nativeCursorSupport", false); +#else + // Need to initialize this to satisfy IPDL. + aSettings->nativeCursorsSupported() = false; +#endif +} + +void PluginModuleChromeParent::CachedSettingChanged() { + PluginSettings settings; + GetSettings(&settings); + Unused << SendSettingChanged(settings); +} + +/* static */ +void PluginModuleChromeParent::CachedSettingChanged(const char* aPref, + void* aModule) { + auto module = static_cast(aModule); + module->CachedSettingChanged(); +} + +#if defined(XP_UNIX) && !defined(XP_MACOSX) +nsresult PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, + NPPluginFuncs* pFuncs, + NPError* error) { + PLUGIN_LOG_DEBUG_METHOD; + + mNPNIface = bFuncs; + mNPPIface = pFuncs; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + *error = NPERR_NO_ERROR; + SetPluginFuncs(pFuncs); + + return NS_OK; +} + +nsresult PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, + NPPluginFuncs* pFuncs, + NPError* error) { + PLUGIN_LOG_DEBUG_METHOD; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + *error = NPERR_NO_ERROR; + + mNPNIface = bFuncs; + mNPPIface = pFuncs; + + PluginSettings settings; + GetSettings(&settings); + + if (!CallNP_Initialize(settings, error)) { + Close(); + return NS_ERROR_FAILURE; + } else if (*error != NPERR_NO_ERROR) { + Close(); + return NS_ERROR_FAILURE; + } + + if (*error != NPERR_NO_ERROR) { + OnInitFailure(); + return NS_OK; + } + + SetPluginFuncs(mNPPIface); + + return NS_OK; +} + +#else + +nsresult PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, + NPError* error) { + PLUGIN_LOG_DEBUG_METHOD; + + mNPNIface = bFuncs; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + *error = NPERR_NO_ERROR; + return NS_OK; +} + +# if defined(XP_WIN) || defined(XP_MACOSX) + +nsresult PluginModuleContentParent::NP_Initialize(NPNetscapeFuncs* bFuncs, + NPError* error) { + PLUGIN_LOG_DEBUG_METHOD; + return PluginModuleParent::NP_Initialize(bFuncs, error); +} + +# endif + +nsresult PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, + NPError* error) { + nsresult rv = PluginModuleParent::NP_Initialize(bFuncs, error); + if (NS_FAILED(rv)) return rv; + + PluginSettings settings; + GetSettings(&settings); + + if (!CallNP_Initialize(settings, error)) { + Close(); + return NS_ERROR_FAILURE; + } + + bool ok = true; + if (*error == NPERR_NO_ERROR) { + // Initialization steps for (e10s && !asyncInit) || !e10s +# if defined XP_WIN + // Send the info needed to join the browser process's audio session to + // the plugin process. + nsID id; + nsString sessionName; + nsString iconPath; + + if (NS_SUCCEEDED( + mozilla::widget::GetAudioSessionData(id, sessionName, iconPath))) { + Unused << SendSetAudioSessionData(id, sessionName, iconPath); + } +# endif + +# ifdef MOZ_CRASHREPORTER_INJECTOR + InitializeInjector(); +# endif + } + + if (!ok) { + return NS_ERROR_FAILURE; + } + + if (*error != NPERR_NO_ERROR) { + OnInitFailure(); + return NS_OK; + } + + return NS_OK; +} + +#endif + +nsresult PluginModuleParent::NP_Shutdown(NPError* error) { + PLUGIN_LOG_DEBUG_METHOD; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + if (!DoShutdown(error)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool PluginModuleParent::DoShutdown(NPError* error) { + bool ok = true; + if (IsChrome() && mHadLocalInstance) { + // We synchronously call NP_Shutdown if the chrome process was using + // plugins itself. That way we can service any requests the plugin + // makes. If we're in e10s, though, the content processes will have + // already shut down and there's no one to talk to. So we shut down + // asynchronously in PluginModuleChild::ActorDestroy. + ok = CallNP_Shutdown(error); + } + + // if NP_Shutdown() is nested within another interrupt call, this will + // break things. but lord help us if we're doing that anyway; the + // plugin dso will have been unloaded on the other side by the + // CallNP_Shutdown() message + Close(); + + // mShutdown should either be initialized to false, or be transitiong from + // false to true. It is never ok to go from true to false. Using OR for + // the following assignment to ensure this. + mShutdown |= ok; + if (!ok) { + *error = NPERR_GENERIC_ERROR; + } + return ok; +} + +nsresult PluginModuleParent::NP_GetMIMEDescription(const char** mimeDesc) { + PLUGIN_LOG_DEBUG_METHOD; + + *mimeDesc = "application/x-foobar"; + return NS_OK; +} + +nsresult PluginModuleParent::NP_GetValue(void* future, NPPVariable aVariable, + void* aValue, NPError* error) { + MOZ_LOG(GetPluginLog(), LogLevel::Warning, + ("%s Not implemented, requested variable %i", __FUNCTION__, + (int)aVariable)); + + // TODO: implement this correctly + *error = NPERR_GENERIC_ERROR; + return NS_OK; +} + +#if defined(XP_WIN) || defined(XP_MACOSX) +nsresult PluginModuleParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, + NPError* error) { + NS_ASSERTION(pFuncs, "Null pointer!"); + + *error = NPERR_NO_ERROR; + SetPluginFuncs(pFuncs); + + return NS_OK; +} + +nsresult PluginModuleChromeParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, + NPError* error) { +# if !defined(XP_MACOSX) + if (!mSubprocess->IsConnected()) { + mNPPIface = pFuncs; + *error = NPERR_NO_ERROR; + return NS_OK; + } +# endif + + // We need to have the plugin process update its function table here by + // actually calling NP_GetEntryPoints. The parent's function table will + // reflect nullptr entries in the child's table once SetPluginFuncs is + // called. + + if (!CallNP_GetEntryPoints(error)) { + return NS_ERROR_FAILURE; + } else if (*error != NPERR_NO_ERROR) { + return NS_OK; + } + + return PluginModuleParent::NP_GetEntryPoints(pFuncs, error); +} + +#endif + +nsresult PluginModuleParent::NPP_New(NPMIMEType pluginType, NPP instance, + int16_t argc, char* argn[], char* argv[], + NPSavedData* saved, NPError* error) { + PLUGIN_LOG_DEBUG_METHOD; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + // create the instance on the other side + nsTArray names; + nsTArray values; + + for (int i = 0; i < argc; ++i) { + names.AppendElement(NullableString(argn[i])); + values.AppendElement(NullableString(argv[i])); + } + + return NPP_NewInternal(pluginType, instance, names, values, saved, error); +} + +class nsCaseInsensitiveUTF8StringArrayComparator { + public: + template + bool Equals(const A& a, const B& b) const { + return a.Equals(b.get(), nsCaseInsensitiveUTF8StringComparator); + } +}; + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) +static void ForceWindowless(nsTArray& names, + nsTArray& values) { + nsCaseInsensitiveUTF8StringArrayComparator comparator; + constexpr auto wmodeAttributeName = "wmode"_ns; + constexpr auto opaqueAttributeValue = "opaque"_ns; + auto wmodeAttributeIndex = names.IndexOf(wmodeAttributeName, 0, comparator); + if (wmodeAttributeIndex != names.NoIndex) { + if (!values[wmodeAttributeIndex].EqualsLiteral("transparent")) { + values[wmodeAttributeIndex].Assign(opaqueAttributeValue); + } + } else { + names.AppendElement(wmodeAttributeName); + values.AppendElement(opaqueAttributeValue); + } +} +#endif // windows or linux +#if defined(XP_WIN) +static void ForceDirect(nsTArray& names, + nsTArray& values) { + nsCaseInsensitiveUTF8StringArrayComparator comparator; + constexpr auto wmodeAttributeName = "wmode"_ns; + constexpr auto directAttributeValue = "direct"_ns; + auto wmodeAttributeIndex = names.IndexOf(wmodeAttributeName, 0, comparator); + if (wmodeAttributeIndex != names.NoIndex) { + if ((!values[wmodeAttributeIndex].EqualsLiteral("transparent")) && + (!values[wmodeAttributeIndex].EqualsLiteral("opaque"))) { + values[wmodeAttributeIndex].Assign(directAttributeValue); + } + } else { + names.AppendElement(wmodeAttributeName); + values.AppendElement(directAttributeValue); + } +} +#endif // windows + +nsresult PluginModuleParent::NPP_NewInternal( + NPMIMEType pluginType, NPP instance, nsTArray& names, + nsTArray& values, NPSavedData* saved, NPError* error) { + MOZ_ASSERT(names.Length() == values.Length()); + if (mPluginName.IsEmpty()) { + GetPluginDetails(); + InitQuirksModes(nsDependentCString(pluginType)); + } + + nsCaseInsensitiveUTF8StringArrayComparator comparator; + constexpr auto srcAttributeName = "src"_ns; + auto srcAttributeIndex = names.IndexOf(srcAttributeName, 0, comparator); + nsAutoCString srcAttribute; + if (srcAttributeIndex != names.NoIndex) { + srcAttribute = values[srcAttributeIndex]; + } + + nsDependentCString strPluginType(pluginType); + PluginInstanceParent* parentInstance = + new PluginInstanceParent(this, instance, strPluginType, mNPNIface); + + if (mIsFlashPlugin) { + parentInstance->InitMetadata(strPluginType, srcAttribute); +#ifdef XP_WIN + bool supportsAsyncRender = + Preferences::GetBool("dom.ipc.plugins.asyncdrawing.enabled", false); + bool supportsForceDirect = + Preferences::GetBool("dom.ipc.plugins.forcedirect.enabled", false); + if (supportsAsyncRender) { + // Prefs indicates we want async plugin rendering, make sure + // the flash module has support. + if (!CallModuleSupportsAsyncRender(&supportsAsyncRender)) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + } +# ifdef _WIN64 + // For 64-bit builds force windowless if the flash library doesn't support + // async rendering regardless of sandbox level. + if (!supportsAsyncRender) { +# else + // For 32-bit builds force windowless if the flash library doesn't support + // async rendering and the sandbox level is 2 or greater. + if (!supportsAsyncRender && mSandboxLevel >= 2) { +# endif + ForceWindowless(names, values); + } +#elif defined(MOZ_WIDGET_GTK) + // We no longer support windowed mode on Linux. + ForceWindowless(names, values); +#endif +#ifdef XP_WIN + // For all builds that use async rendering force use of the accelerated + // direct path for flash objects that have wmode=window or no wmode + // specified. + if (supportsAsyncRender && supportsForceDirect && + PluginInstanceParent::SupportsPluginDirectDXGISurfaceDrawing()) { + ForceDirect(names, values); + } +#endif + } + + instance->pdata = parentInstance; + + // Any IPC messages for the PluginInstance actor should be dispatched to the + // DocGroup for the plugin's document. + RefPtr owner = parentInstance->GetOwner(); + RefPtr elt; + owner->GetDOMElement(getter_AddRefs(elt)); + if (elt) { + RefPtr doc = elt->OwnerDoc(); + nsCOMPtr eventTarget = + doc->EventTargetFor(TaskCategory::Other); + SetEventTargetForActor(parentInstance, eventTarget); + } + + if (!SendPPluginInstanceConstructor( + parentInstance, nsDependentCString(pluginType), names, values)) { + // |parentInstance| is automatically deleted. + instance->pdata = nullptr; + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + if (!CallSyncNPP_New(parentInstance, error)) { + // if IPC is down, we'll get an immediate "failed" return, but + // without *error being set. So make sure that the error + // condition is signaled to nsNPAPIPluginInstance + if (NPERR_NO_ERROR == *error) { + *error = NPERR_GENERIC_ERROR; + } + return NS_ERROR_FAILURE; + } + + if (*error != NPERR_NO_ERROR) { + NPP_Destroy(instance, 0); + return NS_ERROR_FAILURE; + } + + Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_PLUGIN_INSTANTIATED, + 1); + + UpdatePluginTimeout(); + + return NS_OK; +} + +void PluginModuleChromeParent::UpdatePluginTimeout() { + TimeoutChanged(kParentTimeoutPref, this); +} + +nsresult PluginModuleParent::NPP_ClearSiteData( + const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr callback) { + if (!mClearSiteDataSupported) return NS_ERROR_NOT_AVAILABLE; + + static uint64_t callbackId = 0; + callbackId++; + mClearSiteDataCallbacks[callbackId] = callback; + + if (!SendNPP_ClearSiteData(NullableString(site), flags, maxAge, callbackId)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult PluginModuleParent::NPP_GetSitesWithData( + nsCOMPtr callback) { + if (!mGetSitesWithDataSupported) return NS_ERROR_NOT_AVAILABLE; + + static uint64_t callbackId = 0; + callbackId++; + mSitesWithDataCallbacks[callbackId] = callback; + + if (!SendNPP_GetSitesWithData(callbackId)) return NS_ERROR_FAILURE; + + return NS_OK; +} + +#if defined(XP_MACOSX) +nsresult PluginModuleParent::IsRemoteDrawingCoreAnimation(NPP instance, + bool* aDrawing) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->IsRemoteDrawingCoreAnimation(aDrawing) : NS_ERROR_FAILURE; +} +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +nsresult PluginModuleParent::ContentsScaleFactorChanged( + NPP instance, double aContentsScaleFactor) { + PluginInstanceParent* pip = PluginInstanceParent::Cast(instance); + return pip ? pip->ContentsScaleFactorChanged(aContentsScaleFactor) + : NS_ERROR_FAILURE; +} +#endif // #if defined(XP_MACOSX) + +#if defined(XP_MACOSX) +mozilla::ipc::IPCResult PluginModuleParent::AnswerProcessSomeEvents() { + mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop(); + return IPC_OK(); +} + +#elif !defined(MOZ_WIDGET_GTK) +mozilla::ipc::IPCResult PluginModuleParent::AnswerProcessSomeEvents() { + MOZ_CRASH("unreached"); +} + +#else +static const int kMaxChancesToProcessEvents = 20; + +mozilla::ipc::IPCResult PluginModuleParent::AnswerProcessSomeEvents() { + PLUGIN_LOG_DEBUG(("Spinning mini nested loop ...")); + + int i = 0; + for (; i < kMaxChancesToProcessEvents; ++i) + if (!g_main_context_iteration(nullptr, FALSE)) break; + + PLUGIN_LOG_DEBUG(("... quitting mini nested loop; processed %i tasks", i)); + + return IPC_OK(); +} +#endif + +mozilla::ipc::IPCResult +PluginModuleParent::RecvProcessNativeEventsInInterruptCall() { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(OS_WIN) + ProcessNativeEventsInInterruptCall(); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginModuleParent::RecvProcessNativeEventsInInterruptCall not " + "implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +void PluginModuleParent::ProcessRemoteNativeEventsInInterruptCall() { +#if defined(OS_WIN) + Unused << SendProcessNativeEventsInInterruptCall(); + return; +#endif + MOZ_ASSERT_UNREACHABLE( + "PluginModuleParent::ProcessRemoteNativeEventsInInterruptCall not " + "implemented!"); +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvPluginShowWindow( + const uint32_t& aWindowId, const bool& aModal, const int32_t& aX, + const int32_t& aY, const double& aWidth, const double& aHeight) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + CGRect windowBound = ::CGRectMake(aX, aY, aWidth, aHeight); + mac_plugin_interposing::parent::OnPluginShowWindow(aWindowId, windowBound, + aModal); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvPluginShowWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvPluginHideWindow( + const uint32_t& aWindowId) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnPluginHideWindow(aWindowId, OtherPid()); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvPluginHideWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvSetCursor( + const NSCursorInfo& aCursorInfo) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnSetCursor(aCursorInfo); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvSetCursor not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvShowCursor(const bool& aShow) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnShowCursor(aShow); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvShowCursor not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvPushCursor( + const NSCursorInfo& aCursorInfo) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnPushCursor(aCursorInfo); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvPushCursor not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvPopCursor() { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); +#if defined(XP_MACOSX) + mac_plugin_interposing::parent::OnPopCursor(); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "PluginInstanceParent::RecvPopCursor not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvNPN_SetException( + const nsCString& aMessage) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + // This function ignores its first argument. + mozilla::plugins::parent::_setexception(nullptr, NullableStringGet(aMessage)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvNPN_ReloadPlugins( + const bool& aReloadPages) { + PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION)); + + mozilla::plugins::parent::_reloadplugins(aReloadPages); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +PluginModuleChromeParent::RecvNotifyContentModuleDestroyed() { + RefPtr host = nsPluginHost::GetInst(); + if (host) { + host->NotifyContentModuleDestroyed(mPluginId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvReturnClearSiteData( + const NPError& aRv, const uint64_t& aCallbackId) { + if (mClearSiteDataCallbacks.find(aCallbackId) == + mClearSiteDataCallbacks.end()) { + return IPC_OK(); + } + if (!!mClearSiteDataCallbacks[aCallbackId]) { + nsresult rv; + switch (aRv) { + case NPERR_NO_ERROR: + rv = NS_OK; + break; + case NPERR_TIME_RANGE_NOT_SUPPORTED: + rv = NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED; + break; + case NPERR_MALFORMED_SITE: + rv = NS_ERROR_INVALID_ARG; + break; + default: + rv = NS_ERROR_FAILURE; + } + mClearSiteDataCallbacks[aCallbackId]->Callback(rv); + } + mClearSiteDataCallbacks.erase(aCallbackId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginModuleParent::RecvReturnSitesWithData( + nsTArray&& aSites, const uint64_t& aCallbackId) { + if (mSitesWithDataCallbacks.find(aCallbackId) == + mSitesWithDataCallbacks.end()) { + return IPC_OK(); + } + + if (!!mSitesWithDataCallbacks[aCallbackId]) { + mSitesWithDataCallbacks[aCallbackId]->SitesWithData(aSites); + } + mSitesWithDataCallbacks.erase(aCallbackId); + return IPC_OK(); +} + +layers::TextureClientRecycleAllocator* +PluginModuleParent::EnsureTextureAllocatorForDirectBitmap() { + if (!mTextureAllocatorForDirectBitmap) { + mTextureAllocatorForDirectBitmap = + new layers::TextureClientRecycleAllocator( + layers::ImageBridgeChild::GetSingleton().get()); + } + return mTextureAllocatorForDirectBitmap; +} + +layers::TextureClientRecycleAllocator* +PluginModuleParent::EnsureTextureAllocatorForDXGISurface() { + if (!mTextureAllocatorForDXGISurface) { + mTextureAllocatorForDXGISurface = new layers::TextureClientRecycleAllocator( + layers::ImageBridgeChild::GetSingleton().get()); + } + return mTextureAllocatorForDXGISurface; +} + +mozilla::ipc::IPCResult +PluginModuleParent::AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, NPError* result) { + MOZ_CRASH( + "SetValue_NPPVpluginRequiresAudioDeviceChanges is only valid " + "with PluginModuleChromeParent"); +} + +#ifdef MOZ_CRASHREPORTER_INJECTOR + +// We only add the crash reporter to subprocess which have the filename +// FlashPlayerPlugin* +# define FLASH_PROCESS_PREFIX u"FLASHPLAYERPLUGIN" + +static DWORD GetFlashChildOfPID(DWORD pid, HANDLE snapshot) { + PROCESSENTRY32 entry = {sizeof(entry)}; + for (BOOL ok = Process32First(snapshot, &entry); ok; + ok = Process32Next(snapshot, &entry)) { + if (entry.th32ParentProcessID == pid) { + nsString name(entry.szExeFile); + ToUpperCase(name); + if (StringBeginsWith(name, nsLiteralString(FLASH_PROCESS_PREFIX))) { + return entry.th32ProcessID; + } + } + } + return 0; +} + +// We only look for child processes of the Flash plugin, NPSWF* +# define FLASH_PLUGIN_PREFIX "NPSWF" + +void PluginModuleChromeParent::InitializeInjector() { + if (!Preferences::GetBool( + "dom.ipc.plugins.flash.subprocess.crashreporter.enabled", false)) + return; + + nsCString path(Process()->GetPluginFilePath().c_str()); + ToUpperCase(path); + int32_t lastSlash = path.RFindCharInSet("\\/"); + if (kNotFound == lastSlash) return; + + if (!StringBeginsWith(Substring(path, lastSlash + 1), + nsLiteralCString(FLASH_PLUGIN_PREFIX))) + return; + + mFinishInitTask = mChromeTaskFactory.NewTask(); + mFinishInitTask->Init(this); + if (!::QueueUserWorkItem(&PluginModuleChromeParent::GetToolhelpSnapshot, + mFinishInitTask, WT_EXECUTEDEFAULT)) { + mFinishInitTask = nullptr; + return; + } +} + +void PluginModuleChromeParent::DoInjection(const nsAutoHandle& aSnapshot) { + DWORD pluginProcessPID = GetProcessId(Process()->GetChildProcessHandle()); + mFlashProcess1 = GetFlashChildOfPID(pluginProcessPID, aSnapshot); + if (mFlashProcess1) { + InjectCrashReporterIntoProcess(mFlashProcess1, this); + + mFlashProcess2 = GetFlashChildOfPID(mFlashProcess1, aSnapshot); + if (mFlashProcess2) { + InjectCrashReporterIntoProcess(mFlashProcess2, this); + } + } + mFinishInitTask = nullptr; +} + +DWORD WINAPI PluginModuleChromeParent::GetToolhelpSnapshot(LPVOID aContext) { + FinishInjectorInitTask* task = static_cast(aContext); + MOZ_ASSERT(task); + task->PostToMainThread(); + return 0; +} + +void PluginModuleChromeParent::OnCrash(DWORD processID) { + if (!mShutdown) { + GetIPCChannel()->CloseWithError(); + mozilla::ipc::ScopedProcessHandle geckoPluginChild; + if (base::OpenProcessHandle(OtherPid(), &geckoPluginChild.rwget())) { + if (!base::KillProcess(geckoPluginChild, base::PROCESS_END_KILLED_BY_USER, + false)) { + NS_ERROR("May have failed to kill child process."); + } + } else { + NS_ERROR("Failed to open child process when attempting kill."); + } + } +} + +#endif // MOZ_CRASHREPORTER_INJECTOR diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h new file mode 100644 index 0000000000..9fd74904a2 --- /dev/null +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -0,0 +1,545 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginModuleParent_h +#define mozilla_plugins_PluginModuleParent_h + +#include "base/process.h" +#include "mozilla/FileUtils.h" +#include "mozilla/HangAnnotations.h" +#include "mozilla/PluginLibrary.h" +#include "mozilla/plugins/PluginProcessParent.h" +#include "mozilla/plugins/PPluginModuleParent.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/plugins/PluginTypes.h" +#include "mozilla/ipc/TaskFactory.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "npapi.h" +#include "npfunctions.h" +#include "nsExceptionHandler.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsIObserver.h" +#ifdef XP_WIN +# include "nsWindowsHelpers.h" +#endif + +class nsPluginTag; + +namespace mozilla { + +namespace ipc { +class CrashReporterHost; +} // namespace ipc +namespace layers { +class TextureClientRecycleAllocator; +} // namespace layers + +namespace plugins { +//----------------------------------------------------------------------------- + +class BrowserStreamParent; +class PluginInstanceParent; + +#ifdef XP_WIN +class PluginHangUIParent; +class FunctionBrokerParent; +#endif +#ifdef MOZ_CRASHREPORTER_INJECTOR +class FinishInjectorInitTask; +#endif + +/** + * PluginModuleParent + * + * This class implements the NPP API from the perspective of the rest + * of Gecko, forwarding NPP calls along to the child process that is + * actually running the plugin. + * + * This class /also/ implements a version of the NPN API, because the + * child process needs to make these calls back into Gecko proper. + * This class is responsible for "actually" making those function calls. + * + * If a plugin is running, there will always be one PluginModuleParent for it in + * the chrome process. In addition, any content process using the plugin will + * have its own PluginModuleParent. The subclasses PluginModuleChromeParent and + * PluginModuleContentParent implement functionality that is specific to one + * case or the other. + */ +class PluginModuleParent : public PPluginModuleParent, + public PluginLibrary +#ifdef MOZ_CRASHREPORTER_INJECTOR + , + public CrashReporter::InjectorCrashCallback +#endif +{ + friend class PPluginModuleParent; + + protected: + typedef mozilla::PluginLibrary PluginLibrary; + + PPluginInstanceParent* AllocPPluginInstanceParent( + const nsCString& aMimeType, const nsTArray& aNames, + const nsTArray& aValues); + + bool DeallocPPluginInstanceParent(PPluginInstanceParent* aActor); + + public: + explicit PluginModuleParent(bool aIsChrome); + virtual ~PluginModuleParent(); + + bool IsChrome() const { return mIsChrome; } + + virtual void SetPlugin(nsNPAPIPlugin* plugin) override { mPlugin = plugin; } + + virtual void ActorDestroy(ActorDestroyReason why) override; + + const NPNetscapeFuncs* GetNetscapeFuncs() { return mNPNIface; } + + bool OkToCleanup() const { return !IsOnCxxStack(); } + + void ProcessRemoteNativeEventsInInterruptCall() override; + + virtual nsresult GetRunID(uint32_t* aRunID) override; + virtual void SetHasLocalInstance() override { mHadLocalInstance = true; } + + int GetQuirks() { return mQuirks; } + + protected: + virtual mozilla::ipc::RacyInterruptPolicy MediateInterruptRace( + const MessageInfo& parent, const MessageInfo& child) override { + return MediateRace(parent, child); + } + + mozilla::ipc::IPCResult RecvBackUpXResources( + const FileDescriptor& aXSocketFd); + + mozilla::ipc::IPCResult AnswerProcessSomeEvents(); + + mozilla::ipc::IPCResult RecvProcessNativeEventsInInterruptCall(); + + mozilla::ipc::IPCResult RecvPluginShowWindow( + const uint32_t& aWindowId, const bool& aModal, const int32_t& aX, + const int32_t& aY, const double& aWidth, const double& aHeight); + + mozilla::ipc::IPCResult RecvPluginHideWindow(const uint32_t& aWindowId); + + mozilla::ipc::IPCResult RecvSetCursor(const NSCursorInfo& aCursorInfo); + + mozilla::ipc::IPCResult RecvShowCursor(const bool& aShow); + + mozilla::ipc::IPCResult RecvPushCursor(const NSCursorInfo& aCursorInfo); + + mozilla::ipc::IPCResult RecvPopCursor(); + + mozilla::ipc::IPCResult RecvNPN_SetException(const nsCString& aMessage); + + mozilla::ipc::IPCResult RecvNPN_ReloadPlugins(const bool& aReloadPages); + + static BrowserStreamParent* StreamCast(NPP instance, NPStream* s); + + virtual mozilla::ipc::IPCResult + AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, NPError* result); + + protected: + void SetChildTimeout(const int32_t aChildTimeout); + static void TimeoutChanged(const char* aPref, void* aModule); + + virtual void UpdatePluginTimeout() {} + + virtual mozilla::ipc::IPCResult RecvNotifyContentModuleDestroyed() { + return IPC_OK(); + } + + mozilla::ipc::IPCResult RecvReturnClearSiteData(const NPError& aRv, + const uint64_t& aCallbackId); + + mozilla::ipc::IPCResult RecvReturnSitesWithData(nsTArray&& aSites, + const uint64_t& aCallbackId); + + void SetPluginFuncs(NPPluginFuncs* aFuncs); + + nsresult NPP_NewInternal(NPMIMEType pluginType, NPP instance, + nsTArray& names, + nsTArray& values, NPSavedData* saved, + NPError* error); + + // NPP-like API that Gecko calls are trampolined into. These + // messages then get forwarded along to the plugin instance, + // and then eventually the child process. + + static NPError NPP_Destroy(NPP instance, NPSavedData** save); + + static NPError NPP_SetWindow(NPP instance, NPWindow* window); + static NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype); + static NPError NPP_DestroyStream(NPP instance, NPStream* stream, + NPReason reason); + static int32_t NPP_WriteReady(NPP instance, NPStream* stream); + static int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, + int32_t len, void* buffer); + static void NPP_Print(NPP instance, NPPrint* platformPrint); + static int16_t NPP_HandleEvent(NPP instance, void* event); + static void NPP_URLNotify(NPP instance, const char* url, NPReason reason, + void* notifyData); + static NPError NPP_GetValue(NPP instance, NPPVariable variable, + void* ret_value); + static NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value); + static void NPP_URLRedirectNotify(NPP instance, const char* url, + int32_t status, void* notifyData); + + virtual bool HasRequiredFunctions() override; + virtual nsresult AsyncSetWindow(NPP aInstance, NPWindow* aWindow) override; + virtual nsresult GetImageContainer( + NPP aInstance, mozilla::layers::ImageContainer** aContainer) override; + virtual nsresult GetImageSize(NPP aInstance, nsIntSize* aSize) override; + virtual void DidComposite(NPP aInstance) override; + virtual bool IsOOP() override { return true; } + virtual nsresult SetBackgroundUnknown(NPP instance) override; + virtual nsresult BeginUpdateBackground(NPP instance, const nsIntRect& aRect, + DrawTarget** aDrawTarget) override; + virtual nsresult EndUpdateBackground(NPP instance, + const nsIntRect& aRect) override; + +#if defined(XP_WIN) + virtual nsresult GetScrollCaptureContainer( + NPP aInstance, mozilla::layers::ImageContainer** aContainer) override; +#endif + + virtual nsresult HandledWindowedPluginKeyEvent( + NPP aInstance, const mozilla::NativeEventData& aNativeKeyData, + bool aIsConsumed) override; + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, + NPError* error) override; +#else + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, + NPError* error) override; +#endif + virtual nsresult NP_Shutdown(NPError* error) override; + + virtual nsresult NP_GetMIMEDescription(const char** mimeDesc) override; + virtual nsresult NP_GetValue(void* future, NPPVariable aVariable, + void* aValue, NPError* error) override; +#if defined(XP_WIN) || defined(XP_MACOSX) + virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, + NPError* error) override; +#endif + virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance, int16_t argc, + char* argn[], char* argv[], NPSavedData* saved, + NPError* error) override; + virtual nsresult NPP_ClearSiteData( + const char* site, uint64_t flags, uint64_t maxAge, + nsCOMPtr callback) override; + virtual nsresult NPP_GetSitesWithData( + nsCOMPtr callback) override; + + private: + std::map> + mClearSiteDataCallbacks; + std::map> + mSitesWithDataCallbacks; + + nsCString mPluginFilename; + int mQuirks; + void InitQuirksModes(const nsCString& aMimeType); + + public: +#if defined(XP_MACOSX) + virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, + bool* aDrawing) override; +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) + virtual nsresult ContentsScaleFactorChanged( + NPP instance, double aContentsScaleFactor) override; +#endif + + layers::TextureClientRecycleAllocator* + EnsureTextureAllocatorForDirectBitmap(); + layers::TextureClientRecycleAllocator* EnsureTextureAllocatorForDXGISurface(); + + protected: + void NotifyFlashHang(); + void NotifyPluginCrashed(); + void OnInitFailure(); + bool DoShutdown(NPError* error); + + bool GetSetting(NPNVariable aVariable); + void GetSettings(PluginSettings* aSettings); + + bool mIsChrome; + bool mShutdown; + bool mHadLocalInstance; + bool mClearSiteDataSupported; + bool mGetSitesWithDataSupported; + NPNetscapeFuncs* mNPNIface; + NPPluginFuncs* mNPPIface; + nsNPAPIPlugin* mPlugin; + ipc::TaskFactory mTaskFactory; + nsString mHangID; + nsCString mPluginName; + nsCString mPluginVersion; + int32_t mSandboxLevel; + bool mIsFlashPlugin; + +#ifdef MOZ_X11 + // Dup of plugin's X socket, used to scope its resources to this + // object instead of the plugin process's lifetime + ScopedClose mPluginXSocketFdDup; +#endif + + bool GetPluginDetails(); + + uint32_t mRunID; + + RefPtr + mTextureAllocatorForDirectBitmap; + RefPtr mTextureAllocatorForDXGISurface; + + /** + * This mutex protects the crash reporter when the Plugin Hang UI event + * handler is executing off main thread. It is intended to protect both + * the mCrashReporter variable in addition to the CrashReporterHost object + * that mCrashReporter refers to. + */ + mozilla::Mutex mCrashReporterMutex; + UniquePtr mCrashReporter; + nsString mOrphanedDumpId; +}; + +class PluginModuleContentParent : public PluginModuleParent { + public: + explicit PluginModuleContentParent(); + + static PluginLibrary* LoadModule(uint32_t aPluginId, nsPluginTag* aPluginTag); + + virtual ~PluginModuleContentParent(); + +#if defined(XP_WIN) || defined(XP_MACOSX) + nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override; +#endif + + private: + static void Initialize(Endpoint&& aEndpoint); + + virtual bool ShouldContinueFromReplyTimeout() override; + virtual void OnExitedSyncSend() override; + +#ifdef MOZ_CRASHREPORTER_INJECTOR + void OnCrash(DWORD processID) override {} +#endif + + static PluginModuleContentParent* sSavedModuleParent; + + uint32_t mPluginId; +}; + +class PluginModuleChromeParent : public PluginModuleParent, + public mozilla::BackgroundHangAnnotator { + friend class mozilla::ipc::CrashReporterHost; + + public: + /** + * LoadModule + * + * This may or may not launch a plugin child process, + * and may or may not be very expensive. + */ + static PluginLibrary* LoadModule(const char* aFilePath, uint32_t aPluginId, + nsPluginTag* aPluginTag); + + virtual ~PluginModuleChromeParent(); + + /* + * Takes a full multi-process dump including the plugin process and the + * content process. If aBrowserDumpId is not empty then the browser dump + * associated with it will be paired to the resulting minidump. + * Takes ownership of the file associated with aBrowserDumpId. + * + * @param aContentPid PID of the e10s content process from which a hang was + * reported. May be kInvalidProcessId if not applicable. + * @param aBrowserDumpId (optional) previously taken browser dump id. If + * provided TakeFullMinidump will use this dump file instead of + * generating a new one. If not provided a browser dump will be taken at + * the time of this call. + * @param aDumpId Returns the ID of the newly generated crash dump. Left + * untouched upon failure. + */ + void TakeFullMinidump(base::ProcessId aContentPid, + const nsAString& aBrowserDumpId, nsString& aDumpId); + + /* + * Terminates the plugin process associated with this plugin module. Also + * generates appropriate crash reports unless an existing one is provided. + * Takes ownership of the file associated with aDumpId on success. + * + * @param aMsgLoop the main message pump associated with the module + * protocol. + * @param aContentPid PID of the e10s content process from which a hang was + * reported. May be kInvalidProcessId if not applicable. + * @param aMonitorDescription a string describing the hang monitor that + * is making this call. This string is added to the crash reporter + * annotations for the plugin process. + * @param aDumpId (optional) previously taken dump id. If provided + * TerminateChildProcess will use this dump file instead of generating a + * multi-process crash report. If not provided a multi-process dump will + * be taken at the time of this call. + */ + void TerminateChildProcess(MessageLoop* aMsgLoop, base::ProcessId aContentPid, + const nsCString& aMonitorDescription, + const nsAString& aDumpId); + +#ifdef XP_WIN + /** + * Called by Plugin Hang UI to notify that the user has clicked continue. + * Used for chrome hang annotations. + */ + void OnHangUIContinue(); + + void EvaluateHangUIState(const bool aReset); +#endif // XP_WIN + + void CachedSettingChanged(); + + private: + virtual void EnteredCxxStack() override; + + void ExitedCxxStack() override; + + mozilla::ipc::IProtocol* GetInvokingProtocol(); + PluginInstanceParent* GetManagingInstance(mozilla::ipc::IProtocol* aProtocol); + + virtual void AnnotateHang( + mozilla::BackgroundHangAnnotations& aAnnotations) override; + + virtual bool ShouldContinueFromReplyTimeout() override; + + void ProcessFirstMinidump(); + void HandleOrphanedMinidump(); + void AddCrashAnnotations(); + + PluginProcessParent* Process() const { return mSubprocess; } + base::ProcessHandle ChildProcessHandle() { + return mSubprocess->GetChildProcessHandle(); + } + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, + NPError* error) override; +#else + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, + NPError* error) override; +#endif + +#if defined(XP_WIN) || defined(XP_MACOSX) + virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, + NPError* error) override; +#endif + + virtual void ActorDestroy(ActorDestroyReason why) override; + + // aFilePath is UTF8, not native! + explicit PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId, + int32_t aSandboxLevel); + + void CleanupFromTimeout(const bool aByHangUI); + + virtual void UpdatePluginTimeout() override; + + void RegisterSettingsCallbacks(); + void UnregisterSettingsCallbacks(); + + bool InitCrashReporter(); + + mozilla::ipc::IPCResult RecvNotifyContentModuleDestroyed() override; + + static void CachedSettingChanged(const char* aPref, void* aModule); + + mozilla::ipc::IPCResult + AnswerNPN_SetValue_NPPVpluginRequiresAudioDeviceChanges( + const bool& shouldRegister, NPError* result) override; + + PluginProcessParent* mSubprocess; + uint32_t mPluginId; + + ipc::TaskFactory mChromeTaskFactory; + + enum HangAnnotationFlags { + kInPluginCall = (1u << 0), + kHangUIShown = (1u << 1), + kHangUIContinued = (1u << 2), + kHangUIDontShow = (1u << 3) + }; + Atomic mHangAnnotationFlags; +#ifdef XP_WIN + nsTArray mPluginCpuUsageOnHang; + PluginHangUIParent* mHangUIParent; + bool mHangUIEnabled; + bool mIsTimerReset; + + /** + * Launches the Plugin Hang UI. + * + * @return true if plugin-hang-ui.exe has been successfully launched. + * false if the Plugin Hang UI is disabled, already showing, + * or the launch failed. + */ + bool LaunchHangUI(); + + /** + * Finishes the Plugin Hang UI and cancels if it is being shown to the user. + */ + void FinishHangUI(); + + FunctionBrokerParent* mBrokerParent; +#endif + +#ifdef MOZ_CRASHREPORTER_INJECTOR + friend class mozilla::plugins::FinishInjectorInitTask; + + void InitializeInjector(); + void DoInjection(const nsAutoHandle& aSnapshot); + static DWORD WINAPI GetToolhelpSnapshot(LPVOID aContext); + + void OnCrash(DWORD processID) override; + + DWORD mFlashProcess1; + DWORD mFlashProcess2; + RefPtr mFinishInitTask; +#endif + + void OnProcessLaunched(const bool aSucceeded); + + class LaunchedTask : public LaunchCompleteTask { + public: + explicit LaunchedTask(PluginModuleChromeParent* aModule) + : mModule(aModule) { + MOZ_ASSERT(aModule); + } + + NS_IMETHOD Run() override { + mModule->OnProcessLaunched(mLaunchSucceeded); + return NS_OK; + } + + private: + PluginModuleChromeParent* mModule; + }; + + friend class LaunchedTask; + + nsCOMPtr mPluginOfflineObserver; + bool mIsBlocklisted; + bool mIsCleaningFromTimeout; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginModuleParent_h diff --git a/dom/plugins/ipc/PluginProcessChild.cpp b/dom/plugins/ipc/PluginProcessChild.cpp new file mode 100644 index 0000000000..986243f93f --- /dev/null +++ b/dom/plugins/ipc/PluginProcessChild.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/plugins/PluginProcessChild.h" + +#include "ClearOnShutdown.h" +#include "base/command_line.h" +#include "base/message_loop.h" // for MessageLoop +#include "base/string_util.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/TaskController.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "nsDebugImpl.h" +#include "nsThreadManager.h" +#include "prlink.h" + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +#endif + +#ifdef XP_WIN +# if defined(MOZ_SANDBOX) +# include "mozilla/sandboxTarget.h" +# include "ProcessUtils.h" +# include "nsDirectoryService.h" +# endif +#endif + +using mozilla::ipc::IOThreadChild; + +#ifdef OS_WIN +# include +#endif + +namespace mozilla::plugins { + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +static void SetSandboxTempPath(const std::wstring& aFullTmpPath) { + // Save the TMP environment variable so that is is picked up by GetTempPath(). + // Note that we specifically write to the TMP variable, as that is the first + // variable that is checked by GetTempPath() to determine its output. + Unused << NS_WARN_IF(!SetEnvironmentVariableW(L"TMP", aFullTmpPath.c_str())); + + // We also set TEMP in case there is naughty third-party code that is + // referencing the environment variable directly. + Unused << NS_WARN_IF(!SetEnvironmentVariableW(L"TEMP", aFullTmpPath.c_str())); +} +#endif + +bool PluginProcessChild::Init(int aArgc, char* aArgv[]) { + nsDebugImpl::SetMultiprocessMode("NPAPI"); + +#if defined(XP_MACOSX) + // Remove the trigger for "dyld interposing" that we added in + // GeckoChildProcessHost::PerformAsyncLaunch(), in the host + // process just before we were launched. Dyld interposing will still + // happen in our process (the plugin child process). But we don't want + // it to happen in any processes that the plugin might launch from our + // process. + nsCString interpose(PR_GetEnv("DYLD_INSERT_LIBRARIES")); + if (!interpose.IsEmpty()) { + // If we added the path to libplugin_child_interpose.dylib to an + // existing DYLD_INSERT_LIBRARIES, we appended it to the end, after a + // ":" path seperator. + int32_t lastSeparatorPos = interpose.RFind(":"); + int32_t lastTriggerPos = interpose.RFind("libplugin_child_interpose.dylib"); + bool needsReset = false; + if (lastTriggerPos != -1) { + if (lastSeparatorPos == -1) { + interpose.Truncate(); + needsReset = true; + } else if (lastTriggerPos > lastSeparatorPos) { + interpose.SetLength(lastSeparatorPos); + needsReset = true; + } + } + if (needsReset) { + nsCString setInterpose("DYLD_INSERT_LIBRARIES="); + if (!interpose.IsEmpty()) { + setInterpose.Append(interpose); + } + // Values passed to PR_SetEnv() must be seperately allocated. + char* setInterposePtr = strdup(setInterpose.get()); + PR_SetEnv(setInterposePtr); + } + } +#endif + + // Certain plugins, such as flash, steal the unhandled exception filter + // thus we never get crash reports when they fault. This call fixes it. + message_loop()->set_exception_restoration(true); + + std::string pluginFilename; + +#if defined(OS_POSIX) + // NB: need to be very careful in ensuring that the first arg + // (after the binary name) here is indeed the plugin module path. + // Keep in sync with dom/plugins/PluginModuleParent. + std::vector values = CommandLine::ForCurrentProcess()->argv(); + MOZ_ASSERT(values.size() >= 2, "not enough args"); + + pluginFilename = UnmungePluginDsoPath(values[1]); + +# if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + int level; + if (values.size() >= 4 && values[2] == "-flashSandboxLevel" && + (level = std::stoi(values[3], nullptr)) > 0) { + level = ClampFlashSandboxLevel(level); + MOZ_ASSERT(level > 0); + + bool enableLogging = false; + if (values.size() >= 5 && values[4] == "-flashSandboxLogging") { + enableLogging = true; + } + + mPlugin.EnableFlashSandbox(level, enableLogging); + } +# endif + +#elif defined(OS_WIN) + std::vector values = + CommandLine::ForCurrentProcess()->GetLooseValues(); + MOZ_ASSERT(values.size() >= 1, "not enough loose args"); + + // parameters are: + // values[0] is path to plugin DLL + // values[1] is path to folder that should be used for temp files + // values[2] is path to the Flash Player roaming folder + // (this is always that Flash folder, regardless of what plugin is being + // run) + pluginFilename = WideToUTF8(values[0]); + + // We don't initialize XPCOM but we need the thread manager and the + // logging framework for the FunctionBroker. + NS_SetMainThread(); + mozilla::TimeStamp::Startup(); + NS_LogInit(); + mozilla::LogModule::Init(aArgc, aArgv); + nsThreadManager::get().Init(); + +# if defined(MOZ_SANDBOX) + MOZ_ASSERT(values.size() >= 3, + "not enough loose args for sandboxed plugin process"); + + // The sandbox closes off the default location temp file location so we set + // a new one here (regardless of whether or not we are sandboxing). + SetSandboxTempPath(values[1]); + PluginModuleChild::SetFlashRoamingPath(values[2]); + + // This is probably the earliest we would want to start the sandbox. + // As we attempt to tighten the sandbox, we may need to consider moving this + // to later in the plugin initialization. + mozilla::SandboxTarget::Instance()->StartSandbox(); +# endif +#else +# error Sorry +#endif + + return mPlugin.InitForChrome(pluginFilename, ParentPid(), + IOThreadChild::message_loop(), + IOThreadChild::TakeChannel()); +} + +void PluginProcessChild::CleanUp() { +#if defined(OS_WIN) + MOZ_ASSERT(NS_IsMainThread()); + + // Shutdown components we started in Init. Note that KillClearOnShutdown + // is an event that is regularly part of XPCOM shutdown. We do not + // call XPCOM's shutdown but we need this event to be sent to avoid + // leaking objects labeled as ClearOnShutdown. + nsThreadManager::get().Shutdown(); + NS_LogTerm(); +#endif + + mozilla::KillClearOnShutdown(ShutdownPhase::ShutdownFinal); + + AbstractThread::ShutdownMainThread(); + + mozilla::TaskController::Shutdown(); +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/PluginProcessChild.h b/dom/plugins/ipc/PluginProcessChild.h new file mode 100644 index 0000000000..36c8077ce8 --- /dev/null +++ b/dom/plugins/ipc/PluginProcessChild.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginProcessChild_h +#define dom_plugins_PluginProcessChild_h 1 + +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/plugins/PluginModuleChild.h" + +#if defined(XP_WIN) +# include "mozilla/mscom/ProcessRuntime.h" +#endif + +namespace mozilla { +namespace plugins { +//----------------------------------------------------------------------------- + +class PluginProcessChild : public mozilla::ipc::ProcessChild { + protected: + typedef mozilla::ipc::ProcessChild ProcessChild; + + public: + explicit PluginProcessChild(ProcessId aParentPid) + : ProcessChild(aParentPid), mPlugin(true) {} + + virtual ~PluginProcessChild() = default; + + virtual bool Init(int aArgc, char* aArgv[]) override; + virtual void CleanUp() override; + + protected: + static PluginProcessChild* current() { + return static_cast(ProcessChild::current()); + } + + private: +#if defined(XP_WIN) + /* Drag-and-drop depends on the host initializing COM. + * This object initializes and configures COM. */ + mozilla::mscom::ProcessRuntime mCOMRuntime; +#endif + PluginModuleChild mPlugin; + + DISALLOW_EVIL_CONSTRUCTORS(PluginProcessChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // ifndef dom_plugins_PluginProcessChild_h diff --git a/dom/plugins/ipc/PluginProcessParent.cpp b/dom/plugins/ipc/PluginProcessParent.cpp new file mode 100644 index 0000000000..2f65311263 --- /dev/null +++ b/dom/plugins/ipc/PluginProcessParent.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/plugins/PluginProcessParent.h" + +#include "base/string_util.h" +#include "base/process_util.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsServiceManagerUtils.h" + +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/Telemetry.h" +#include "nsThreadUtils.h" + +using std::string; +using std::vector; + +using mozilla::ipc::BrowserProcessSubThread; +using mozilla::ipc::GeckoChildProcessHost; +using mozilla::plugins::LaunchCompleteTask; +using mozilla::plugins::PluginProcessParent; + +#ifdef XP_WIN +PluginProcessParent::PidSet* PluginProcessParent::sPidSet = nullptr; +#endif + +PluginProcessParent::PluginProcessParent(const std::string& aPluginFilePath) + : GeckoChildProcessHost(GeckoProcessType_Plugin), + mPluginFilePath(aPluginFilePath), + mTaskFactory(this), + mMainMsgLoop(MessageLoop::current()) +#ifdef XP_WIN + , + mChildPid(0) +#endif +{ +} + +PluginProcessParent::~PluginProcessParent() { +#ifdef XP_WIN + if (sPidSet && mChildPid) { + sPidSet->RemoveEntry(mChildPid); + if (sPidSet->IsEmpty()) { + delete sPidSet; + sPidSet = nullptr; + } + } +#endif +} + +bool PluginProcessParent::Launch( + mozilla::UniquePtr aLaunchCompleteTask, + int32_t aSandboxLevel, bool aIsSandboxLoggingEnabled) { +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_SANDBOX) + // At present, the Mac Flash plugin sandbox does not support different + // levels and is enabled via a boolean pref or environment variable. + // On Mac, when |aSandboxLevel| is positive, we enable the sandbox. +# if defined(XP_WIN) + mSandboxLevel = aSandboxLevel; + + // The sandbox process sometimes needs read access to the plugin file. + if (aSandboxLevel >= 3) { + std::wstring pluginFile( + NS_ConvertUTF8toUTF16(mPluginFilePath.c_str()).get()); + mAllowedFilesRead.push_back(pluginFile); + } +# endif // XP_WIN +#else + if (aSandboxLevel != 0) { + MOZ_ASSERT(false, + "Can't enable an NPAPI process sandbox for platform/build."); + } +#endif + + mLaunchCompleteTask = std::move(aLaunchCompleteTask); + + vector args; + args.push_back(MungePluginDsoPath(mPluginFilePath)); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (aSandboxLevel > 0) { + args.push_back("-flashSandboxLevel"); + args.push_back(std::to_string(aSandboxLevel)); + if (aIsSandboxLoggingEnabled) { + args.push_back("-flashSandboxLogging"); + } + } +#elif defined(XP_WIN) && defined(MOZ_SANDBOX) + nsresult rv; + nsCOMPtr dirSvc = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to get directory service."); + return false; + } + + nsCOMPtr dir; + rv = dirSvc->Get(NS_APP_PLUGIN_PROCESS_TEMP_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(dir)); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to get plugin process temp directory."); + return false; + } + + nsAutoString tempDir; + MOZ_ALWAYS_SUCCEEDS(dir->GetPath(tempDir)); + args.push_back(NS_ConvertUTF16toUTF8(tempDir).get()); + + rv = + dirSvc->Get(NS_WIN_APPDATA_DIR, NS_GET_IID(nsIFile), getter_AddRefs(dir)); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to get appdata directory."); + return false; + } + + nsAutoString appdataDir; + MOZ_ALWAYS_SUCCEEDS(dir->GetPath(appdataDir)); + appdataDir.Append(L"\\Adobe\\"); + args.push_back(NS_ConvertUTF16toUTF8(appdataDir).get()); +#endif + + bool result = AsyncLaunch(args); + if (!result) { + mLaunchCompleteTask = nullptr; + } + return result; +} + +/** + * This function exists so that we may provide an additional level of + * indirection between the task being posted to main event loop (a + * RunnableMethod) and the launch complete task itself. This is needed + * for cases when both WaitUntilConnected or OnChannel* race to invoke the + * task. + */ +void PluginProcessParent::RunLaunchCompleteTask() { + if (mLaunchCompleteTask) { + mLaunchCompleteTask->Run(); + mLaunchCompleteTask = nullptr; + } +} + +bool PluginProcessParent::WaitUntilConnected(int32_t aTimeoutMs) { + bool result = GeckoChildProcessHost::WaitUntilConnected(aTimeoutMs); + if (mLaunchCompleteTask) { + if (result) { + mLaunchCompleteTask->SetLaunchSucceeded(); + } + RunLaunchCompleteTask(); + } + return result; +} + +void PluginProcessParent::OnChannelConnected(int32_t peer_pid) { +#ifdef XP_WIN + mChildPid = static_cast(peer_pid); + if (!sPidSet) { + sPidSet = new PluginProcessParent::PidSet(); + } + sPidSet->PutEntry(mChildPid); +#endif + + GeckoChildProcessHost::OnChannelConnected(peer_pid); +} + +void PluginProcessParent::OnChannelError() { + GeckoChildProcessHost::OnChannelError(); +} + +bool PluginProcessParent::IsConnected() { + mozilla::MonitorAutoLock lock(mMonitor); + return mProcessState == PROCESS_CONNECTED; +} + +bool PluginProcessParent::IsPluginProcessId(base::ProcessId procId) { +#ifdef XP_WIN + MOZ_ASSERT(XRE_IsParentProcess()); + return sPidSet && sPidSet->Contains(static_cast(procId)); +#else + NS_ERROR("IsPluginProcessId not available on this platform."); + return false; +#endif +} diff --git a/dom/plugins/ipc/PluginProcessParent.h b/dom/plugins/ipc/PluginProcessParent.h new file mode 100644 index 0000000000..48ce24cc70 --- /dev/null +++ b/dom/plugins/ipc/PluginProcessParent.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginProcessParent_h +#define dom_plugins_PluginProcessParent_h 1 + +#include "mozilla/Attributes.h" +#include "base/basictypes.h" + +#include "base/file_path.h" +#include "base/task.h" +#include "base/thread.h" +#include "chrome/common/child_process_host.h" + +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/TaskFactory.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" + +namespace mozilla { +namespace plugins { + +class LaunchCompleteTask : public Runnable { + public: + LaunchCompleteTask() + : Runnable("plugins::LaunchCompleteTask"), mLaunchSucceeded(false) {} + + void SetLaunchSucceeded() { mLaunchSucceeded = true; } + + protected: + bool mLaunchSucceeded; +}; + +class PluginProcessParent final : public mozilla::ipc::GeckoChildProcessHost { + public: + explicit PluginProcessParent(const std::string& aPluginFilePath); + + /** + * Launch the plugin process. If the process fails to launch, + * this method will return false. + * + * @param aLaunchCompleteTask Task that is executed on the main + * thread once the asynchonous launch has completed. + * @param aSandboxLevel Determines the strength of the sandbox. + * <= 0 means no sandbox. + * @param aIsSandboxLoggingEnabled Indicates if sandbox violation + * logging should be enabled for the plugin process. + */ + bool Launch(UniquePtr aLaunchCompleteTask = + UniquePtr(), + int32_t aSandboxLevel = 0, bool aIsSandboxLoggingEnabled = false); + + virtual bool CanShutdown() override { return true; } + + const std::string& GetPluginFilePath() { return mPluginFilePath; } + + using mozilla::ipc::GeckoChildProcessHost::GetChannel; + + virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0) override; + + virtual void OnChannelConnected(int32_t peer_pid) override; + virtual void OnChannelError() override; + + bool IsConnected(); + + static bool IsPluginProcessId(base::ProcessId procId); + + private: + ~PluginProcessParent(); + + void RunLaunchCompleteTask(); + + std::string mPluginFilePath; + ipc::TaskFactory mTaskFactory; + UniquePtr mLaunchCompleteTask; + MessageLoop* mMainMsgLoop; +#ifdef XP_WIN + typedef nsTHashtable PidSet; + // Set of PIDs for all plugin child processes or NULL if empty. + static PidSet* sPidSet; + uint32_t mChildPid; +#endif + + DISALLOW_EVIL_CONSTRUCTORS(PluginProcessParent); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // ifndef dom_plugins_PluginProcessParent_h diff --git a/dom/plugins/ipc/PluginQuirks.cpp b/dom/plugins/ipc/PluginQuirks.cpp new file mode 100644 index 0000000000..2e83cbc37b --- /dev/null +++ b/dom/plugins/ipc/PluginQuirks.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginQuirks.h" + +#include "nsPluginHost.h" + +namespace mozilla::plugins { + +int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType, + const nsCString& aPluginFilename) { + int quirks = 0; + + nsPluginHost::SpecialType specialType = + nsPluginHost::GetSpecialType(aMimeType); + + if (specialType == nsPluginHost::eSpecialType_Flash) { + quirks |= QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN; +#ifdef OS_WIN + quirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK; + quirks |= QUIRK_FLASH_THROTTLE_WMUSER_EVENTS; + quirks |= QUIRK_FLASH_HOOK_SETLONGPTR; + quirks |= QUIRK_FLASH_HOOK_GETWINDOWINFO; + quirks |= QUIRK_FLASH_FIXUP_MOUSE_CAPTURE; + quirks |= QUIRK_WINLESS_HOOK_IME; +# if defined(_M_X64) || defined(__x86_64__) + quirks |= QUIRK_FLASH_HOOK_GETKEYSTATE; + quirks |= QUIRK_FLASH_HOOK_PRINTDLGW; + quirks |= QUIRK_FLASH_HOOK_SSL; + quirks |= QUIRK_FLASH_HOOK_CREATEMUTEXW; +# endif +#endif + } + +#ifdef XP_MACOSX + // Whitelist Flash to support offline renderer. + if (specialType == nsPluginHost::eSpecialType_Flash) { + quirks |= QUIRK_ALLOW_OFFLINE_RENDERER; + } +#endif + +#ifdef OS_WIN + if (specialType == nsPluginHost::eSpecialType_Test) { + quirks |= QUIRK_WINLESS_HOOK_IME; + } +#endif + + return quirks; +} + +} // namespace mozilla::plugins diff --git a/dom/plugins/ipc/PluginQuirks.h b/dom/plugins/ipc/PluginQuirks.h new file mode 100644 index 0000000000..852ebb6b7a --- /dev/null +++ b/dom/plugins/ipc/PluginQuirks.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginQuirks_h +#define dom_plugins_PluginQuirks_h + +#include "nsString.h" + +namespace mozilla { +namespace plugins { + +// Quirks mode support for various plugin mime types +enum PluginQuirks { + QUIRKS_NOT_INITIALIZED = 0, + // Win32: Hook TrackPopupMenu api so that we can swap out parent + // hwnds. The api will fail with parents not associated with our + // child ui thread. See WinlessHandleEvent for details. + QUIRK_WINLESS_TRACKPOPUP_HOOK = 1 << 1, + // Win32: Throttle flash WM_USER+1 heart beat messages to prevent + // flooding chromium's dispatch loop, which can cause ipc traffic + // processing lag. + QUIRK_FLASH_THROTTLE_WMUSER_EVENTS = 1 << 2, + // Win32: Catch resets on our subclass by hooking SetWindowLong. + QUIRK_FLASH_HOOK_SETLONGPTR = 1 << 3, + // X11: Work around a bug in Flash up to 10.1 d51 at least, where + // expose event top left coordinates within the plugin-rect and + // not at the drawable origin are misinterpreted. + QUIRK_FLASH_EXPOSE_COORD_TRANSLATION = 1 << 4, + // Win32: Catch get window info calls on the browser and tweak the + // results so mouse input works when flash is displaying it's settings + // window. + QUIRK_FLASH_HOOK_GETWINDOWINFO = 1 << 5, + // Win: Addresses a flash bug with mouse capture and full screen + // windows. + QUIRK_FLASH_FIXUP_MOUSE_CAPTURE = 1 << 6, + // Mac: Allow the plugin to use offline renderer mode. + // Use this only if the plugin is certified the support the offline renderer. + QUIRK_ALLOW_OFFLINE_RENDERER = 1 << 9, + // Work around a Flash bug where it fails to check the error code of a + // NPN_GetValue(NPNVdocumentOrigin) call before trying to dereference + // its char* output. + QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN = 1 << 10, + // Win: Hook IMM32 API to handle IME event on windowless plugin + QUIRK_WINLESS_HOOK_IME = 1 << 12, + // Win: Hook GetKeyState to get keyboard state on sandbox process + QUIRK_FLASH_HOOK_GETKEYSTATE = 1 << 13, + // Win: Hook PrintDlgW to show print settings dialog on sandbox process + QUIRK_FLASH_HOOK_PRINTDLGW = 1 << 14, + // Win: Broker Win32 SSL operations + QUIRK_FLASH_HOOK_SSL = 1 << 15, + // Win: Hook CreateMutexW for brokering when using the camera + QUIRK_FLASH_HOOK_CREATEMUTEXW = 1 << 16, +}; + +int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType, + const nsCString& aPluginFilename); + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif // ifndef dom_plugins_PluginQuirks_h diff --git a/dom/plugins/ipc/PluginScriptableObjectChild.cpp b/dom/plugins/ipc/PluginScriptableObjectChild.cpp new file mode 100644 index 0000000000..86c4b83d18 --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectChild.cpp @@ -0,0 +1,1205 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginScriptableObjectChild.h" +#include "PluginScriptableObjectUtils.h" +#include "mozilla/plugins/PluginTypes.h" + +using namespace mozilla::plugins; + +/** + * NPIdentifiers in the plugin process use a tagged representation. The low bit + * stores the tag. If it's zero, the identifier is a string, and the value is a + * pointer to a StoredIdentifier. If the tag bit is 1, then the rest of the + * NPIdentifier value is the integer itself. Like the JSAPI, we require that all + * integers stored in NPIdentifier be non-negative. + * + * String identifiers are stored in the sIdentifiers hashtable to ensure + * uniqueness. The lifetime of these identifiers is only as long as the incoming + * IPC call from the chrome process. If the plugin wants to retain an + * identifier, it needs to call NPN_GetStringIdentifier, which causes the + * mPermanent flag to be set on the identifier. When this flag is set, the + * identifier is saved until the plugin process exits. + * + * The StackIdentifier RAII class is used to manage ownership of + * identifiers. Any identifier obtained from this class should not be used + * outside its scope, except when the MakePermanent() method has been called on + * it. + * + * The lifetime of an NPIdentifier in the plugin process is totally divorced + * from the lifetime of an NPIdentifier in the chrome process (where an + * NPIdentifier is stored as a jsid). The JS GC in the chrome process is able to + * trace through the entire heap, unlike in the plugin process, so there is no + * reason to retain identifiers there. + */ + +PluginScriptableObjectChild::IdentifierTable + PluginScriptableObjectChild::sIdentifiers; + +/* static */ PluginScriptableObjectChild::StoredIdentifier* +PluginScriptableObjectChild::HashIdentifier(const nsCString& aIdentifier) { + StoredIdentifier* stored = sIdentifiers.Get(aIdentifier).get(); + if (stored) { + return stored; + } + + stored = new StoredIdentifier(aIdentifier); + sIdentifiers.Put(aIdentifier, stored); + return stored; +} + +/* static */ +void PluginScriptableObjectChild::UnhashIdentifier(StoredIdentifier* aStored) { + MOZ_ASSERT(sIdentifiers.Get(aStored->mIdentifier)); + sIdentifiers.Remove(aStored->mIdentifier); +} + +/* static */ +void PluginScriptableObjectChild::ClearIdentifiers() { sIdentifiers.Clear(); } + +PluginScriptableObjectChild::StackIdentifier::StackIdentifier( + const PluginIdentifier& aIdentifier) + : mIdentifier(aIdentifier), mStored(nullptr) { + if (aIdentifier.type() == PluginIdentifier::TnsCString) { + mStored = PluginScriptableObjectChild::HashIdentifier( + mIdentifier.get_nsCString()); + } +} + +PluginScriptableObjectChild::StackIdentifier::StackIdentifier( + NPIdentifier aIdentifier) + : mStored(nullptr) { + uintptr_t bits = reinterpret_cast(aIdentifier); + if (bits & 1) { + int32_t num = int32_t(bits >> 1); + mIdentifier = PluginIdentifier(num); + } else { + mStored = static_cast(aIdentifier); + mIdentifier = mStored->mIdentifier; + } +} + +PluginScriptableObjectChild::StackIdentifier::~StackIdentifier() { + if (!mStored) { + return; + } + + // Each StackIdentifier owns one reference to its StoredIdentifier. In + // addition, the sIdentifiers table owns a reference. If mPermanent is false + // and sIdentifiers has the last reference, then we want to remove the + // StoredIdentifier from the table (and destroy it). + StoredIdentifier* stored = mStored; + mStored = nullptr; + if (stored->mRefCnt == 1 && !stored->mPermanent) { + PluginScriptableObjectChild::UnhashIdentifier(stored); + } +} + +NPIdentifier PluginScriptableObjectChild::StackIdentifier::ToNPIdentifier() + const { + if (mStored) { + MOZ_ASSERT(mIdentifier.type() == PluginIdentifier::TnsCString); + MOZ_ASSERT((reinterpret_cast(mStored.get()) & 1) == 0); + return mStored; + } + + int32_t num = mIdentifier.get_int32_t(); + // The JS engine imposes this condition on int32s in jsids, so we assume it. + MOZ_ASSERT(num >= 0); + return reinterpret_cast((num << 1) | 1); +} + +static PluginIdentifier FromNPIdentifier(NPIdentifier aIdentifier) { + PluginScriptableObjectChild::StackIdentifier stack(aIdentifier); + return stack.GetIdentifier(); +} + +// static +NPObject* PluginScriptableObjectChild::ScriptableAllocate(NPP aInstance, + NPClass* aClass) { + AssertPluginThread(); + + if (aClass != GetClass()) { + MOZ_CRASH("Huh?! Wrong class!"); + } + + return new ChildNPObject(); +} + +// static +void PluginScriptableObjectChild::ScriptableInvalidate(NPObject* aObject) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + // This can happen more than once, and is just fine. + return; + } + + object->invalidated = true; +} + +// static +void PluginScriptableObjectChild::ScriptableDeallocate(NPObject* aObject) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + PluginScriptableObjectChild* actor = object->parent; + if (actor) { + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + actor->DropNPObject(); + } + + delete object; +} + +// static +bool PluginScriptableObjectChild::ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + actor->CallHasMethod(FromNPIdentifier(aName), &result); + + return result; +} + +// static +bool PluginScriptableObjectChild::ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + actor->CallInvoke(FromNPIdentifier(aName), args, &remoteResult, &success); + + if (!success) { + return false; + } + + ConvertToVariant(remoteResult, *aResult); + return true; +} + +// static +bool PluginScriptableObjectChild::ScriptableInvokeDefault( + NPObject* aObject, const NPVariant* aArgs, uint32_t aArgCount, + NPVariant* aResult) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + actor->CallInvokeDefault(args, &remoteResult, &success); + + if (!success) { + return false; + } + + ConvertToVariant(remoteResult, *aResult); + return true; +} + +// static +bool PluginScriptableObjectChild::ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + actor->CallHasProperty(FromNPIdentifier(aName), &result); + + return result; +} + +// static +bool PluginScriptableObjectChild::ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + PluginInstanceChild::AutoStackHelper guard(actor->mInstance); + + Variant result; + bool success; + actor->CallGetParentProperty(FromNPIdentifier(aName), &result, &success); + + if (!success) { + return false; + } + + ConvertToVariant(result, *aResult); + return true; +} + +// static +bool PluginScriptableObjectChild::ScriptableSetProperty( + NPObject* aObject, NPIdentifier aName, const NPVariant* aValue) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariant value(*aValue, actor->GetInstance()); + if (!value.IsOk()) { + NS_WARNING("Failed to convert variant!"); + return false; + } + + bool success; + actor->CallSetProperty(FromNPIdentifier(aName), value, &success); + + return success; +} + +// static +bool PluginScriptableObjectChild::ScriptableRemoveProperty(NPObject* aObject, + NPIdentifier aName) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool success; + actor->CallRemoveProperty(FromNPIdentifier(aName), &success); + + return success; +} + +// static +bool PluginScriptableObjectChild::ScriptableEnumerate( + NPObject* aObject, NPIdentifier** aIdentifiers, uint32_t* aCount) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + AutoTArray identifiers; + bool success; + actor->CallEnumerate(&identifiers, &success); + + if (!success) { + return false; + } + + *aCount = identifiers.Length(); + if (!*aCount) { + *aIdentifiers = nullptr; + return true; + } + + *aIdentifiers = + reinterpret_cast(PluginModuleChild::sBrowserFuncs.memalloc( + *aCount * sizeof(NPIdentifier))); + if (!*aIdentifiers) { + NS_ERROR("Out of memory!"); + return false; + } + + for (uint32_t index = 0; index < *aCount; index++) { + StackIdentifier id(identifiers[index]); + // Make the id permanent in case the plugin retains it. + id.MakePermanent(); + (*aIdentifiers)[index] = id.ToNPIdentifier(); + } + return true; +} + +// static +bool PluginScriptableObjectChild::ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) { + AssertPluginThread(); + + if (aObject->_class != GetClass()) { + MOZ_CRASH("Don't know what kind of object this is!"); + } + + ChildNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + NS_ASSERTION(actor, "This shouldn't ever be null!"); + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + actor->CallConstruct(args, &remoteResult, &success); + + if (!success) { + return false; + } + + ConvertToVariant(remoteResult, *aResult); + return true; +} + +const NPClass PluginScriptableObjectChild::sNPClass = { + NP_CLASS_STRUCT_VERSION, + PluginScriptableObjectChild::ScriptableAllocate, + PluginScriptableObjectChild::ScriptableDeallocate, + PluginScriptableObjectChild::ScriptableInvalidate, + PluginScriptableObjectChild::ScriptableHasMethod, + PluginScriptableObjectChild::ScriptableInvoke, + PluginScriptableObjectChild::ScriptableInvokeDefault, + PluginScriptableObjectChild::ScriptableHasProperty, + PluginScriptableObjectChild::ScriptableGetProperty, + PluginScriptableObjectChild::ScriptableSetProperty, + PluginScriptableObjectChild::ScriptableRemoveProperty, + PluginScriptableObjectChild::ScriptableEnumerate, + PluginScriptableObjectChild::ScriptableConstruct}; + +PluginScriptableObjectChild::PluginScriptableObjectChild( + ScriptableObjectType aType) + : mInstance(nullptr), + mObject(nullptr), + mInvalidated(false), + mProtectCount(0), + mType(aType) { + AssertPluginThread(); +} + +PluginScriptableObjectChild::~PluginScriptableObjectChild() { + AssertPluginThread(); + + if (mObject) { + UnregisterActor(mObject); + + if (mObject->_class == GetClass()) { + NS_ASSERTION(mType == Proxy, "Wrong type!"); + static_cast(mObject)->parent = nullptr; + } else { + NS_ASSERTION(mType == LocalObject, "Wrong type!"); + PluginModuleChild::sBrowserFuncs.releaseobject(mObject); + } + } +} + +bool PluginScriptableObjectChild::InitializeProxy() { + AssertPluginThread(); + NS_ASSERTION(mType == Proxy, "Bad type!"); + NS_ASSERTION(!mObject, "Calling Initialize more than once!"); + NS_ASSERTION(!mInvalidated, "Already invalidated?!"); + + mInstance = static_cast(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + NPObject* object = CreateProxyObject(); + if (!object) { + NS_ERROR("Failed to create object!"); + return false; + } + + if (!RegisterActor(object)) { + NS_ERROR("RegisterActor failed"); + return false; + } + + mObject = object; + return true; +} + +void PluginScriptableObjectChild::InitializeLocal(NPObject* aObject) { + AssertPluginThread(); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + NS_ASSERTION(!mObject, "Calling Initialize more than once!"); + NS_ASSERTION(!mInvalidated, "Already invalidated?!"); + + mInstance = static_cast(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + PluginModuleChild::sBrowserFuncs.retainobject(aObject); + + NS_ASSERTION(!mProtectCount, "Should be zero!"); + mProtectCount++; + + if (!RegisterActor(aObject)) { + NS_ERROR("RegisterActor failed"); + } + + mObject = aObject; +} + +NPObject* PluginScriptableObjectChild::CreateProxyObject() { + NS_ASSERTION(mInstance, "Must have an instance!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + NPClass* proxyClass = const_cast(GetClass()); + NPObject* npobject = PluginModuleChild::sBrowserFuncs.createobject( + mInstance->GetNPP(), proxyClass); + NS_ASSERTION(npobject, "Failed to create object?!"); + NS_ASSERTION(npobject->_class == GetClass(), "Wrong kind of object!"); + NS_ASSERTION(npobject->referenceCount == 1, "Some kind of live object!"); + + ChildNPObject* object = static_cast(npobject); + NS_ASSERTION(!object->invalidated, "Bad object!"); + NS_ASSERTION(!object->parent, "Bad object!"); + + // We don't want to have the actor own this object but rather let the object + // own this actor. Set the reference count to 0 here so that when the object + // dies we will send the destructor message to the child. + object->referenceCount = 0; + NS_LOG_RELEASE(object, 0, "NPObject"); + + object->parent = const_cast(this); + return object; +} + +bool PluginScriptableObjectChild::ResurrectProxyObject() { + NS_ASSERTION(mInstance, "Must have an instance already!"); + NS_ASSERTION(!mObject, "Should not have an object already!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + if (!InitializeProxy()) { + NS_ERROR("Initialize failed!"); + return false; + } + + SendProtect(); + return true; +} + +NPObject* PluginScriptableObjectChild::GetObject(bool aCanResurrect) { + if (!mObject && aCanResurrect && !ResurrectProxyObject()) { + NS_ERROR("Null object!"); + return nullptr; + } + return mObject; +} + +void PluginScriptableObjectChild::Protect() { + NS_ASSERTION(mObject, "No object!"); + NS_ASSERTION(mProtectCount >= 0, "Negative retain count?!"); + + if (mType == LocalObject) { + ++mProtectCount; + } +} + +void PluginScriptableObjectChild::Unprotect() { + NS_ASSERTION(mObject, "Bad state!"); + NS_ASSERTION(mProtectCount >= 0, "Negative retain count?!"); + + if (mType == LocalObject) { + if (--mProtectCount == 0) { + PluginScriptableObjectChild::Send__delete__(this); + } + } +} + +void PluginScriptableObjectChild::DropNPObject() { + NS_ASSERTION(mObject, "Invalidated object!"); + NS_ASSERTION(mObject->_class == GetClass(), "Wrong type of object!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + // We think we're about to be deleted, but we could be racing with the other + // process. + UnregisterActor(mObject); + mObject = nullptr; + + SendUnprotect(); +} + +void PluginScriptableObjectChild::NPObjectDestroyed() { + NS_ASSERTION(LocalObject == mType, + "ScriptableDeallocate should have handled this for proxies"); + mInvalidated = true; + mObject = nullptr; +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerInvalidate() { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + return IPC_OK(); + } + + mInvalidated = true; + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (mObject->_class && mObject->_class->invalidate) { + mObject->_class->invalidate(mObject); + } + + Unprotect(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerHasMethod( + const PluginIdentifier& aId, bool* aHasMethod) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerHasMethod with an invalidated object!"); + *aHasMethod = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasMethod)) { + *aHasMethod = false; + return IPC_OK(); + } + + StackIdentifier id(aId); + *aHasMethod = mObject->_class->hasMethod(mObject, id.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerInvoke( + const PluginIdentifier& aId, nsTArray&& aArgs, Variant* aResult, + bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerInvoke with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->invoke)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + AutoTArray convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, mozilla::fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + for (uint32_t index = 0; index < argCount; index++) { + ConvertToVariant(aArgs[index], convertedArgs[index]); + } + + NPVariant result; + VOID_TO_NPVARIANT(result); + StackIdentifier id(aId); + bool success = + mObject->_class->invoke(mObject, id.ToNPIdentifier(), + convertedArgs.Elements(), argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + PluginModuleChild::sBrowserFuncs.releasevariantvalue(&convertedArgs[index]); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + Variant convertedResult; + success = + ConvertToRemoteVariant(result, convertedResult, GetInstance(), false); + + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aSuccess = true; + *aResult = convertedResult; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerInvokeDefault( + nsTArray&& aArgs, Variant* aResult, bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerInvokeDefault with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->invokeDefault)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + AutoTArray convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, mozilla::fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + for (uint32_t index = 0; index < argCount; index++) { + ConvertToVariant(aArgs[index], convertedArgs[index]); + } + + NPVariant result; + VOID_TO_NPVARIANT(result); + bool success = mObject->_class->invokeDefault( + mObject, convertedArgs.Elements(), argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + PluginModuleChild::sBrowserFuncs.releasevariantvalue(&convertedArgs[index]); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + Variant convertedResult; + success = + ConvertToRemoteVariant(result, convertedResult, GetInstance(), false); + + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aResult = convertedResult; + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerHasProperty( + const PluginIdentifier& aId, bool* aHasProperty) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerHasProperty with an invalidated object!"); + *aHasProperty = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasProperty)) { + *aHasProperty = false; + return IPC_OK(); + } + + StackIdentifier id(aId); + *aHasProperty = mObject->_class->hasProperty(mObject, id.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerGetChildProperty( + const PluginIdentifier& aId, bool* aHasProperty, bool* aHasMethod, + Variant* aResult, bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + *aHasProperty = *aHasMethod = *aSuccess = false; + *aResult = void_t(); + + if (mInvalidated) { + NS_WARNING("Calling AnswerGetProperty with an invalidated object!"); + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasProperty && + mObject->_class->hasMethod && mObject->_class->getProperty)) { + return IPC_OK(); + } + + StackIdentifier stackID(aId); + NPIdentifier id = stackID.ToNPIdentifier(); + + *aHasProperty = mObject->_class->hasProperty(mObject, id); + *aHasMethod = mObject->_class->hasMethod(mObject, id); + + if (*aHasProperty) { + NPVariant result; + VOID_TO_NPVARIANT(result); + + if (!mObject->_class->getProperty(mObject, id, &result)) { + return IPC_OK(); + } + + Variant converted; + if ((*aSuccess = + ConvertToRemoteVariant(result, converted, GetInstance(), false))) { + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + *aResult = converted; + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerSetProperty( + const PluginIdentifier& aId, const Variant& aValue, bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerSetProperty with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasProperty && + mObject->_class->setProperty)) { + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + NPIdentifier id = stackID.ToNPIdentifier(); + + if (!mObject->_class->hasProperty(mObject, id)) { + *aSuccess = false; + return IPC_OK(); + } + + NPVariant converted; + ConvertToVariant(aValue, converted); + + if ((*aSuccess = mObject->_class->setProperty(mObject, id, &converted))) { + PluginModuleChild::sBrowserFuncs.releasevariantvalue(&converted); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerRemoveProperty( + const PluginIdentifier& aId, bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerRemoveProperty with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->hasProperty && + mObject->_class->removeProperty)) { + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + NPIdentifier id = stackID.ToNPIdentifier(); + *aSuccess = mObject->_class->hasProperty(mObject, id) + ? mObject->_class->removeProperty(mObject, id) + : true; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerEnumerate( + nsTArray* aProperties, bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerEnumerate with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->enumerate)) { + *aSuccess = false; + return IPC_OK(); + } + + NPIdentifier* ids; + uint32_t idCount; + if (!mObject->_class->enumerate(mObject, &ids, &idCount)) { + *aSuccess = false; + return IPC_OK(); + } + + aProperties->SetCapacity(idCount); + + for (uint32_t index = 0; index < idCount; index++) { + aProperties->AppendElement(FromNPIdentifier(ids[index])); + } + + PluginModuleChild::sBrowserFuncs.memfree(ids); + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::AnswerConstruct( + nsTArray&& aArgs, Variant* aResult, bool* aSuccess) { + AssertPluginThread(); + PluginInstanceChild::AutoStackHelper guard(mInstance); + + if (mInvalidated) { + NS_WARNING("Calling AnswerConstruct with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + if (!(mObject->_class && mObject->_class->construct)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + AutoTArray convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, mozilla::fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + for (uint32_t index = 0; index < argCount; index++) { + ConvertToVariant(aArgs[index], convertedArgs[index]); + } + + NPVariant result; + VOID_TO_NPVARIANT(result); + bool success = mObject->_class->construct(mObject, convertedArgs.Elements(), + argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + PluginModuleChild::sBrowserFuncs.releasevariantvalue(&convertedArgs[index]); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + Variant convertedResult; + success = + ConvertToRemoteVariant(result, convertedResult, GetInstance(), false); + + DeferNPVariantLastRelease(&PluginModuleChild::sBrowserFuncs, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aResult = convertedResult; + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::RecvProtect() { + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Protect(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectChild::RecvUnprotect() { + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Unprotect(); + return IPC_OK(); +} + +bool PluginScriptableObjectChild::Evaluate(NPString* aScript, + NPVariant* aResult) { + PluginInstanceChild::AutoStackHelper guard(mInstance); + + nsDependentCString script(""); + if (aScript->UTF8Characters && aScript->UTF8Length) { + script.Rebind(aScript->UTF8Characters, aScript->UTF8Length); + } + + bool success; + Variant result; + CallNPN_Evaluate(script, &result, &success); + + if (!success) { + return false; + } + + ConvertToVariant(result, *aResult); + return true; +} + +nsTHashtable* + PluginScriptableObjectChild::sObjectMap; + +bool PluginScriptableObjectChild::RegisterActor(NPObject* aObject) { + AssertPluginThread(); + MOZ_ASSERT(aObject, "Null pointer!"); + + NPObjectData* d = sObjectMap->GetEntry(aObject); + if (!d) { + NS_ERROR("NPObject not in object table"); + return false; + } + + d->actor = this; + return true; +} + +void PluginScriptableObjectChild::UnregisterActor(NPObject* aObject) { + AssertPluginThread(); + MOZ_ASSERT(aObject, "Null pointer!"); + + NPObjectData* d = sObjectMap->GetEntry(aObject); + MOZ_ASSERT(d, "NPObject not in object table"); + if (d) { + d->actor = nullptr; + } +} + +/* static */ +PluginScriptableObjectChild* PluginScriptableObjectChild::GetActorForNPObject( + NPObject* aObject) { + AssertPluginThread(); + MOZ_ASSERT(aObject, "Null pointer!"); + + NPObjectData* d = sObjectMap->GetEntry(aObject); + if (!d) { + NS_ERROR("Plugin using object not created with NPN_CreateObject?"); + return nullptr; + } + + return d->actor; +} + +/* static */ +void PluginScriptableObjectChild::RegisterObject( + NPObject* aObject, PluginInstanceChild* aInstance) { + AssertPluginThread(); + + if (!sObjectMap) { + sObjectMap = new nsTHashtable(); + } + + NPObjectData* d = sObjectMap->PutEntry(aObject); + MOZ_ASSERT(!d->instance, "New NPObject already mapped?"); + d->instance = aInstance; +} + +/* static */ +void PluginScriptableObjectChild::UnregisterObject(NPObject* aObject) { + AssertPluginThread(); + + sObjectMap->RemoveEntry(aObject); + + if (!sObjectMap->Count()) { + delete sObjectMap; + sObjectMap = nullptr; + } +} + +/* static */ +PluginInstanceChild* PluginScriptableObjectChild::GetInstanceForNPObject( + NPObject* aObject) { + AssertPluginThread(); + if (!sObjectMap) { + // All PluginInstanceChilds have been destroyed + return nullptr; + } + + NPObjectData* d = sObjectMap->GetEntry(aObject); + if (!d) { + return nullptr; + } + return d->instance; +} + +/* static */ +void PluginScriptableObjectChild::NotifyOfInstanceShutdown( + PluginInstanceChild* aInstance) { + AssertPluginThread(); + if (!sObjectMap) { + return; + } + + for (auto iter = sObjectMap->Iter(); !iter.Done(); iter.Next()) { + NPObjectData* d = iter.Get(); + if (d->instance == aInstance) { + NPObject* o = d->GetKey(); + aInstance->mDeletingHash->PutEntry(o); + } + } +} diff --git a/dom/plugins/ipc/PluginScriptableObjectChild.h b/dom/plugins/ipc/PluginScriptableObjectChild.h new file mode 100644 index 0000000000..46ce160c9a --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectChild.h @@ -0,0 +1,276 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginScriptableObjectChild_h +#define dom_plugins_PluginScriptableObjectChild_h 1 + +#include "mozilla/plugins/PPluginScriptableObjectChild.h" +#include "mozilla/plugins/PluginMessageUtils.h" +#include "mozilla/plugins/PluginTypes.h" + +#include "npruntime.h" +#include "nsDataHashtable.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceChild; +class PluginScriptableObjectChild; + +struct ChildNPObject : NPObject { + ChildNPObject() : NPObject(), parent(nullptr), invalidated(false) { + MOZ_COUNT_CTOR(ChildNPObject); + } + + MOZ_COUNTED_DTOR(ChildNPObject) + + // |parent| is always valid as long as the actor is alive. Once the actor is + // destroyed this will be set to null. + PluginScriptableObjectChild* parent; + bool invalidated; +}; + +class PluginScriptableObjectChild : public PPluginScriptableObjectChild { + friend class PluginInstanceChild; + + public: + explicit PluginScriptableObjectChild(ScriptableObjectType aType); + virtual ~PluginScriptableObjectChild(); + + bool InitializeProxy(); + + void InitializeLocal(NPObject* aObject); + + mozilla::ipc::IPCResult AnswerInvalidate(); + + mozilla::ipc::IPCResult AnswerHasMethod(const PluginIdentifier& aId, + bool* aHasMethod); + + mozilla::ipc::IPCResult AnswerInvoke(const PluginIdentifier& aId, + nsTArray&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerInvokeDefault(nsTArray&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerHasProperty(const PluginIdentifier& aId, + bool* aHasProperty); + + mozilla::ipc::IPCResult AnswerGetChildProperty(const PluginIdentifier& aId, + bool* aHasProperty, + bool* aHasMethod, + Variant* aResult, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerSetProperty(const PluginIdentifier& aId, + const Variant& aValue, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerRemoveProperty(const PluginIdentifier& aId, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerEnumerate( + nsTArray* aProperties, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerConstruct(nsTArray&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult RecvProtect(); + + mozilla::ipc::IPCResult RecvUnprotect(); + + NPObject* GetObject(bool aCanResurrect); + + static const NPClass* GetClass() { return &sNPClass; } + + PluginInstanceChild* GetInstance() const { return mInstance; } + + // Protect only affects LocalObject actors. It is called by the + // ProtectedVariant/Actor helper classes before the actor is used as an + // argument to an IPC call and when the parent process resurrects a + // proxy object to the NPObject associated with this actor. + void Protect(); + + // Unprotect only affects LocalObject actors. It is called by the + // ProtectedVariant/Actor helper classes after the actor is used as an + // argument to an IPC call and when the parent process is no longer using + // this actor. + void Unprotect(); + + // DropNPObject is only used for Proxy actors and is called when the child + // process is no longer using the NPObject associated with this actor. The + // parent process may subsequently use this actor again in which case a new + // NPObject will be created and associated with this actor (see + // ResurrectProxyObject). + void DropNPObject(); + + /** + * After NPP_Destroy, all NPObjects associated with an instance are + * destroyed. We are informed of this destruction. This should only be called + * on Local actors. + */ + void NPObjectDestroyed(); + + bool Evaluate(NPString* aScript, NPVariant* aResult); + + ScriptableObjectType Type() const { return mType; } + + private: + struct StoredIdentifier { + nsCString mIdentifier; + nsAutoRefCnt mRefCnt; + bool mPermanent; + + nsrefcnt AddRef() { + ++mRefCnt; + return mRefCnt; + } + + nsrefcnt Release() { + --mRefCnt; + if (mRefCnt == 0) { + delete this; + return 0; + } + return mRefCnt; + } + + explicit StoredIdentifier(const nsCString& aIdentifier) + : mIdentifier(aIdentifier), mRefCnt(), mPermanent(false) { + MOZ_COUNT_CTOR(StoredIdentifier); + } + + MOZ_COUNTED_DTOR(StoredIdentifier) + }; + + public: + class MOZ_STACK_CLASS StackIdentifier { + public: + explicit StackIdentifier(const PluginIdentifier& aIdentifier); + explicit StackIdentifier(NPIdentifier aIdentifier); + ~StackIdentifier(); + + void MakePermanent() { + if (mStored) { + mStored->mPermanent = true; + } + } + NPIdentifier ToNPIdentifier() const; + + bool IsString() const { + return mIdentifier.type() == PluginIdentifier::TnsCString; + } + const nsCString& GetString() const { return mIdentifier.get_nsCString(); } + + int32_t GetInt() const { return mIdentifier.get_int32_t(); } + + PluginIdentifier GetIdentifier() const { return mIdentifier; } + + private: + DISALLOW_COPY_AND_ASSIGN(StackIdentifier); + + PluginIdentifier mIdentifier; + RefPtr mStored; + }; + + static void ClearIdentifiers(); + + bool RegisterActor(NPObject* aObject); + void UnregisterActor(NPObject* aObject); + + static PluginScriptableObjectChild* GetActorForNPObject(NPObject* aObject); + + static void RegisterObject(NPObject* aObject, PluginInstanceChild* aInstance); + static void UnregisterObject(NPObject* aObject); + + static PluginInstanceChild* GetInstanceForNPObject(NPObject* aObject); + + /** + * Fill PluginInstanceChild.mDeletingHash with all the remaining NPObjects + * associated with that instance. + */ + static void NotifyOfInstanceShutdown(PluginInstanceChild* aInstance); + + private: + static NPObject* ScriptableAllocate(NPP aInstance, NPClass* aClass); + + static void ScriptableInvalidate(NPObject* aObject); + + static void ScriptableDeallocate(NPObject* aObject); + + static bool ScriptableHasMethod(NPObject* aObject, NPIdentifier aName); + + static bool ScriptableInvoke(NPObject* aObject, NPIdentifier aName, + const NPVariant* aArgs, uint32_t aArgCount, + NPVariant* aResult); + + static bool ScriptableInvokeDefault(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + + static bool ScriptableHasProperty(NPObject* aObject, NPIdentifier aName); + + static bool ScriptableGetProperty(NPObject* aObject, NPIdentifier aName, + NPVariant* aResult); + + static bool ScriptableSetProperty(NPObject* aObject, NPIdentifier aName, + const NPVariant* aValue); + + static bool ScriptableRemoveProperty(NPObject* aObject, NPIdentifier aName); + + static bool ScriptableEnumerate(NPObject* aObject, + NPIdentifier** aIdentifiers, + uint32_t* aCount); + + static bool ScriptableConstruct(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + + NPObject* CreateProxyObject(); + + // ResurrectProxyObject is only used with Proxy actors. It is called when the + // parent process uses an actor whose NPObject was deleted by the child + // process. + bool ResurrectProxyObject(); + + private: + PluginInstanceChild* mInstance; + NPObject* mObject; + bool mInvalidated; + int mProtectCount; + + ScriptableObjectType mType; + + static const NPClass sNPClass; + + static StoredIdentifier* HashIdentifier(const nsCString& aIdentifier); + static void UnhashIdentifier(StoredIdentifier* aIdentifier); + + typedef nsDataHashtable> + IdentifierTable; + static IdentifierTable sIdentifiers; + + struct NPObjectData : public nsPtrHashKey { + explicit NPObjectData(const NPObject* key) + : nsPtrHashKey(key), instance(nullptr), actor(nullptr) {} + + // never nullptr + PluginInstanceChild* instance; + + // sometimes nullptr (no actor associated with an NPObject) + PluginScriptableObjectChild* actor; + }; + + /** + * mObjectMap contains all the currently active NPObjects (from + * NPN_CreateObject until the final release/dealloc, whether or not an actor + * is currently associated with the object. + */ + static nsTHashtable* sObjectMap; +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif /* dom_plugins_PluginScriptableObjectChild_h */ diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.cpp b/dom/plugins/ipc/PluginScriptableObjectParent.cpp new file mode 100644 index 0000000000..d12474c999 --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectParent.cpp @@ -0,0 +1,1289 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginScriptableObjectParent.h" + +#include "jsapi.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/plugins/PluginTypes.h" +#include "mozilla/Unused.h" +#include "nsNPAPIPlugin.h" +#include "PluginScriptableObjectUtils.h" + +using namespace mozilla; +using namespace mozilla::plugins; +using namespace mozilla::plugins::parent; + +/** + * NPIdentifiers in the chrome process are stored as jsids. The difficulty is in + * ensuring that string identifiers are rooted without pinning them all. We + * assume that all NPIdentifiers passed into nsJSNPRuntime will not be used + * outside the scope of the NPAPI call (i.e., they won't be stored in the + * heap). Rooting is done using the StackIdentifier class, which roots the + * identifier via RootedId. + * + * This system does not allow jsids to be moved, as would be needed for + * generational or compacting GC. When Firefox implements a moving GC for + * strings, we will need to ensure that no movement happens while NPAPI code is + * on the stack: although StackIdentifier roots all identifiers used, the GC has + * no way to know that a jsid cast to an NPIdentifier needs to be fixed up if it + * is moved. + */ + +class MOZ_STACK_CLASS StackIdentifier { + public: + explicit StackIdentifier(const PluginIdentifier& aIdentifier, + bool aAtomizeAndPin = false); + + bool Failed() const { return mFailed; } + NPIdentifier ToNPIdentifier() const { return mIdentifier; } + + private: + bool mFailed; + NPIdentifier mIdentifier; + AutoSafeJSContext mCx; + JS::RootedId mId; +}; + +StackIdentifier::StackIdentifier(const PluginIdentifier& aIdentifier, + bool aAtomizeAndPin) + : mFailed(false), mId(mCx) { + if (aIdentifier.type() == PluginIdentifier::TnsCString) { + // We don't call _getstringidentifier because we may not want to intern the + // string. + NS_ConvertUTF8toUTF16 utf16name(aIdentifier.get_nsCString()); + JS::RootedString str( + mCx, JS_NewUCStringCopyN(mCx, utf16name.get(), utf16name.Length())); + if (!str) { + NS_ERROR("Id can't be allocated"); + mFailed = true; + return; + } + if (aAtomizeAndPin) { + str = JS_AtomizeAndPinJSString(mCx, str); + if (!str) { + NS_ERROR("Id can't be allocated"); + mFailed = true; + return; + } + } + if (!JS_StringToId(mCx, str, &mId)) { + NS_ERROR("Id can't be allocated"); + mFailed = true; + return; + } + mIdentifier = JSIdToNPIdentifier(mId); + return; + } + + mIdentifier = + mozilla::plugins::parent::_getintidentifier(aIdentifier.get_int32_t()); +} + +static bool FromNPIdentifier(NPIdentifier aIdentifier, + PluginIdentifier* aResult) { + if (mozilla::plugins::parent::_identifierisstring(aIdentifier)) { + nsCString string; + NPUTF8* chars = mozilla::plugins::parent::_utf8fromidentifier(aIdentifier); + if (!chars) { + return false; + } + string.Adopt(chars); + *aResult = PluginIdentifier(string); + return true; + } else { + int32_t intval = mozilla::plugins::parent::_intfromidentifier(aIdentifier); + *aResult = PluginIdentifier(intval); + return true; + } +} + +namespace { + +inline void ReleaseVariant(NPVariant& aVariant, + PluginInstanceParent* aInstance) { + const NPNetscapeFuncs* npn = GetNetscapeFuncs(aInstance); + if (npn) { + npn->releasevariantvalue(&aVariant); + } +} + +} // namespace + +// static +NPObject* PluginScriptableObjectParent::ScriptableAllocate(NPP aInstance, + NPClass* aClass) { + if (aClass != GetClass()) { + NS_ERROR("Huh?! Wrong class!"); + return nullptr; + } + + return new ParentNPObject(); +} + +// static +void PluginScriptableObjectParent::ScriptableInvalidate(NPObject* aObject) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + // This can happen more than once, and is just fine. + return; + } + + object->invalidated = true; + + // |object->parent| may be null already if the instance has gone away. + if (object->parent && !object->parent->CallInvalidate()) { + NS_ERROR("Failed to send message!"); + } +} + +// static +void PluginScriptableObjectParent::ScriptableDeallocate(NPObject* aObject) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + ParentNPObject* object = reinterpret_cast(aObject); + + if (object->asyncWrapperCount > 0) { + // In this case we should just drop the refcount to the asyncWrapperCount + // instead of deallocating because there are still some async wrappers + // out there that are referencing this object. + object->referenceCount = object->asyncWrapperCount; + return; + } + + PluginScriptableObjectParent* actor = object->parent; + if (actor) { + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + actor->DropNPObject(); + } + + delete object; +} + +// static +bool PluginScriptableObjectParent::ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + if (!actor->CallHasMethod(identifier, &result)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return result; +} + +// static +bool PluginScriptableObjectParent::ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + if (!actor->CallInvoke(identifier, args, &remoteResult, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + return true; +} + +// static +bool PluginScriptableObjectParent::ScriptableInvokeDefault( + NPObject* aObject, const NPVariant* aArgs, uint32_t aArgCount, + NPVariant* aResult) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + if (!actor) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + if (!actor->CallInvokeDefault(args, &remoteResult, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + return true; +} + +// static +bool PluginScriptableObjectParent::ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool result; + if (!actor->CallHasProperty(identifier, &result)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return result; +} + +// static +bool PluginScriptableObjectParent::ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult) { + // See GetPropertyHelper below. + MOZ_ASSERT_UNREACHABLE("Shouldn't ever call this directly!"); + return false; +} + +// static +bool PluginScriptableObjectParent::ScriptableSetProperty( + NPObject* aObject, NPIdentifier aName, const NPVariant* aValue) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariant value(*aValue, actor->GetInstance()); + if (!value.IsOk()) { + NS_WARNING("Failed to convert variant!"); + return false; + } + + bool success; + if (!actor->CallSetProperty(identifier, value, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return success; +} + +// static +bool PluginScriptableObjectParent::ScriptableRemoveProperty( + NPObject* aObject, NPIdentifier aName) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + if (!actor) { + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + bool success; + if (!actor->CallRemoveProperty(identifier, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + return success; +} + +// static +bool PluginScriptableObjectParent::ScriptableEnumerate( + NPObject* aObject, NPIdentifier** aIdentifiers, uint32_t* aCount) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + if (!actor) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(aObject); + if (!npn) { + NS_ERROR("No netscape funcs!"); + return false; + } + + AutoTArray identifiers; + bool success; + if (!actor->CallEnumerate(&identifiers, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + *aCount = identifiers.Length(); + if (!*aCount) { + *aIdentifiers = nullptr; + return true; + } + + *aIdentifiers = (NPIdentifier*)npn->memalloc(*aCount * sizeof(NPIdentifier)); + if (!*aIdentifiers) { + NS_ERROR("Out of memory!"); + return false; + } + + for (uint32_t index = 0; index < *aCount; index++) { + // We pin the ID to avoid a GC hazard here. This could probably be fixed + // if the interface with nsJSNPRuntime were smarter. + StackIdentifier stackID(identifiers[index], true /* aAtomizeAndPin */); + if (stackID.Failed()) { + return false; + } + (*aIdentifiers)[index] = stackID.ToNPIdentifier(); + } + return true; +} + +// static +bool PluginScriptableObjectParent::ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) { + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + ProtectedActor actor(object->parent); + if (!actor) { + return false; + } + + NS_ASSERTION(actor->Type() == Proxy, "Bad type!"); + + ProtectedVariantArray args(aArgs, aArgCount, actor->GetInstance()); + if (!args.IsOk()) { + NS_ERROR("Failed to convert arguments!"); + return false; + } + + Variant remoteResult; + bool success; + if (!actor->CallConstruct(args, &remoteResult, &success)) { + NS_WARNING("Failed to send message!"); + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(remoteResult, *aResult, actor->GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + return true; +} + +const NPClass PluginScriptableObjectParent::sNPClass = { + NP_CLASS_STRUCT_VERSION, + PluginScriptableObjectParent::ScriptableAllocate, + PluginScriptableObjectParent::ScriptableDeallocate, + PluginScriptableObjectParent::ScriptableInvalidate, + PluginScriptableObjectParent::ScriptableHasMethod, + PluginScriptableObjectParent::ScriptableInvoke, + PluginScriptableObjectParent::ScriptableInvokeDefault, + PluginScriptableObjectParent::ScriptableHasProperty, + PluginScriptableObjectParent::ScriptableGetProperty, + PluginScriptableObjectParent::ScriptableSetProperty, + PluginScriptableObjectParent::ScriptableRemoveProperty, + PluginScriptableObjectParent::ScriptableEnumerate, + PluginScriptableObjectParent::ScriptableConstruct}; + +PluginScriptableObjectParent::PluginScriptableObjectParent( + ScriptableObjectType aType) + : mInstance(nullptr), mObject(nullptr), mProtectCount(0), mType(aType) {} + +PluginScriptableObjectParent::~PluginScriptableObjectParent() { + if (mObject) { + if (mObject->_class == GetClass()) { + NS_ASSERTION(mType == Proxy, "Wrong type!"); + static_cast(mObject)->parent = nullptr; + } else { + NS_ASSERTION(mType == LocalObject, "Wrong type!"); + GetInstance()->GetNPNIface()->releaseobject(mObject); + } + } +} + +void PluginScriptableObjectParent::InitializeProxy() { + NS_ASSERTION(mType == Proxy, "Bad type!"); + NS_ASSERTION(!mObject, "Calling Initialize more than once!"); + + mInstance = static_cast(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + NPObject* object = CreateProxyObject(); + NS_ASSERTION(object, "Failed to create object!"); + + if (!mInstance->RegisterNPObjectForActor(object, this)) { + NS_ERROR("Out of memory?"); + } + + mObject = object; +} + +void PluginScriptableObjectParent::InitializeLocal(NPObject* aObject) { + NS_ASSERTION(mType == LocalObject, "Bad type!"); + NS_ASSERTION(!(mInstance && mObject), "Calling Initialize more than once!"); + + mInstance = static_cast(Manager()); + NS_ASSERTION(mInstance, "Null manager?!"); + + mInstance->GetNPNIface()->retainobject(aObject); + + NS_ASSERTION(!mProtectCount, "Should be zero!"); + mProtectCount++; + + if (!mInstance->RegisterNPObjectForActor(aObject, this)) { + NS_ERROR("Out of memory?"); + } + + mObject = aObject; +} + +NPObject* PluginScriptableObjectParent::CreateProxyObject() { + NS_ASSERTION(mInstance, "Must have an instance!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(mInstance); + + NPObject* npobject = + npn->createobject(mInstance->GetNPP(), const_cast(GetClass())); + NS_ASSERTION(npobject, "Failed to create object?!"); + NS_ASSERTION(npobject->_class == GetClass(), "Wrong kind of object!"); + NS_ASSERTION(npobject->referenceCount == 1, "Some kind of live object!"); + + ParentNPObject* object = static_cast(npobject); + NS_ASSERTION(!object->invalidated, "Bad object!"); + NS_ASSERTION(!object->parent, "Bad object!"); + + // We don't want to have the actor own this object but rather let the object + // own this actor. Set the reference count to 0 here so that when the object + // dies we will send the destructor message to the child. + object->referenceCount = 0; + NS_LOG_RELEASE(object, 0, "BrowserNPObject"); + + object->parent = const_cast(this); + return object; +} + +bool PluginScriptableObjectParent::ResurrectProxyObject() { + NS_ASSERTION(mInstance, "Must have an instance already!"); + NS_ASSERTION(!mObject, "Should not have an object already!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + InitializeProxy(); + NS_ASSERTION(mObject, "Initialize failed!"); + + if (!SendProtect()) { + NS_WARNING("Failed to send message!"); + return false; + } + + return true; +} + +NPObject* PluginScriptableObjectParent::GetObject(bool aCanResurrect) { + if (!mObject && aCanResurrect && !ResurrectProxyObject()) { + NS_ERROR("Null object!"); + return nullptr; + } + return mObject; +} + +void PluginScriptableObjectParent::Protect() { + NS_ASSERTION(mObject, "No object!"); + NS_ASSERTION(mProtectCount >= 0, "Negative protect count?!"); + + if (mType == LocalObject) { + ++mProtectCount; + } +} + +void PluginScriptableObjectParent::Unprotect() { + NS_ASSERTION(mObject, "No object!"); + NS_ASSERTION(mProtectCount >= 0, "Negative protect count?!"); + + if (mType == LocalObject) { + if (--mProtectCount == 0) { + Unused << PluginScriptableObjectParent::Send__delete__(this); + } + } +} + +void PluginScriptableObjectParent::DropNPObject() { + NS_ASSERTION(mObject, "Invalidated object!"); + NS_ASSERTION(mObject->_class == GetClass(), "Wrong type of object!"); + NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + + // We think we're about to be deleted, but we could be racing with the other + // process. + PluginInstanceParent* instance = GetInstance(); + NS_ASSERTION(instance, "Must have an instance!"); + + instance->UnregisterNPObject(mObject); + mObject = nullptr; + + Unused << SendUnprotect(); +} + +void PluginScriptableObjectParent::ActorDestroy(ActorDestroyReason aWhy) { + // Implement me! Bug 1005163 +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerHasMethod( + const PluginIdentifier& aId, bool* aHasMethod) { + if (!mObject) { + NS_WARNING("Calling AnswerHasMethod with an invalidated object!"); + *aHasMethod = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aHasMethod = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aHasMethod = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aHasMethod = false; + return IPC_OK(); + } + *aHasMethod = + npn->hasmethod(instance->GetNPP(), mObject, stackID.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerInvoke( + const PluginIdentifier& aId, nsTArray&& aArgs, Variant* aResult, + bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerInvoke with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + AutoTArray convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + for (uint32_t index = 0; index < argCount; index++) { + if (!ConvertToVariant(aArgs[index], convertedArgs[index], instance)) { + // Don't leak things we've already converted! + while (index-- > 0) { + ReleaseVariant(convertedArgs[index], instance); + } + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + } + + NPVariant result; + bool success = + npn->invoke(instance->GetNPP(), mObject, stackID.ToNPIdentifier(), + convertedArgs.Elements(), argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + ReleaseVariant(convertedArgs[index], instance); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, GetInstance()); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aResult = convertedResult; + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerInvokeDefault( + nsTArray&& aArgs, Variant* aResult, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerInvoke with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + AutoTArray convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + for (uint32_t index = 0; index < argCount; index++) { + if (!ConvertToVariant(aArgs[index], convertedArgs[index], instance)) { + // Don't leak things we've already converted! + while (index-- > 0) { + ReleaseVariant(convertedArgs[index], instance); + } + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + } + + NPVariant result; + bool success = npn->invokeDefault( + instance->GetNPP(), mObject, convertedArgs.Elements(), argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + ReleaseVariant(convertedArgs[index], instance); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, GetInstance()); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aResult = convertedResult; + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerHasProperty( + const PluginIdentifier& aId, bool* aHasProperty) { + if (!mObject) { + NS_WARNING("Calling AnswerHasProperty with an invalidated object!"); + *aHasProperty = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aHasProperty = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aHasProperty = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aHasProperty = false; + return IPC_OK(); + } + + *aHasProperty = + npn->hasproperty(instance->GetNPP(), mObject, stackID.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerGetParentProperty( + const PluginIdentifier& aId, Variant* aResult, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerGetProperty with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NPVariant result; + if (!npn->getproperty(instance->GetNPP(), mObject, stackID.ToNPIdentifier(), + &result)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + Variant converted; + if ((*aSuccess = ConvertToRemoteVariant(result, converted, instance))) { + DeferNPVariantLastRelease(npn, &result); + *aResult = converted; + } else { + *aResult = void_t(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerSetProperty( + const PluginIdentifier& aId, const Variant& aValue, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerSetProperty with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aSuccess = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aSuccess = false; + return IPC_OK(); + } + + NPVariant converted; + if (!ConvertToVariant(aValue, converted, instance)) { + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aSuccess = false; + return IPC_OK(); + } + + if ((*aSuccess = npn->setproperty(instance->GetNPP(), mObject, + stackID.ToNPIdentifier(), &converted))) { + ReleaseVariant(converted, instance); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerRemoveProperty( + const PluginIdentifier& aId, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerRemoveProperty with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aSuccess = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aSuccess = false; + return IPC_OK(); + } + + StackIdentifier stackID(aId); + if (stackID.Failed()) { + *aSuccess = false; + return IPC_OK(); + } + + *aSuccess = npn->removeproperty(instance->GetNPP(), mObject, + stackID.ToNPIdentifier()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerEnumerate( + nsTArray* aProperties, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerEnumerate with an invalidated object!"); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aSuccess = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_WARNING("No netscape funcs?!"); + *aSuccess = false; + return IPC_OK(); + } + + NPIdentifier* ids; + uint32_t idCount; + if (!npn->enumerate(instance->GetNPP(), mObject, &ids, &idCount)) { + *aSuccess = false; + return IPC_OK(); + } + + aProperties->SetCapacity(idCount); + + for (uint32_t index = 0; index < idCount; index++) { + PluginIdentifier id; + if (!FromNPIdentifier(ids[index], &id)) { + return IPC_FAIL_NO_REASON(this); + } + aProperties->AppendElement(id); + } + + npn->memfree(ids); + *aSuccess = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerConstruct( + nsTArray&& aArgs, Variant* aResult, bool* aSuccess) { + if (!mObject) { + NS_WARNING("Calling AnswerConstruct with an invalidated object!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + AutoTArray convertedArgs; + uint32_t argCount = aArgs.Length(); + + if (!convertedArgs.SetLength(argCount, fallible)) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + for (uint32_t index = 0; index < argCount; index++) { + if (!ConvertToVariant(aArgs[index], convertedArgs[index], instance)) { + // Don't leak things we've already converted! + while (index-- > 0) { + ReleaseVariant(convertedArgs[index], instance); + } + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + } + + NPVariant result; + bool success = npn->construct(instance->GetNPP(), mObject, + convertedArgs.Elements(), argCount, &result); + + for (uint32_t index = 0; index < argCount; index++) { + ReleaseVariant(convertedArgs[index], instance); + } + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, instance); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aSuccess = true; + *aResult = convertedResult; + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::RecvProtect() { + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Protect(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::RecvUnprotect() { + NS_ASSERTION(mObject->_class != GetClass(), "Bad object type!"); + NS_ASSERTION(mType == LocalObject, "Bad type!"); + + Unprotect(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginScriptableObjectParent::AnswerNPN_Evaluate( + const nsCString& aScript, Variant* aResult, bool* aSuccess) { + PluginInstanceParent* instance = GetInstance(); + if (!instance) { + NS_ERROR("No instance?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); + if (!npn) { + NS_ERROR("No netscape funcs?!"); + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + NPString script = {aScript.get(), aScript.Length()}; + + NPVariant result; + bool success = npn->evaluate(instance->GetNPP(), mObject, &script, &result); + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + Variant convertedResult; + success = ConvertToRemoteVariant(result, convertedResult, instance); + + DeferNPVariantLastRelease(npn, &result); + + if (!success) { + *aResult = void_t(); + *aSuccess = false; + return IPC_OK(); + } + + *aSuccess = true; + *aResult = convertedResult; + return IPC_OK(); +} + +bool PluginScriptableObjectParent::GetPropertyHelper(NPIdentifier aName, + bool* aHasProperty, + bool* aHasMethod, + NPVariant* aResult) { + NS_ASSERTION(Type() == Proxy, "Bad type!"); + + ParentNPObject* object = static_cast(mObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return false; + } + + PluginIdentifier identifier; + if (!FromNPIdentifier(aName, &identifier)) { + return false; + } + + bool hasProperty, hasMethod, success; + Variant result; + if (!CallGetChildProperty(identifier, &hasProperty, &hasMethod, &result, + &success)) { + return false; + } + + if (!success) { + return false; + } + + if (!ConvertToVariant(result, *aResult, GetInstance())) { + NS_WARNING("Failed to convert result!"); + return false; + } + + *aHasProperty = hasProperty; + *aHasMethod = hasMethod; + return true; +} diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.h b/dom/plugins/ipc/PluginScriptableObjectParent.h new file mode 100644 index 0000000000..cf8bc5f04f --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectParent.h @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginScriptableObjectParent_h +#define dom_plugins_PluginScriptableObjectParent_h 1 + +#include "mozilla/plugins/PPluginScriptableObjectParent.h" +#include "mozilla/plugins/PluginMessageUtils.h" + +#include "npfunctions.h" +#include "npruntime.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceParent; +class PluginScriptableObjectParent; + +struct ParentNPObject : NPObject { + ParentNPObject() + : NPObject(), parent(nullptr), invalidated(false), asyncWrapperCount(0) {} + + // |parent| is always valid as long as the actor is alive. Once the actor is + // destroyed this will be set to null. + PluginScriptableObjectParent* parent; + bool invalidated; + int32_t asyncWrapperCount; +}; + +class PluginScriptableObjectParent : public PPluginScriptableObjectParent { + friend class PluginInstanceParent; + + public: + explicit PluginScriptableObjectParent(ScriptableObjectType aType); + virtual ~PluginScriptableObjectParent(); + + void InitializeProxy(); + + void InitializeLocal(NPObject* aObject); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult AnswerHasMethod(const PluginIdentifier& aId, + bool* aHasMethod); + + mozilla::ipc::IPCResult AnswerInvoke(const PluginIdentifier& aId, + nsTArray&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerInvokeDefault(nsTArray&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerHasProperty(const PluginIdentifier& aId, + bool* aHasProperty); + + mozilla::ipc::IPCResult AnswerGetParentProperty(const PluginIdentifier& aId, + Variant* aResult, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerSetProperty(const PluginIdentifier& aId, + const Variant& aValue, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerRemoveProperty(const PluginIdentifier& aId, + bool* aSuccess); + + mozilla::ipc::IPCResult AnswerEnumerate( + nsTArray* aProperties, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerConstruct(nsTArray&& aArgs, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult AnswerNPN_Evaluate(const nsCString& aScript, + Variant* aResult, bool* aSuccess); + + mozilla::ipc::IPCResult RecvProtect(); + + mozilla::ipc::IPCResult RecvUnprotect(); + + static const NPClass* GetClass() { return &sNPClass; } + + PluginInstanceParent* GetInstance() const { return mInstance; } + + NPObject* GetObject(bool aCanResurrect); + + // Protect only affects LocalObject actors. It is called by the + // ProtectedVariant/Actor helper classes before the actor is used as an + // argument to an IPC call and when the child process resurrects a + // proxy object to the NPObject associated with this actor. + void Protect(); + + // Unprotect only affects LocalObject actors. It is called by the + // ProtectedVariant/Actor helper classes after the actor is used as an + // argument to an IPC call and when the child process is no longer using this + // actor. + void Unprotect(); + + // DropNPObject is only used for Proxy actors and is called when the parent + // process is no longer using the NPObject associated with this actor. The + // child process may subsequently use this actor again in which case a new + // NPObject will be created and associated with this actor (see + // ResurrectProxyObject). + void DropNPObject(); + + ScriptableObjectType Type() const { return mType; } + + bool GetPropertyHelper(NPIdentifier aName, bool* aHasProperty, + bool* aHasMethod, NPVariant* aResult); + + private: + static NPObject* ScriptableAllocate(NPP aInstance, NPClass* aClass); + + static void ScriptableInvalidate(NPObject* aObject); + + static void ScriptableDeallocate(NPObject* aObject); + + static bool ScriptableHasMethod(NPObject* aObject, NPIdentifier aName); + + static bool ScriptableInvoke(NPObject* aObject, NPIdentifier aName, + const NPVariant* aArgs, uint32_t aArgCount, + NPVariant* aResult); + + static bool ScriptableInvokeDefault(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + + static bool ScriptableHasProperty(NPObject* aObject, NPIdentifier aName); + + static bool ScriptableGetProperty(NPObject* aObject, NPIdentifier aName, + NPVariant* aResult); + + static bool ScriptableSetProperty(NPObject* aObject, NPIdentifier aName, + const NPVariant* aValue); + + static bool ScriptableRemoveProperty(NPObject* aObject, NPIdentifier aName); + + static bool ScriptableEnumerate(NPObject* aObject, + NPIdentifier** aIdentifiers, + uint32_t* aCount); + + static bool ScriptableConstruct(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + + NPObject* CreateProxyObject(); + + // ResurrectProxyObject is only used with Proxy actors. It is called when the + // child process uses an actor whose NPObject was deleted by the parent + // process. + bool ResurrectProxyObject(); + + private: + PluginInstanceParent* mInstance; + + // This may be a ParentNPObject or some other kind depending on who created + // it. Have to check its class to find out. + NPObject* mObject; + int mProtectCount; + + ScriptableObjectType mType; + + static const NPClass sNPClass; +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#endif /* dom_plugins_PluginScriptableObjectParent_h */ diff --git a/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h b/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h new file mode 100644 index 0000000000..b211e1687f --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h @@ -0,0 +1,154 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginScriptableObjectUtils.h" + +namespace { + +template +class VariantTraits; + +template <> +class VariantTraits { + public: + typedef mozilla::plugins::PluginScriptableObjectParent ScriptableObjectType; +}; + +template <> +class VariantTraits { + public: + typedef mozilla::plugins::PluginScriptableObjectChild ScriptableObjectType; +}; + +} /* anonymous namespace */ + +inline bool mozilla::plugins::ConvertToVariant( + const Variant& aRemoteVariant, NPVariant& aVariant, + PluginInstanceParent* aInstance) { + switch (aRemoteVariant.type()) { + case Variant::Tvoid_t: { + VOID_TO_NPVARIANT(aVariant); + break; + } + + case Variant::Tnull_t: { + NULL_TO_NPVARIANT(aVariant); + break; + } + + case Variant::Tbool: { + BOOLEAN_TO_NPVARIANT(aRemoteVariant.get_bool(), aVariant); + break; + } + + case Variant::Tint: { + INT32_TO_NPVARIANT(aRemoteVariant.get_int(), aVariant); + break; + } + + case Variant::Tdouble: { + DOUBLE_TO_NPVARIANT(aRemoteVariant.get_double(), aVariant); + break; + } + + case Variant::TnsCString: { + const nsCString& string = aRemoteVariant.get_nsCString(); + const size_t length = string.Length(); + NPUTF8* buffer = + static_cast(::malloc(sizeof(NPUTF8) * (length + 1))); + if (!buffer) { + NS_ERROR("Out of memory!"); + return false; + } + + std::copy(string.get(), string.get() + length, buffer); + buffer[length] = '\0'; + STRINGN_TO_NPVARIANT(buffer, length, aVariant); + break; + } + + case Variant::TPPluginScriptableObjectParent: { + NS_ASSERTION(aInstance, "Must have an instance!"); + NPObject* object = NPObjectFromVariant(aRemoteVariant); + if (!object) { + NS_ERROR("Er, this shouldn't fail!"); + return false; + } + + const NPNetscapeFuncs* npn = GetNetscapeFuncs(aInstance); + if (!npn) { + NS_ERROR("Null netscape funcs!"); + return false; + } + + npn->retainobject(object); + OBJECT_TO_NPVARIANT(object, aVariant); + break; + } + + case Variant::TPPluginScriptableObjectChild: { + NS_ASSERTION(!aInstance, "No instance should be given!"); + NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Plugin, + "Should be running on child only!"); + + NPObject* object = NPObjectFromVariant(aRemoteVariant); + NS_ASSERTION(object, "Null object?!"); + + PluginModuleChild::sBrowserFuncs.retainobject(object); + OBJECT_TO_NPVARIANT(object, aVariant); + break; + } + + default: + MOZ_ASSERT_UNREACHABLE("Shouldn't get here!"); + return false; + } + + return true; +} + +template +bool mozilla::plugins::ConvertToRemoteVariant(const NPVariant& aVariant, + Variant& aRemoteVariant, + InstanceType* aInstance, + bool aProtectActors) { + if (NPVARIANT_IS_VOID(aVariant)) { + aRemoteVariant = mozilla::void_t(); + } else if (NPVARIANT_IS_NULL(aVariant)) { + aRemoteVariant = mozilla::null_t(); + } else if (NPVARIANT_IS_BOOLEAN(aVariant)) { + aRemoteVariant = NPVARIANT_TO_BOOLEAN(aVariant); + } else if (NPVARIANT_IS_INT32(aVariant)) { + aRemoteVariant = NPVARIANT_TO_INT32(aVariant); + } else if (NPVARIANT_IS_DOUBLE(aVariant)) { + aRemoteVariant = NPVARIANT_TO_DOUBLE(aVariant); + } else if (NPVARIANT_IS_STRING(aVariant)) { + NPString str = NPVARIANT_TO_STRING(aVariant); + nsCString string(str.UTF8Characters, str.UTF8Length); + aRemoteVariant = string; + } else if (NPVARIANT_IS_OBJECT(aVariant)) { + NPObject* object = NPVARIANT_TO_OBJECT(aVariant); + + typename VariantTraits::ScriptableObjectType* actor = + aInstance->GetActorForNPObject(object); + + if (!actor) { + NS_ERROR("Null actor!"); + return false; + } + + if (aProtectActors) { + actor->Protect(); + } + + aRemoteVariant = actor; + } else { + MOZ_ASSERT_UNREACHABLE("Shouldn't get here!"); + return false; + } + + return true; +} diff --git a/dom/plugins/ipc/PluginScriptableObjectUtils.h b/dom/plugins/ipc/PluginScriptableObjectUtils.h new file mode 100644 index 0000000000..e620e017e9 --- /dev/null +++ b/dom/plugins/ipc/PluginScriptableObjectUtils.h @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginScriptableObjectUtils_h +#define dom_plugins_PluginScriptableObjectUtils_h + +#include "PluginModuleParent.h" +#include "PluginModuleChild.h" +#include "PluginInstanceParent.h" +#include "PluginInstanceChild.h" +#include "PluginScriptableObjectParent.h" +#include "PluginScriptableObjectChild.h" + +#include "npapi.h" +#include "npfunctions.h" +#include "npruntime.h" + +#include "nsDebug.h" + +namespace mozilla { +namespace plugins { + +inline PluginInstanceParent* GetInstance(NPObject* aObject) { + NS_ASSERTION(aObject->_class == PluginScriptableObjectParent::GetClass(), + "Bad class!"); + + ParentNPObject* object = reinterpret_cast(aObject); + if (object->invalidated) { + NS_WARNING("Calling method on an invalidated object!"); + return nullptr; + } + if (!object->parent) { + return nullptr; + } + return object->parent->GetInstance(); +} + +inline NPObject* NPObjectFromVariant(const Variant& aRemoteVariant) { + switch (aRemoteVariant.type()) { + case Variant::TPPluginScriptableObjectParent: { + PluginScriptableObjectParent* actor = + const_cast( + reinterpret_cast( + aRemoteVariant.get_PPluginScriptableObjectParent())); + return actor->GetObject(true); + } + + case Variant::TPPluginScriptableObjectChild: { + PluginScriptableObjectChild* actor = + const_cast( + reinterpret_cast( + aRemoteVariant.get_PPluginScriptableObjectChild())); + return actor->GetObject(true); + } + + default: + MOZ_ASSERT_UNREACHABLE("Shouldn't get here!"); + return nullptr; + } +} + +inline NPObject* NPObjectFromVariant(const NPVariant& aVariant) { + NS_ASSERTION(NPVARIANT_IS_OBJECT(aVariant), "Wrong variant type!"); + return NPVARIANT_TO_OBJECT(aVariant); +} + +inline const NPNetscapeFuncs* GetNetscapeFuncs( + PluginInstanceParent* aInstance) { + PluginModuleParent* module = aInstance->Module(); + if (!module) { + NS_WARNING("Null module?!"); + return nullptr; + } + return module->GetNetscapeFuncs(); +} + +inline const NPNetscapeFuncs* GetNetscapeFuncs(NPObject* aObject) { + NS_ASSERTION(aObject->_class == PluginScriptableObjectParent::GetClass(), + "Bad class!"); + + PluginInstanceParent* instance = GetInstance(aObject); + if (!instance) { + return nullptr; + } + + return GetNetscapeFuncs(instance); +} + +inline void ReleaseRemoteVariant(Variant& aVariant) { + switch (aVariant.type()) { + case Variant::TPPluginScriptableObjectParent: { + PluginScriptableObjectParent* actor = + const_cast( + reinterpret_cast( + aVariant.get_PPluginScriptableObjectParent())); + actor->Unprotect(); + break; + } + + case Variant::TPPluginScriptableObjectChild: { + NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Plugin, + "Should only be running in the child!"); + PluginScriptableObjectChild* actor = + const_cast( + reinterpret_cast( + aVariant.get_PPluginScriptableObjectChild())); + actor->Unprotect(); + break; + } + + default: + break; // Intentional fall-through for other variant types. + } + + aVariant = mozilla::void_t(); +} + +bool ConvertToVariant(const Variant& aRemoteVariant, NPVariant& aVariant, + PluginInstanceParent* aInstance = nullptr); + +template +bool ConvertToRemoteVariant(const NPVariant& aVariant, Variant& aRemoteVariant, + InstanceType* aInstance, + bool aProtectActors = false); + +class ProtectedVariant { + public: + ProtectedVariant(const NPVariant& aVariant, PluginInstanceParent* aInstance) { + mOk = ConvertToRemoteVariant(aVariant, mVariant, aInstance, true); + } + + ProtectedVariant(const NPVariant& aVariant, PluginInstanceChild* aInstance) { + mOk = ConvertToRemoteVariant(aVariant, mVariant, aInstance, true); + } + + ~ProtectedVariant() { ReleaseRemoteVariant(mVariant); } + + bool IsOk() { return mOk; } + + operator const Variant&() { return mVariant; } + + private: + Variant mVariant; + bool mOk; +}; + +class ProtectedVariantArray { + public: + ProtectedVariantArray(const NPVariant* aArgs, uint32_t aCount, + PluginInstanceParent* aInstance) + : mUsingShadowArray(false) { + for (uint32_t index = 0; index < aCount; index++) { + Variant* remoteVariant = mArray.AppendElement(); + if (!(remoteVariant && + ConvertToRemoteVariant(aArgs[index], *remoteVariant, aInstance, + true))) { + mOk = false; + return; + } + } + mOk = true; + } + + ProtectedVariantArray(const NPVariant* aArgs, uint32_t aCount, + PluginInstanceChild* aInstance) + : mUsingShadowArray(false) { + for (uint32_t index = 0; index < aCount; index++) { + Variant* remoteVariant = mArray.AppendElement(); + if (!(remoteVariant && + ConvertToRemoteVariant(aArgs[index], *remoteVariant, aInstance, + true))) { + mOk = false; + return; + } + } + mOk = true; + } + + ~ProtectedVariantArray() { + nsTArray& vars = EnsureAndGetShadowArray(); + uint32_t count = vars.Length(); + for (uint32_t index = 0; index < count; index++) { + ReleaseRemoteVariant(vars[index]); + } + } + + operator const nsTArray&() { return EnsureAndGetShadowArray(); } + + bool IsOk() { return mOk; } + + private: + nsTArray& EnsureAndGetShadowArray() { + if (!mUsingShadowArray) { + mShadowArray.SwapElements(mArray); + mUsingShadowArray = true; + } + return mShadowArray; + } + + // We convert the variants fallibly, but pass them to Call*() + // methods as an infallible array + nsTArray mArray; + nsTArray mShadowArray; + bool mOk; + bool mUsingShadowArray; +}; + +template +struct ProtectedActorTraits { + static bool Nullable(); +}; + +template > +class ProtectedActor { + public: + explicit ProtectedActor(ActorType* aActor) : mActor(aActor) { + if (!Traits::Nullable()) { + NS_ASSERTION(mActor, "This should never be null!"); + } + } + + ~ProtectedActor() { + if (Traits::Nullable() && !mActor) return; + mActor->Unprotect(); + } + + ActorType* operator->() { return mActor; } + + explicit operator bool() { return !!mActor; } + + private: + ActorType* mActor; +}; + +template <> +struct ProtectedActorTraits { + static bool Nullable() { return true; } +}; + +template <> +struct ProtectedActorTraits { + static bool Nullable() { return false; } +}; + +} /* namespace plugins */ +} /* namespace mozilla */ + +#include "PluginScriptableObjectUtils-inl.h" + +#endif /* dom_plugins_PluginScriptableObjectUtils_h */ diff --git a/dom/plugins/ipc/PluginSurfaceParent.cpp b/dom/plugins/ipc/PluginSurfaceParent.cpp new file mode 100644 index 0000000000..251572995e --- /dev/null +++ b/dom/plugins/ipc/PluginSurfaceParent.cpp @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/plugins/PluginSurfaceParent.h" +#include "mozilla/gfx/SharedDIBSurface.h" + +using mozilla::gfx::SharedDIBSurface; + +namespace mozilla { +namespace plugins { + +PluginSurfaceParent::PluginSurfaceParent( + const WindowsSharedMemoryHandle& handle, const gfx::IntSize& size, + bool transparent) { + SharedDIBSurface* dibsurf = new SharedDIBSurface(); + if (dibsurf->Attach(handle, size.width, size.height, transparent)) + mSurface = dibsurf; +} + +PluginSurfaceParent::~PluginSurfaceParent() {} + +void PluginSurfaceParent::ActorDestroy(ActorDestroyReason aWhy) { + // Implement me! Bug 1005167 +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginSurfaceParent.h b/dom/plugins/ipc/PluginSurfaceParent.h new file mode 100644 index 0000000000..201ecf9da7 --- /dev/null +++ b/dom/plugins/ipc/PluginSurfaceParent.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginSurfaceParent_h +#define dom_plugins_PluginSurfaceParent_h + +#include "mozilla/plugins/PPluginSurfaceParent.h" +#include "mozilla/plugins/PluginMessageUtils.h" + +#ifndef XP_WIN +# error "This header is for Windows only." +#endif + +class gfxASurface; + +namespace mozilla { +namespace plugins { + +class PluginSurfaceParent : public PPluginSurfaceParent { + public: + PluginSurfaceParent(const WindowsSharedMemoryHandle& handle, + const gfx::IntSize& size, const bool transparent); + ~PluginSurfaceParent(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + gfxASurface* Surface() { return mSurface; } + + private: + RefPtr mSurface; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugin_PluginSurfaceParent_h diff --git a/dom/plugins/ipc/PluginTypes.ipdlh b/dom/plugins/ipc/PluginTypes.ipdlh new file mode 100644 index 0000000000..0c674c3653 --- /dev/null +++ b/dom/plugins/ipc/PluginTypes.ipdlh @@ -0,0 +1,48 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include URIParams; + +namespace mozilla { +namespace plugins { + +struct PluginTag +{ + uint32_t id; + nsCString name; + nsCString description; + nsCString[] mimeTypes; + nsCString[] mimeDescriptions; + nsCString[] extensions; + bool isFlashPlugin; + bool supportsAsyncRender; // flash specific + nsCString filename; + nsCString version; + int64_t lastModifiedTime; + int32_t sandboxLevel; + uint16_t blocklistState; +}; + +struct FakePluginTag +{ + uint32_t id; + URIParams handlerURI; + nsCString name; + nsCString description; + nsCString[] mimeTypes; + nsCString[] mimeDescriptions; + nsCString[] extensions; + nsCString niceName; + nsString sandboxScript; +}; + +union PluginIdentifier +{ + nsCString; + int32_t; +}; + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginUtilsOSX.h b/dom/plugins/ipc/PluginUtilsOSX.h new file mode 100644 index 0000000000..bf03c90c78 --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsOSX.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginUtilsOSX_h +#define dom_plugins_PluginUtilsOSX_h 1 + +#include "npapi.h" +#include "mozilla/gfx/QuartzSupport.h" +#include "nsRect.h" + +namespace mozilla { +namespace plugins { +namespace PluginUtilsOSX { + +// Need to call back into the browser's message loop to process event. +typedef void (*RemoteProcessEvents)(void*); + +NPError ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, + RemoteProcessEvents remoteEvent); + +void InvokeNativeEventLoop(); + +// Need to call back and send a cocoa draw event to the plugin. +typedef void (*DrawPluginFunc)(CGContextRef, void*, nsIntRect aUpdateRect); + +void* GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, + double aContentsScaleFactor); +void ReleaseCGLayer(void* cgLayer); +void Repaint(void* cgLayer, nsIntRect aRect); + +bool SetProcessName(const char* aProcessName); + +/* + * Provides a wrapper around nsCARenderer to manage double buffering + * without having to unbind nsCARenderer on every surface swaps. + * + * The double buffer renderer begins with no initialize surfaces. + * The buffers can be initialized and cleared individually. + * Swapping still occurs regardless if the buffers are initialized. + */ +class nsDoubleBufferCARenderer { + public: + nsDoubleBufferCARenderer() : mCALayer(nullptr), mContentsScaleFactor(1.0) {} + // Returns width in "display pixels". A "display pixel" is the smallest + // fully addressable part of a display. But in HiDPI modes each "display + // pixel" corresponds to more than one device pixel. Multiply display pixels + // by mContentsScaleFactor to get device pixels. + size_t GetFrontSurfaceWidth(); + // Returns height in "display pixels". Multiply by + // mContentsScaleFactor to get device pixels. + size_t GetFrontSurfaceHeight(); + double GetFrontSurfaceContentsScaleFactor(); + // Returns width in "display pixels". Multiply by + // mContentsScaleFactor to get device pixels. + size_t GetBackSurfaceWidth(); + // Returns height in "display pixels". Multiply by + // mContentsScaleFactor to get device pixels. + size_t GetBackSurfaceHeight(); + double GetBackSurfaceContentsScaleFactor(); + IOSurfaceID GetFrontSurfaceID(); + + bool HasBackSurface(); + bool HasFrontSurface(); + bool HasCALayer(); + + void SetCALayer(void* aCALayer); + // aWidth and aHeight are in "display pixels". Multiply by + // aContentsScaleFactor to get device pixels. + bool InitFrontSurface(size_t aWidth, size_t aHeight, + double aContentsScaleFactor, + AllowOfflineRendererEnum aAllowOfflineRenderer); + void Render(); + void SwapSurfaces(); + void ClearFrontSurface(); + void ClearBackSurface(); + + double GetContentsScaleFactor() { return mContentsScaleFactor; } + + private: + void* mCALayer; + RefPtr mCARenderer; + RefPtr mFrontSurface; + RefPtr mBackSurface; + double mContentsScaleFactor; +}; + +} // namespace PluginUtilsOSX +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_PluginUtilsOSX_h diff --git a/dom/plugins/ipc/PluginUtilsOSX.mm b/dom/plugins/ipc/PluginUtilsOSX.mm new file mode 100644 index 0000000000..9aac0b3c3d --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsOSX.mm @@ -0,0 +1,429 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#import +#import +#include "PluginUtilsOSX.h" + +// Remove definitions for try/catch interfering with ObjCException macros. +#include "nsObjCExceptions.h" +#include "nsCocoaUtils.h" + +#include "nsDebug.h" + +#include "mozilla/Sprintf.h" + +@interface CALayer (ContentsScale) +- (double)contentsScale; +- (void)setContentsScale:(double)scale; +@end + +using namespace mozilla::plugins::PluginUtilsOSX; + +@interface CGBridgeLayer : CALayer { + DrawPluginFunc mDrawFunc; + void* mPluginInstance; + nsIntRect mUpdateRect; +} +- (void)setDrawFunc:(DrawPluginFunc)aFunc pluginInstance:(void*)aPluginInstance; +- (void)updateRect:(nsIntRect)aRect; + +@end + +// CGBitmapContextSetData() is an undocumented function present (with +// the same signature) since at least OS X 10.5. As the name suggests, +// it's used to replace the "data" in a bitmap context that was +// originally specified in a call to CGBitmapContextCreate() or +// CGBitmapContextCreateWithData(). +typedef void (*CGBitmapContextSetDataFunc)(CGContextRef c, size_t x, size_t y, size_t width, + size_t height, void* data, size_t bitsPerComponent, + size_t bitsPerPixel, size_t bytesPerRow); +CGBitmapContextSetDataFunc CGBitmapContextSetDataPtr = NULL; + +@implementation CGBridgeLayer +- (void)updateRect:(nsIntRect)aRect { + mUpdateRect.UnionRect(mUpdateRect, aRect); +} + +- (void)setDrawFunc:(DrawPluginFunc)aFunc pluginInstance:(void*)aPluginInstance { + mDrawFunc = aFunc; + mPluginInstance = aPluginInstance; +} + +- (void)drawInContext:(CGContextRef)aCGContext { + ::CGContextSaveGState(aCGContext); + ::CGContextTranslateCTM(aCGContext, 0, self.bounds.size.height); + ::CGContextScaleCTM(aCGContext, (CGFloat)1, (CGFloat)-1); + + mUpdateRect = nsIntRect::Truncate(0, 0, self.bounds.size.width, self.bounds.size.height); + + mDrawFunc(aCGContext, mPluginInstance, mUpdateRect); + + ::CGContextRestoreGState(aCGContext); + + mUpdateRect.SetEmpty(); +} + +@end + +void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, + double aContentsScaleFactor) { + CGBridgeLayer* bridgeLayer = [[CGBridgeLayer alloc] init]; + + // We need to make bridgeLayer behave properly when its superlayer changes + // size (in nsCARenderer::SetBounds()). + bridgeLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; + bridgeLayer.needsDisplayOnBoundsChange = YES; + NSNull* nullValue = [NSNull null]; + NSDictionary* actions = [NSDictionary + dictionaryWithObjectsAndKeys:nullValue, @"bounds", nullValue, @"contents", nullValue, + @"contentsRect", nullValue, @"position", nil]; + [bridgeLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]]; + + // For reasons that aren't clear (perhaps one or more OS bugs), we can only + // use full HiDPI resolution here if the tree is built with the 10.7 SDK or + // up. If we build with the 10.6 SDK, changing the contentsScale property + // of bridgeLayer (even to the same value) causes it to stop working (go + // blank). This doesn't happen with objects that are members of the CALayer + // class (as opposed to one of its subclasses). +#if defined(MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if ([bridgeLayer respondsToSelector:@selector(setContentsScale:)]) { + bridgeLayer.contentsScale = aContentsScaleFactor; + } +#endif + + [bridgeLayer setDrawFunc:aFunc pluginInstance:aPluginInstance]; + return bridgeLayer; +} + +void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void* cgLayer) { + CGBridgeLayer* bridgeLayer = (CGBridgeLayer*)cgLayer; + [bridgeLayer release]; +} + +void mozilla::plugins::PluginUtilsOSX::Repaint(void* caLayer, nsIntRect aRect) { + CGBridgeLayer* bridgeLayer = (CGBridgeLayer*)caLayer; + [CATransaction begin]; + [bridgeLayer updateRect:aRect]; + [bridgeLayer setNeedsDisplay]; + [bridgeLayer displayIfNeeded]; + [CATransaction commit]; +} + +@interface EventProcessor : NSObject { + RemoteProcessEvents aRemoteEvents; + void* aPluginModule; +} +- (void)setRemoteEvents:(RemoteProcessEvents)remoteEvents pluginModule:(void*)pluginModule; +- (void)onTick; +@end + +@implementation EventProcessor +- (void)onTick { + aRemoteEvents(aPluginModule); +} + +- (void)setRemoteEvents:(RemoteProcessEvents)remoteEvents pluginModule:(void*)pluginModule { + aRemoteEvents = remoteEvents; + aPluginModule = pluginModule; +} +@end + +#define EVENT_PROCESS_DELAY 0.05 // 50 ms + +NPError mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(void* aMenu, int aX, int aY, + void* pluginModule, + RemoteProcessEvents remoteEvent) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + // Set the native cursor to the OS default (an arrow) before displaying the + // context menu. Otherwise (if the plugin has changed the cursor) it may + // stay as the plugin has set it -- which means it may be invisible. We + // need to do this because we display the context menu without making the + // plugin process the foreground process. If we did, the cursor would + // change to an arrow cursor automatically -- as it does in Chrome. + [[NSCursor arrowCursor] set]; + + EventProcessor* eventProcessor = nullptr; + NSTimer* eventTimer = nullptr; + if (pluginModule) { + // Create a timer to process browser events while waiting + // on the menu. This prevents the browser from hanging + // during the lifetime of the menu. + eventProcessor = [[EventProcessor alloc] init]; + [eventProcessor setRemoteEvents:remoteEvent pluginModule:pluginModule]; + eventTimer = [NSTimer timerWithTimeInterval:EVENT_PROCESS_DELAY + target:eventProcessor + selector:@selector(onTick) + userInfo:nil + repeats:TRUE]; + // Use NSEventTrackingRunLoopMode otherwise the timer will + // not fire during the right click menu. + [[NSRunLoop currentRunLoop] addTimer:eventTimer forMode:NSEventTrackingRunLoopMode]; + } + + NSMenu* nsmenu = reinterpret_cast(aMenu); + NSPoint screen_point = ::NSMakePoint(aX, aY); + + [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil]; + + if (pluginModule) { + [eventTimer invalidate]; + [eventProcessor release]; + } + + return NPERR_NO_ERROR; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NPERR_GENERIC_ERROR); +} + +void mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + ::CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true); + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +#define UNDOCUMENTED_SESSION_CONSTANT ((int)-2) +namespace mozilla { +namespace plugins { +namespace PluginUtilsOSX { +static void* sApplicationASN = NULL; +static void* sApplicationInfoItem = NULL; +} // namespace PluginUtilsOSX +} // namespace plugins +} // namespace mozilla + +bool mozilla::plugins::PluginUtilsOSX::SetProcessName(const char* aProcessName) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + nsAutoreleasePool localPool; + + if (!aProcessName || strcmp(aProcessName, "") == 0) { + return false; + } + + NSString* currentName = + [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:(NSString*)kCFBundleNameKey]; + + char formattedName[1024]; + SprintfLiteral(formattedName, "%s %s", [currentName UTF8String], aProcessName); + + aProcessName = formattedName; + + // This function is based on Chrome/Webkit's and relies on potentially dangerous SPI. + typedef CFTypeRef (*LSGetASNType)(); + typedef OSStatus (*LSSetInformationItemType)(int, CFTypeRef, CFStringRef, CFStringRef, + CFDictionaryRef*); + + CFBundleRef launchServices = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices")); + if (!launchServices) { + NS_WARNING("Failed to set process name: Could not open LaunchServices bundle"); + return false; + } + + if (!sApplicationASN) { + sApplicationASN = + ::CFBundleGetFunctionPointerForName(launchServices, CFSTR("_LSGetCurrentApplicationASN")); + if (!sApplicationASN) { + NS_WARNING("Failed to set process name: Could not get function pointer " + "for LaunchServices"); + return false; + } + } + + LSGetASNType getASNFunc = reinterpret_cast(sApplicationASN); + + if (!sApplicationInfoItem) { + sApplicationInfoItem = ::CFBundleGetFunctionPointerForName( + launchServices, CFSTR("_LSSetApplicationInformationItem")); + } + + LSSetInformationItemType setInformationItemFunc = + reinterpret_cast(sApplicationInfoItem); + + void* displayNameKeyAddr = + ::CFBundleGetDataPointerForName(launchServices, CFSTR("_kLSDisplayNameKey")); + + CFStringRef displayNameKey = nil; + if (displayNameKeyAddr) { + displayNameKey = reinterpret_cast(*(CFStringRef*)displayNameKeyAddr); + } + + // Rename will fail without this + ProcessSerialNumber psn; + if (::GetCurrentProcess(&psn) != noErr) { + return false; + } + + CFTypeRef currentAsn = getASNFunc ? getASNFunc() : nullptr; + + if (!getASNFunc || !setInformationItemFunc || !displayNameKey || !currentAsn) { + NS_WARNING("Failed to set process name: Accessing launchServices failed"); + return false; + } + + CFStringRef processName = ::CFStringCreateWithCString(nil, aProcessName, kCFStringEncodingASCII); + if (!processName) { + NS_WARNING("Failed to set process name: Could not create CFStringRef"); + return false; + } + + OSErr err = + setInformationItemFunc(UNDOCUMENTED_SESSION_CONSTANT, currentAsn, displayNameKey, processName, + nil); // Optional out param + ::CFRelease(processName); + if (err != noErr) { + NS_WARNING("Failed to set process name: LSSetInformationItemType err"); + return false; + } + + return true; + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false); +} + +namespace mozilla { +namespace plugins { +namespace PluginUtilsOSX { + +size_t nsDoubleBufferCARenderer::GetFrontSurfaceWidth() { + if (!HasFrontSurface()) { + return 0; + } + + return mFrontSurface->GetWidth(); +} + +size_t nsDoubleBufferCARenderer::GetFrontSurfaceHeight() { + if (!HasFrontSurface()) { + return 0; + } + + return mFrontSurface->GetHeight(); +} + +double nsDoubleBufferCARenderer::GetFrontSurfaceContentsScaleFactor() { + if (!HasFrontSurface()) { + return 1.0; + } + + return mFrontSurface->GetContentsScaleFactor(); +} + +size_t nsDoubleBufferCARenderer::GetBackSurfaceWidth() { + if (!HasBackSurface()) { + return 0; + } + + return mBackSurface->GetWidth(); +} + +size_t nsDoubleBufferCARenderer::GetBackSurfaceHeight() { + if (!HasBackSurface()) { + return 0; + } + + return mBackSurface->GetHeight(); +} + +double nsDoubleBufferCARenderer::GetBackSurfaceContentsScaleFactor() { + if (!HasBackSurface()) { + return 1.0; + } + + return mBackSurface->GetContentsScaleFactor(); +} + +IOSurfaceID nsDoubleBufferCARenderer::GetFrontSurfaceID() { + if (!HasFrontSurface()) { + return 0; + } + + return mFrontSurface->GetIOSurfaceID(); +} + +bool nsDoubleBufferCARenderer::HasBackSurface() { return !!mBackSurface; } + +bool nsDoubleBufferCARenderer::HasFrontSurface() { return !!mFrontSurface; } + +bool nsDoubleBufferCARenderer::HasCALayer() { return !!mCALayer; } + +void nsDoubleBufferCARenderer::SetCALayer(void* aCALayer) { mCALayer = aCALayer; } + +bool nsDoubleBufferCARenderer::InitFrontSurface(size_t aWidth, size_t aHeight, + double aContentsScaleFactor, + AllowOfflineRendererEnum aAllowOfflineRenderer) { + if (!mCALayer) { + return false; + } + + mContentsScaleFactor = aContentsScaleFactor; + mFrontSurface = MacIOSurface::CreateIOSurface(aWidth, aHeight, mContentsScaleFactor); + if (!mFrontSurface) { + mCARenderer = nullptr; + return false; + } + + if (!mCARenderer) { + mCARenderer = new nsCARenderer(); + if (!mCARenderer) { + mFrontSurface = nullptr; + return false; + } + + mCARenderer->AttachIOSurface(mFrontSurface); + + nsresult result = + mCARenderer->SetupRenderer(mCALayer, mFrontSurface->GetWidth(), mFrontSurface->GetHeight(), + mContentsScaleFactor, aAllowOfflineRenderer); + + if (result != NS_OK) { + mCARenderer = nullptr; + mFrontSurface = nullptr; + return false; + } + } else { + mCARenderer->AttachIOSurface(mFrontSurface); + } + + return true; +} + +void nsDoubleBufferCARenderer::Render() { + if (!HasFrontSurface() || !mCARenderer) { + return; + } + + mCARenderer->Render(GetFrontSurfaceWidth(), GetFrontSurfaceHeight(), mContentsScaleFactor, + nullptr); +} + +void nsDoubleBufferCARenderer::SwapSurfaces() { + RefPtr prevFrontSurface = mFrontSurface; + mFrontSurface = mBackSurface; + mBackSurface = prevFrontSurface; + + if (mFrontSurface) { + mCARenderer->AttachIOSurface(mFrontSurface); + } +} + +void nsDoubleBufferCARenderer::ClearFrontSurface() { + mFrontSurface = nullptr; + if (!mFrontSurface && !mBackSurface) { + mCARenderer = nullptr; + } +} + +void nsDoubleBufferCARenderer::ClearBackSurface() { + mBackSurface = nullptr; + if (!mFrontSurface && !mBackSurface) { + mCARenderer = nullptr; + } +} + +} // namespace PluginUtilsOSX +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginUtilsWin.cpp b/dom/plugins/ipc/PluginUtilsWin.cpp new file mode 100644 index 0000000000..647d0d385e --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsWin.cpp @@ -0,0 +1,272 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* PluginUtilsWin.cpp - top-level Windows plugin management code */ + +#include +#include "PluginUtilsWin.h" +#include "PluginModuleParent.h" +#include "mozilla/StaticMutex.h" + +namespace mozilla { +namespace plugins { +namespace PluginUtilsWin { + +class AudioNotification; +typedef nsTHashtable> PluginModuleSet; +StaticMutex sMutex; + +class AudioDeviceMessageRunnable : public Runnable { + public: + explicit AudioDeviceMessageRunnable( + AudioNotification* aAudioNotification, + NPAudioDeviceChangeDetailsIPC aChangeDetails); + + explicit AudioDeviceMessageRunnable( + AudioNotification* aAudioNotification, + NPAudioDeviceStateChangedIPC aDeviceState); + + NS_IMETHOD Run() override; + + protected: + // The potential payloads for the message. Type determined by mMessageType. + NPAudioDeviceChangeDetailsIPC mChangeDetails; + NPAudioDeviceStateChangedIPC mDeviceState; + enum { DEFAULT_DEVICE_CHANGED, DEVICE_STATE_CHANGED } mMessageType; + + AudioNotification* mAudioNotification; +}; + +class AudioNotification final : public IMMNotificationClient { + public: + AudioNotification() : mIsRegistered(false), mRefCt(1) { + HRESULT hr = + CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&mDeviceEnum)); + if (FAILED(hr)) { + mDeviceEnum = nullptr; + return; + } + + hr = mDeviceEnum->RegisterEndpointNotificationCallback(this); + if (FAILED(hr)) { + mDeviceEnum->Release(); + mDeviceEnum = nullptr; + return; + } + + mIsRegistered = true; + } + + // IMMNotificationClient Implementation + HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, + LPCWSTR device_id) override { + NPAudioDeviceChangeDetailsIPC changeDetails; + changeDetails.flow = (int32_t)flow; + changeDetails.role = (int32_t)role; + changeDetails.defaultDevice = device_id ? std::wstring(device_id) : L""; + + // Make sure that plugin is notified on the main thread. + RefPtr runnable = + new AudioDeviceMessageRunnable(this, changeDetails); + NS_DispatchToMainThread(runnable); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) override { + return S_OK; + }; + + HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id) override { + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id, + DWORD new_state) override { + NPAudioDeviceStateChangedIPC deviceStateIPC; + deviceStateIPC.device = device_id ? std::wstring(device_id) : L""; + deviceStateIPC.state = (uint32_t)new_state; + + // Make sure that plugin is notified on the main thread. + RefPtr runnable = + new AudioDeviceMessageRunnable(this, deviceStateIPC); + NS_DispatchToMainThread(runnable); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE + OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) override { + return S_OK; + } + + // IUnknown Implementation + ULONG STDMETHODCALLTYPE AddRef() override { + return InterlockedIncrement(&mRefCt); + } + + ULONG STDMETHODCALLTYPE Release() override { + ULONG ulRef = InterlockedDecrement(&mRefCt); + if (0 == ulRef) { + delete this; + } + return ulRef; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, + VOID** ppvInterface) override { + if (__uuidof(IUnknown) == riid) { + AddRef(); + *ppvInterface = (IUnknown*)this; + } else if (__uuidof(IMMNotificationClient) == riid) { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } else { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; + } + + /* + * A Valid instance must be Unregistered before Releasing it. + */ + void Unregister() { + if (mDeviceEnum) { + mDeviceEnum->UnregisterEndpointNotificationCallback(this); + } + mIsRegistered = false; + } + + /* + * True whenever the notification server is set to report events to this + * object. + */ + bool IsRegistered() { return mIsRegistered; } + + void AddModule(PluginModuleParent* aModule) { + StaticMutexAutoLock lock(sMutex); + mAudioNotificationSet.PutEntry(aModule); + } + + void RemoveModule(PluginModuleParent* aModule) { + StaticMutexAutoLock lock(sMutex); + mAudioNotificationSet.RemoveEntry(aModule); + } + + /* + * Are any modules registered for audio notifications? + */ + bool HasModules() { return !mAudioNotificationSet.IsEmpty(); } + + const PluginModuleSet* GetModuleSet() const { return &mAudioNotificationSet; } + + private: + bool mIsRegistered; // only used to make sure that Unregister is called + // before destroying a Valid instance. + LONG mRefCt; + IMMDeviceEnumerator* mDeviceEnum; + + // Set of plugin modules that have registered to be notified when the audio + // device changes. + PluginModuleSet mAudioNotificationSet; + + ~AudioNotification() { + MOZ_ASSERT(!mIsRegistered, + "Destroying AudioNotification without first calling Unregister"); + if (mDeviceEnum) { + mDeviceEnum->Release(); + } + } +}; // class AudioNotification + +// callback that gets notified of audio device events, or NULL +AudioNotification* sAudioNotification = nullptr; + +nsresult RegisterForAudioDeviceChanges(PluginModuleParent* aModuleParent, + bool aShouldRegister) { + // Hold the AudioNotification singleton iff there are PluginModuleParents + // that are subscribed to it. + if (aShouldRegister) { + if (!sAudioNotification) { + // We are registering the first module. Create the singleton. + sAudioNotification = new AudioNotification(); + if (!sAudioNotification->IsRegistered()) { + PLUGIN_LOG_DEBUG( + ("Registered for plugin audio device notification failed.")); + sAudioNotification->Release(); + sAudioNotification = nullptr; + return NS_ERROR_FAILURE; + } + PLUGIN_LOG_DEBUG(("Registered for plugin audio device notification.")); + } + sAudioNotification->AddModule(aModuleParent); + } else if (!aShouldRegister && sAudioNotification) { + sAudioNotification->RemoveModule(aModuleParent); + if (!sAudioNotification->HasModules()) { + // We have removed the last module from the notification mechanism + // so we can destroy the singleton. + PLUGIN_LOG_DEBUG(("Unregistering for plugin audio device notification.")); + sAudioNotification->Unregister(); + sAudioNotification->Release(); + sAudioNotification = nullptr; + } + } + return NS_OK; +} + +AudioDeviceMessageRunnable::AudioDeviceMessageRunnable( + AudioNotification* aAudioNotification, + NPAudioDeviceChangeDetailsIPC aChangeDetails) + : Runnable("AudioDeviceMessageRunnableCD"), + mChangeDetails(aChangeDetails), + mMessageType(DEFAULT_DEVICE_CHANGED), + mAudioNotification(aAudioNotification) { + // We increment the AudioNotification ref-count here -- the runnable will + // decrement it when it is done with us. + mAudioNotification->AddRef(); +} + +AudioDeviceMessageRunnable::AudioDeviceMessageRunnable( + AudioNotification* aAudioNotification, + NPAudioDeviceStateChangedIPC aDeviceState) + : Runnable("AudioDeviceMessageRunnableSC"), + mDeviceState(aDeviceState), + mMessageType(DEVICE_STATE_CHANGED), + mAudioNotification(aAudioNotification) { + // We increment the AudioNotification ref-count here -- the runnable will + // decrement it when it is done with us. + mAudioNotification->AddRef(); +} + +NS_IMETHODIMP +AudioDeviceMessageRunnable::Run() { + StaticMutexAutoLock lock(sMutex); + PLUGIN_LOG_DEBUG(("Notifying %d plugins of audio device change.", + mAudioNotification->GetModuleSet()->Count())); + + bool success = true; + for (auto iter = mAudioNotification->GetModuleSet()->ConstIter(); + !iter.Done(); iter.Next()) { + PluginModuleParent* pluginModule = iter.Get()->GetKey(); + switch (mMessageType) { + case DEFAULT_DEVICE_CHANGED: + success &= pluginModule->SendNPP_SetValue_NPNVaudioDeviceChangeDetails( + mChangeDetails); + break; + case DEVICE_STATE_CHANGED: + success &= pluginModule->SendNPP_SetValue_NPNVaudioDeviceStateChanged( + mDeviceState); + break; + default: + MOZ_ASSERT_UNREACHABLE("bad AudioDeviceMessageRunnable state"); + } + } + mAudioNotification->Release(); + return success ? NS_OK : NS_ERROR_FAILURE; +} + +} // namespace PluginUtilsWin +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginUtilsWin.h b/dom/plugins/ipc/PluginUtilsWin.h new file mode 100644 index 0000000000..f3afa79d2c --- /dev/null +++ b/dom/plugins/ipc/PluginUtilsWin.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_PluginUtilsWin_h +#define dom_plugins_PluginUtilsWin_h 1 + +#include "npapi.h" +#include "nscore.h" + +namespace mozilla { +namespace plugins { + +class PluginModuleParent; + +namespace PluginUtilsWin { + +nsresult RegisterForAudioDeviceChanges(PluginModuleParent* aModuleParent, + bool aShouldRegister); + +} // namespace PluginUtilsWin +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_PluginUtilsWin_h diff --git a/dom/plugins/ipc/PluginWidgetChild.cpp b/dom/plugins/ipc/PluginWidgetChild.cpp new file mode 100644 index 0000000000..c25c8529fd --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetChild.cpp @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/plugins/PluginWidgetChild.h" + +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/plugins/PluginWidgetParent.h" +#include "PluginWidgetProxy.h" + +#include "mozilla/Unused.h" +#include "mozilla/DebugOnly.h" +#include "nsDebug.h" + +#include "mozilla/plugins/PluginInstanceParent.h" + +#define PWLOG(...) +//#define PWLOG(...) printf_stderr(__VA_ARGS__) + +namespace mozilla { +namespace plugins { + +PluginWidgetChild::PluginWidgetChild() : mWidget(nullptr) { + PWLOG("PluginWidgetChild::PluginWidgetChild()\n"); + MOZ_COUNT_CTOR(PluginWidgetChild); +} + +PluginWidgetChild::~PluginWidgetChild() { + PWLOG("PluginWidgetChild::~PluginWidgetChild()\n"); + MOZ_COUNT_DTOR(PluginWidgetChild); +} + +// Called by the proxy widget when it is destroyed by layout. Only gets +// called once. +void PluginWidgetChild::ProxyShutdown() { + PWLOG("PluginWidgetChild::ProxyShutdown()\n"); + if (mWidget) { + mWidget = nullptr; + auto tab = static_cast(Manager()); + if (!tab->IsDestroyed()) { + Unused << Send__delete__(this); + } + } +} + +void PluginWidgetChild::KillWidget() { + PWLOG("PluginWidgetChild::KillWidget()\n"); + if (mWidget) { + mWidget->ChannelDestroyed(); + } + mWidget = nullptr; +} + +void PluginWidgetChild::ActorDestroy(ActorDestroyReason aWhy) { + PWLOG("PluginWidgetChild::ActorDestroy(%d)\n", aWhy); + KillWidget(); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginWidgetChild.h b/dom/plugins/ipc/PluginWidgetChild.h new file mode 100644 index 0000000000..1fc2d6ee98 --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetChild.h @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginWidgetChild_h +#define mozilla_plugins_PluginWidgetChild_h + +#ifndef XP_WIN +# error "This header should be Windows-only." +#endif + +#include "mozilla/plugins/PPluginWidgetChild.h" + +namespace mozilla { +namespace widget { +class PluginWidgetProxy; +} // namespace widget +namespace plugins { + +class PluginWidgetChild : public PPluginWidgetChild { + public: + PluginWidgetChild(); + virtual ~PluginWidgetChild(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + void SetWidget(mozilla::widget::PluginWidgetProxy* aWidget) { + mWidget = aWidget; + } + void ProxyShutdown(); + + private: + void KillWidget(); + + mozilla::widget::PluginWidgetProxy* mWidget; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginWidgetChild_h diff --git a/dom/plugins/ipc/PluginWidgetParent.cpp b/dom/plugins/ipc/PluginWidgetParent.cpp new file mode 100644 index 0000000000..0def364dc9 --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetParent.cpp @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginWidgetParent.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/ContentParent.h" +#include "nsComponentManagerUtils.h" +#include "nsWidgetsCID.h" + +#include "mozilla/Unused.h" +#include "mozilla/DebugOnly.h" +#include "nsDebug.h" + +using namespace mozilla; +using namespace mozilla::widget; + +#define PWLOG(...) +//#define PWLOG(...) printf_stderr(__VA_ARGS__) + +namespace mozilla { +namespace dom { +// For nsWindow +const wchar_t* kPluginWidgetContentParentProperty = + L"kPluginWidgetParentProperty"; +} // namespace dom +} // namespace mozilla + +namespace mozilla { +namespace plugins { + +// This macro returns IPC_OK() to prevent an abort in the child process when +// ipc message delivery fails. +#define ENSURE_CHANNEL \ + { \ + if (!mWidget) { \ + NS_WARNING("called on an invalid remote widget."); \ + return IPC_OK(); \ + } \ + } + +PluginWidgetParent::PluginWidgetParent() { + PWLOG("PluginWidgetParent::PluginWidgetParent()\n"); + MOZ_COUNT_CTOR(PluginWidgetParent); +} + +PluginWidgetParent::~PluginWidgetParent() { + PWLOG("PluginWidgetParent::~PluginWidgetParent()\n"); + MOZ_COUNT_DTOR(PluginWidgetParent); + // A destroy call can actually get skipped if a widget is associated + // with the last out-of-process page, make sure and cleanup any left + // over widgets if we have them. + KillWidget(); +} + +mozilla::dom::BrowserParent* PluginWidgetParent::GetBrowserParent() { + return static_cast(Manager()); +} + +void PluginWidgetParent::SetParent(nsIWidget* aParent) { + // This will trigger sync send messages to the plugin process window + // procedure and a cascade of events to that window related to focus + // and activation. + if (mWidget && aParent) { + mWidget->SetParent(aParent); + } +} + +// When plugins run in chrome, nsPluginNativeWindow(Plat) implements platform +// specific functionality that wraps plugin widgets. With e10s we currently +// bypass this code on Window, and reuse a bit of it on Linux. Content still +// makes use of some of the utility functions as well. + +mozilla::ipc::IPCResult PluginWidgetParent::RecvCreate( + nsresult* aResult, uint64_t* aScrollCaptureId, + uintptr_t* aPluginInstanceId) { + PWLOG("PluginWidgetParent::RecvCreate()\n"); + + *aScrollCaptureId = 0; + *aPluginInstanceId = 0; + + mWidget = nsIWidget::CreateChildWindow(); + *aResult = mWidget ? NS_OK : NS_ERROR_FAILURE; + + // This returns the top level window widget + nsCOMPtr parentWidget = GetBrowserParent()->GetWidget(); + // If this fails, bail. + if (!parentWidget) { + *aResult = NS_ERROR_NOT_AVAILABLE; + KillWidget(); + return IPC_OK(); + } + + nsWidgetInitData initData; + initData.mWindowType = eWindowType_plugin_ipc_chrome; + initData.clipChildren = true; + initData.clipSiblings = true; + *aResult = mWidget->Create(parentWidget.get(), nullptr, + LayoutDeviceIntRect(0, 0, 0, 0), &initData); + if (NS_FAILED(*aResult)) { + KillWidget(); + // This should never fail, abort. + return IPC_FAIL_NO_REASON(this); + } + + mWidget->EnableDragDrop(true); + + // This is a special call we make to nsBaseWidget to register this + // window as a remote plugin window which is expected to receive + // visibility updates from the compositor, which ships this data + // over with corresponding layer updates. + mWidget->RegisterPluginWindowForRemoteUpdates(); + + return IPC_OK(); +} + +void PluginWidgetParent::KillWidget() { + PWLOG("PluginWidgetParent::KillWidget() widget=%p\n", (void*)mWidget.get()); + if (mWidget) { + mWidget->UnregisterPluginWindowForRemoteUpdates(); + mWidget->Destroy(); + ::RemovePropW((HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW), + mozilla::dom::kPluginWidgetContentParentProperty); + mWidget = nullptr; + } +} + +void PluginWidgetParent::ActorDestroy(ActorDestroyReason aWhy) { + PWLOG("PluginWidgetParent::ActorDestroy(%d)\n", aWhy); + KillWidget(); +} + +// Called by BrowserParent's Destroy() in response to an early tear down (Early +// in that this is happening before layout in the child has had a chance +// to destroy the child widget.) when the tab is closing. +void PluginWidgetParent::ParentDestroy() { + PWLOG("PluginWidgetParent::ParentDestroy()\n"); +} + +mozilla::ipc::IPCResult PluginWidgetParent::RecvSetFocus( + const bool& aRaise, const mozilla::dom::CallerType& aCallerType) { + ENSURE_CHANNEL; + PWLOG("PluginWidgetParent::RecvSetFocus(%d)\n", aRaise); + mWidget->SetFocus(aRaise ? nsIWidget::Raise::Yes : nsIWidget::Raise::No, + aCallerType); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginWidgetParent::RecvGetNativePluginPort( + uintptr_t* value) { + ENSURE_CHANNEL; + *value = (uintptr_t)mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT); + NS_ASSERTION(*value, "no native port??"); + PWLOG("PluginWidgetParent::RecvGetNativeData() %p\n", (void*)*value); + return IPC_OK(); +} + +mozilla::ipc::IPCResult PluginWidgetParent::RecvSetNativeChildWindow( + const uintptr_t& aChildWindow) { + ENSURE_CHANNEL; + PWLOG("PluginWidgetParent::RecvSetNativeChildWindow(%p)\n", + (void*)aChildWindow); + mWidget->SetNativeData(NS_NATIVE_CHILD_WINDOW, aChildWindow); + return IPC_OK(); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginWidgetParent.h b/dom/plugins/ipc/PluginWidgetParent.h new file mode 100644 index 0000000000..78228345cb --- /dev/null +++ b/dom/plugins/ipc/PluginWidgetParent.h @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginWidgetParent_h +#define mozilla_plugins_PluginWidgetParent_h + +#ifndef XP_WIN +# error "This header should be Windows-only." +#endif + +#include "mozilla/plugins/PPluginWidgetParent.h" +#include "mozilla/UniquePtr.h" +#include "nsIWidget.h" +#include "nsCOMPtr.h" + +namespace mozilla { + +namespace dom { +class BrowserParent; +} // namespace dom + +namespace plugins { + +class PluginWidgetParent : public PPluginWidgetParent { + public: + PluginWidgetParent(); + virtual ~PluginWidgetParent(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + virtual mozilla::ipc::IPCResult RecvCreate( + nsresult* aResult, uint64_t* aScrollCaptureId, + uintptr_t* aPluginInstanceId) override; + virtual mozilla::ipc::IPCResult RecvSetFocus( + const bool& aRaise, const mozilla::dom::CallerType& aCallerType) override; + virtual mozilla::ipc::IPCResult RecvGetNativePluginPort( + uintptr_t* value) override; + mozilla::ipc::IPCResult RecvSetNativeChildWindow( + const uintptr_t& aChildWindow) override; + + // Helper for compositor checks on the channel + bool ActorDestroyed() { return !mWidget; } + + // Called by PBrowser when it receives a Destroy() call from the child. + void ParentDestroy(); + + // Sets mWidget's parent + void SetParent(nsIWidget* aParent); + + private: + // The tab our connection is associated with. + mozilla::dom::BrowserParent* GetBrowserParent(); + + private: + void KillWidget(); + + // The chrome side native widget. + nsCOMPtr mWidget; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginWidgetParent_h diff --git a/dom/plugins/ipc/StreamNotifyChild.h b/dom/plugins/ipc/StreamNotifyChild.h new file mode 100644 index 0000000000..a08f37e723 --- /dev/null +++ b/dom/plugins/ipc/StreamNotifyChild.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=2 et : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_StreamNotifyChild_h +#define mozilla_plugins_StreamNotifyChild_h + +#include "mozilla/plugins/PStreamNotifyChild.h" + +namespace mozilla { +namespace plugins { + +class BrowserStreamChild; + +class StreamNotifyChild : public PStreamNotifyChild { + friend class PluginInstanceChild; + friend class BrowserStreamChild; + friend class PStreamNotifyChild; + + public: + explicit StreamNotifyChild(const nsCString& aURL) + : mURL(aURL), mClosure(nullptr), mBrowserStream(nullptr) {} + + virtual void ActorDestroy(ActorDestroyReason why) override; + + void SetValid(void* aClosure) { mClosure = aClosure; } + + void NPP_URLNotify(NPReason reason); + + private: + mozilla::ipc::IPCResult Recv__delete__(const NPReason& reason); + + mozilla::ipc::IPCResult RecvRedirectNotify(const nsCString& url, + const int32_t& status); + + /** + * If a stream is created for this this URLNotify, we associate the objects + * so that the NPP_URLNotify call is not fired before the stream data is + * completely delivered. The BrowserStreamChild takes responsibility for + * calling NPP_URLNotify and deleting this object. + */ + void SetAssociatedStream(BrowserStreamChild* bs); + + nsCString mURL; + void* mClosure; + + /** + * If mBrowserStream is true, it is responsible for deleting this C++ object + * and DeallocPStreamNotify is not, so that the delayed delivery of + * NPP_URLNotify is possible. + */ + BrowserStreamChild* mBrowserStream; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/StreamNotifyParent.h b/dom/plugins/ipc/StreamNotifyParent.h new file mode 100644 index 0000000000..6020f702ac --- /dev/null +++ b/dom/plugins/ipc/StreamNotifyParent.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=2 ts=2 et : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_StreamNotifyParent_h +#define mozilla_plugins_StreamNotifyParent_h + +#include "mozilla/plugins/PStreamNotifyParent.h" + +namespace mozilla { +namespace plugins { + +class StreamNotifyParent : public PStreamNotifyParent { + friend class PluginInstanceParent; + friend class PStreamNotifyParent; + + StreamNotifyParent() : mDestructionFlag(nullptr) {} + ~StreamNotifyParent() { + if (mDestructionFlag) *mDestructionFlag = true; + } + + public: + // If we are destroyed within the call to NPN_GetURLNotify, notify the caller + // so that we aren't destroyed again. see bug 536437. + void SetDestructionFlag(bool* flag) { mDestructionFlag = flag; } + void ClearDestructionFlag() { mDestructionFlag = nullptr; } + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + mozilla::ipc::IPCResult RecvRedirectNotifyResponse(const bool& allow); + + bool* mDestructionFlag; +}; + +} // namespace plugins +} // namespace mozilla + +#endif diff --git a/dom/plugins/ipc/hangui/HangUIDlg.h b/dom/plugins/ipc/hangui/HangUIDlg.h new file mode 100644 index 0000000000..79cdfc74b4 --- /dev/null +++ b/dom/plugins/ipc/hangui/HangUIDlg.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_HangUIDlg_h +#define mozilla_plugins_HangUIDlg_h + +#define IDD_HANGUIDLG 102 +#define IDC_MSG 1000 +#define IDC_CONTINUE 1001 +#define IDC_STOP 1002 +#define IDC_NOFUTURE 1003 +#define IDC_DLGICON 1004 + +#endif // mozilla_plugins_HangUIDlg_h diff --git a/dom/plugins/ipc/hangui/HangUIDlg.rc b/dom/plugins/ipc/hangui/HangUIDlg.rc new file mode 100644 index 0000000000..62e98ca249 --- /dev/null +++ b/dom/plugins/ipc/hangui/HangUIDlg.rc @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "HangUIDlg.h" +#include + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_HANGUIDLG DIALOGEX 0, 0, 400, 75 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Continue",IDC_CONTINUE,283,51,50,18 + PUSHBUTTON "Stop",IDC_STOP,341,51,50,18 + CONTROL "Check1",IDC_NOFUTURE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,37,32,354,10 + LTEXT "Static",IDC_MSG,37,7,353,24 + ICON "",IDC_DLGICON,7,7,20,20 +END + diff --git a/dom/plugins/ipc/hangui/MiniShmBase.h b/dom/plugins/ipc/hangui/MiniShmBase.h new file mode 100644 index 0000000000..9782330b12 --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmBase.h @@ -0,0 +1,280 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_MiniShmBase_h +#define mozilla_plugins_MiniShmBase_h + +#include "base/basictypes.h" + +#include "nsDebug.h" + +#include + +namespace mozilla { +namespace plugins { + +/** + * This class is used to provide RAII semantics for mapped views. + * @see ScopedHandle + */ +class ScopedMappedFileView { + public: + explicit ScopedMappedFileView(LPVOID aView) : mView(aView) {} + + ~ScopedMappedFileView() { Close(); } + + void Close() { + if (mView) { + ::UnmapViewOfFile(mView); + mView = nullptr; + } + } + + void Set(LPVOID aView) { + Close(); + mView = aView; + } + + LPVOID + Get() const { return mView; } + + LPVOID + Take() { + LPVOID result = mView; + mView = nullptr; + return result; + } + + operator LPVOID() { return mView; } + + bool IsValid() const { return (mView); } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedMappedFileView); + + LPVOID mView; +}; + +class MiniShmBase; + +class MiniShmObserver { + public: + /** + * This function is called whenever there is a new shared memory request. + * @param aMiniShmObj MiniShmBase object that may be used to read and + * write from shared memory. + */ + virtual void OnMiniShmEvent(MiniShmBase* aMiniShmObj) = 0; + /** + * This function is called once when a MiniShmParent and a MiniShmChild + * object have successfully negotiated a connection. + * + * @param aMiniShmObj MiniShmBase object that may be used to read and + * write from shared memory. + */ + virtual void OnMiniShmConnect(MiniShmBase* aMiniShmObj) {} +}; + +/** + * Base class for MiniShm connections. This class defines the common + * interfaces and code between parent and child. + */ +class MiniShmBase { + public: + /** + * Obtains a writable pointer into shared memory of type T. + * typename T must be plain-old-data and contain an unsigned integral + * member T::identifier that uniquely identifies T with respect to + * other types used by the protocol being implemented. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T is not valid for MiniShm. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + * NS_ERROR_NOT_AVAILABLE if the memory is not safe to write. + */ + template + nsresult GetWritePtr(T*& aPtr) { + if (!mWriteHeader || !mGuard) { + return NS_ERROR_NOT_INITIALIZED; + } + if (sizeof(T) > mPayloadMaxLen || + (int)T::identifier <= (int)RESERVED_CODE_LAST) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (::WaitForSingleObject(mGuard, mTimeout) != WAIT_OBJECT_0) { + return NS_ERROR_NOT_AVAILABLE; + } + mWriteHeader->mId = T::identifier; + mWriteHeader->mPayloadLen = sizeof(T); + aPtr = reinterpret_cast(mWriteHeader + 1); + return NS_OK; + } + + /** + * Obtains a readable pointer into shared memory of type T. + * typename T must be plain-old-data and contain an unsigned integral + * member T::identifier that uniquely identifies T with respect to + * other types used by the protocol being implemented. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T is not valid for MiniShm or if + * type T does not match the type of the data + * stored in shared memory. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + */ + template + nsresult GetReadPtr(const T*& aPtr) { + if (!mReadHeader) { + return NS_ERROR_NOT_INITIALIZED; + } + if (mReadHeader->mId != T::identifier || + sizeof(T) != mReadHeader->mPayloadLen) { + return NS_ERROR_ILLEGAL_VALUE; + } + aPtr = reinterpret_cast(mReadHeader + 1); + return NS_OK; + } + + /** + * Fires the peer's event causing its request handler to execute. + * + * @return Should return NS_OK if the send was successful. + */ + virtual nsresult Send() = 0; + + protected: + /** + * MiniShm reserves some identifier codes for its own use. Any + * identifiers used by MiniShm protocol implementations must be + * greater than RESERVED_CODE_LAST. + */ + enum ReservedCodes { + RESERVED_CODE_INIT = 0, + RESERVED_CODE_INIT_COMPLETE = 1, + RESERVED_CODE_LAST = RESERVED_CODE_INIT_COMPLETE + }; + + struct MiniShmHeader { + unsigned int mId; + unsigned int mPayloadLen; + }; + + struct MiniShmInit { + enum identifier_t { identifier = RESERVED_CODE_INIT }; + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + }; + + struct MiniShmInitComplete { + enum identifier_t { identifier = RESERVED_CODE_INIT_COMPLETE }; + bool mSucceeded; + }; + + MiniShmBase() + : mObserver(nullptr), + mWriteHeader(nullptr), + mReadHeader(nullptr), + mPayloadMaxLen(0), + mGuard(nullptr), + mTimeout(INFINITE) {} + virtual ~MiniShmBase() {} + + virtual void OnEvent() { + if (mObserver) { + mObserver->OnMiniShmEvent(this); + } + } + + virtual void OnConnect() { + if (mObserver) { + mObserver->OnMiniShmConnect(this); + } + } + + nsresult SetView(LPVOID aView, const unsigned int aSize, bool aIsChild) { + if (!aView || aSize <= 2 * sizeof(MiniShmHeader)) { + return NS_ERROR_ILLEGAL_VALUE; + } + // Divide the region into halves for parent and child + if (aIsChild) { + mReadHeader = static_cast(aView); + mWriteHeader = reinterpret_cast( + static_cast(aView) + aSize / 2U); + } else { + mWriteHeader = static_cast(aView); + mReadHeader = reinterpret_cast(static_cast(aView) + + aSize / 2U); + } + mPayloadMaxLen = aSize / 2U - sizeof(MiniShmHeader); + return NS_OK; + } + + nsresult SetGuard(HANDLE aGuard, DWORD aTimeout) { + if (!aGuard || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + mGuard = aGuard; + mTimeout = aTimeout; + return NS_OK; + } + + inline void SetObserver(MiniShmObserver* aObserver) { mObserver = aObserver; } + + /** + * Obtains a writable pointer into shared memory of type T. This version + * differs from GetWritePtr in that it allows typename T to be one of + * the private data structures declared in MiniShmBase. + * + * @param aPtr Pointer to receive the shared memory address. + * This value is set if and only if the function + * succeeded. + * @return NS_OK if and only if aPtr was successfully obtained. + * NS_ERROR_ILLEGAL_VALUE if type T not an internal MiniShm struct. + * NS_ERROR_NOT_INITIALIZED if there is no valid MiniShm connection. + */ + template + nsresult GetWritePtrInternal(T*& aPtr) { + if (!mWriteHeader) { + return NS_ERROR_NOT_INITIALIZED; + } + if (sizeof(T) > mPayloadMaxLen || + (int)T::identifier > (int)RESERVED_CODE_LAST) { + return NS_ERROR_ILLEGAL_VALUE; + } + mWriteHeader->mId = T::identifier; + mWriteHeader->mPayloadLen = sizeof(T); + aPtr = reinterpret_cast(mWriteHeader + 1); + return NS_OK; + } + + static VOID CALLBACK SOnEvent(PVOID aContext, BOOLEAN aIsTimer) { + MiniShmBase* object = static_cast(aContext); + object->OnEvent(); + } + + private: + MiniShmObserver* mObserver; + MiniShmHeader* mWriteHeader; + MiniShmHeader* mReadHeader; + unsigned int mPayloadMaxLen; + HANDLE mGuard; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmBase); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmBase_h diff --git a/dom/plugins/ipc/hangui/MiniShmChild.cpp b/dom/plugins/ipc/hangui/MiniShmChild.cpp new file mode 100644 index 0000000000..ec5a79714f --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmChild.cpp @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MiniShmChild.h" + +#include +#include + +namespace mozilla { +namespace plugins { + +MiniShmChild::MiniShmChild() + : mParentEvent(nullptr), + mParentGuard(nullptr), + mChildEvent(nullptr), + mChildGuard(nullptr), + mFileMapping(nullptr), + mRegWait(nullptr), + mView(nullptr), + mTimeout(INFINITE) {} + +MiniShmChild::~MiniShmChild() { + if (mRegWait) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + } + if (mParentGuard) { + // Try to avoid shutting down while the parent's event handler is running. + ::WaitForSingleObject(mParentGuard, mTimeout); + ::CloseHandle(mParentGuard); + } + if (mParentEvent) { + ::CloseHandle(mParentEvent); + } + if (mChildEvent) { + ::CloseHandle(mChildEvent); + } + if (mChildGuard) { + ::CloseHandle(mChildGuard); + } + if (mView) { + ::UnmapViewOfFile(mView); + } + if (mFileMapping) { + ::CloseHandle(mFileMapping); + } +} + +nsresult MiniShmChild::Init(MiniShmObserver* aObserver, + const std::wstring& aCookie, const DWORD aTimeout) { + if (aCookie.empty() || !aTimeout) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (mFileMapping) { + return NS_ERROR_ALREADY_INITIALIZED; + } + std::wistringstream iss(aCookie); + HANDLE mapHandle = nullptr; + iss >> mapHandle; + if (!iss) { + return NS_ERROR_ILLEGAL_VALUE; + } + ScopedMappedFileView view( + ::MapViewOfFile(mapHandle, FILE_MAP_WRITE, 0, 0, 0)); + if (!view.IsValid()) { + return NS_ERROR_FAILURE; + } + MEMORY_BASIC_INFORMATION memInfo = {0}; + SIZE_T querySize = ::VirtualQuery(view, &memInfo, sizeof(memInfo)); + unsigned int mappingSize = 0; + if (querySize) { + if (memInfo.RegionSize <= std::numeric_limits::max()) { + mappingSize = static_cast(memInfo.RegionSize); + } + } + if (!querySize || !mappingSize) { + return NS_ERROR_FAILURE; + } + nsresult rv = SetView(view, mappingSize, true); + if (NS_FAILED(rv)) { + return rv; + } + + const MiniShmInit* initStruct = nullptr; + rv = GetReadPtr(initStruct); + if (NS_FAILED(rv)) { + return rv; + } + if (!initStruct->mParentEvent || !initStruct->mParentGuard || + !initStruct->mChildEvent || !initStruct->mChildGuard) { + return NS_ERROR_FAILURE; + } + rv = SetGuard(initStruct->mParentGuard, aTimeout); + if (NS_FAILED(rv)) { + return rv; + } + if (!::RegisterWaitForSingleObject(&mRegWait, initStruct->mChildEvent, + &SOnEvent, this, INFINITE, + WT_EXECUTEDEFAULT)) { + return NS_ERROR_FAILURE; + } + + MiniShmInitComplete* initCompleteStruct = nullptr; + rv = GetWritePtrInternal(initCompleteStruct); + if (NS_FAILED(rv)) { + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = nullptr; + return NS_ERROR_FAILURE; + } + + initCompleteStruct->mSucceeded = true; + + // We must set the member variables before we signal the event + mFileMapping = mapHandle; + mView = view.Take(); + mParentEvent = initStruct->mParentEvent; + mParentGuard = initStruct->mParentGuard; + mChildEvent = initStruct->mChildEvent; + mChildGuard = initStruct->mChildGuard; + SetObserver(aObserver); + mTimeout = aTimeout; + + rv = Send(); + if (NS_FAILED(rv)) { + initCompleteStruct->mSucceeded = false; + mFileMapping = nullptr; + view.Set(mView); + mView = nullptr; + mParentEvent = nullptr; + mParentGuard = nullptr; + mChildEvent = nullptr; + mChildGuard = nullptr; + ::UnregisterWaitEx(mRegWait, INVALID_HANDLE_VALUE); + mRegWait = nullptr; + return rv; + } + + OnConnect(); + return NS_OK; +} + +nsresult MiniShmChild::Send() { + if (!mParentEvent) { + return NS_ERROR_NOT_INITIALIZED; + } + if (!::SetEvent(mParentEvent)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void MiniShmChild::OnEvent() { + MiniShmBase::OnEvent(); + ::SetEvent(mChildGuard); +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/hangui/MiniShmChild.h b/dom/plugins/ipc/hangui/MiniShmChild.h new file mode 100644 index 0000000000..ddaa3277b2 --- /dev/null +++ b/dom/plugins/ipc/hangui/MiniShmChild.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_MiniShmChild_h +#define mozilla_plugins_MiniShmChild_h + +#include "MiniShmBase.h" + +#include + +namespace mozilla { +namespace plugins { + +/** + * This class provides a lightweight shared memory interface for a child + * process in Win32. + * This code assumes that there is a parent-child relationship between + * processes, as it inherits handles from the parent process. + * Note that this class is *not* an IPDL actor. + * + * @see MiniShmParent + */ +class MiniShmChild : public MiniShmBase { + public: + MiniShmChild(); + virtual ~MiniShmChild(); + + /** + * Initialize shared memory on the child side. + * + * @param aObserver A MiniShmObserver object to receive event notifications. + * @param aCookie Cookie obtained from MiniShmParent::GetCookie + * @param aTimeout Timeout in milliseconds. + * @return nsresult error code + */ + nsresult Init(MiniShmObserver* aObserver, const std::wstring& aCookie, + const DWORD aTimeout); + + virtual nsresult Send() override; + + protected: + void OnEvent() override; + + private: + HANDLE mParentEvent; + HANDLE mParentGuard; + HANDLE mChildEvent; + HANDLE mChildGuard; + HANDLE mFileMapping; + HANDLE mRegWait; + LPVOID mView; + DWORD mTimeout; + + DISALLOW_COPY_AND_ASSIGN(MiniShmChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_MiniShmChild_h diff --git a/dom/plugins/ipc/hangui/PluginHangUI.h b/dom/plugins/ipc/hangui/PluginHangUI.h new file mode 100644 index 0000000000..c0880f18cc --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUI.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginHangUI_h +#define mozilla_plugins_PluginHangUI_h + +namespace mozilla { +namespace plugins { + +enum HangUIUserResponse { + HANGUI_USER_RESPONSE_CANCEL = 1, + HANGUI_USER_RESPONSE_CONTINUE = 2, + HANGUI_USER_RESPONSE_STOP = 4, + HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN = 8 +}; + +enum PluginHangUIStructID { + PLUGIN_HANGUI_COMMAND = 0x10, + PLUGIN_HANGUI_RESULT +}; + +struct PluginHangUICommand { + enum { identifier = PLUGIN_HANGUI_COMMAND }; + enum CmdCode { HANGUI_CMD_SHOW = 1, HANGUI_CMD_CANCEL = 2 }; + CmdCode mCode; +}; + +struct PluginHangUIResponse { + enum { identifier = PLUGIN_HANGUI_RESULT }; + unsigned int mResponseBits; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUI_h diff --git a/dom/plugins/ipc/hangui/PluginHangUIChild.cpp b/dom/plugins/ipc/hangui/PluginHangUIChild.cpp new file mode 100644 index 0000000000..01bae5cb68 --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUIChild.cpp @@ -0,0 +1,386 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginHangUI.h" + +#include "PluginHangUIChild.h" +#include "HangUIDlg.h" + +#include +#include +#include +#include +#include +#include + +namespace mozilla { +namespace plugins { + +struct WinInfo { + WinInfo(HWND aHwnd, POINT& aPos, SIZE& aSize) : hwnd(aHwnd) { + pos.x = aPos.x; + pos.y = aPos.y; + size.cx = aSize.cx; + size.cy = aSize.cy; + } + HWND hwnd; + POINT pos; + SIZE size; +}; +typedef std::vector WinInfoVec; + +PluginHangUIChild* PluginHangUIChild::sSelf = nullptr; +const int PluginHangUIChild::kExpectedMinimumArgc = 10; + +PluginHangUIChild::PluginHangUIChild() + : mResponseBits(0), + mParentWindow(nullptr), + mDlgHandle(nullptr), + mMainThread(nullptr), + mParentProcess(nullptr), + mRegWaitProcess(nullptr), + mIPCTimeoutMs(0) {} + +PluginHangUIChild::~PluginHangUIChild() { + if (mMainThread) { + CloseHandle(mMainThread); + } + if (mRegWaitProcess) { + UnregisterWaitEx(mRegWaitProcess, INVALID_HANDLE_VALUE); + } + if (mParentProcess) { + CloseHandle(mParentProcess); + } + sSelf = nullptr; +} + +bool PluginHangUIChild::Init(int aArgc, wchar_t* aArgv[]) { + if (aArgc < kExpectedMinimumArgc) { + return false; + } + unsigned int i = 1; + mMessageText = aArgv[i]; + mWindowTitle = aArgv[++i]; + mWaitBtnText = aArgv[++i]; + mKillBtnText = aArgv[++i]; + mNoFutureText = aArgv[++i]; + std::wistringstream issHwnd(aArgv[++i]); + issHwnd >> reinterpret_cast(mParentWindow); + if (!issHwnd) { + return false; + } + std::wistringstream issProc(aArgv[++i]); + issProc >> mParentProcess; + if (!issProc) { + return false; + } + // Only set the App User Model ID if it's present in the args + if (wcscmp(aArgv[++i], L"-")) { + HMODULE shell32 = LoadLibrary(L"shell32.dll"); + if (shell32) { + SETAPPUSERMODELID fSetAppUserModelID = (SETAPPUSERMODELID)GetProcAddress( + shell32, "SetCurrentProcessExplicitAppUserModelID"); + if (fSetAppUserModelID) { + fSetAppUserModelID(aArgv[i]); + } + FreeLibrary(shell32); + } + } + std::wistringstream issTimeout(aArgv[++i]); + issTimeout >> mIPCTimeoutMs; + if (!issTimeout) { + return false; + } + + nsresult rv = mMiniShm.Init(this, std::wstring(aArgv[++i]), + IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); + if (NS_FAILED(rv)) { + return false; + } + sSelf = this; + return true; +} + +void PluginHangUIChild::OnMiniShmEvent(MiniShmBase* aMiniShmObj) { + const PluginHangUICommand* cmd = nullptr; + nsresult rv = aMiniShmObj->GetReadPtr(cmd); + assert(NS_SUCCEEDED(rv)); + bool returnStatus = false; + if (NS_SUCCEEDED(rv)) { + switch (cmd->mCode) { + case PluginHangUICommand::HANGUI_CMD_SHOW: + returnStatus = RecvShow(); + break; + case PluginHangUICommand::HANGUI_CMD_CANCEL: + returnStatus = RecvCancel(); + break; + default: + break; + } + } +} + +// static +INT_PTR CALLBACK PluginHangUIChild::SHangUIDlgProc(HWND aDlgHandle, + UINT aMsgCode, + WPARAM aWParam, + LPARAM aLParam) { + PluginHangUIChild* self = PluginHangUIChild::sSelf; + if (self) { + return self->HangUIDlgProc(aDlgHandle, aMsgCode, aWParam, aLParam); + } + return FALSE; +} + +void PluginHangUIChild::ResizeButtons() { + // Control IDs are specified right-to-left as they appear in the dialog + UINT ids[] = {IDC_STOP, IDC_CONTINUE}; + UINT numIds = sizeof(ids) / sizeof(ids[0]); + + // Pass 1: Compute the ideal size + bool needResizing = false; + SIZE idealSize = {0}; + WinInfoVec winInfo; + for (UINT i = 0; i < numIds; ++i) { + HWND wnd = GetDlgItem(mDlgHandle, ids[i]); + if (!wnd) { + return; + } + + // Get the button's dimensions in screen coordinates + RECT curRect; + if (!GetWindowRect(wnd, &curRect)) { + return; + } + + // Get (x,y) position of the button in client coordinates + POINT pt; + pt.x = curRect.left; + pt.y = curRect.top; + if (!ScreenToClient(mDlgHandle, &pt)) { + return; + } + + // Request the button's text margins + RECT margins; + if (!Button_GetTextMargin(wnd, &margins)) { + return; + } + + // Compute the button's width and height + SIZE curSize; + curSize.cx = curRect.right - curRect.left; + curSize.cy = curRect.bottom - curRect.top; + + // Request the button's ideal width and height and add in the margins + SIZE size = {0}; + if (!Button_GetIdealSize(wnd, &size)) { + return; + } + size.cx += margins.left + margins.right; + size.cy += margins.top + margins.bottom; + + // Size all buttons to be the same width as the longest button encountered + idealSize.cx = std::max(idealSize.cx, size.cx); + idealSize.cy = std::max(idealSize.cy, size.cy); + + // We won't bother resizing unless we need extra space + if (idealSize.cx > curSize.cx) { + needResizing = true; + } + + // Save the relevant info for the resize, if any. We do this even if + // needResizing is false because another button may trigger a resize later. + winInfo.push_back(WinInfo(wnd, pt, curSize)); + } + + if (!needResizing) { + return; + } + + // Pass 2: Resize the windows + int deltaX = 0; + HDWP hwp = BeginDeferWindowPos((int)winInfo.size()); + if (!hwp) { + return; + } + for (WinInfoVec::const_iterator itr = winInfo.begin(); itr != winInfo.end(); + ++itr) { + // deltaX accumulates the size changes so that each button's x coordinate + // can compensate for the width increases + deltaX += idealSize.cx - itr->size.cx; + hwp = DeferWindowPos(hwp, itr->hwnd, nullptr, itr->pos.x - deltaX, + itr->pos.y, idealSize.cx, itr->size.cy, + SWP_NOZORDER | SWP_NOACTIVATE); + if (!hwp) { + return; + } + } + EndDeferWindowPos(hwp); +} + +INT_PTR +PluginHangUIChild::HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, + LPARAM aLParam) { + mDlgHandle = aDlgHandle; + switch (aMsgCode) { + case WM_INITDIALOG: { + // Register a wait on the Firefox process so that we will be informed + // if it dies while the dialog is showing + RegisterWaitForSingleObject(&mRegWaitProcess, mParentProcess, + &SOnParentProcessExit, this, INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + SetWindowText(aDlgHandle, mWindowTitle); + SetDlgItemText(aDlgHandle, IDC_MSG, mMessageText); + SetDlgItemText(aDlgHandle, IDC_NOFUTURE, mNoFutureText); + SetDlgItemText(aDlgHandle, IDC_CONTINUE, mWaitBtnText); + SetDlgItemText(aDlgHandle, IDC_STOP, mKillBtnText); + ResizeButtons(); + HANDLE icon = LoadImage(nullptr, IDI_QUESTION, IMAGE_ICON, 0, 0, + LR_DEFAULTSIZE | LR_SHARED); + if (icon) { + SendDlgItemMessage(aDlgHandle, IDC_DLGICON, STM_SETICON, (WPARAM)icon, + 0); + } + EnableWindow(mParentWindow, FALSE); + return TRUE; + } + case WM_CLOSE: { + mResponseBits |= HANGUI_USER_RESPONSE_CANCEL; + EndDialog(aDlgHandle, 0); + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + case WM_COMMAND: { + switch (LOWORD(aWParam)) { + case IDC_CONTINUE: + if (HIWORD(aWParam) == BN_CLICKED) { + mResponseBits |= HANGUI_USER_RESPONSE_CONTINUE; + EndDialog(aDlgHandle, 1); + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + break; + case IDC_STOP: + if (HIWORD(aWParam) == BN_CLICKED) { + mResponseBits |= HANGUI_USER_RESPONSE_STOP; + EndDialog(aDlgHandle, 1); + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + break; + case IDC_NOFUTURE: + if (HIWORD(aWParam) == BN_CLICKED) { + if (Button_GetCheck(GetDlgItem(aDlgHandle, IDC_NOFUTURE)) == + BST_CHECKED) { + mResponseBits |= HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN; + } else { + mResponseBits &= + ~static_cast(HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); + } + SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); + return TRUE; + } + break; + default: + break; + } + break; + } + case WM_DESTROY: { + EnableWindow(mParentWindow, TRUE); + SetForegroundWindow(mParentWindow); + break; + } + default: + break; + } + return FALSE; +} + +// static +VOID CALLBACK PluginHangUIChild::SOnParentProcessExit(PVOID aObject, + BOOLEAN aIsTimer) { + // Simulate a cancel if the parent process died + PluginHangUIChild* object = static_cast(aObject); + object->RecvCancel(); +} + +bool PluginHangUIChild::RecvShow() { + return ( + QueueUserAPC(&ShowAPC, mMainThread, reinterpret_cast(this))); +} + +bool PluginHangUIChild::Show() { + INT_PTR dlgResult = + DialogBox(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDD_HANGUIDLG), + nullptr, &SHangUIDlgProc); + mDlgHandle = nullptr; + assert(dlgResult != -1); + bool result = false; + if (dlgResult != -1) { + PluginHangUIResponse* response = nullptr; + nsresult rv = mMiniShm.GetWritePtr(response); + if (NS_SUCCEEDED(rv)) { + response->mResponseBits = mResponseBits; + result = NS_SUCCEEDED(mMiniShm.Send()); + } + } + return result; +} + +// static +VOID CALLBACK PluginHangUIChild::ShowAPC(ULONG_PTR aContext) { + PluginHangUIChild* object = reinterpret_cast(aContext); + object->Show(); +} + +bool PluginHangUIChild::RecvCancel() { + if (mDlgHandle) { + PostMessage(mDlgHandle, WM_CLOSE, 0, 0); + } + return true; +} + +bool PluginHangUIChild::WaitForDismissal() { + if (!SetMainThread()) { + return false; + } + DWORD waitResult = WaitForSingleObjectEx(mParentProcess, mIPCTimeoutMs, TRUE); + return waitResult == WAIT_OBJECT_0 || waitResult == WAIT_IO_COMPLETION; +} + +bool PluginHangUIChild::SetMainThread() { + if (mMainThread) { + CloseHandle(mMainThread); + mMainThread = nullptr; + } + mMainThread = OpenThread(THREAD_SET_CONTEXT, FALSE, GetCurrentThreadId()); + return !(!mMainThread); +} + +} // namespace plugins +} // namespace mozilla + +#ifdef __MINGW32__ +extern "C" +#endif + int + wmain(int argc, wchar_t* argv[]) { + INITCOMMONCONTROLSEX icc = {sizeof(INITCOMMONCONTROLSEX), + ICC_STANDARD_CLASSES}; + if (!InitCommonControlsEx(&icc)) { + return 1; + } + mozilla::plugins::PluginHangUIChild hangui; + if (!hangui.Init(argc, argv)) { + return 1; + } + if (!hangui.WaitForDismissal()) { + return 1; + } + return 0; +} diff --git a/dom/plugins/ipc/hangui/PluginHangUIChild.h b/dom/plugins/ipc/hangui/PluginHangUIChild.h new file mode 100644 index 0000000000..d21c717666 --- /dev/null +++ b/dom/plugins/ipc/hangui/PluginHangUIChild.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_plugins_PluginHangUIChild_h +#define mozilla_plugins_PluginHangUIChild_h + +#include "MiniShmChild.h" + +#include + +#include + +namespace mozilla { +namespace plugins { + +/** + * This class implements the plugin-hang-ui. + * + * NOTE: PluginHangUIChild is *not* an IPDL actor! In this case, "Child" + * is describing the fact that plugin-hang-ui is a child process to the + * firefox process, which is the PluginHangUIParent. + * PluginHangUIParent and PluginHangUIChild are a matched pair. + * @see PluginHangUIParent + */ +class PluginHangUIChild : public MiniShmObserver { + public: + PluginHangUIChild(); + virtual ~PluginHangUIChild(); + + bool Init(int aArgc, wchar_t* aArgv[]); + + /** + * Displays the Plugin Hang UI and does not return until the UI has + * been dismissed. + * + * @return true if the UI was displayed and the user response was + * successfully sent back to the parent. Otherwise false. + */ + bool Show(); + + /** + * Causes the calling thread to wait either for the Hang UI to be + * dismissed or for the parent process to terminate. This should + * be called by the main thread. + * + * @return true unless there was an error initiating the wait + */ + bool WaitForDismissal(); + + virtual void OnMiniShmEvent(MiniShmBase* aMiniShmObj) override; + + private: + bool RecvShow(); + + bool RecvCancel(); + + bool SetMainThread(); + + void ResizeButtons(); + + INT_PTR + HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, LPARAM aLParam); + + static VOID CALLBACK ShowAPC(ULONG_PTR aContext); + + static INT_PTR CALLBACK SHangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, + WPARAM aWParam, LPARAM aLParam); + + static VOID CALLBACK SOnParentProcessExit(PVOID aObject, BOOLEAN aIsTimer); + + static PluginHangUIChild* sSelf; + + const wchar_t* mMessageText; + const wchar_t* mWindowTitle; + const wchar_t* mWaitBtnText; + const wchar_t* mKillBtnText; + const wchar_t* mNoFutureText; + unsigned int mResponseBits; + HWND mParentWindow; + HWND mDlgHandle; + HANDLE mMainThread; + HANDLE mParentProcess; + HANDLE mRegWaitProcess; + DWORD mIPCTimeoutMs; + MiniShmChild mMiniShm; + + static const int kExpectedMinimumArgc; + + typedef HRESULT(WINAPI* SETAPPUSERMODELID)(PCWSTR); + + DISALLOW_COPY_AND_ASSIGN(PluginHangUIChild); +}; + +} // namespace plugins +} // namespace mozilla + +#endif // mozilla_plugins_PluginHangUIChild_h diff --git a/dom/plugins/ipc/hangui/module.ver b/dom/plugins/ipc/hangui/module.ver new file mode 100644 index 0000000000..d11506f4a1 --- /dev/null +++ b/dom/plugins/ipc/hangui/module.ver @@ -0,0 +1,6 @@ +WIN32_MODULE_COMPANYNAME=Mozilla Corporation +WIN32_MODULE_PRODUCTVERSION=@MOZ_APP_WINVERSION@ +WIN32_MODULE_PRODUCTVERSION_STRING=@MOZ_APP_VERSION@ +WIN32_MODULE_DESCRIPTION=Plugin Hang UI for @MOZ_APP_DISPLAYNAME@ +WIN32_MODULE_PRODUCTNAME=@MOZ_APP_DISPLAYNAME@ +WIN32_MODULE_NAME=@MOZ_APP_DISPLAYNAME@ diff --git a/dom/plugins/ipc/hangui/moz.build b/dom/plugins/ipc/hangui/moz.build new file mode 100644 index 0000000000..db07f43d9b --- /dev/null +++ b/dom/plugins/ipc/hangui/moz.build @@ -0,0 +1,27 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Program("plugin-hang-ui") + +UNIFIED_SOURCES += [ + "MiniShmChild.cpp", + "PluginHangUIChild.cpp", +] +include("/ipc/chromium/chromium-config.mozbuild") + +DEFINES["NS_NO_XPCOM"] = True +DEFINES["_HAS_EXCEPTIONS"] = 0 + +DisableStlWrapping() + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + WIN32_EXE_LDFLAGS += ["-municode"] + +RCINCLUDE = "HangUIDlg.rc" + +OS_LIBS += [ + "comctl32", +] diff --git a/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest b/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest new file mode 100644 index 0000000000..f5b7345f99 --- /dev/null +++ b/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest @@ -0,0 +1,37 @@ + + + +Firefox Plugin Hang User Interface + + + + + + + + + + + + + + + + + + + + + diff --git a/dom/plugins/ipc/interpose/moz.build b/dom/plugins/ipc/interpose/moz.build new file mode 100644 index 0000000000..ee24953570 --- /dev/null +++ b/dom/plugins/ipc/interpose/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SharedLibrary("plugin_child_interpose") + +UNIFIED_SOURCES += ["%s.mm" % (LIBRARY_NAME)] + +OS_LIBS += ["-framework Carbon"] + +DIST_INSTALL = True diff --git a/dom/plugins/ipc/interpose/plugin_child_interpose.mm b/dom/plugins/ipc/interpose/plugin_child_interpose.mm new file mode 100644 index 0000000000..7f7e0ea372 --- /dev/null +++ b/dom/plugins/ipc/interpose/plugin_child_interpose.mm @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Use "dyld interposing" to hook methods imported from other libraries in the +// plugin child process. The basic technique is described at +// http://books.google.com/books?id=K8vUkpOXhN4C&pg=PA73&lpg=PA73&dq=__interpose&source=bl&ots=OJnnXZYpZC&sig=o7I3lXvoduUi13SrPfOON7o3do4&hl=en&ei=AoehS9brCYGQNrvsmeUM&sa=X&oi=book_result&ct=result&resnum=6&ved=0CBsQ6AEwBQ#v=onepage&q=__interpose&f=false. +// The idea of doing it for the plugin child process comes from Chromium code, +// particularly from plugin_carbon_interpose_mac.cc +// (http://codesearch.google.com/codesearch/p?hl=en#OAMlx_jo-ck/src/chrome/browser/plugin_carbon_interpose_mac.cc&q=nscursor&exact_package=chromium&d=1&l=168) +// and from PluginProcessHost::Init() in plugin_process_host.cc +// (http://codesearch.google.com/codesearch/p?hl=en#OAMlx_jo-ck/src/content/browser/plugin_process_host.cc&q=nscursor&exact_package=chromium&d=1&l=222). + +// These hooks are needed to make certain OS calls work from the child process +// (a background process) that would normally only work when called in the +// parent process (the foreground process). They allow us to serialize +// information from the child process to the parent process, so that the same +// (or equivalent) calls can be made from the parent process. + +// This file lives in a seperate module (libplugin_child_interpose.dylib), +// which will get loaded by the OS before any other modules when the plugin +// child process is launched (from GeckoChildProcessHost:: +// PerformAsyncLaunch()). For this reason it shouldn't link in other +// browser modules when loaded. Instead it should use dlsym() to load +// pointers to the methods it wants to call in other modules. + +#if !defined(__LP64__) + +# include +# import + +// The header file QuickdrawAPI.h is missing on OS X 10.7 and up (though the +// QuickDraw APIs defined in it are still present) -- so we need to supply the +// relevant parts of its contents here. It's likely that Apple will eventually +// remove the APIs themselves (probably in OS X 10.8), so we need to make them +// weak imports, and test for their presence before using them. +# if !defined(__QUICKDRAWAPI__) + +struct Cursor; +extern "C" void SetCursor(const Cursor* crsr) __attribute__((weak_import)); + +# endif /* __QUICKDRAWAPI__ */ + +BOOL (*OnSetThemeCursorPtr)(ThemeCursor) = NULL; +BOOL (*OnSetCursorPtr)(const Cursor*) = NULL; +BOOL (*OnHideCursorPtr)() = NULL; +BOOL (*OnShowCursorPtr)() = NULL; + +static BOOL loadXULPtrs() { + if (!OnSetThemeCursorPtr) { + // mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor) is in + // PluginInterposeOSX.mm + OnSetThemeCursorPtr = + (BOOL(*)(ThemeCursor))dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnSetThemeCursor"); + } + if (!OnSetCursorPtr) { + // mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor) is in + // PluginInterposeOSX.mm + OnSetCursorPtr = + (BOOL(*)(const Cursor*))dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnSetCursor"); + } + if (!OnHideCursorPtr) { + // mac_plugin_interposing_child_OnHideCursor() is in PluginInterposeOSX.mm + OnHideCursorPtr = (BOOL(*)())dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnHideCursor"); + } + if (!OnShowCursorPtr) { + // mac_plugin_interposing_child_OnShowCursor() is in PluginInterposeOSX.mm + OnShowCursorPtr = (BOOL(*)())dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnShowCursor"); + } + return (OnSetCursorPtr && OnSetThemeCursorPtr && OnHideCursorPtr && OnShowCursorPtr); +} + +static OSStatus MacPluginChildSetThemeCursor(ThemeCursor cursor) { + if (loadXULPtrs()) { + OnSetThemeCursorPtr(cursor); + } + return ::SetThemeCursor(cursor); +} + +static void MacPluginChildSetCursor(const Cursor* cursor) { + if (::SetCursor) { + if (loadXULPtrs()) { + OnSetCursorPtr(cursor); + } + ::SetCursor(cursor); + } +} + +static CGError MacPluginChildCGDisplayHideCursor(CGDirectDisplayID display) { + if (loadXULPtrs()) { + OnHideCursorPtr(); + } + return ::CGDisplayHideCursor(display); +} + +static CGError MacPluginChildCGDisplayShowCursor(CGDirectDisplayID display) { + if (loadXULPtrs()) { + OnShowCursorPtr(); + } + return ::CGDisplayShowCursor(display); +} + +# pragma mark - + +struct interpose_substitution { + const void* replacement; + const void* original; +}; + +# define INTERPOSE_FUNCTION(function) \ + { \ + reinterpret_cast(MacPluginChild##function), \ + reinterpret_cast(function) \ + } + +__attribute__((used)) static const interpose_substitution substitutions[] + __attribute__((section("__DATA, __interpose"))) = { + INTERPOSE_FUNCTION(SetThemeCursor), + INTERPOSE_FUNCTION(CGDisplayHideCursor), + INTERPOSE_FUNCTION(CGDisplayShowCursor), + // SetCursor() and other QuickDraw APIs will probably be removed in OS X + // 10.8. But this will make 'SetCursor' NULL, which will just stop the OS + // from interposing it (tested using an INTERPOSE_FUNCTION_BROKEN macro + // that just sets the second address of each tuple to NULL). + INTERPOSE_FUNCTION(SetCursor), +}; + +#endif // !__LP64__ diff --git a/dom/plugins/ipc/moz.build b/dom/plugins/ipc/moz.build new file mode 100644 index 0000000000..62a726e25c --- /dev/null +++ b/dom/plugins/ipc/moz.build @@ -0,0 +1,149 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + DIRS += ["interpose"] + +EXPORTS.mozilla += [ + "PluginLibrary.h", +] + +EXPORTS.mozilla.plugins += [ + "AStream.h", + "BrowserStreamChild.h", + "BrowserStreamParent.h", + "ChildTimer.h", + "FunctionBrokerIPCUtils.h", + "IpdlTuple.h", + "NPEventAndroid.h", + "NPEventOSX.h", + "NPEventUnix.h", + "NPEventWindows.h", + "PluginBridge.h", + "PluginInstanceChild.h", + "PluginInstanceParent.h", + "PluginMessageUtils.h", + "PluginModuleChild.h", + "PluginModuleParent.h", + "PluginProcessChild.h", + "PluginProcessParent.h", + "PluginQuirks.h", + "PluginScriptableObjectChild.h", + "PluginScriptableObjectParent.h", + "PluginScriptableObjectUtils-inl.h", + "PluginScriptableObjectUtils.h", + "PluginUtilsOSX.h", + "StreamNotifyChild.h", + "StreamNotifyParent.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS.mozilla.plugins += [ + "PluginSurfaceParent.h", + ] + UNIFIED_SOURCES += [ + "PluginHangUIParent.cpp", + "PluginSurfaceParent.cpp", + ] + SOURCES += [ + "MiniShmParent.cpp", # Issues with CreateEvent + ] + DEFINES["MOZ_HANGUI_PROCESS_NAME"] = '"plugin-hang-ui%s"' % CONFIG["BIN_SUFFIX"] + LOCAL_INCLUDES += [ + "/widget", + "hangui", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + EXPORTS.mozilla.plugins += [ + "PluginInterposeOSX.h", + ] + +UNIFIED_SOURCES += [ + "BrowserStreamChild.cpp", + "BrowserStreamParent.cpp", + "ChildTimer.cpp", + "FunctionBroker.cpp", + "FunctionBrokerChild.cpp", + "FunctionBrokerIPCUtils.cpp", + "FunctionBrokerParent.cpp", + "FunctionHook.cpp", + "PluginBackgroundDestroyer.cpp", + "PluginInstanceParent.cpp", + "PluginMessageUtils.cpp", + "PluginModuleChild.cpp", + "PluginModuleParent.cpp", + "PluginProcessChild.cpp", + "PluginProcessParent.cpp", + "PluginQuirks.cpp", + "PluginScriptableObjectChild.cpp", + "PluginScriptableObjectParent.cpp", +] + +SOURCES += [ + "PluginInstanceChild.cpp", # 'PluginThreadCallback' : ambiguous symbol +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "PluginInterposeOSX.mm", + "PluginUtilsOSX.mm", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + EXPORTS.mozilla.plugins += [ + "PluginWidgetChild.h", + "PluginWidgetParent.h", + ] + UNIFIED_SOURCES += ["D3D11SurfaceHolder.cpp", "PluginUtilsWin.cpp"] + SOURCES += [ + "PluginWidgetChild.cpp", + "PluginWidgetParent.cpp", + ] + +IPDL_SOURCES += [ + "PBrowserStream.ipdl", + "PFunctionBroker.ipdl", + "PluginTypes.ipdlh", + "PPluginBackgroundDestroyer.ipdl", + "PPluginInstance.ipdl", + "PPluginModule.ipdl", + "PPluginScriptableObject.ipdl", + "PPluginSurface.ipdl", + "PStreamNotify.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") + +FINAL_LIBRARY = "xul" +LOCAL_INCLUDES += [ + "../base", + "/xpcom/base/", + "/xpcom/threads/", +] + +if CONFIG["MOZ_SANDBOX"] and CONFIG["OS_ARCH"] == "WINNT": + LOCAL_INCLUDES += [ + "/security/sandbox/chromium", + "/security/sandbox/chromium-shim", + "/security/sandbox/win/src/sandboxpermissions", + ] + +DEFINES["FORCE_PR_LOG"] = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] != "gtk": + CXXFLAGS += CONFIG["TK_CFLAGS"] +else: + # Force build against gtk+2 for struct offsets and such. + CXXFLAGS += CONFIG["MOZ_GTK2_CFLAGS"] + +CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] diff --git a/dom/plugins/test/crashtests/110650-1.html b/dom/plugins/test/crashtests/110650-1.html new file mode 100644 index 0000000000..9826227b03 --- /dev/null +++ b/dom/plugins/test/crashtests/110650-1.html @@ -0,0 +1,11 @@ + + +123246 testcase + + + + + + + + diff --git a/dom/plugins/test/crashtests/41276-1.html b/dom/plugins/test/crashtests/41276-1.html new file mode 100644 index 0000000000..ba35c34fa0 --- /dev/null +++ b/dom/plugins/test/crashtests/41276-1.html @@ -0,0 +1,28 @@ +Plugin Limit + + +Mozilla has a hardcoded limit of 10 simultaneously embedded plugins.
+If that limit is exceeded, plugin instances are prematurely destroyed (see the empty boxes below).
+Leave or reload a page that has a prematurely destroyed plugin and mozilla will crash.
+Sometimes, just loading this page will cause mozilla to crash.
+ + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ diff --git a/dom/plugins/test/crashtests/48856-1.html b/dom/plugins/test/crashtests/48856-1.html new file mode 100644 index 0000000000..cd0de2ab94 --- /dev/null +++ b/dom/plugins/test/crashtests/48856-1.html @@ -0,0 +1,10 @@ + + + + Mozilla Bug 48856 + + + + + diff --git a/dom/plugins/test/crashtests/539897-1.html b/dom/plugins/test/crashtests/539897-1.html new file mode 100644 index 0000000000..f280e62e6e --- /dev/null +++ b/dom/plugins/test/crashtests/539897-1.html @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/dom/plugins/test/crashtests/540114-1.html b/dom/plugins/test/crashtests/540114-1.html new file mode 100644 index 0000000000..8243649aa3 --- /dev/null +++ b/dom/plugins/test/crashtests/540114-1.html @@ -0,0 +1,40 @@ + + + + + + + + + + diff --git a/dom/plugins/test/crashtests/570884.html b/dom/plugins/test/crashtests/570884.html new file mode 100644 index 0000000000..7af5cdba53 --- /dev/null +++ b/dom/plugins/test/crashtests/570884.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/dom/plugins/test/crashtests/598862.html b/dom/plugins/test/crashtests/598862.html new file mode 100644 index 0000000000..02ffb05428 --- /dev/null +++ b/dom/plugins/test/crashtests/598862.html @@ -0,0 +1,77 @@ + + + + + + + + + + + diff --git a/dom/plugins/test/crashtests/626602-1.html b/dom/plugins/test/crashtests/626602-1.html new file mode 100644 index 0000000000..0a878bbd1d --- /dev/null +++ b/dom/plugins/test/crashtests/626602-1.html @@ -0,0 +1,109 @@ + + + + + + + + + +
+ + + + + diff --git a/dom/plugins/test/crashtests/752340.html b/dom/plugins/test/crashtests/752340.html new file mode 100644 index 0000000000..c4c8c464f5 --- /dev/null +++ b/dom/plugins/test/crashtests/752340.html @@ -0,0 +1,19 @@ + + + + + + + +
+ + diff --git a/dom/plugins/test/mochitest/plugin_test.html b/dom/plugins/test/mochitest/plugin_test.html new file mode 100644 index 0000000000..88b70e8ee6 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_test.html @@ -0,0 +1,16 @@ + + + + + + + +
+ + + +Navigate to about:blank + + + diff --git a/dom/plugins/test/mochitest/plugin_window.html b/dom/plugins/test/mochitest/plugin_window.html new file mode 100644 index 0000000000..d3a298e89c --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_window.html @@ -0,0 +1,23 @@ + + + + NPAPI Stream Tests + + +

+ +
+ +
+ + + + diff --git a/dom/plugins/test/mochitest/pluginstream.js b/dom/plugins/test/mochitest/pluginstream.js new file mode 100644 index 0000000000..c4ab769d51 --- /dev/null +++ b/dom/plugins/test/mochitest/pluginstream.js @@ -0,0 +1,46 @@ +SimpleTest.waitForExplicitFinish(); + +function frameLoaded(finishWhenCalled = true, lastObject = false) { + var testframe = document.getElementById("testframe"); + function getNode(list) { + if (list.length === 0) { + return undefined; + } + return lastObject ? list[list.length - 1] : list[0]; + } + var embed = getNode(document.getElementsByTagName("embed")); + if (undefined === embed) { + embed = getNode(document.getElementsByTagName("object")); + } + + // In the file:// URI case, this ends up being cross-origin. + // Skip these checks in that case. + if (testframe.contentDocument) { + var content = testframe.contentDocument.body.innerHTML; + if (!content.length) { + return; + } + + var filename = + embed.getAttribute("src") || + embed.getAttribute("geturl") || + embed.getAttribute("geturlnotify") || + embed.getAttribute("data"); + + var req = new XMLHttpRequest(); + req.open("GET", filename, false); + req.overrideMimeType("text/plain; charset=x-user-defined"); + req.send(null); + is(req.status, 200, "bad XMLHttpRequest status"); + is( + content, + req.responseText.replace(/\r\n/g, "\n"), + "content doesn't match" + ); + } + + is(embed.getError(), "pass", "plugin reported error"); + if (finishWhenCalled) { + SimpleTest.finish(); + } +} diff --git a/dom/plugins/test/mochitest/post.sjs b/dom/plugins/test/mochitest/post.sjs new file mode 100644 index 0000000000..b391dbdd81 --- /dev/null +++ b/dom/plugins/test/mochitest/post.sjs @@ -0,0 +1,17 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) +{ + var body = ""; + var bodyStream = new BinaryInputStream(request.bodyInputStream); + var bytes = [], avail = 0; + while ((avail = bodyStream.available()) > 0) + body += String.fromCharCode.apply(String, bodyStream.readByteArray(avail)); + + response.setHeader("Content-Type", "text/html", false); + response.write(body); +} + diff --git a/dom/plugins/test/mochitest/test_hanging.html b/dom/plugins/test/mochitest/test_hanging.html new file mode 100644 index 0000000000..b6dd1b12a7 --- /dev/null +++ b/dom/plugins/test/mochitest/test_hanging.html @@ -0,0 +1,59 @@ + + Plugin hanging + + + + + + diff --git a/dom/plugins/test/mochitest/test_mixed_case_mime.html b/dom/plugins/test/mochitest/test_mixed_case_mime.html new file mode 100644 index 0000000000..85e5f19ac9 --- /dev/null +++ b/dom/plugins/test/mochitest/test_mixed_case_mime.html @@ -0,0 +1,25 @@ + + + Test mixed case mimetype for plugins + + + + + +

+ + + + + diff --git a/dom/plugins/test/mochitest/test_plugin_fallback_focus.html b/dom/plugins/test/mochitest/test_plugin_fallback_focus.html new file mode 100644 index 0000000000..e89abb44df --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_fallback_focus.html @@ -0,0 +1,80 @@ + + + + + Test that plugins reject focus + + + + + +
+ + + +
+ + + + + diff --git a/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html b/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html new file mode 100644 index 0000000000..5fdd2a7b6c --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html @@ -0,0 +1,109 @@ + + + + Test for plugin child widgets not being invalidated by scrolling + + + + + + + +

+ +

+ +
+
+ + + + diff --git a/dom/plugins/test/mochitest/test_plugin_scroll_painting.html b/dom/plugins/test/mochitest/test_plugin_scroll_painting.html new file mode 100644 index 0000000000..1041b948da --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_scroll_painting.html @@ -0,0 +1,64 @@ + + + + Test that scrolling a windowless plugin doesn't force us to repaint it + + + + + + +

+ + + +
+
+ + + +
+ + + diff --git a/dom/plugins/test/mochitest/test_pluginstream_geturl.html b/dom/plugins/test/mochitest/test_pluginstream_geturl.html new file mode 100644 index 0000000000..fe69427a42 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_geturl.html @@ -0,0 +1,31 @@ + + + NPAPI NPN_GetURL NPStream Test + + + + + + + +

+ + + + + + + + \ No newline at end of file diff --git a/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html b/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html new file mode 100644 index 0000000000..ee4c2b119d --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html @@ -0,0 +1,30 @@ + + + NPAPI NPN_GetURLNotify Test + + + + + + + +

+ + + + + + + diff --git a/dom/plugins/test/mochitest/test_positioning.html b/dom/plugins/test/mochitest/test_positioning.html new file mode 100644 index 0000000000..b73f4b06fd --- /dev/null +++ b/dom/plugins/test/mochitest/test_positioning.html @@ -0,0 +1,56 @@ + + + + Test whether windowless plugins receive correct visible/invisible notifications. + + + + + + + +

+ + + + diff --git a/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html b/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html new file mode 100644 index 0000000000..565c1494f4 --- /dev/null +++ b/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html @@ -0,0 +1,31 @@ + + + NPAPI NPNVcontentsScaleFactor Test + + + + + + + + + + diff --git a/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html b/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html new file mode 100644 index 0000000000..8db018fd66 --- /dev/null +++ b/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html @@ -0,0 +1,31 @@ + + + NPAPI NPNVcontentsScaleFactor Test + + + + + + + + + + diff --git a/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html b/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html new file mode 100644 index 0000000000..fc54370a0b --- /dev/null +++ b/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html @@ -0,0 +1,33 @@ + + + + + + Test Refreshing navigator.plugins (bug 820708) + + + + + +

+ + + diff --git a/dom/plugins/test/moz.build b/dom/plugins/test/moz.build new file mode 100644 index 0000000000..7f40fb3cbe --- /dev/null +++ b/dom/plugins/test/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ["testplugin"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("gtk", "cocoa", "windows"): + MOCHITEST_MANIFESTS += ["mochitest/mochitest.ini"] + BROWSER_CHROME_MANIFESTS += ["mochitest/browser.ini"] diff --git a/dom/plugins/test/reftest/border-padding-1-ref.html b/dom/plugins/test/reftest/border-padding-1-ref.html new file mode 100644 index 0000000000..1a33644ac4 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-1-ref.html @@ -0,0 +1,10 @@ + + + +
+
+
+ + diff --git a/dom/plugins/test/reftest/border-padding-1.html b/dom/plugins/test/reftest/border-padding-1.html new file mode 100644 index 0000000000..6fa2446f40 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-1.html @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/dom/plugins/test/reftest/border-padding-2-ref.html b/dom/plugins/test/reftest/border-padding-2-ref.html new file mode 100644 index 0000000000..ae92da4032 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-2-ref.html @@ -0,0 +1,17 @@ + + + + + + +
+ + +
+ + diff --git a/dom/plugins/test/reftest/border-padding-2.html b/dom/plugins/test/reftest/border-padding-2.html new file mode 100644 index 0000000000..6a39d2d819 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-2.html @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/dom/plugins/test/reftest/border-padding-3-ref.html b/dom/plugins/test/reftest/border-padding-3-ref.html new file mode 100644 index 0000000000..5c7bb74564 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-3-ref.html @@ -0,0 +1,10 @@ + + + +
+
+ + + diff --git a/dom/plugins/test/reftest/border-padding-3.html b/dom/plugins/test/reftest/border-padding-3.html new file mode 100644 index 0000000000..4d240a7eb4 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-3.html @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/dom/plugins/test/reftest/div-alpha-opacity.html b/dom/plugins/test/reftest/div-alpha-opacity.html new file mode 100644 index 0000000000..fec913b640 --- /dev/null +++ b/dom/plugins/test/reftest/div-alpha-opacity.html @@ -0,0 +1,28 @@ + + + + + + +

+
+ + + diff --git a/dom/plugins/test/reftest/div-alpha-zindex.html b/dom/plugins/test/reftest/div-alpha-zindex.html new file mode 100644 index 0000000000..e4672b913b --- /dev/null +++ b/dom/plugins/test/reftest/div-alpha-zindex.html @@ -0,0 +1,27 @@ + + + + + + +
+
+ + + diff --git a/dom/plugins/test/reftest/div-sanity.html b/dom/plugins/test/reftest/div-sanity.html new file mode 100644 index 0000000000..9ffa539191 --- /dev/null +++ b/dom/plugins/test/reftest/div-sanity.html @@ -0,0 +1,17 @@ + + +div boxes + + + +
+
+
+
+ diff --git a/dom/plugins/test/reftest/plugin-alpha-opacity.html b/dom/plugins/test/reftest/plugin-alpha-opacity.html new file mode 100644 index 0000000000..2db6cc4de3 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-alpha-opacity.html @@ -0,0 +1,29 @@ + + + + + + + +
+ + + + diff --git a/dom/plugins/test/reftest/plugin-alpha-zindex.html b/dom/plugins/test/reftest/plugin-alpha-zindex.html new file mode 100644 index 0000000000..ead9b6f4ce --- /dev/null +++ b/dom/plugins/test/reftest/plugin-alpha-zindex.html @@ -0,0 +1,26 @@ + + + + + + + +
+ + + + diff --git a/dom/plugins/test/reftest/plugin-background-1-step.html b/dom/plugins/test/reftest/plugin-background-1-step.html new file mode 100644 index 0000000000..9498633b41 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-1-step.html @@ -0,0 +1,22 @@ + + + + + + + + +
Test some plugin stuff.
+
+ + + +
+
+
+
+ + diff --git a/dom/plugins/test/reftest/plugin-background-10-step.html b/dom/plugins/test/reftest/plugin-background-10-step.html new file mode 100644 index 0000000000..7a0824a565 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-10-step.html @@ -0,0 +1,22 @@ + + + + + + + + +
Test some plugin stuff.
+
+ + + +
+
+
+
+ + diff --git a/dom/plugins/test/reftest/plugin-background-2-step.html b/dom/plugins/test/reftest/plugin-background-2-step.html new file mode 100644 index 0000000000..cc186a5f29 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-2-step.html @@ -0,0 +1,22 @@ + + + + + + + + +
Test some plugin stuff.
+
+ + + +
+
+
+
+ + diff --git a/dom/plugins/test/reftest/plugin-background-5-step.html b/dom/plugins/test/reftest/plugin-background-5-step.html new file mode 100644 index 0000000000..2630719c88 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-5-step.html @@ -0,0 +1,22 @@ + + + + + + + + +
Test some plugin stuff.
+
+ + + +
+
+
+
+ + diff --git a/dom/plugins/test/reftest/plugin-background-ref.html b/dom/plugins/test/reftest/plugin-background-ref.html new file mode 100644 index 0000000000..651fdecef5 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-ref.html @@ -0,0 +1,17 @@ + + + + + + +
Test some plugin stuff.
+
+ +
+ +
+
+
+
+ + diff --git a/dom/plugins/test/reftest/plugin-background.css b/dom/plugins/test/reftest/plugin-background.css new file mode 100644 index 0000000000..f6b251214d --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.css @@ -0,0 +1,61 @@ +div { + position: absolute; +} +#bad { + left:220px; top:0px; + z-index: 0; +} +#good { + left:0px; top:0px; + width:220px; height:220px; + /* Core Animation alpha blending rounding differs + from the Core Graphics blending, adjust with care */ + background-color: rgba(0,255,0, 0.51); + z-index: 0; +} + +#topbar { + left:0px; top:0px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} +#topbar { + left:0px; top:0px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} +#leftbar { + left:0px; top:0px; + width:20px; height:220px; + background-color: rgb(0,0,0); + z-index: 2; +} +#rightbar { + left:200px; top:0px; + width:20px; height:220px; + background-color: rgb(0,0,0); + z-index: 2; +} +#bottombar { + left:0px; top:200px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} + +div#plugin { + position: absolute; + left:1px; top:1px; + width:199px; height:199px; + background-color: rgba(0,0,255, 0.2); + z-index: 1; +} + +embed#plugin { + position: absolute; + left:1px; top:1px; + z-index: 1; +} + diff --git a/dom/plugins/test/reftest/plugin-background.html b/dom/plugins/test/reftest/plugin-background.html new file mode 100644 index 0000000000..4cd1e3f538 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.html @@ -0,0 +1,20 @@ + + + + + + + +
Test some plugin stuff.
+
+ + + +
+
+
+
+ + diff --git a/dom/plugins/test/reftest/plugin-background.js b/dom/plugins/test/reftest/plugin-background.js new file mode 100644 index 0000000000..8c6d28572d --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.js @@ -0,0 +1,73 @@ +// The including script sets this for us +//var NUM_STEPS; + +var plugin; +var left = 1, top = 1, width = 199, height = 199; +function movePluginTo(x, y, w, h) { + left = x; top = y; width = w; height = h; + plugin.width = w; + plugin.height = h; + plugin.style.left = left + "px"; + plugin.style.top = top + "px"; +} +function deltaInBounds(dx,dy, dw,dh) { + var l = dx + left; + var r = l + width + dw; + var t = dy + top; + var b = t + height + dh; + return (0 <= l && l <= 20 && + 0 <= t && t <= 20 && + 200 <= r && r <= 220 && + 200 <= b && b <= 220); +} + +function start() { + window.removeEventListener("MozReftestInvalidate", start); + + window.addEventListener("MozAfterPaint", step); + window.addEventListener("MozPaintWaitFinished", step); + + plugin = document.getElementById("plugin"); + + movePluginTo(0,0, 200,200); +} + +var steps = 0; +var which = "move"; // or "grow" +var dx = 1, dy = 1, dw = 1, dh = 1; +function step() { + if (++steps >= NUM_STEPS) { + window.removeEventListener("MozAfterPaint", step); + window.removeEventListener("MozPaintWaitFinished", step); + return finish(); + } + + var didSomething = false; + if (which == "grow") { + if (deltaInBounds(0,0, dw,dh)) { + movePluginTo(left,top, width+dw, height+dh); + didSomething = true; + } else { + dw = -dw; dh = -dh; + } + } else { + // "move" + if (deltaInBounds(dx,dy, 0,0)) { + movePluginTo(left+dx,top+dy, width, height); + didSomething = true; + } else { + dx = -dx; dy = -dy; + } + } + which = (which == "grow") ? "move" : "grow"; + + if (!didSomething) { + step(); + } +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +window.addEventListener("MozReftestInvalidate", start); diff --git a/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html b/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html new file mode 100644 index 0000000000..e339dd2669 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html @@ -0,0 +1,56 @@ + + + + + + + + + + +
+ + + + + diff --git a/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html b/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html new file mode 100644 index 0000000000..517099d1b1 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/dom/plugins/test/reftest/plugin-sanity.html b/dom/plugins/test/reftest/plugin-sanity.html new file mode 100644 index 0000000000..4f9c30eee4 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-sanity.html @@ -0,0 +1,13 @@ + + + +Plugin boxes + + + + + + + + diff --git a/dom/plugins/test/reftest/plugin-transform-1-ref.html b/dom/plugins/test/reftest/plugin-transform-1-ref.html new file mode 100644 index 0000000000..259a78b41b --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-1-ref.html @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/dom/plugins/test/reftest/plugin-transform-1.html b/dom/plugins/test/reftest/plugin-transform-1.html new file mode 100644 index 0000000000..19f6e8c20f --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-1.html @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/dom/plugins/test/reftest/plugin-transform-2-ref.html b/dom/plugins/test/reftest/plugin-transform-2-ref.html new file mode 100644 index 0000000000..93a3924d7e --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-2-ref.html @@ -0,0 +1,7 @@ + + + +
+ + diff --git a/dom/plugins/test/reftest/plugin-transform-2.html b/dom/plugins/test/reftest/plugin-transform-2.html new file mode 100644 index 0000000000..7f48640c19 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-2.html @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html b/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html new file mode 100644 index 0000000000..52fda4bcf9 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html @@ -0,0 +1,28 @@ + + + + + + + +
+ + + + diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html b/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html new file mode 100644 index 0000000000..fafec34f43 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html @@ -0,0 +1,21 @@ + + + + Plugin Problem UI directionality test + + + + + + diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-1.html b/dom/plugins/test/reftest/pluginproblemui-direction-1.html new file mode 100644 index 0000000000..9888850dc9 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-1.html @@ -0,0 +1,21 @@ + + + + Plugin Problem UI directionality test + + + + + + diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html b/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html new file mode 100644 index 0000000000..e807b86b5b --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html @@ -0,0 +1,25 @@ + + + + Plugin Problem UI directionality test + + + +
+ + + + diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-2.html b/dom/plugins/test/reftest/pluginproblemui-direction-2.html new file mode 100644 index 0000000000..95b358e372 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-2.html @@ -0,0 +1,25 @@ + + + + Plugin Problem UI directionality test + + + +
+ + + + diff --git a/dom/plugins/test/reftest/reftest.list b/dom/plugins/test/reftest/reftest.list new file mode 100644 index 0000000000..2db57bab37 --- /dev/null +++ b/dom/plugins/test/reftest/reftest.list @@ -0,0 +1,26 @@ +# basic sanity checking +random-if(!haveTestPlugin) HTTP != plugin-sanity.html about:blank +fails-if(!haveTestPlugin) HTTP == plugin-sanity.html div-sanity.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-alpha-zindex.html div-alpha-zindex.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-164000) HTTP == plugin-alpha-opacity.html div-alpha-opacity.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) HTTP == windowless-clipping-1.html windowless-clipping-1-ref.html # bug 631832 +# fuzzy because of anti-aliasing in dashed border +fuzzy(0-16,0-256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) HTTP == border-padding-1.html border-padding-1-ref.html # bug 629430 +fuzzy(0-16,0-256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) HTTP == border-padding-2.html border-padding-2-ref.html # bug 629430 +fuzzy(0-16,0-256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin||Android) HTTP == border-padding-3.html border-padding-3-ref.html # bug 629430 # bug 773482 +# The following two "pluginproblemui-direction" tests are unreliable on all platforms. They should be re-written or replaced. +#random-if(cocoaWidget||d2d||/^Windows\x20NT\x205\.1/.test(http.oscpu)) fails-if(!haveTestPlugin&&!Android) HTTP == pluginproblemui-direction-1.html pluginproblemui-direction-1-ref.html # bug 567367 +#random-if(cocoaWidget) fails-if(!haveTestPlugin&&!Android) HTTP == pluginproblemui-direction-2.html pluginproblemui-direction-2-ref.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-canvas-alpha-zindex.html div-alpha-zindex.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-transform-alpha-zindex.html div-alpha-zindex.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-busy-alpha-zindex.html div-alpha-zindex.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-1-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-2-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-5-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-10-step.html plugin-background-ref.html +random-if(!haveTestPlugin) HTTP == plugin-transform-1.html plugin-transform-1-ref.html +fails-if(!haveTestPlugin) HTTP == plugin-transform-2.html plugin-transform-2-ref.html +skip-if(!haveTestPlugin) HTTP == shrink-1.html shrink-1-ref.html +pref(dom.mozPaintCount.enabled,true) skip-if(!haveTestPlugin) HTTP == update-1.html update-1-ref.html +skip-if(!haveTestPlugin) HTTP == windowless-layers.html windowless-layers-ref.html diff --git a/dom/plugins/test/reftest/shrink-1-ref.html b/dom/plugins/test/reftest/shrink-1-ref.html new file mode 100644 index 0000000000..0906fe5789 --- /dev/null +++ b/dom/plugins/test/reftest/shrink-1-ref.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/dom/plugins/test/reftest/shrink-1.html b/dom/plugins/test/reftest/shrink-1.html new file mode 100644 index 0000000000..a277e1afaa --- /dev/null +++ b/dom/plugins/test/reftest/shrink-1.html @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/dom/plugins/test/reftest/update-1-ref.html b/dom/plugins/test/reftest/update-1-ref.html new file mode 100644 index 0000000000..7303d19840 --- /dev/null +++ b/dom/plugins/test/reftest/update-1-ref.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/dom/plugins/test/reftest/update-1.html b/dom/plugins/test/reftest/update-1.html new file mode 100644 index 0000000000..cff85bc9f0 --- /dev/null +++ b/dom/plugins/test/reftest/update-1.html @@ -0,0 +1,62 @@ + + + + Test for bugs 807728 and 810426 + + + + + + + + diff --git a/dom/plugins/test/reftest/utils.js b/dom/plugins/test/reftest/utils.js new file mode 100644 index 0000000000..f046089afc --- /dev/null +++ b/dom/plugins/test/reftest/utils.js @@ -0,0 +1,18 @@ +function forceLoadPluginElement(id) { + var e = document.getElementById(id); + var found = e.pluginFoundElement; +} + +function forceLoadPlugin(ids, skipRemoveAttribute) { + if (Array.isArray(ids)) { + ids.forEach(function(element, index, array) { + forceLoadPluginElement(element); + }); + } else { + forceLoadPluginElement(ids); + } + if (skipRemoveAttribute) { + return; + } + document.documentElement.removeAttribute("class"); +} diff --git a/dom/plugins/test/reftest/windowless-clipping-1-ref.html b/dom/plugins/test/reftest/windowless-clipping-1-ref.html new file mode 100644 index 0000000000..e59ecb79b2 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-clipping-1-ref.html @@ -0,0 +1,14 @@ + + + + + +
+
+ +
+
+
+ + diff --git a/dom/plugins/test/reftest/windowless-clipping-1.html b/dom/plugins/test/reftest/windowless-clipping-1.html new file mode 100644 index 0000000000..dc1c25ac10 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-clipping-1.html @@ -0,0 +1,15 @@ + + + + + +
+ +
+
+ +
+ + diff --git a/dom/plugins/test/reftest/windowless-layers-ref.html b/dom/plugins/test/reftest/windowless-layers-ref.html new file mode 100644 index 0000000000..765527b68f --- /dev/null +++ b/dom/plugins/test/reftest/windowless-layers-ref.html @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/dom/plugins/test/reftest/windowless-layers.html b/dom/plugins/test/reftest/windowless-layers.html new file mode 100644 index 0000000000..9e24c13a68 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-layers.html @@ -0,0 +1,15 @@ + + + + + + +
+ +
+
+ +
+ + diff --git a/dom/plugins/test/testplugin/Info.plist b/dom/plugins/test/testplugin/Info.plist new file mode 100644 index 0000000000..dc6aa5cec3 --- /dev/null +++ b/dom/plugins/test/testplugin/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + libnptest.dylib + CFBundleIdentifier + org.mozilla.TestPlugin + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BRPL + CFBundleShortVersionString + 1.0.0.0 + CFBundleSignature + TEST + CFBundleVersion + 1.0.0.0 + WebPluginName + Test Plug-in + WebPluginDescription + Plug-in for testing purposes.™ (हिन्दी 中文 العربية) + WebPluginMIMETypes + + application/x-test + + WebPluginExtensions + + tst + + WebPluginTypeDescription + Test ™ mimetype + + + + diff --git a/dom/plugins/test/testplugin/README b/dom/plugins/test/testplugin/README new file mode 100644 index 0000000000..993de360ce --- /dev/null +++ b/dom/plugins/test/testplugin/README @@ -0,0 +1,424 @@ += Instructions for using the test plugin = + +== MIME type == + +The test plugin registers itself for the MIME type "application/x-test". + +== Event Model == + +* getEventModel() +Returns the NPAPI event model in use. On platforms without event models, +simply returns 0; + +== Rendering == + +By default, the plugin fills its rectangle with gray, with a black border, and +renders the user-agent string (obtained from NPN_UserAgent) centered in black. +This rendering method is not supported for the async drawing models. + +The test plugin supports the following parameters: + +* drawmode="solid" +The plugin will draw a solid color instead of the default rendering described +above. The default solid color is completely transparent black (i.e., nothing). +This should be specified when using one of the async models. + +* asyncmodel="bitmap" +The plugin will use the NPAPI Async Bitmap drawing model extension. On +unsupported platforms this will fallback to non-async rendering. + +* asyncmodel="dxgi" +The plugin will use the NPAPI Async DXGI drawing model extension. Only +supported on Windows Vista or higher. On unsupported platforms this will +fallback to non-async rendering. + +* color +This specifies the color to use for drawmode="solid". The value should be 8 hex +digits, 2 per channel in AARRGGBB format. + +== Generic API Tests == + +* setUndefinedValueTest +Attempts to set the value of an undefined variable (0x0) via NPN_SetValue, +returns true if it succeeds and false if it doesn't. It should never succeed. + +* .getReflector() +Hands back an object which reflects properties as values, e.g. + .getReflector().foo = 'foo' + .getReflector()['foo'] = 'foo' + .getReflector()[1] = 1 + +* .getNPNVdocumentOrigin() +Returns the origin string retrieved from the browser by a NPNVdocumentOrigin +variable request. Does not cache the value, gets it from the browser every time. + +== NPN_ConvertPoint testing == + +* convertPointX(sourceSpace, sourceX, sourceY, destSpace) +* convertPointY(sourceSpace, sourceX, sourceY, destSpace) +The plugin uses NPN_ConvertPoint to convert sourceX and sourceY from the source +to dest space and returns the X or Y result based on the call. + +== NPCocoaEventWindowFocusChanged == + +* getTopLevelWindowActivationState() +Returns the activation state for the top-level window as set by the last +NPCocoaEventWindowFocusChanged event. Returns true for active, false for +inactive, and throws an exception if the state is unknown (uninitialized). + +* getTopLevelWindowActivationEventCount() +Returns the number of NPCocoaEventWindowFocusChanged events received by +the instance. + +== Focus Tests == + +* getFocusState() +Returns the plugin's focus state. Returns true for focused, false for unfocused, +and throws an exception if the state is unknown (uninitialized). This does not +necessarily correspond to actual input focus - this corresponds to focus as +defined by the NPAPI event model in use. + +* getFocusEventCount() +Returns the number of focus events received by the instance. + +== NPRuntime testing == + +The test plugin object supports the following scriptable methods: + +* identifierToStringTest(ident) +Converts a string, int32 or double parameter 'ident' to an NPIdentifier and +then to a string, which is returned. + +* npnEvaluateTest(script) +Calls NPN_Evaluate on the 'script' argument, which is a string containing +some script to be executed. Returns the result of the evaluation. + +* npnInvokeTest(method, expected, args...) +Causes the plugin to call the specified script method using NPN_Invoke, +passing it 1 or more arguments specified in args. The return value of this +call is compared against 'expected', and if they match, npnInvokeTest will +return true. Otherwise, it will return false. + +* npnInvokeDefaultTest(object, argument) +Causes the plugin to call NPN_InvokeDefault on the specified object, +with the specified argument. Returns the result of the invocation. + +* getError() +If an error has occurred during the last stream or npruntime function, +this will return a string error message, otherwise it returns "pass". + +* throwExceptionNextInvoke() +Sets a flag which causes the next call to a scriptable method to throw +one or more exceptions. If no parameters are passed to the next +scriptable method call, it will cause a generic exception to be thrown. +Otherwise there will be one exception thrown per argument, with the argument +used as the exception message. Example: + + plugin.throwExceptionNextInvoke(); + plugin.npnInvokeTest("first exception message", "second exception message"); + +* () - default method +Returns a string consisting of the plugin name, concatenated with any +arguments passed to the method. + +* .crash() - Crashes the plugin + +* getObjectValue() - Returns a custom plugin-implemented scriptable object. +* checkObjectValue(obj) - Returns true if the object from getObjectValue() is + the same object passed into this function. It should return true when + the object is passed to the same plugin instance, and false when passed + to other plugin instances, see bug 532246 and + test_multipleinstanceobjects.html. + +* callOnDestroy(fn) - Calls `fn` when the plugin instance is being destroyed + +* getAuthInfo(protocol, host, port, scheme, realm) - a wrapper for +NPN_GetAuthenticationInfo(). Returns a string "username|password" for +the specified auth criteria, or throws an exception if no data is +available. + +* timerTest(callback) - initiates tests of NPN_ScheduleTimer & +NPN_UnscheduleTimer. When finished, calls the script callback +with a boolean value, indicating whether the tests were successful. + +* asyncCallbackTest(callback) - initiates tests of +NPN_PluginThreadAsyncCall. When finished, calls the script callback +with a boolean value, indicating whether the tests were successful. + +* paintscript="..." content attribute +If the "paintscript" attribute is set on the plugin element during plugin +initialization, then every time the plugin paints it gets the contents of that +attribute and evaluates it as a script in the context of the plugin's DOM +window. This is useful for testing evil plugin code that might, for example, +modify the DOM during painting. + +== Private browsing == + +The test plugin object supports the following scriptable methods: + +* queryPrivateModeState +Returns the value of NPN_GetValue(NPNVprivateModeBool). + +* lastReportedPrivateModeState +Returns the last value set by NPP_SetValue(NPNVprivateModeBool). + +== Windowed/windowless mode == + +The test plugin is windowless by default. + +The test plugin supports the following parameter: + +* wmode="window" +The plugin will be given a native widget on platforms where we support this +(Windows and X). + +The test plugin object supports the following scriptable method: + +* hasWidget() +Returns true if the plugin has an associated widget. This will return true if +wmode="window" was specified and the platform supports windowed plugins. + +== Plugin invalidation == + +* setColor(colorString) +Sets the color used for drawmode="solid" and invalidates the plugin area. +This calls NPN_Invalidate, even for windowed plugins, since that should work +for windowed plugins too (Silverlight depends on it). + +* getPaintCount() +Returns the number of times this plugin instance has processed a paint request. +This can be used to detect whether painting has happened in a plugin's +window. + +* getWidthAtLastPaint() +Returns the window width that was current when the plugin last painted. + +* setInvalidateDuringPaint(value) +When value is true, every time the plugin paints, it will invalidate +itself *during the paint* using NPN_Invalidate. + +* setSlowPaint(value) +When value is true, the instance will sleep briefly during paint. + +== Plugin geometry == + +The test plugin supports the following scriptable methods: + +* getEdge(edge) +Returns the integer screen pixel coordinate of an edge of the plugin's +area: +-- edge=0: returns left edge coordinate +-- edge=1: returns top edge coordinate +-- edge=2: returns right edge coordinate +-- edge=3: returns bottom edge coordinate +The coordinates are relative to the top-left corner of the top-level window +containing the plugin, including the window decorations. Therefore: +-- On Mac, they're relative to the top-left corner of the toplevel Cocoa +window. +-- On Windows, they're relative to the top-left corner of the toplevel HWND's +non-client area. +-- On GTK2, they're relative to the top-left corner of the toplevel window's +window manager frame. +This means they can be added to Gecko's window.screenX/screenY (if DPI is set +to 96) to get screen coordinates. +On the platforms that support window-mode plugins (Windows/GTK2), this only +works for window-mode plugins. It will throw an error for windowless plugins. + +* getClipRegionRectCount() +Returns the number of rectangles in the plugin's clip region. +For plugins with widgets, the clip region is computed as the intersection of the +clip region for the widget (if the platform does not support clip regions +on native widgets, this would just be the widget's rectangle) with the +clip regions of all ancestor widgets which would clip this widget. +On the platforms that support window-mode plugins (Windows/GTK2), this only +works for window-mode plugins. It will throw an error for windowless plugins. +On Mac, all plugins have a clip region containing just a single clip +rectangle only. So if you request wmode="window" but the plugin reports +!hasWidget, you can assume that complex clip regions are not supported. + +* getClipRegionRectEdge(i, edge) +Returns the integer screen pixel coordinate of an edge of a rectangle from the +plugin's clip region. If i is less than zero or greater than or equal to +getClipRegionRectCount(), this will throw an error. The coordinates are +the same as for getEdge. See getClipRegionRectCount() above for +notes on platform plugin limitations. + +== Keyboard events == + +* getLastKeyText() +Returns the text which was inputted by last keyboard events. This is cleared at +every keydown event. +NOTE: Currently, this is implemented only on Windows. + +== Mouse events == + +The test plugin supports the following scriptable methods: + +* getLastMouseX() +Returns the X coordinate of the last mouse event (move, button up, or +button down), relative to the left edge of the plugin, or -1 if no mouse +event has been received. + +* getLastMouseX() +Returns the Y coordinate of the last mouse event (move, button up, or +button down), relative to the top edge of the plugin, or -1 if no mouse +event has been received. + +== Instance lifecycle == + +The test plugin supports the following scriptable methods: + +* startWatchingInstanceCount() +Marks all currently running instances as "ignored". Throws an exception if +there is already a watch (startWatchingInstanceCount has already been +called on some instance without a corresponding stopWatchingInstanceCount). + +* getInstanceCount() +Returns the count of currently running instances that are not ignored. +Throws an exception if there is no watch. + +* stopWatchingInstanceCount() +Stops watching. Throws an exception if there is no watch. + +== NPAPI Timers == + +* unscheduleAllTimers() +Instructs the plugin instance to cancel all timers created via +NPN_ScheduleTimer. + +== Stream Functionality == + +The test plugin enables a variety of NPAPI streaming tests, which are +initiated by passing a variety of attributes to the element which +causes the plugin to be initialized. The plugin stream test code is +designed to receive a stream from the browser (by specifying a "src", +"geturl", or "geturlnotify" attribute), and then (if a "frame" attribute +is specified) send the data from that stream back to the browser in another +stream, whereupon it will be displayed in the specified frame. If some +error occurs during stream processing, an error message will appear in the +frame instead of the stream data. If no "frame" attribute is present, a +stream can still be received by the plugin, but the plugin will do nothing +with it. + +The attributes which control stream tests are: + +"streamchunksize": the number of bytes the plugin reports it can accept + in calls to NPP_WriteReady. Defaults to 1,024. + +"src": a url. If specified, the browser will call NPP_NewStream for + this url as soon as the plugin is initialized. + +"geturl": a url. If specified, the plugin will request this url + from the browser when the plugin is initialized, via a call to + NPN_GetURL. + +"geturlnotify": a url. If specified, the plugin will request this url + from the browser when the plugin is initialized, via a call to + NPN_GetURLNotify. The plugin passes some "notifyData" to + NPN_GetURLNotify, which it verifies is present in the call to + NPP_URLNotify. If the "notifyData" does not match, an error + will be displayed in the test frame (if any), instead of the stream + data. + +"frame": the name of a frame in the same HTML document as the + element which instantiated the plugin. For any of the preceding three + attributes, a stream is received by the plugin via calls to NPP_NewStream, + NPP_WriteReady, NPP_Write, and NPP_DestroyStream. When NPP_DestroyStream + is called (or NPP_UrlNotify, in the case of "geturlnotify"), and a + "frame" attribute is present, the data from the stream is converted into a + data: url, and sent back to the browser in another stream via a call to + NPN_GetURL, whereupon it should be displayed in the specified frame. + +"posturl": a url. After the plugin receives a stream, and NPP_DestroyStream + is called, if "posturl" is specified, the plugin will post the contents + of the stream to the specified url via NPN_PostURL. See "postmode" for + additional details. + +"postmode": either "frame" or "stream". If "frame", and a "frame" attribute + is present, the plugin will pass the frame name to calls to NPN_PostURL, + so that the HTTP response from that operation will be displayed in the + specified frame. If "stream", the HTTP response is delivered to the plugin + via calls to NPP_NewStream etc, and if a "frame" attribute is present, the + contents of that stream will be passed back to the browser and displayed + in the specified frame via NPN_GetURL. + +"newstream": if "true", then any stream which is sent to a frame in the browser + is sent via calls to NPN_NewStream and NPN_Write. Doing so will cause + the browser to store the stream data in a file, and set the frame's + location to the corresponding file:// url. + +"functiontofail": one of "npp_newstream", "npp_write", "npp_destroystream". + When specified, the given function will return an error code (-1 for + NPP_Write, or else the value of the "failurecode" attribute) the first time + it is called by the browser. + +"failurecode": one of the NPError constants. Used to specify the error + that will be returned by the "functiontofail". + +* streamTest(url, doPost, postData, writeCallback, notifyCallback, redirectCallback, allowRedirects, postFile = false) +This will test how NPN_GetURLNotify and NPN_PostURLNotify behave when they are +called with arbitrary (malformed) URLs. The function will return `true` if +NPN_[Get/Post]URLNotify succeeds, and `false` if it fails. +@url url to request +@param doPost whether to call NPN_PostURLNotify +@param postData null, or a string to send a postdata +@writeCallback will be called when data is received for the stream +@notifyCallback will be called when the urlnotify is received with the notify result +@redirectCallback will be called from urlredirectnotify if a redirect is attempted +@allowRedirects boolean value indicating whether or not to allow redirects +@postFile boolean optional, defaults to false, set to true if postData contains a filename + +* postFileToURLTest(url) +Calls NPN_PostURL/NPN_PostURLNotify to make a POST request to the URL with +request from postFile. +The function will return `0` if NPN_PostURL/NPN_PostURLNotify succeeds, and +the error code if it fails. +@param url string, url to request + +* setPluginWantsAllStreams(wantsAllStreams) +Set the value returned by the plugin for NPPVpluginWantsAllNetworkStreams. + +== Internal consistency == + +* doInternalConsistencyCheck() +Does internal consistency checking, returning an empty string if everything is +OK, otherwise returning some kind of error string. On Windows, in windowed +mode, this checks that the position of the plugin's internal child +window has not been disturbed relative to the plugin window. + +== Windows native widget message behaviour == + +* Mouse events are handled (saving the last mouse event coordinate) and not +passed to the overridden windowproc. + +* WM_MOUSEWHEEL events are handled and not passed to the parent window or the +overridden windowproc. + +* WM_MOUSEACTIVATE events are handled by calling SetFocus on the plugin's +widget, if the plugin is windowed. If it's not windowed they're passed to +the overriden windowproc (but hopefully never sent by the browser anyway). + +== FPU Control == + +x86-only on some OSes: + +* The .enableFPExceptions() method will enable floating-point exceptions, + as evil plugins or extensions might do. + +== HiDPI Mode == + +* queryContentsScaleFactor() +Returns the contents scale factor. On platforms without support for this query +always returns 1.0 (a double value). Likewise on hardware without HiDPI mode +support. + +== Plugin audio channel support == + +* startAudioPlayback() +Simulates the plugin starting to play back audio. + +* stopAudioPlayback() +Simulates the plugin stopping to play back audio. + +* audioMuted() +Returns the last value set by NPP_SetValue(NPNVmuteAudioBool). diff --git a/dom/plugins/test/testplugin/flashplugin/Info.plist b/dom/plugins/test/testplugin/flashplugin/Info.plist new file mode 100644 index 0000000000..0e6168e686 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + libnpswftest.dylib + CFBundleIdentifier + org.mozilla.FlashTestPlugin + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BRPL + CFBundleShortVersionString + 1.0.0.0 + CFBundleSignature + FLASHTEST + CFBundleVersion + 1.0.0.0 + WebPluginName + Shockwave Flash + WebPluginDescription + Flash plug-in for testing purposes. + WebPluginMIMETypes + + application/x-shockwave-flash-test + + WebPluginExtensions + + swf + + WebPluginTypeDescription + Flash test type + + + + diff --git a/dom/plugins/test/testplugin/flashplugin/moz.build b/dom/plugins/test/testplugin/flashplugin/moz.build new file mode 100644 index 0000000000..f66fb6eca4 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SharedLibrary("npswftest") + +relative_path = "flashplugin" +cocoa_name = "npswftest" +include("../testplugin.mozbuild") diff --git a/dom/plugins/test/testplugin/flashplugin/nptest.def b/dom/plugins/test/testplugin/flashplugin/nptest.def new file mode 100644 index 0000000000..3a62d05d95 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPSWFTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/flashplugin/nptest.rc b/dom/plugins/test/testplugin/flashplugin/nptest.rc new file mode 100644 index 0000000000..e970d26091 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest.rc @@ -0,0 +1,42 @@ +#include + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Flash plug-in for testing purposes." + VALUE "FileExtents", "swf" + VALUE "FileOpenName", "Flash test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npswftest" + VALUE "MIMEType", "application/x-shockwave-flash-test" + VALUE "OriginalFilename", "npswftest.dll" + VALUE "ProductName", "Shockwave Flash" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp b/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp new file mode 100644 index 0000000000..31f4f6321f --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const char* sPluginName = "Shockwave Flash"; +const char* sPluginDescription = "Flash plug-in for testing purposes."; +const char* sMimeDescription = + "application/x-shockwave-flash-test:swf:Flash test type"; diff --git a/dom/plugins/test/testplugin/moz.build b/dom/plugins/test/testplugin/moz.build new file mode 100644 index 0000000000..cb380e2db8 --- /dev/null +++ b/dom/plugins/test/testplugin/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ["secondplugin", "flashplugin"] + +SharedLibrary("nptest") + +relative_path = "." +cocoa_name = "Test" +include("testplugin.mozbuild") diff --git a/dom/plugins/test/testplugin/nptest.cpp b/dom/plugins/test/testplugin/nptest.cpp new file mode 100644 index 0000000000..2a9d20cdca --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.cpp @@ -0,0 +1,3282 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Dave Townsend + * Josh Aas + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest.h" +#include "nptest_utils.h" +#include "nptest_platform.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/IntentionalCrash.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef XP_WIN +# include +# include +# include +# define getpid _getpid +# define strcasecmp _stricmp +#else +# include +# include +#endif + +using std::list; +using std::ostringstream; +using std::string; + +#define PLUGIN_VERSION "1.0.0.0" + +extern const char* sPluginName; +extern const char* sPluginDescription; +static char sPluginVersion[] = PLUGIN_VERSION; + +// +// Intentional crash +// + +int gCrashCount = 0; + +static void Crash() { + int* pi = nullptr; + *pi = 55; // Crash dereferencing null pointer + ++gCrashCount; +} + +static void IntentionalCrash() { + mozilla::NoteIntentionalCrash("plugin"); + Crash(); +} + +// +// static data +// + +static NPNetscapeFuncs* sBrowserFuncs = nullptr; +static NPClass sNPClass; + +// +// identifiers +// + +typedef bool (*ScriptableFunction)(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +static bool npnEvaluateTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool npnInvokeTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool npnInvokeDefaultTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setUndefinedValueTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool identifierToStringTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool getClipRegionRectCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastMouseX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastMouseY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool resetPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getWidthAtLastPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setInvalidateDuringPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setSlowPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool doInternalConsistencyCheck(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setColor(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool throwExceptionNextInvoke(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool convertPointX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool convertPointY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool streamTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool postFileToURLTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setPluginWantsAllStreams(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool crashPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool crashOnDestroy(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool checkObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool hangPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool stallPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getClipboardText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool callOnDestroy(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool reinitWidget(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool triggerXError(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool propertyAndMethod(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getTopLevelWindowActivationState(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); +static bool getTopLevelWindowActivationEventCount(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); +static bool getFocusState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getFocusEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getEventModel(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getReflector(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool isVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool getWindowPosition(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool constructObject(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setSitesWithData(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastKeyText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getNPNVdocumentOrigin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getMouseUpEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool queryContentsScaleFactor(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool queryCSSZoomFactorGetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool queryCSSZoomFactorSetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool echoString(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool startAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool stopAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getAudioMuted(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastCompositionText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getInvokeDefaultObject(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +static const NPUTF8* sPluginMethodIdentifierNames[] = { + "npnEvaluateTest", + "npnInvokeTest", + "npnInvokeDefaultTest", + "setUndefinedValueTest", + "identifierToStringTest", + "timerTest", + "queryPrivateModeState", + "lastReportedPrivateModeState", + "hasWidget", + "getEdge", + "getClipRegionRectCount", + "getClipRegionRectEdge", + "startWatchingInstanceCount", + "getInstanceCount", + "stopWatchingInstanceCount", + "getLastMouseX", + "getLastMouseY", + "getPaintCount", + "resetPaintCount", + "getWidthAtLastPaint", + "setInvalidateDuringPaint", + "setSlowPaint", + "getError", + "doInternalConsistencyCheck", + "setColor", + "throwExceptionNextInvoke", + "convertPointX", + "convertPointY", + "streamTest", + "postFileToURLTest", + "setPluginWantsAllStreams", + "crash", + "crashOnDestroy", + "getObjectValue", + "checkObjectValue", + "enableFPExceptions", + "hang", + "stall", + "getClipboardText", + "callOnDestroy", + "reinitWidget", + "crashInNestedLoop", + "triggerXError", + "destroySharedGfxStuff", + "propertyAndMethod", + "getTopLevelWindowActivationState", + "getTopLevelWindowActivationEventCount", + "getFocusState", + "getFocusEventCount", + "getEventModel", + "getReflector", + "isVisible", + "getWindowPosition", + "constructObject", + "setSitesWithData", + "setSitesWithDataCapabilities", + "getLastKeyText", + "getNPNVdocumentOrigin", + "getMouseUpEventCount", + "queryContentsScaleFactor", + "queryCSSZoomFactorSetValue", + "queryCSSZoomFactorGetValue", + "echoString", + "startAudioPlayback", + "stopAudioPlayback", + "audioMuted", + "nativeWidgetIsVisible", + "getLastCompositionText", + "getInvokeDefaultObject", +}; +static NPIdentifier + sPluginMethodIdentifiers[MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames)]; +static const ScriptableFunction sPluginMethodFunctions[] = { + npnEvaluateTest, + npnInvokeTest, + npnInvokeDefaultTest, + setUndefinedValueTest, + identifierToStringTest, + timerTest, + queryPrivateModeState, + lastReportedPrivateModeState, + hasWidget, + getEdge, + getClipRegionRectCount, + getClipRegionRectEdge, + startWatchingInstanceCount, + getInstanceCount, + stopWatchingInstanceCount, + getLastMouseX, + getLastMouseY, + getPaintCount, + resetPaintCount, + getWidthAtLastPaint, + setInvalidateDuringPaint, + setSlowPaint, + getError, + doInternalConsistencyCheck, + setColor, + throwExceptionNextInvoke, + convertPointX, + convertPointY, + streamTest, + postFileToURLTest, + setPluginWantsAllStreams, + crashPlugin, + crashOnDestroy, + getObjectValue, + checkObjectValue, + enableFPExceptions, + hangPlugin, + stallPlugin, + getClipboardText, + callOnDestroy, + reinitWidget, + crashPluginInNestedLoop, + triggerXError, + destroySharedGfxStuff, + propertyAndMethod, + getTopLevelWindowActivationState, + getTopLevelWindowActivationEventCount, + getFocusState, + getFocusEventCount, + getEventModel, + getReflector, + isVisible, + getWindowPosition, + constructObject, + setSitesWithData, + setSitesWithDataCapabilities, + getLastKeyText, + getNPNVdocumentOrigin, + getMouseUpEventCount, + queryContentsScaleFactor, + queryCSSZoomFactorGetValue, + queryCSSZoomFactorSetValue, + echoString, + startAudioPlayback, + stopAudioPlayback, + getAudioMuted, + nativeWidgetIsVisible, + getLastCompositionText, + getInvokeDefaultObject, +}; + +static_assert(MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames) == + MOZ_ARRAY_LENGTH(sPluginMethodFunctions), + "Arrays should have the same size"); + +static const NPUTF8* sPluginPropertyIdentifierNames[] = {"propertyAndMethod"}; +static NPIdentifier sPluginPropertyIdentifiers[MOZ_ARRAY_LENGTH( + sPluginPropertyIdentifierNames)]; +static NPVariant + sPluginPropertyValues[MOZ_ARRAY_LENGTH(sPluginPropertyIdentifierNames)]; + +struct URLNotifyData { + const char* cookie; + NPObject* writeCallback; + NPObject* notifyCallback; + NPObject* redirectCallback; + bool allowRedirects; + uint32_t size; + char* data; +}; + +static URLNotifyData kNotifyData = {"static-cookie", nullptr, nullptr, nullptr, + false, 0, nullptr}; + +static const char* SUCCESS_STRING = "pass"; + +static bool sIdentifiersInitialized = false; + +struct timerEvent { + int32_t timerIdReceive; + int32_t timerIdSchedule; + uint32_t timerInterval; + bool timerRepeat; + int32_t timerIdUnschedule; +}; +static timerEvent timerEvents[] = { + // clang-format off + {-1, 0, 200, false, -1}, + {0, 0, 400, false, -1}, + {0, 0, 200, true, -1}, + {0, 1, 400, true, -1}, + {0, -1, 0, false, 0}, + {1, -1, 0, false, -1}, + {1, -1, 0, false, 1}, + // clang-format on +}; +static uint32_t currentTimerEventCount = 0; +static uint32_t totalTimerEvents = sizeof(timerEvents) / sizeof(timerEvent); + +/** + * Incremented for every startWatchingInstanceCount. + */ +static int32_t sCurrentInstanceCountWatchGeneration = 0; +/** + * Tracks the number of instances created or destroyed since the last + * startWatchingInstanceCount. + */ +static int32_t sInstanceCount = 0; +/** + * True when we've had a startWatchingInstanceCount with no corresponding + * stopWatchingInstanceCount. + */ +static bool sWatchingInstanceCount = false; + +/** + * A list representing sites for which the plugin has stored data. See + * NPP_ClearSiteData and NPP_GetSitesWithData. + */ +struct siteData { + string site; + uint64_t flags; + uint64_t age; +}; +static list* sSitesWithData; +static bool sClearByAgeSupported; + +static void initializeIdentifiers() { + if (!sIdentifiersInitialized) { + NPN_GetStringIdentifiers(sPluginMethodIdentifierNames, + MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames), + sPluginMethodIdentifiers); + NPN_GetStringIdentifiers(sPluginPropertyIdentifierNames, + MOZ_ARRAY_LENGTH(sPluginPropertyIdentifierNames), + sPluginPropertyIdentifiers); + + sIdentifiersInitialized = true; + + // Check whether nullptr is handled in NPN_GetStringIdentifiers + NPIdentifier IDList[2]; + static char const* const kIDNames[2] = {nullptr, "setCookie"}; + NPN_GetStringIdentifiers(const_cast(kIDNames), 2, IDList); + } +} + +static void clearIdentifiers() { + memset(sPluginMethodIdentifiers, 0, + MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers) * sizeof(NPIdentifier)); + memset(sPluginPropertyIdentifiers, 0, + MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers) * sizeof(NPIdentifier)); + + sIdentifiersInitialized = false; +} + +static void sendBufferToFrame(NPP instance) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + string outbuf; + if (!instanceData->npnNewStream) outbuf = "data:text/html,"; + const char* buf = reinterpret_cast(instanceData->streamBuf); + int32_t bufsize = instanceData->streamBufSize; + if (instanceData->err.str().length() > 0) { + outbuf.append(instanceData->err.str()); + } else if (bufsize > 0) { + outbuf.append(buf); + } else { + outbuf.append("Error: no data in buffer"); + } + + // Convert CRLF to LF, and escape most other non-alphanumeric chars. + for (size_t i = 0; i < outbuf.length(); i++) { + if (outbuf[i] == '\n') { + outbuf.replace(i, 1, "%0a"); + i += 2; + } else if (outbuf[i] == '\r') { + outbuf.replace(i, 1, ""); + i -= 1; + } else { + int ascii = outbuf[i]; + if (!((ascii >= ',' && ascii <= ';') || (ascii >= 'A' && ascii <= 'Z') || + (ascii >= 'a' && ascii <= 'z'))) { + char hex[10]; + sprintf(hex, "%%%x", ascii); + outbuf.replace(i, 1, hex); + i += 2; + } + } + } + + NPError err = + NPN_GetURL(instance, outbuf.c_str(), instanceData->frame.c_str()); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURL returned " << err; + } +} + +static void XPSleep(unsigned int seconds) { +#ifdef XP_WIN + Sleep(1000 * seconds); +#else + sleep(seconds); +#endif +} + +TestFunction getFuncFromString(const char* funcname) { + FunctionTable funcTable[] = { + {FUNCTION_NPP_NEWSTREAM, "npp_newstream"}, + {FUNCTION_NPP_WRITEREADY, "npp_writeready"}, + {FUNCTION_NPP_WRITE, "npp_write"}, + {FUNCTION_NPP_DESTROYSTREAM, "npp_destroystream"}, + {FUNCTION_NPP_WRITE_RPC, "npp_write_rpc"}, + {FUNCTION_NONE, nullptr}}; + int32_t i = 0; + while (funcTable[i].funcName) { + if (!strcmp(funcname, funcTable[i].funcName)) return funcTable[i].funcId; + i++; + } + return FUNCTION_NONE; +} + +static void DuplicateNPVariant(NPVariant& aDest, const NPVariant& aSrc) { + if (NPVARIANT_IS_STRING(aSrc)) { + NPString src = NPVARIANT_TO_STRING(aSrc); + char* buf = new char[src.UTF8Length]; + strncpy(buf, src.UTF8Characters, src.UTF8Length); + STRINGN_TO_NPVARIANT(buf, src.UTF8Length, aDest); + } else if (NPVARIANT_IS_OBJECT(aSrc)) { + NPObject* obj = NPN_RetainObject(NPVARIANT_TO_OBJECT(aSrc)); + OBJECT_TO_NPVARIANT(obj, aDest); + } else { + aDest = aSrc; + } +} + +static bool bug813906(NPP npp, const char* const function, + const char* const url, const char* const frame) { + NPObject* windowObj = nullptr; + NPError err = NPN_GetValue(npp, NPNVWindowNPObject, &windowObj); + if (err != NPERR_NO_ERROR) { + return false; + } + + NPVariant result; + bool res = NPN_Invoke(npp, windowObj, NPN_GetStringIdentifier(function), + nullptr, 0, &result); + NPN_ReleaseObject(windowObj); + if (!res) { + return false; + } + + NPN_ReleaseVariantValue(&result); + + err = NPN_GetURL(npp, url, frame); + if (err != NPERR_NO_ERROR) { + err = NPN_GetURL(npp, "about:blank", frame); + return false; + } + + return true; +} + +void drawAsyncBitmapColor(InstanceData* instanceData) { + NPP npp = instanceData->npp; + + uint32_t* pixelData = (uint32_t*)instanceData->backBuffer->bitmap.data; + + uint32_t rgba = instanceData->scriptableObject->drawColor; + + unsigned char subpixels[4]; + memcpy(subpixels, &rgba, sizeof(subpixels)); + + subpixels[0] = uint8_t(float(subpixels[3] * subpixels[0]) / 0xFF); + subpixels[1] = uint8_t(float(subpixels[3] * subpixels[1]) / 0xFF); + subpixels[2] = uint8_t(float(subpixels[3] * subpixels[2]) / 0xFF); + uint32_t premultiplied; + memcpy(&premultiplied, subpixels, sizeof(premultiplied)); + + for (uint32_t* lastPixel = + pixelData + instanceData->backBuffer->size.width * + instanceData->backBuffer->size.height; + pixelData < lastPixel; ++pixelData) { + *pixelData = premultiplied; + } + + NPN_SetCurrentAsyncSurface(npp, instanceData->backBuffer, NULL); + NPAsyncSurface* oldFront = instanceData->frontBuffer; + instanceData->frontBuffer = instanceData->backBuffer; + instanceData->backBuffer = oldFront; +} + +// +// function signatures +// + +NPObject* scriptableAllocate(NPP npp, NPClass* aClass); +void scriptableDeallocate(NPObject* npobj); +void scriptableInvalidate(NPObject* npobj); +bool scriptableHasMethod(NPObject* npobj, NPIdentifier name); +bool scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, + uint32_t argCount, NPVariant* result); +bool scriptableHasProperty(NPObject* npobj, NPIdentifier name); +bool scriptableGetProperty(NPObject* npobj, NPIdentifier name, + NPVariant* result); +bool scriptableSetProperty(NPObject* npobj, NPIdentifier name, + const NPVariant* value); +bool scriptableRemoveProperty(NPObject* npobj, NPIdentifier name); +bool scriptableEnumerate(NPObject* npobj, NPIdentifier** identifier, + uint32_t* count); +bool scriptableConstruct(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +// +// npapi plugin functions +// + +#ifdef XP_UNIX +NP_EXPORT(char*) +NP_GetPluginVersion() { return sPluginVersion; } +#endif + +extern const char* sMimeDescription; + +#if defined(XP_UNIX) +NP_EXPORT(const char*) +NP_GetMIMEDescription() +#elif defined(XP_WIN) +const char* NP_GetMIMEDescription() +#endif +{ + return sMimeDescription; +} + +#ifdef XP_UNIX +NP_EXPORT(NPError) +NP_GetValue(void* future, NPPVariable aVariable, void* aValue) { + switch (aVariable) { + case NPPVpluginNameString: + *((const char**)aValue) = sPluginName; + break; + case NPPVpluginDescriptionString: + *((const char**)aValue) = sPluginDescription; + break; + default: + return NPERR_INVALID_PARAM; + } + return NPERR_NO_ERROR; +} +#endif + +static bool fillPluginFunctionTable(NPPluginFuncs* pFuncs) { + // Check the size of the provided structure based on the offset of the + // last member we need. + if (pFuncs->size < + (offsetof(NPPluginFuncs, getsiteswithdata) + sizeof(void*))) + return false; + + pFuncs->newp = NPP_New; + pFuncs->destroy = NPP_Destroy; + pFuncs->setwindow = NPP_SetWindow; + pFuncs->newstream = NPP_NewStream; + pFuncs->destroystream = NPP_DestroyStream; + pFuncs->writeready = NPP_WriteReady; + pFuncs->write = NPP_Write; + pFuncs->print = NPP_Print; + pFuncs->event = NPP_HandleEvent; + pFuncs->urlnotify = NPP_URLNotify; + pFuncs->getvalue = NPP_GetValue; + pFuncs->setvalue = NPP_SetValue; + pFuncs->urlredirectnotify = NPP_URLRedirectNotify; + pFuncs->clearsitedata = NPP_ClearSiteData; + pFuncs->getsiteswithdata = NPP_GetSitesWithData; + + return true; +} + +#if defined(XP_MACOSX) +NP_EXPORT(NPError) +NP_Initialize(NPNetscapeFuncs* bFuncs) +#elif defined(XP_WIN) +NPError OSCALL NP_Initialize(NPNetscapeFuncs* bFuncs) +#elif defined(XP_UNIX) +NP_EXPORT(NPError) +NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs) +#endif +{ + sBrowserFuncs = bFuncs; + + initializeIdentifiers(); + + for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sPluginPropertyValues); i++) { + VOID_TO_NPVARIANT(sPluginPropertyValues[i]); + } + + memset(&sNPClass, 0, sizeof(NPClass)); + sNPClass.structVersion = NP_CLASS_STRUCT_VERSION; + sNPClass.allocate = (NPAllocateFunctionPtr)scriptableAllocate; + sNPClass.deallocate = (NPDeallocateFunctionPtr)scriptableDeallocate; + sNPClass.invalidate = (NPInvalidateFunctionPtr)scriptableInvalidate; + sNPClass.hasMethod = (NPHasMethodFunctionPtr)scriptableHasMethod; + sNPClass.invoke = (NPInvokeFunctionPtr)scriptableInvoke; + sNPClass.invokeDefault = nullptr; + sNPClass.hasProperty = (NPHasPropertyFunctionPtr)scriptableHasProperty; + sNPClass.getProperty = (NPGetPropertyFunctionPtr)scriptableGetProperty; + sNPClass.setProperty = (NPSetPropertyFunctionPtr)scriptableSetProperty; + sNPClass.removeProperty = + (NPRemovePropertyFunctionPtr)scriptableRemoveProperty; + sNPClass.enumerate = (NPEnumerationFunctionPtr)scriptableEnumerate; + sNPClass.construct = (NPConstructFunctionPtr)scriptableConstruct; + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + if (!fillPluginFunctionTable(pFuncs)) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } +#endif + + return NPERR_NO_ERROR; +} + +#if defined(XP_MACOSX) +NP_EXPORT(NPError) +NP_GetEntryPoints(NPPluginFuncs* pFuncs) +#elif defined(XP_WIN) +NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs) +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +{ + if (!fillPluginFunctionTable(pFuncs)) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } + + return NPERR_NO_ERROR; +} +#endif + +#if defined(XP_UNIX) +NP_EXPORT(NPError) +NP_Shutdown() +#elif defined(XP_WIN) + NPError OSCALL NP_Shutdown() +#endif +{ + clearIdentifiers(); + + for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sPluginPropertyValues); i++) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + } + + return NPERR_NO_ERROR; +} + +NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, + int16_t argc, char* argn[], char* argv[], NPSavedData* saved) { + // Make sure our pdata field is nullptr at this point. If it isn't, that + // probably means the browser gave us uninitialized memory. + if (instance->pdata) { + printf("NPP_New called with non-NULL NPP->pdata pointer!\n"); + return NPERR_GENERIC_ERROR; + } + + // Make sure we can render this plugin + NPBool browserSupportsWindowless = false; + NPN_GetValue(instance, NPNVSupportsWindowless, &browserSupportsWindowless); + if (!browserSupportsWindowless && !pluginSupportsWindowMode()) { + printf( + "Windowless mode not supported by the browser, windowed mode not " + "supported by the plugin!\n"); + return NPERR_GENERIC_ERROR; + } + + // set up our our instance data + InstanceData* instanceData = new InstanceData; + instanceData->npp = instance; + instanceData->testFunction = FUNCTION_NONE; + instanceData->functionToFail = FUNCTION_NONE; + instanceData->failureCode = 0; + instanceData->callOnDestroy = nullptr; + instanceData->streamChunkSize = 1024; + instanceData->streamBuf = nullptr; + instanceData->streamBufSize = 0; + instanceData->throwOnNextInvoke = false; + instanceData->runScriptOnPaint = false; + instanceData->dontTouchElement = false; + instanceData->hasWidget = false; + instanceData->npnNewStream = false; + instanceData->invalidateDuringPaint = false; + instanceData->slowPaint = false; + instanceData->playingAudio = false; + instanceData->audioMuted = false; + instanceData->writeCount = 0; + instanceData->writeReadyCount = 0; + memset(&instanceData->window, 0, sizeof(instanceData->window)); + instanceData->crashOnDestroy = false; + instanceData->cleanupWidget = true; // only used by nptest_gtk + instanceData->topLevelWindowActivationState = ACTIVATION_STATE_UNKNOWN; + instanceData->topLevelWindowActivationEventCount = 0; + instanceData->focusState = ACTIVATION_STATE_UNKNOWN; + instanceData->focusEventCount = 0; + instanceData->eventModel = 0; + instanceData->wantsAllStreams = false; + instanceData->mouseUpEventCount = 0; + instanceData->bugMode = -1; + instanceData->asyncDrawing = AD_NONE; + instanceData->frontBuffer = nullptr; + instanceData->backBuffer = nullptr; + instanceData->placeholderWnd = nullptr; + instanceData->cssZoomFactor = 1.0; + instance->pdata = instanceData; + + TestNPObject* scriptableObject = + (TestNPObject*)NPN_CreateObject(instance, &sNPClass); + if (!scriptableObject) { + printf( + "NPN_CreateObject failed to create an object, can't create a plugin " + "instance\n"); + delete instanceData; + return NPERR_GENERIC_ERROR; + } + scriptableObject->npp = instance; + scriptableObject->drawMode = DM_DEFAULT; + scriptableObject->drawColor = 0; + instanceData->scriptableObject = scriptableObject; + + instanceData->instanceCountWatchGeneration = + sCurrentInstanceCountWatchGeneration; + + AsyncDrawing requestAsyncDrawing = AD_NONE; + + bool requestWindow = false; + bool alreadyHasSalign = false; + // handle extra params + for (int i = 0; i < argc; i++) { + if (strcmp(argn[i], "drawmode") == 0) { + if (strcmp(argv[i], "solid") == 0) + scriptableObject->drawMode = DM_SOLID_COLOR; + } else if (strcmp(argn[i], "color") == 0) { + scriptableObject->drawColor = parseHexColor(argv[i], strlen(argv[i])); + } else if (strcmp(argn[i], "wmode") == 0) { + if (strcmp(argv[i], "window") == 0) { + requestWindow = true; + } + } else if (strcmp(argn[i], "asyncmodel") == 0) { + if (strcmp(argv[i], "bitmap") == 0) { + requestAsyncDrawing = AD_BITMAP; + } else if (strcmp(argv[i], "dxgi") == 0) { + requestAsyncDrawing = AD_DXGI; + } + } + if (strcmp(argn[i], "streamchunksize") == 0) { + instanceData->streamChunkSize = atoi(argv[i]); + } + if (strcmp(argn[i], "failurecode") == 0) { + instanceData->failureCode = atoi(argv[i]); + } + if (strcmp(argn[i], "functiontofail") == 0) { + instanceData->functionToFail = getFuncFromString(argv[i]); + } + if (strcmp(argn[i], "geturl") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_GETURL; + } + if (strcmp(argn[i], "posturl") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_POSTURL; + } + if (strcmp(argn[i], "geturlnotify") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_GETURLNOTIFY; + } + if (strcmp(argn[i], "postmode") == 0) { + if (strcmp(argv[i], "frame") == 0) { + instanceData->postMode = POSTMODE_FRAME; + } else if (strcmp(argv[i], "stream") == 0) { + instanceData->postMode = POSTMODE_STREAM; + } + } + if (strcmp(argn[i], "frame") == 0) { + instanceData->frame = argv[i]; + } + if (strcmp(argn[i], "newstream") == 0 && strcmp(argv[i], "true") == 0) { + instanceData->npnNewStream = true; + } + if (strcmp(argn[i], "newcrash") == 0) { + IntentionalCrash(); + } + if (strcmp(argn[i], "paintscript") == 0) { + instanceData->runScriptOnPaint = true; + } + + if (strcmp(argn[i], "donttouchelement") == 0) { + instanceData->dontTouchElement = true; + } + // "cleanupwidget" is only used with nptest_gtk, defaulting to true. It + // indicates whether the plugin should destroy its window in response to + // NPP_Destroy (or let the platform destroy the widget when the parent + // window gets destroyed). + if (strcmp(argn[i], "cleanupwidget") == 0 && + strcmp(argv[i], "false") == 0) { + instanceData->cleanupWidget = false; + } + if (strcmp(argn[i], "bugmode") == 0) { + instanceData->bugMode = atoi(argv[i]); + } + + // Bug 1307694 - There are two flash parameters that are order dependent for + // scaling/sizing the plugin. If they ever change from what is expected, it + // breaks flash on the web. In a test, if the scale tag ever happens + // with an salign before it, fail the plugin creation. + if (strcmp(argn[i], "scale") == 0) { + if (alreadyHasSalign) { + // If salign came before this parameter, error out now. + return NPERR_GENERIC_ERROR; + } + } + if (strcmp(argn[i], "salign") == 0) { + alreadyHasSalign = true; + } + } + + if (!browserSupportsWindowless || !pluginSupportsWindowlessMode()) { + requestWindow = true; + } else if (!pluginSupportsWindowMode()) { + requestWindow = false; + } + if (requestWindow) { + instanceData->hasWidget = true; + } else { + // NPPVpluginWindowBool should default to true, so we may as well + // test that by not setting it in the window case + NPN_SetValue(instance, NPPVpluginWindowBool, (void*)false); + } + + if (scriptableObject->drawMode == DM_SOLID_COLOR && + (scriptableObject->drawColor & 0xFF000000) != 0xFF000000) { + NPN_SetValue(instance, NPPVpluginTransparentBool, (void*)true); + } + + if (requestAsyncDrawing == AD_BITMAP) { + NPBool supportsAsyncBitmap = false; + if ((NPN_GetValue(instance, NPNVsupportsAsyncBitmapSurfaceBool, + &supportsAsyncBitmap) == NPERR_NO_ERROR) && + supportsAsyncBitmap) { + if (NPN_SetValue(instance, NPPVpluginDrawingModel, + (void*)NPDrawingModelAsyncBitmapSurface) == + NPERR_NO_ERROR) { + instanceData->asyncDrawing = AD_BITMAP; + } + } + } +#ifdef XP_WIN + else if (requestAsyncDrawing == AD_DXGI) { + NPBool supportsAsyncDXGI = false; + if ((NPN_GetValue(instance, NPNVsupportsAsyncWindowsDXGISurfaceBool, + &supportsAsyncDXGI) == NPERR_NO_ERROR) && + supportsAsyncDXGI) { + if (NPN_SetValue(instance, NPPVpluginDrawingModel, + (void*)NPDrawingModelAsyncWindowsDXGISurface) == + NPERR_NO_ERROR) { + instanceData->asyncDrawing = AD_DXGI; + } + } + } +#endif + + // If we can't get the right drawing mode, we fail, otherwise our tests might + // appear to be passing when they shouldn't. Real plugins should not do this. + if (instanceData->asyncDrawing != requestAsyncDrawing) { + return NPERR_GENERIC_ERROR; + } + + instanceData->lastReportedPrivateModeState = false; + instanceData->lastMouseX = instanceData->lastMouseY = -1; + instanceData->widthAtLastPaint = -1; + instanceData->paintCount = 0; + + // do platform-specific initialization + NPError err = pluginInstanceInit(instanceData); + if (err != NPERR_NO_ERROR) { + NPN_ReleaseObject(scriptableObject); + delete instanceData; + return err; + } + + NPVariant variantTrue; + BOOLEAN_TO_NPVARIANT(true, variantTrue); + NPObject* o = nullptr; + + // Set a property on NPNVPluginElementNPObject, unless the consumer explicitly + // opted out of this behavior. + if (!instanceData->dontTouchElement) { + err = NPN_GetValue(instance, NPNVPluginElementNPObject, &o); + if (err == NPERR_NO_ERROR) { + NPN_SetProperty(instance, o, + NPN_GetStringIdentifier("pluginFoundElement"), + &variantTrue); + NPN_ReleaseObject(o); + o = nullptr; + } + } + + // Set a property on NPNVWindowNPObject + err = NPN_GetValue(instance, NPNVWindowNPObject, &o); + if (err == NPERR_NO_ERROR) { + NPN_SetProperty(instance, o, NPN_GetStringIdentifier("pluginFoundWindow"), + &variantTrue); + NPN_ReleaseObject(o); + o = nullptr; + } + + ++sInstanceCount; + + if (instanceData->testFunction == FUNCTION_NPP_GETURL) { + NPError err = NPN_GetURL(instance, instanceData->testUrl.c_str(), nullptr); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURL returned " << err; + } + } else if (instanceData->testFunction == FUNCTION_NPP_GETURLNOTIFY) { + NPError err = NPN_GetURLNotify(instance, instanceData->testUrl.c_str(), + nullptr, static_cast(&kNotifyData)); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURLNotify returned " << err; + } + } + + if ((instanceData->bugMode == 813906) && instanceData->frame.length()) { + bug813906(instance, "f", "browser.xhtml", instanceData->frame.c_str()); + } + + return NPERR_NO_ERROR; +} + +NPError NPP_Destroy(NPP instance, NPSavedData** save) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->crashOnDestroy) IntentionalCrash(); + + if (instanceData->callOnDestroy) { + NPVariant result; + NPN_InvokeDefault(instance, instanceData->callOnDestroy, nullptr, 0, + &result); + NPN_ReleaseVariantValue(&result); + NPN_ReleaseObject(instanceData->callOnDestroy); + } + + if (instanceData->streamBuf) { + free(instanceData->streamBuf); + } + + if (instanceData->frontBuffer) { + NPN_SetCurrentAsyncSurface(instance, nullptr, nullptr); + NPN_FinalizeAsyncSurface(instance, instanceData->frontBuffer); + NPN_MemFree(instanceData->frontBuffer); + } + if (instanceData->backBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->backBuffer); + NPN_MemFree(instanceData->backBuffer); + } + + pluginInstanceShutdown(instanceData); + NPN_ReleaseObject(instanceData->scriptableObject); + + if (sCurrentInstanceCountWatchGeneration == + instanceData->instanceCountWatchGeneration) { + --sInstanceCount; + } + delete instanceData; + + return NPERR_NO_ERROR; +} + +NPError NPP_SetWindow(NPP instance, NPWindow* window) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->scriptableObject->drawMode == DM_DEFAULT && + (instanceData->window.width != window->width || + instanceData->window.height != window->height)) { + NPRect r; + r.left = r.top = 0; + r.right = window->width; + r.bottom = window->height; + NPN_InvalidateRect(instance, &r); + } + + void* oldWindow = instanceData->window.window; + pluginDoSetWindow(instanceData, window); + if (instanceData->hasWidget && oldWindow != instanceData->window.window) { + pluginWidgetInit(instanceData, oldWindow); + } + + if (instanceData->asyncDrawing != AD_NONE) { + if (instanceData->frontBuffer && + instanceData->frontBuffer->size.width >= 0 && + (uint32_t)instanceData->frontBuffer->size.width == window->width && + instanceData->frontBuffer->size.height >= 0 && + (uint32_t)instanceData->frontBuffer->size.height == window->height) { + return NPERR_NO_ERROR; + } + if (instanceData->frontBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->frontBuffer); + NPN_MemFree(instanceData->frontBuffer); + } + if (instanceData->backBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->backBuffer); + NPN_MemFree(instanceData->backBuffer); + } + instanceData->frontBuffer = + (NPAsyncSurface*)NPN_MemAlloc(sizeof(NPAsyncSurface)); + instanceData->backBuffer = + (NPAsyncSurface*)NPN_MemAlloc(sizeof(NPAsyncSurface)); + + NPSize size; + size.width = window->width; + size.height = window->height; + + memcpy(instanceData->backBuffer, instanceData->frontBuffer, + sizeof(NPAsyncSurface)); + + NPN_InitAsyncSurface(instance, &size, NPImageFormatBGRA32, nullptr, + instanceData->frontBuffer); + NPN_InitAsyncSurface(instance, &size, NPImageFormatBGRA32, nullptr, + instanceData->backBuffer); + +#if defined(XP_WIN) + if (instanceData->asyncDrawing == AD_DXGI) { + if (!setupDxgiSurfaces(instance, instanceData)) { + return NPERR_GENERIC_ERROR; + } + } +#endif + } + + if (instanceData->asyncDrawing == AD_BITMAP) { + drawAsyncBitmapColor(instanceData); + } +#if defined(XP_WIN) + else if (instanceData->asyncDrawing == AD_DXGI) { + drawDxgiBitmapColor(instanceData); + } +#endif + + return NPERR_NO_ERROR; +} + +NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM && + instanceData->failureCode) { + instanceData->err << SUCCESS_STRING; + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + return instanceData->failureCode; + } + + if (stream->notifyData && + static_cast(stream->notifyData) != &kNotifyData) { + // stream from streamTest + *stype = NP_NORMAL; + } else { + *stype = NP_NORMAL; + + if (instanceData->streamBufSize) { + free(instanceData->streamBuf); + instanceData->streamBufSize = 0; + if (instanceData->testFunction == FUNCTION_NPP_POSTURL && + instanceData->postMode == POSTMODE_STREAM) { + instanceData->testFunction = FUNCTION_NPP_GETURL; + } else { + // We already got a stream and didn't ask for another one. + instanceData->err << "Received unexpected multiple NPP_NewStream"; + } + } + } + return NPERR_NO_ERROR; +} + +NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_DestroyStream called"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE) { + if (instanceData->writeCount == 1) + instanceData->err << SUCCESS_STRING; + else + instanceData->err << "NPP_Write called after returning -1"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_DESTROYSTREAM && + instanceData->failureCode) { + instanceData->err << SUCCESS_STRING; + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + return instanceData->failureCode; + } + + URLNotifyData* nd = static_cast(stream->notifyData); + if (nd && nd != &kNotifyData) { + return NPERR_NO_ERROR; + } + + if (instanceData->frame.length() > 0 && + instanceData->testFunction != FUNCTION_NPP_GETURLNOTIFY && + instanceData->testFunction != FUNCTION_NPP_POSTURL) { + sendBufferToFrame(instance); + } + if (instanceData->testFunction == FUNCTION_NPP_POSTURL) { + NPError err = NPN_PostURL( + instance, instanceData->testUrl.c_str(), + instanceData->postMode == POSTMODE_FRAME ? instanceData->frame.c_str() + : nullptr, + instanceData->streamBufSize, + reinterpret_cast(instanceData->streamBuf), false); + if (err != NPERR_NO_ERROR) + instanceData->err << "Error: NPN_PostURL returned error value " << err; + } + return NPERR_NO_ERROR; +} + +int32_t NPP_WriteReady(NPP instance, NPStream* stream) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->writeReadyCount++; + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_WriteReady called"; + } + + // temporarily disabled per bug 519870 + // if (instanceData->writeReadyCount == 1) { + // return 0; + //} + + return instanceData->streamChunkSize; +} + +int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, + void* buffer) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->writeCount++; + + // temporarily disabled per bug 519870 + // if (instanceData->writeReadyCount == 1) { + // instanceData->err << "NPP_Write called even though NPP_WriteReady " << + // "returned 0"; + //} + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE_RPC) { + // Make an RPC call and pretend to consume the data + NPObject* windowObject = nullptr; + NPN_GetValue(instance, NPNVWindowNPObject, &windowObject); + if (windowObject) NPN_ReleaseObject(windowObject); + + return len; + } + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_Write called"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE) { + return -1; + } + + URLNotifyData* nd = static_cast(stream->notifyData); + + if (nd && nd->writeCallback) { + NPVariant args[1]; + STRINGN_TO_NPVARIANT(stream->url, strlen(stream->url), args[0]); + + NPVariant result; + NPN_InvokeDefault(instance, nd->writeCallback, args, 1, &result); + NPN_ReleaseVariantValue(&result); + } + + if (nd && nd != &kNotifyData) { + uint32_t newsize = nd->size + len; + nd->data = (char*)realloc(nd->data, newsize); + memcpy(nd->data + nd->size, buffer, len); + nd->size = newsize; + return len; + } + + char* streamBuf = reinterpret_cast(instanceData->streamBuf); + if (offset + len <= instanceData->streamBufSize) { + if (memcmp(buffer, streamBuf + offset, len)) { + instanceData->err << "Error: data written doesn't match"; + } else { + printf("data matches!\n"); + } + } else { + if (instanceData->streamBufSize == 0) { + instanceData->streamBuf = malloc(len + 1); + streamBuf = reinterpret_cast(instanceData->streamBuf); + } else { + instanceData->streamBuf = + realloc(reinterpret_cast(instanceData->streamBuf), + instanceData->streamBufSize + len + 1); + streamBuf = reinterpret_cast(instanceData->streamBuf); + } + memcpy(streamBuf + instanceData->streamBufSize, buffer, len); + instanceData->streamBufSize = instanceData->streamBufSize + len; + streamBuf[instanceData->streamBufSize] = '\0'; + } + return len; +} + +void NPP_Print(NPP instance, NPPrint* platformPrint) {} + +int16_t NPP_HandleEvent(NPP instance, void* event) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + return pluginHandleEvent(instanceData, event); +} + +void NPP_URLNotify(NPP instance, const char* url, NPReason reason, + void* notifyData) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + URLNotifyData* ndata = static_cast(notifyData); + + if (&kNotifyData == ndata) { + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + } else if (!strcmp(ndata->cookie, "dynamic-cookie")) { + if (ndata->notifyCallback) { + NPVariant args[2]; + INT32_TO_NPVARIANT(reason, args[0]); + if (ndata->data) { + STRINGN_TO_NPVARIANT(ndata->data, ndata->size, args[1]); + } else { + STRINGN_TO_NPVARIANT("", 0, args[1]); + } + + NPVariant result; + NPN_InvokeDefault(instance, ndata->notifyCallback, args, 2, &result); + NPN_ReleaseVariantValue(&result); + } + + // clean up the URLNotifyData + if (ndata->writeCallback) { + NPN_ReleaseObject(ndata->writeCallback); + } + if (ndata->notifyCallback) { + NPN_ReleaseObject(ndata->notifyCallback); + } + if (ndata->redirectCallback) { + NPN_ReleaseObject(ndata->redirectCallback); + } + free(ndata->data); + delete ndata; + } else { + printf("ERROR! NPP_URLNotify called with wrong cookie\n"); + instanceData->err << "Error: NPP_URLNotify called with wrong cookie"; + } +} + +NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value) { + InstanceData* instanceData = (InstanceData*)instance->pdata; + if (variable == NPPVpluginScriptableNPObject) { + NPObject* object = instanceData->scriptableObject; + NPN_RetainObject(object); + *((NPObject**)value) = object; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginNeedsXEmbed) { + // Only relevant for X plugins + // use 4-byte writes like some plugins may do + *(uint32_t*)value = instanceData->hasWidget; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginWantsAllNetworkStreams) { + // use 4-byte writes like some plugins may do + *(uint32_t*)value = instanceData->wantsAllStreams; + return NPERR_NO_ERROR; + } + + return NPERR_GENERIC_ERROR; +} + +NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value) { + if (variable == NPNVprivateModeBool) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->lastReportedPrivateModeState = + bool(*static_cast(value)); + return NPERR_NO_ERROR; + } + if (variable == NPNVmuteAudioBool) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->audioMuted = bool(*static_cast(value)); + return NPERR_NO_ERROR; + } + if (variable == NPNVCSSZoomFactor) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->cssZoomFactor = *static_cast(value); + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; +} + +void NPP_URLRedirectNotify(NPP instance, const char* url, int32_t status, + void* notifyData) { + if (notifyData) { + URLNotifyData* nd = static_cast(notifyData); + if (nd->redirectCallback) { + NPVariant args[2]; + STRINGN_TO_NPVARIANT(url, strlen(url), args[0]); + INT32_TO_NPVARIANT(status, args[1]); + + NPVariant result; + NPN_InvokeDefault(instance, nd->redirectCallback, args, 2, &result); + NPN_ReleaseVariantValue(&result); + } + NPN_URLRedirectResponse(instance, notifyData, nd->allowRedirects); + return; + } + NPN_URLRedirectResponse(instance, notifyData, true); +} + +NPError NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge) { + if (!sSitesWithData) return NPERR_NO_ERROR; + + // Error condition: no support for clear-by-age + if (!sClearByAgeSupported && maxAge != uint64_t(int64_t(-1))) + return NPERR_TIME_RANGE_NOT_SUPPORTED; + + // Iterate over list and remove matches + list::iterator iter = sSitesWithData->begin(); + list::iterator end = sSitesWithData->end(); + while (iter != end) { + const siteData& data = *iter; + list::iterator next = iter; + ++next; + if ((!site || data.site.compare(site) == 0) && + (flags == NP_CLEAR_ALL || data.flags & flags) && data.age <= maxAge) { + sSitesWithData->erase(iter); + } + iter = next; + } + + return NPERR_NO_ERROR; +} + +char** NPP_GetSitesWithData() { + int length = 0; + char** result; + + if (sSitesWithData) length = sSitesWithData->size(); + + // Allocate the maximum possible size the list could be. + result = static_cast(NPN_MemAlloc((length + 1) * sizeof(char*))); + result[length] = nullptr; + + if (length == 0) { + // Represent the no site data case as an array of length 1 with a nullptr + // entry. + return result; + } + + // Iterate the list of stored data, and build a list of strings. + list sites; + { + list::iterator iter = sSitesWithData->begin(); + list::iterator end = sSitesWithData->end(); + for (; iter != end; ++iter) { + const siteData& data = *iter; + sites.push_back(data.site); + } + } + + // Remove duplicate strings. + sites.sort(); + sites.unique(); + + // Add strings to the result array, and null terminate. + { + int i = 0; + list::iterator iter = sites.begin(); + list::iterator end = sites.end(); + for (; iter != end; ++iter, ++i) { + const string& site = *iter; + result[i] = static_cast(NPN_MemAlloc(site.length() + 1)); + memcpy(result[i], site.c_str(), site.length() + 1); + } + } + result[sites.size()] = nullptr; + + return result; +} + +// +// npapi browser functions +// + +bool NPN_SetProperty(NPP instance, NPObject* obj, NPIdentifier propertyName, + const NPVariant* value) { + return sBrowserFuncs->setproperty(instance, obj, propertyName, value); +} + +NPIdentifier NPN_GetIntIdentifier(int32_t intid) { + return sBrowserFuncs->getintidentifier(intid); +} + +NPIdentifier NPN_GetStringIdentifier(const NPUTF8* name) { + return sBrowserFuncs->getstringidentifier(name); +} + +void NPN_GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers) { + return sBrowserFuncs->getstringidentifiers(names, nameCount, identifiers); +} + +bool NPN_IdentifierIsString(NPIdentifier identifier) { + return sBrowserFuncs->identifierisstring(identifier); +} + +NPUTF8* NPN_UTF8FromIdentifier(NPIdentifier identifier) { + return sBrowserFuncs->utf8fromidentifier(identifier); +} + +int32_t NPN_IntFromIdentifier(NPIdentifier identifier) { + return sBrowserFuncs->intfromidentifier(identifier); +} + +NPError NPN_GetValue(NPP instance, NPNVariable variable, void* value) { + return sBrowserFuncs->getvalue(instance, variable, value); +} + +NPError NPN_SetValue(NPP instance, NPPVariable variable, void* value) { + return sBrowserFuncs->setvalue(instance, variable, value); +} + +void NPN_InvalidateRect(NPP instance, NPRect* rect) { + sBrowserFuncs->invalidaterect(instance, rect); +} + +bool NPN_HasProperty(NPP instance, NPObject* obj, NPIdentifier propertyName) { + return sBrowserFuncs->hasproperty(instance, obj, propertyName); +} + +NPObject* NPN_CreateObject(NPP instance, NPClass* aClass) { + return sBrowserFuncs->createobject(instance, aClass); +} + +bool NPN_Invoke(NPP npp, NPObject* obj, NPIdentifier methodName, + const NPVariant* args, uint32_t argCount, NPVariant* result) { + return sBrowserFuncs->invoke(npp, obj, methodName, args, argCount, result); +} + +bool NPN_InvokeDefault(NPP npp, NPObject* obj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return sBrowserFuncs->invokeDefault(npp, obj, args, argCount, result); +} + +bool NPN_Construct(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return sBrowserFuncs->construct(npp, npobj, args, argCount, result); +} + +const char* NPN_UserAgent(NPP instance) { + return sBrowserFuncs->uagent(instance); +} + +NPObject* NPN_RetainObject(NPObject* obj) { + return sBrowserFuncs->retainobject(obj); +} + +void NPN_ReleaseObject(NPObject* obj) { + return sBrowserFuncs->releaseobject(obj); +} + +void* NPN_MemAlloc(uint32_t size) { return sBrowserFuncs->memalloc(size); } + +char* NPN_StrDup(const char* str) { + return strcpy((char*)sBrowserFuncs->memalloc(strlen(str) + 1), str); +} + +void NPN_MemFree(void* ptr) { return sBrowserFuncs->memfree(ptr); } + +uint32_t NPN_ScheduleTimer(NPP instance, uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)) { + return sBrowserFuncs->scheduletimer(instance, interval, repeat, timerFunc); +} + +void NPN_UnscheduleTimer(NPP instance, uint32_t timerID) { + return sBrowserFuncs->unscheduletimer(instance, timerID); +} + +void NPN_ReleaseVariantValue(NPVariant* variant) { + return sBrowserFuncs->releasevariantvalue(variant); +} + +NPError NPN_GetURLNotify(NPP instance, const char* url, const char* target, + void* notifyData) { + return sBrowserFuncs->geturlnotify(instance, url, target, notifyData); +} + +NPError NPN_GetURL(NPP instance, const char* url, const char* target) { + return sBrowserFuncs->geturl(instance, url, target); +} + +NPError NPN_PostURLNotify(NPP instance, const char* url, const char* target, + uint32_t len, const char* buf, NPBool file, + void* notifyData) { + return sBrowserFuncs->posturlnotify(instance, url, target, len, buf, file, + notifyData); +} + +NPError NPN_PostURL(NPP instance, const char* url, const char* target, + uint32_t len, const char* buf, NPBool file) { + return sBrowserFuncs->posturl(instance, url, target, len, buf, file); +} + +bool NPN_Enumerate(NPP instance, NPObject* npobj, NPIdentifier** identifiers, + uint32_t* identifierCount) { + return sBrowserFuncs->enumerate(instance, npobj, identifiers, + identifierCount); +} + +bool NPN_GetProperty(NPP instance, NPObject* npobj, NPIdentifier propertyName, + NPVariant* result) { + return sBrowserFuncs->getproperty(instance, npobj, propertyName, result); +} + +bool NPN_Evaluate(NPP instance, NPObject* npobj, NPString* script, + NPVariant* result) { + return sBrowserFuncs->evaluate(instance, npobj, script, result); +} + +void NPN_SetException(NPObject* npobj, const NPUTF8* message) { + return sBrowserFuncs->setexception(npobj, message); +} + +NPBool NPN_ConvertPoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace) { + return sBrowserFuncs->convertpoint(instance, sourceX, sourceY, sourceSpace, + destX, destY, destSpace); +} + +NPError NPN_SetValueForURL(NPP instance, NPNURLVariable variable, + const char* url, const char* value, uint32_t len) { + return sBrowserFuncs->setvalueforurl(instance, variable, url, value, len); +} + +NPError NPN_GetValueForURL(NPP instance, NPNURLVariable variable, + const char* url, char** value, uint32_t* len) { + return sBrowserFuncs->getvalueforurl(instance, variable, url, value, len); +} + +void NPN_URLRedirectResponse(NPP instance, void* notifyData, NPBool allow) { + return sBrowserFuncs->urlredirectresponse(instance, notifyData, allow); +} + +NPError NPN_InitAsyncSurface(NPP instance, NPSize* size, NPImageFormat format, + void* initData, NPAsyncSurface* surface) { + return sBrowserFuncs->initasyncsurface(instance, size, format, initData, + surface); +} + +NPError NPN_FinalizeAsyncSurface(NPP instance, NPAsyncSurface* surface) { + return sBrowserFuncs->finalizeasyncsurface(instance, surface); +} + +void NPN_SetCurrentAsyncSurface(NPP instance, NPAsyncSurface* surface, + NPRect* changed) { + sBrowserFuncs->setcurrentasyncsurface(instance, surface, changed); +} + +// +// npruntime object functions +// + +NPObject* scriptableAllocate(NPP npp, NPClass* aClass) { + TestNPObject* object = (TestNPObject*)NPN_MemAlloc(sizeof(TestNPObject)); + if (!object) return nullptr; + memset(object, 0, sizeof(TestNPObject)); + return object; +} + +void scriptableDeallocate(NPObject* npobj) { NPN_MemFree(npobj); } + +void scriptableInvalidate(NPObject* npobj) {} + +bool scriptableHasMethod(NPObject* npobj, NPIdentifier name) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers)); i++) { + if (name == sPluginMethodIdentifiers[i]) return true; + } + return false; +} + +bool scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + if (id->throwOnNextInvoke) { + id->throwOnNextInvoke = false; + if (argCount == 0) { + NPN_SetException(npobj, nullptr); + } else { + for (uint32_t i = 0; i < argCount; i++) { + const NPString* argstr = &NPVARIANT_TO_STRING(args[i]); + NPN_SetException(npobj, argstr->UTF8Characters); + } + } + return false; + } + + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers)); i++) { + if (name == sPluginMethodIdentifiers[i]) + return sPluginMethodFunctions[i](npobj, args, argCount, result); + } + return false; +} + +bool scriptableHasProperty(NPObject* npobj, NPIdentifier name) { + if (NPN_IdentifierIsString(name)) { + NPUTF8* asUTF8 = NPN_UTF8FromIdentifier(name); + if (NPN_GetStringIdentifier(asUTF8) != name) { + Crash(); + } + NPN_MemFree(asUTF8); + } else { + if (NPN_GetIntIdentifier(NPN_IntFromIdentifier(name)) != name) { + Crash(); + } + } + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + return true; + } + } + return false; +} + +bool scriptableGetProperty(NPObject* npobj, NPIdentifier name, + NPVariant* result) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + DuplicateNPVariant(*result, sPluginPropertyValues[i]); + return true; + } + } + return false; +} + +bool scriptableSetProperty(NPObject* npobj, NPIdentifier name, + const NPVariant* value) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + DuplicateNPVariant(sPluginPropertyValues[i], *value); + return true; + } + } + return false; +} + +bool scriptableRemoveProperty(NPObject* npobj, NPIdentifier name) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + + // Avoid double frees (see test_propertyAndMethod.html, which deletes a + // property that doesn't exist). + VOID_TO_NPVARIANT(sPluginPropertyValues[i]); + return true; + } + } + return false; +} + +bool scriptableEnumerate(NPObject* npobj, NPIdentifier** identifier, + uint32_t* count) { + const int bufsize = + sizeof(NPIdentifier) * MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames); + NPIdentifier* ids = (NPIdentifier*)NPN_MemAlloc(bufsize); + if (!ids) return false; + + memcpy(ids, sPluginMethodIdentifiers, bufsize); + *identifier = ids; + *count = MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames); + return true; +} + +bool scriptableConstruct(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return false; +} + +// +// test functions +// + +static bool compareVariants(NPP instance, const NPVariant* var1, + const NPVariant* var2) { + bool success = true; + InstanceData* id = static_cast(instance->pdata); + if (var1->type != var2->type) { + id->err << "Variant types don't match; got " << var1->type << " expected " + << var2->type; + return false; + } + + // Cast var1->type from NPVariantType to int to avoid compiler warnings about + // not needing a default case when we have cases for every enum value. + switch (static_cast(var1->type)) { + case NPVariantType_Int32: { + int32_t result = NPVARIANT_TO_INT32(*var1); + int32_t expected = NPVARIANT_TO_INT32(*var2); + if (result != expected) { + id->err << "Variant values don't match; got " << result << " expected " + << expected; + success = false; + } + break; + } + case NPVariantType_Double: { + double result = NPVARIANT_TO_DOUBLE(*var1); + double expected = NPVARIANT_TO_DOUBLE(*var2); + if (result != expected) { + id->err << "Variant values don't match (double)"; + success = false; + } + break; + } + case NPVariantType_Void: { + // void values are always equivalent + break; + } + case NPVariantType_Null: { + // null values are always equivalent + break; + } + case NPVariantType_Bool: { + bool result = NPVARIANT_TO_BOOLEAN(*var1); + bool expected = NPVARIANT_TO_BOOLEAN(*var2); + if (result != expected) { + id->err << "Variant values don't match (bool)"; + success = false; + } + break; + } + case NPVariantType_String: { + const NPString* result = &NPVARIANT_TO_STRING(*var1); + const NPString* expected = &NPVARIANT_TO_STRING(*var2); + if (strcmp(result->UTF8Characters, expected->UTF8Characters) || + strlen(result->UTF8Characters) != strlen(expected->UTF8Characters)) { + id->err << "Variant values don't match; got " << result->UTF8Characters + << " expected " << expected->UTF8Characters; + success = false; + } + break; + } + case NPVariantType_Object: { + uint32_t i, identifierCount = 0; + NPIdentifier* identifiers; + NPObject* result = NPVARIANT_TO_OBJECT(*var1); + NPObject* expected = NPVARIANT_TO_OBJECT(*var2); + bool enumerate_result = + NPN_Enumerate(instance, expected, &identifiers, &identifierCount); + if (!enumerate_result) { + id->err << "NPN_Enumerate failed"; + success = false; + } + for (i = 0; i < identifierCount; i++) { + NPVariant resultVariant, expectedVariant; + if (!NPN_GetProperty(instance, expected, identifiers[i], + &expectedVariant)) { + id->err << "NPN_GetProperty returned false"; + success = false; + } else { + if (!NPN_HasProperty(instance, result, identifiers[i])) { + id->err << "NPN_HasProperty returned false"; + success = false; + } else { + if (!NPN_GetProperty(instance, result, identifiers[i], + &resultVariant)) { + id->err << "NPN_GetProperty 2 returned false"; + success = false; + } else { + success = + compareVariants(instance, &resultVariant, &expectedVariant); + NPN_ReleaseVariantValue(&expectedVariant); + } + } + NPN_ReleaseVariantValue(&resultVariant); + } + } + NPN_MemFree(identifiers); + break; + } + default: + id->err << "Unknown variant type"; + success = false; + MOZ_ASSERT_UNREACHABLE("Unknown variant type?!"); + } + + return success; +} + +static bool throwExceptionNextInvoke(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + id->throwOnNextInvoke = true; + BOOLEAN_TO_NPVARIANT(true, *result); + return true; +} + +static bool npnInvokeDefaultTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + bool success = false; + NPP npp = static_cast(npobj)->npp; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return false; + + NPIdentifier objectIdentifier = variantToIdentifier(args[0]); + if (!objectIdentifier) return false; + + NPVariant objectVariant; + if (NPN_GetProperty(npp, windowObject, objectIdentifier, &objectVariant)) { + if (NPVARIANT_IS_OBJECT(objectVariant)) { + NPObject* selfObject = NPVARIANT_TO_OBJECT(objectVariant); + if (selfObject != nullptr) { + NPVariant resultVariant; + if (NPN_InvokeDefault(npp, selfObject, + argCount > 1 ? &args[1] : nullptr, argCount - 1, + &resultVariant)) { + *result = resultVariant; + success = true; + } + } + } + NPN_ReleaseVariantValue(&objectVariant); + } + + NPN_ReleaseObject(windowObject); + return success; +} + +static bool npnInvokeTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + id->err.str(""); + if (argCount < 2) return false; + + NPIdentifier function = variantToIdentifier(args[0]); + if (!function) return false; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return false; + + NPVariant invokeResult; + bool invokeReturn = + NPN_Invoke(npp, windowObject, function, argCount > 2 ? &args[2] : nullptr, + argCount - 2, &invokeResult); + + bool compareResult = compareVariants(npp, &invokeResult, &args[1]); + + NPN_ReleaseObject(windowObject); + NPN_ReleaseVariantValue(&invokeResult); + BOOLEAN_TO_NPVARIANT(invokeReturn && compareResult, *result); + return true; +} + +static bool npnEvaluateTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + bool success = false; + NPP npp = static_cast(npobj)->npp; + + if (argCount != 1) return false; + + if (!NPVARIANT_IS_STRING(args[0])) return false; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return false; + + success = NPN_Evaluate(npp, windowObject, + (NPString*)&NPVARIANT_TO_STRING(args[0]), result); + + NPN_ReleaseObject(windowObject); + return success; +} + +static bool setUndefinedValueTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + NPError err = NPN_SetValue(npp, (NPPVariable)0x0, 0x0); + BOOLEAN_TO_NPVARIANT((err == NPERR_NO_ERROR), *result); + return true; +} + +static bool identifierToStringTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1) return false; + NPIdentifier identifier = variantToIdentifier(args[0]); + if (!identifier) return false; + + NPUTF8* utf8String = NPN_UTF8FromIdentifier(identifier); + if (!utf8String) return false; + STRINGZ_TO_NPVARIANT(utf8String, *result); + return true; +} + +static bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPBool pms = false; + NPN_GetValue(static_cast(npobj)->npp, NPNVprivateModeBool, + &pms); + BOOLEAN_TO_NPVARIANT(pms, *result); + return true; +} + +static bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + InstanceData* id = + static_cast(static_cast(npobj)->npp->pdata); + BOOLEAN_TO_NPVARIANT(id->lastReportedPrivateModeState, *result); + return true; +} + +static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + InstanceData* id = + static_cast(static_cast(npobj)->npp->pdata); + BOOLEAN_TO_NPVARIANT(id->hasWidget, *result); + return true; +} + +static bool getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1) return false; + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t edge = NPVARIANT_TO_INT32(args[0]); + if (edge < EDGE_LEFT || edge > EDGE_BOTTOM) return false; + + InstanceData* id = + static_cast(static_cast(npobj)->npp->pdata); + int32_t r = pluginGetEdge(id, RectEdge(edge)); + if (r == NPTEST_INT32_ERROR) return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool getClipRegionRectCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + InstanceData* id = + static_cast(static_cast(npobj)->npp->pdata); + int32_t r = pluginGetClipRegionRectCount(id); + if (r == NPTEST_INT32_ERROR) return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 2) return false; + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t rectIndex = NPVARIANT_TO_INT32(args[0]); + if (rectIndex < 0) return false; + if (!NPVARIANT_IS_INT32(args[1])) return false; + int32_t edge = NPVARIANT_TO_INT32(args[1]); + if (edge < EDGE_LEFT || edge > EDGE_BOTTOM) return false; + + InstanceData* id = + static_cast(static_cast(npobj)->npp->pdata); + int32_t r = pluginGetClipRegionRectEdge(id, rectIndex, RectEdge(edge)); + if (r == NPTEST_INT32_ERROR) return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + if (sWatchingInstanceCount) return false; + + sWatchingInstanceCount = true; + sInstanceCount = 0; + ++sCurrentInstanceCountWatchGeneration; + return true; +} + +static bool getInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + if (!sWatchingInstanceCount) return false; + + INT32_TO_NPVARIANT(sInstanceCount, *result); + return true; +} + +static bool stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + if (!sWatchingInstanceCount) return false; + + sWatchingInstanceCount = false; + return true; +} + +static bool getLastMouseX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + INT32_TO_NPVARIANT(id->lastMouseX, *result); + return true; +} + +static bool getLastMouseY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + INT32_TO_NPVARIANT(id->lastMouseY, *result); + return true; +} + +static bool getPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + INT32_TO_NPVARIANT(id->paintCount, *result); + return true; +} + +static bool resetPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + id->paintCount = 0; + return true; +} + +static bool getWidthAtLastPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + INT32_TO_NPVARIANT(id->widthAtLastPaint, *result); + return true; +} + +static bool setInvalidateDuringPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1) return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) return false; + bool doInvalidate = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + id->invalidateDuringPaint = doInvalidate; + return true; +} + +static bool setSlowPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1) return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) return false; + bool slow = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + id->slowPaint = slow; + return true; +} + +static bool getError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + if (id->err.str().length() == 0) { + char* outval = NPN_StrDup(SUCCESS_STRING); + STRINGZ_TO_NPVARIANT(outval, *result); + } else { + char* outval = NPN_StrDup(id->err.str().c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + } + return true; +} + +static bool doInternalConsistencyCheck(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + string error; + pluginDoInternalConsistencyCheck(id, error); + NPUTF8* utf8String = (NPUTF8*)NPN_MemAlloc(error.length() + 1); + if (!utf8String) { + return false; + } + memcpy(utf8String, error.c_str(), error.length() + 1); + STRINGZ_TO_NPVARIANT(utf8String, *result); + return true; +} + +static bool convertPointX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 4) return false; + + NPP npp = static_cast(npobj)->npp; + + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t sourceSpace = NPVARIANT_TO_INT32(args[0]); + + if (!NPVARIANT_IS_INT32(args[1])) return false; + double sourceX = static_cast(NPVARIANT_TO_INT32(args[1])); + + if (!NPVARIANT_IS_INT32(args[2])) return false; + double sourceY = static_cast(NPVARIANT_TO_INT32(args[2])); + + if (!NPVARIANT_IS_INT32(args[3])) return false; + int32_t destSpace = NPVARIANT_TO_INT32(args[3]); + + double resultX, resultY; + NPN_ConvertPoint(npp, sourceX, sourceY, (NPCoordinateSpace)sourceSpace, + &resultX, &resultY, (NPCoordinateSpace)destSpace); + + DOUBLE_TO_NPVARIANT(resultX, *result); + return true; +} + +static bool convertPointY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 4) return false; + + NPP npp = static_cast(npobj)->npp; + + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t sourceSpace = NPVARIANT_TO_INT32(args[0]); + + if (!NPVARIANT_IS_INT32(args[1])) return false; + double sourceX = static_cast(NPVARIANT_TO_INT32(args[1])); + + if (!NPVARIANT_IS_INT32(args[2])) return false; + double sourceY = static_cast(NPVARIANT_TO_INT32(args[2])); + + if (!NPVARIANT_IS_INT32(args[3])) return false; + int32_t destSpace = NPVARIANT_TO_INT32(args[3]); + + double resultX, resultY; + NPN_ConvertPoint(npp, sourceX, sourceY, (NPCoordinateSpace)sourceSpace, + &resultX, &resultY, (NPCoordinateSpace)destSpace); + + DOUBLE_TO_NPVARIANT(resultY, *result); + return true; +} + +static bool streamTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // .streamTest(url, doPost, postData, writeCallback, notifyCallback, + // redirectCallback, allowRedirects, postFile = false) + if (!(7 <= argCount && argCount <= 8)) return false; + + NPP npp = static_cast(npobj)->npp; + + if (!NPVARIANT_IS_STRING(args[0])) return false; + NPString url = NPVARIANT_TO_STRING(args[0]); + + if (!NPVARIANT_IS_BOOLEAN(args[1])) return false; + bool doPost = NPVARIANT_TO_BOOLEAN(args[1]); + + NPString postData = {nullptr, 0}; + if (NPVARIANT_IS_STRING(args[2])) { + postData = NPVARIANT_TO_STRING(args[2]); + } else { + if (!NPVARIANT_IS_NULL(args[2])) { + return false; + } + } + + NPObject* writeCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[3])) { + writeCallback = NPVARIANT_TO_OBJECT(args[3]); + } else { + if (!NPVARIANT_IS_NULL(args[3])) { + return false; + } + } + + NPObject* notifyCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[4])) { + notifyCallback = NPVARIANT_TO_OBJECT(args[4]); + } else { + if (!NPVARIANT_IS_NULL(args[4])) { + return false; + } + } + + NPObject* redirectCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[5])) { + redirectCallback = NPVARIANT_TO_OBJECT(args[5]); + } else { + if (!NPVARIANT_IS_NULL(args[5])) { + return false; + } + } + + if (!NPVARIANT_IS_BOOLEAN(args[6])) return false; + bool allowRedirects = NPVARIANT_TO_BOOLEAN(args[6]); + + bool postFile = false; + if (argCount >= 8) { + if (!NPVARIANT_IS_BOOLEAN(args[7])) { + return false; + } + postFile = NPVARIANT_TO_BOOLEAN(args[7]); + } + + URLNotifyData* ndata = new URLNotifyData; + ndata->cookie = "dynamic-cookie"; + ndata->writeCallback = writeCallback; + ndata->notifyCallback = notifyCallback; + ndata->redirectCallback = redirectCallback; + ndata->size = 0; + ndata->data = nullptr; + ndata->allowRedirects = allowRedirects; + + /* null-terminate "url" */ + char* urlstr = (char*)malloc(url.UTF8Length + 1); + strncpy(urlstr, url.UTF8Characters, url.UTF8Length); + urlstr[url.UTF8Length] = '\0'; + + NPError err; + if (doPost) { + err = NPN_PostURLNotify(npp, urlstr, nullptr, postData.UTF8Length, + postData.UTF8Characters, postFile, ndata); + } else { + err = NPN_GetURLNotify(npp, urlstr, nullptr, ndata); + } + + free(urlstr); + + if (NPERR_NO_ERROR == err) { + if (ndata->writeCallback) { + NPN_RetainObject(ndata->writeCallback); + } + if (ndata->notifyCallback) { + NPN_RetainObject(ndata->notifyCallback); + } + if (ndata->redirectCallback) { + NPN_RetainObject(ndata->redirectCallback); + } + BOOLEAN_TO_NPVARIANT(true, *result); + } else { + delete ndata; + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +static bool postFileToURLTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (1 != argCount) return false; + + NPP npp = static_cast(npobj)->npp; + + string url; + { + if (!NPVARIANT_IS_STRING(args[0])) return false; + NPString npurl = NPVARIANT_TO_STRING(args[0]); + // make a copy to ensure that the url string is null-terminated + url = string(npurl.UTF8Characters, npurl.UTF8Length); + } + + NPError err; + { + string buf("/path/to/file"); + err = NPN_PostURL(npp, url.c_str(), nullptr /* target */, buf.length(), + buf.c_str(), true /* file */); + } + + BOOLEAN_TO_NPVARIANT(NPERR_NO_ERROR == err, *result); + return true; +} + +static bool setPluginWantsAllStreams(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (1 != argCount) return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) return false; + bool wantsAllStreams = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + id->wantsAllStreams = wantsAllStreams; + + return true; +} + +static bool crashPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + IntentionalCrash(); + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool crashOnDestroy(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + id->crashOnDestroy = true; + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool setColor(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1) return false; + if (!NPVARIANT_IS_STRING(args[0])) return false; + const NPString* str = &NPVARIANT_TO_STRING(args[0]); + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + id->scriptableObject->drawColor = + parseHexColor(str->UTF8Characters, str->UTF8Length); + + NPRect r; + r.left = 0; + r.top = 0; + r.right = id->window.width; + r.bottom = id->window.height; + if (id->asyncDrawing == AD_NONE) { + NPN_InvalidateRect(npp, &r); + } else if (id->asyncDrawing == AD_BITMAP) { + drawAsyncBitmapColor(id); + } + + VOID_TO_NPVARIANT(*result); + return true; +} + +void notifyDidPaint(InstanceData* instanceData) { + ++instanceData->paintCount; + instanceData->widthAtLastPaint = instanceData->window.width; + + if (instanceData->invalidateDuringPaint) { + NPRect r; + r.left = 0; + r.top = 0; + r.right = instanceData->window.width; + r.bottom = instanceData->window.height; + NPN_InvalidateRect(instanceData->npp, &r); + } + + if (instanceData->slowPaint) { + XPSleep(1); + } + + if (instanceData->runScriptOnPaint) { + NPObject* o = nullptr; + NPN_GetValue(instanceData->npp, NPNVPluginElementNPObject, &o); + if (o) { + NPVariant param; + STRINGZ_TO_NPVARIANT("paintscript", param); + NPVariant result; + NPN_Invoke(instanceData->npp, o, NPN_GetStringIdentifier("getAttribute"), + ¶m, 1, &result); + + if (NPVARIANT_IS_STRING(result)) { + NPObject* windowObject; + NPN_GetValue(instanceData->npp, NPNVWindowNPObject, &windowObject); + if (windowObject) { + NPVariant evalResult; + NPN_Evaluate(instanceData->npp, windowObject, + (NPString*)&NPVARIANT_TO_STRING(result), &evalResult); + NPN_ReleaseVariantValue(&evalResult); + NPN_ReleaseObject(windowObject); + } + } + + NPN_ReleaseVariantValue(&result); + NPN_ReleaseObject(o); + } + } +} + +static const NPClass kTestSharedNPClass = { + NP_CLASS_STRUCT_VERSION, + // Everything else is nullptr +}; + +static bool getObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + + NPObject* o = + NPN_CreateObject(npp, const_cast(&kTestSharedNPClass)); + if (!o) return false; + + OBJECT_TO_NPVARIANT(o, *result); + return true; +} + +static bool checkObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + VOID_TO_NPVARIANT(*result); + + if (1 != argCount) return false; + + if (!NPVARIANT_IS_OBJECT(args[0])) return false; + + NPObject* o = NPVARIANT_TO_OBJECT(args[0]); + + BOOLEAN_TO_NPVARIANT(o->_class == &kTestSharedNPClass, *result); + return true; +} + +static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + VOID_TO_NPVARIANT(*result); + +#if defined(XP_WIN) && defined(_M_IX86) + _control87(0, _MCW_EM); + return true; +#else + return false; +#endif +} + +static void timerCallback(NPP npp, uint32_t timerID) { + InstanceData* id = static_cast(npp->pdata); + currentTimerEventCount++; + timerEvent event = timerEvents[currentTimerEventCount]; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return; + + NPVariant rval; + if (timerID != id->timerID[event.timerIdReceive]) { + id->timerTestResult = false; + } + + if (currentTimerEventCount == totalTimerEvents - 1) { + NPVariant arg; + BOOLEAN_TO_NPVARIANT(id->timerTestResult, arg); + NPN_Invoke(npp, windowObject, + NPN_GetStringIdentifier(id->timerTestScriptCallback.c_str()), + &arg, 1, &rval); + NPN_ReleaseVariantValue(&arg); + } + + NPN_ReleaseObject(windowObject); + + if (event.timerIdSchedule > -1) { + id->timerID[event.timerIdSchedule] = NPN_ScheduleTimer( + npp, event.timerInterval, event.timerRepeat, timerCallback); + } + if (event.timerIdUnschedule > -1) { + NPN_UnscheduleTimer(npp, id->timerID[event.timerIdUnschedule]); + } +} + +static bool timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + currentTimerEventCount = 0; + + if (argCount < 1 || !NPVARIANT_IS_STRING(args[0])) return false; + const NPString* argstr = &NPVARIANT_TO_STRING(args[0]); + id->timerTestScriptCallback = argstr->UTF8Characters; + + id->timerTestResult = true; + timerEvent event = timerEvents[currentTimerEventCount]; + + id->timerID[event.timerIdSchedule] = NPN_ScheduleTimer( + npp, event.timerInterval, event.timerRepeat, timerCallback); + + return id->timerID[event.timerIdSchedule] != 0; +} + +bool hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + mozilla::NoteIntentionalCrash("plugin"); + + bool busyHang = false; + if ((argCount == 1) && NPVARIANT_IS_BOOLEAN(args[0])) { + busyHang = NPVARIANT_TO_BOOLEAN(args[0]); + } + + if (busyHang) { + const time_t start = std::time(nullptr); + while ((std::time(nullptr) - start) < 100000) { + volatile int dummy = 0; + for (int i = 0; i < 1000; ++i) { + dummy++; + } + } + } else { +#ifdef XP_WIN + Sleep(100000000); + Sleep(100000000); +#else + pause(); + pause(); +#endif + } + + // NB: returning true here means that we weren't terminated, and + // thus the hang detection/handling didn't work correctly. The + // test harness will succeed in calling this function, and the + // test will fail. + return true; +} + +bool stallPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + uint32_t stallTimeSeconds = 0; + if ((argCount == 1) && NPVARIANT_IS_INT32(args[0])) { + stallTimeSeconds = (uint32_t)NPVARIANT_TO_INT32(args[0]); + } + +#ifdef XP_WIN + Sleep(stallTimeSeconds * 1000U); +#else + sleep(stallTimeSeconds); +#endif + + return true; +} + +#if defined(MOZ_WIDGET_GTK) +bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + string sel = pluginGetClipboardText(id); + + uint32_t len = sel.size(); + char* selCopy = static_cast(NPN_MemAlloc(1 + len)); + if (!selCopy) return false; + + memcpy(selCopy, sel.c_str(), len); + selCopy[len] = '\0'; + + STRINGN_TO_NPVARIANT(selCopy, len, *result); + // *result owns str now + + return true; +} + +bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + return pluginCrashInNestedLoop(id); +} + +bool triggerXError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + return pluginTriggerXError(id); +} + +bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + return pluginDestroySharedGfxStuff(id); +} + +#else +bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + // XXX Not implemented! + return false; +} + +bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // XXX Not implemented! + return false; +} + +bool triggerXError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + // XXX Not implemented! + return false; +} + +bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // XXX Not implemented! + return false; +} +#endif + +#if defined(XP_WIN) +bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + bool visible = pluginNativeWidgetIsVisible(id); + BOOLEAN_TO_NPVARIANT(visible, *result); + return true; +} +#else +bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // XXX Not implemented! + return false; +} +#endif + +bool getLastCompositionText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { +#ifdef XP_WIN + if (argCount != 0) { + return false; + } + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + char* outval = NPN_StrDup(id->lastComposition.c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +#else + // XXX not implemented + return false; +#endif +} + +bool scriptableInvokeDefault(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + ostringstream value; + value << sPluginName; + for (uint32_t i = 0; i < argCount; i++) { + switch (args[i].type) { + case NPVariantType_Int32: + value << ";" << NPVARIANT_TO_INT32(args[i]); + break; + case NPVariantType_String: { + const NPString* argstr = &NPVARIANT_TO_STRING(args[i]); + value << ";" << argstr->UTF8Characters; + break; + } + case NPVariantType_Void: + value << ";undefined"; + break; + case NPVariantType_Null: + value << ";null"; + break; + default: + value << ";other"; + } + } + + char* outval = NPN_StrDup(value.str().c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +} + +static const NPClass kInvokeDefaultClass = {NP_CLASS_STRUCT_VERSION, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + scriptableInvokeDefault, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}; + +bool getInvokeDefaultObject(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (0 != argCount) { + return false; + } + + NPP npp = static_cast(npobj)->npp; + NPObject* testObject = + NPN_CreateObject(npp, const_cast(&kInvokeDefaultClass)); + OBJECT_TO_NPVARIANT(testObject, *result); + return true; +} + +bool callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + if (id->callOnDestroy) return false; + + if (1 != argCount || !NPVARIANT_IS_OBJECT(args[0])) return false; + + id->callOnDestroy = NPVARIANT_TO_OBJECT(args[0]); + NPN_RetainObject(id->callOnDestroy); + + return true; +} + +// On Linux at least, a windowed plugin resize causes Flash Player to +// reconnect to the browser window. This method simulates that. +bool reinitWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + if (!id->hasWidget) return false; + + pluginWidgetInit(id, id->window.window); + return true; +} + +bool propertyAndMethod(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + INT32_TO_NPVARIANT(5, *result); + return true; +} + +// Returns top-level window activation state as indicated by Cocoa NPAPI's +// NPCocoaEventWindowFocusChanged events - 'true' if active, 'false' if not. +// Throws an exception if no events have been received and thus this state +// is unknown. +bool getTopLevelWindowActivationState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + // Throw an exception for unknown state. + if (id->topLevelWindowActivationState == ACTIVATION_STATE_UNKNOWN) { + return false; + } + + if (id->topLevelWindowActivationState == ACTIVATION_STATE_ACTIVATED) { + BOOLEAN_TO_NPVARIANT(true, *result); + } else if (id->topLevelWindowActivationState == + ACTIVATION_STATE_DEACTIVATED) { + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +bool getTopLevelWindowActivationEventCount(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + INT32_TO_NPVARIANT(id->topLevelWindowActivationEventCount, *result); + + return true; +} + +// Returns top-level window activation state as indicated by Cocoa NPAPI's +// NPCocoaEventFocusChanged events - 'true' if active, 'false' if not. +// Throws an exception if no events have been received and thus this state +// is unknown. +bool getFocusState(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + // Throw an exception for unknown state. + if (id->focusState == ACTIVATION_STATE_UNKNOWN) { + return false; + } + + if (id->focusState == ACTIVATION_STATE_ACTIVATED) { + BOOLEAN_TO_NPVARIANT(true, *result); + } else if (id->focusState == ACTIVATION_STATE_DEACTIVATED) { + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +bool getFocusEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + INT32_TO_NPVARIANT(id->focusEventCount, *result); + + return true; +} + +bool getEventModel(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + INT32_TO_NPVARIANT(id->eventModel, *result); + + return true; +} + +static bool ReflectorHasMethod(NPObject* npobj, NPIdentifier name) { + return false; +} + +static bool ReflectorHasProperty(NPObject* npobj, NPIdentifier name) { + return true; +} + +static bool ReflectorGetProperty(NPObject* npobj, NPIdentifier name, + NPVariant* result) { + if (NPN_IdentifierIsString(name)) { + char* s = NPN_UTF8FromIdentifier(name); + STRINGZ_TO_NPVARIANT(s, *result); + return true; + } + + INT32_TO_NPVARIANT(NPN_IntFromIdentifier(name), *result); + return true; +} + +static const NPClass kReflectorNPClass = {NP_CLASS_STRUCT_VERSION, + nullptr, + nullptr, + nullptr, + ReflectorHasMethod, + nullptr, + nullptr, + ReflectorHasProperty, + ReflectorGetProperty, + nullptr, + nullptr, + nullptr, + nullptr}; + +bool getReflector(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (0 != argCount) return false; + + NPP npp = static_cast(npobj)->npp; + + NPObject* reflector = + NPN_CreateObject(npp, + const_cast(&kReflectorNPClass)); // retains + OBJECT_TO_NPVARIANT(reflector, *result); + return true; +} + +bool isVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + BOOLEAN_TO_NPVARIANT( + id->window.clipRect.top != 0 || id->window.clipRect.left != 0 || + id->window.clipRect.bottom != 0 || id->window.clipRect.right != 0, + *result); + return true; +} + +bool getWindowPosition(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + NPObject* window = nullptr; + NPError err = NPN_GetValue(npp, NPNVWindowNPObject, &window); + if (NPERR_NO_ERROR != err || !window) return false; + + NPIdentifier arrayID = NPN_GetStringIdentifier("Array"); + NPVariant arrayFunctionV; + bool ok = NPN_GetProperty(npp, window, arrayID, &arrayFunctionV); + + NPN_ReleaseObject(window); + + if (!ok) return false; + + if (!NPVARIANT_IS_OBJECT(arrayFunctionV)) { + NPN_ReleaseVariantValue(&arrayFunctionV); + return false; + } + NPObject* arrayFunction = NPVARIANT_TO_OBJECT(arrayFunctionV); + + NPVariant elements[4]; + INT32_TO_NPVARIANT(id->window.x, elements[0]); + INT32_TO_NPVARIANT(id->window.y, elements[1]); + INT32_TO_NPVARIANT(id->window.width, elements[2]); + INT32_TO_NPVARIANT(id->window.height, elements[3]); + + ok = NPN_InvokeDefault(npp, arrayFunction, elements, 4, result); + + NPN_ReleaseObject(arrayFunction); + + return ok; +} + +bool constructObject(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount == 0 || !NPVARIANT_IS_OBJECT(args[0])) return false; + + NPObject* ctor = NPVARIANT_TO_OBJECT(args[0]); + + NPP npp = static_cast(npobj)->npp; + + return NPN_Construct(npp, ctor, args + 1, argCount - 1, result); +} + +bool setSitesWithData(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1 || !NPVARIANT_IS_STRING(args[0])) return false; + + // Clear existing data. + delete sSitesWithData; + + const NPString* str = &NPVARIANT_TO_STRING(args[0]); + if (str->UTF8Length == 0) return true; + + // Parse the comma-delimited string into a vector. + sSitesWithData = new list; + const char* iterator = str->UTF8Characters; + const char* end = iterator + str->UTF8Length; + while (1) { + const char* next = strchr(iterator, ','); + if (!next) next = end; + + // Parse out the three tokens into a siteData struct. + const char* siteEnd = strchr(iterator, ':'); + *((char*)siteEnd) = '\0'; + const char* flagsEnd = strchr(siteEnd + 1, ':'); + *((char*)flagsEnd) = '\0'; + *((char*)next) = '\0'; + + siteData data; + data.site = string(iterator); + data.flags = atoi(siteEnd + 1); + data.age = atoi(flagsEnd + 1); + + sSitesWithData->push_back(data); + + if (next == end) break; + + iterator = next + 1; + } + + return true; +} + +bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1 || !NPVARIANT_IS_BOOLEAN(args[0])) return false; + + sClearByAgeSupported = NPVARIANT_TO_BOOLEAN(args[0]); + return true; +} + +bool getLastKeyText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + + char* outval = NPN_StrDup(id->lastKeyText.c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +} + +bool getNPNVdocumentOrigin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast(npobj)->npp; + + char* origin = nullptr; + NPError err = NPN_GetValue(npp, NPNVdocumentOrigin, &origin); + if (err != NPERR_NO_ERROR) { + return false; + } + + STRINGZ_TO_NPVARIANT(origin, *result); + return true; +} + +bool getMouseUpEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + INT32_TO_NPVARIANT(id->mouseUpEventCount, *result); + return true; +} + +bool queryContentsScaleFactor(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + double scaleFactor = 1.0; +#if defined(XP_MACOSX) || defined(XP_WIN) + NPError err = NPN_GetValue(static_cast(npobj)->npp, + NPNVcontentsScaleFactor, &scaleFactor); + if (err != NPERR_NO_ERROR) { + return false; + } +#endif + DOUBLE_TO_NPVARIANT(scaleFactor, *result); + return true; +} + +bool queryCSSZoomFactorSetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast(npobj)->npp; + if (!npp) { + return false; + } + InstanceData* id = static_cast(npp->pdata); + if (!id) { + return false; + } + DOUBLE_TO_NPVARIANT(id->cssZoomFactor, *result); + return true; +} + +bool queryCSSZoomFactorGetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + double zoomFactor = 1.0; + NPError err = NPN_GetValue(static_cast(npobj)->npp, + NPNVCSSZoomFactor, &zoomFactor); + if (err != NPERR_NO_ERROR) { + return false; + } + DOUBLE_TO_NPVARIANT(zoomFactor, *result); + return true; +} + +bool echoString(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1) { + return false; + } + + if (!NPVARIANT_IS_STRING(args[0])) { + return false; + } + + const NPString& arg = NPVARIANT_TO_STRING(args[0]); + NPUTF8* buffer = + static_cast(NPN_MemAlloc(sizeof(NPUTF8) * arg.UTF8Length)); + if (!buffer) { + return false; + } + + std::copy(arg.UTF8Characters, arg.UTF8Characters + arg.UTF8Length, buffer); + STRINGN_TO_NPVARIANT(buffer, arg.UTF8Length, *result); + + return true; +} + +static bool toggleAudioPlayback(NPObject* npobj, uint32_t argCount, + bool playingAudio, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + id->playingAudio = playingAudio; + + NPN_SetValue(npp, NPPVpluginIsPlayingAudio, (void*)playingAudio); + + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool startAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return toggleAudioPlayback(npobj, argCount, true, result); +} + +static bool stopAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return toggleAudioPlayback(npobj, argCount, false, result); +} + +static bool getAudioMuted(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast(npobj)->npp; + InstanceData* id = static_cast(npp->pdata); + BOOLEAN_TO_NPVARIANT(id->audioMuted, *result); + return true; +} diff --git a/dom/plugins/test/testplugin/nptest.def b/dom/plugins/test/testplugin/nptest.def new file mode 100644 index 0000000000..4c543d5b9f --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/nptest.h b/dom/plugins/test/testplugin/nptest.h new file mode 100644 index 0000000000..3bac9cb65b --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.h @@ -0,0 +1,150 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_h_ +#define nptest_h_ + +#include "mozilla-config.h" + +#include "npapi.h" +#include "npfunctions.h" +#include "npruntime.h" +#include +#include +#include + +typedef enum { DM_DEFAULT, DM_SOLID_COLOR } DrawMode; + +typedef enum { + FUNCTION_NONE, + FUNCTION_NPP_GETURL, + FUNCTION_NPP_GETURLNOTIFY, + FUNCTION_NPP_POSTURL, + FUNCTION_NPP_POSTURLNOTIFY, + FUNCTION_NPP_NEWSTREAM, + FUNCTION_NPP_WRITEREADY, + FUNCTION_NPP_WRITE, + FUNCTION_NPP_DESTROYSTREAM, + FUNCTION_NPP_WRITE_RPC +} TestFunction; + +typedef enum { AD_NONE, AD_BITMAP, AD_DXGI } AsyncDrawing; + +typedef enum { + ACTIVATION_STATE_UNKNOWN, + ACTIVATION_STATE_ACTIVATED, + ACTIVATION_STATE_DEACTIVATED +} ActivationState; + +typedef struct FunctionTable { + TestFunction funcId; + const char* funcName; +} FunctionTable; + +typedef enum { POSTMODE_FRAME, POSTMODE_STREAM } PostMode; + +typedef struct TestNPObject : NPObject { + NPP npp; + DrawMode drawMode; + uint32_t drawColor; // 0xAARRGGBB +} TestNPObject; + +typedef struct _PlatformData PlatformData; + +typedef struct InstanceData { + NPP npp; + NPWindow window; + TestNPObject* scriptableObject; + PlatformData* platformData; + int32_t instanceCountWatchGeneration; + bool lastReportedPrivateModeState; + bool hasWidget; + bool npnNewStream; + bool throwOnNextInvoke; + bool runScriptOnPaint; + bool dontTouchElement; + uint32_t timerID[2]; + bool timerTestResult; + bool invalidateDuringPaint; + bool slowPaint; + bool playingAudio; + bool audioMuted; + int32_t winX; + int32_t winY; + int32_t lastMouseX; + int32_t lastMouseY; + int32_t widthAtLastPaint; + int32_t paintCount; + int32_t writeCount; + int32_t writeReadyCount; + TestFunction testFunction; + TestFunction functionToFail; + NPError failureCode; + NPObject* callOnDestroy; + PostMode postMode; + std::string testUrl; + std::string frame; + std::string timerTestScriptCallback; + std::ostringstream err; + int32_t streamChunkSize; + int32_t streamBufSize; + void* streamBuf; + bool crashOnDestroy; + bool cleanupWidget; + ActivationState topLevelWindowActivationState; + int32_t topLevelWindowActivationEventCount; + ActivationState focusState; + int32_t focusEventCount; + int32_t eventModel; + bool closeStream; + std::string lastKeyText; + bool wantsAllStreams; + int32_t mouseUpEventCount; + int32_t bugMode; + AsyncDrawing asyncDrawing; + NPAsyncSurface* frontBuffer; + NPAsyncSurface* backBuffer; + std::string lastComposition; + void* placeholderWnd; + double cssZoomFactor; +} InstanceData; + +void notifyDidPaint(InstanceData* instanceData); + +#if defined(XP_WIN) +bool setupDxgiSurfaces(NPP npp, InstanceData* instanceData); +void drawDxgiBitmapColor(InstanceData* instanceData); +#endif + +#endif // nptest_h_ diff --git a/dom/plugins/test/testplugin/nptest.rc b/dom/plugins/test/testplugin/nptest.rc new file mode 100644 index 0000000000..948fb846ef --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.rc @@ -0,0 +1,42 @@ +#include + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Plug-in for testing purposes.\x2122 (\x0939\x093f\x0928\x094d\x0926\x0940 \x4e2d\x6587 \x0627\x0644\x0639\x0631\x0628\x064a\x0629)" + VALUE "FileExtents", "tst" + VALUE "FileOpenName", L"Test \x2122 mimetype" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "nptest" + VALUE "MIMEType", "application/x-test" + VALUE "OriginalFilename", "nptest.dll" + VALUE "ProductName", "Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/nptest_droid.cpp b/dom/plugins/test/testplugin/nptest_droid.cpp new file mode 100644 index 0000000000..733f18befe --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_droid.cpp @@ -0,0 +1,80 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2010, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Brad Lassey + * + * ***** END LICENSE BLOCK ***** */ +#include "nptest_platform.h" +#include "npapi.h" + +struct _PlatformData {}; + +bool pluginSupportsWindowMode() { return false; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { + printf("NPERR_INCOMPATIBLE_VERSION_ERROR\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; +} + +void pluginInstanceShutdown(InstanceData* instanceData) { + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + instanceData->window = *newWindow; +} + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { + // XXX nothing here yet since we don't support windowed plugins +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { return 0; } + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) { + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error) {} diff --git a/dom/plugins/test/testplugin/nptest_gtk2.cpp b/dom/plugins/test/testplugin/nptest_gtk2.cpp new file mode 100644 index 0000000000..c7779b1d29 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_gtk2.cpp @@ -0,0 +1,707 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas + * Michael Ventnor + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" +#include "npapi.h" +#include +#include +#ifdef MOZ_X11 +# include +# include +#endif +#include +#include +#include + +#include "mozilla/Assertions.h" +#include "mozilla/IntentionalCrash.h" + +struct _PlatformData { +#ifdef MOZ_X11 + Display* display; + Visual* visual; + Colormap colormap; +#endif + GtkWidget* plug; +}; + +bool pluginSupportsWindowMode() { return false; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { +#ifdef MOZ_X11 + instanceData->platformData = + static_cast(NPN_MemAlloc(sizeof(PlatformData))); + if (!instanceData->platformData) return NPERR_OUT_OF_MEMORY_ERROR; + + instanceData->platformData->display = nullptr; + instanceData->platformData->visual = nullptr; + instanceData->platformData->colormap = X11None; + instanceData->platformData->plug = nullptr; + + return NPERR_NO_ERROR; +#else + // we only support X11 here, since thats what the plugin system uses + return NPERR_INCOMPATIBLE_VERSION_ERROR; +#endif +} + +void pluginInstanceShutdown(InstanceData* instanceData) { + if (instanceData->hasWidget) { + Window window = reinterpret_cast(instanceData->window.window); + + if (window != X11None) { + // This window XID should still be valid. + // See bug 429604 and bug 454756. + XWindowAttributes attributes; + if (!XGetWindowAttributes(instanceData->platformData->display, window, + &attributes)) + g_error("XGetWindowAttributes failed at plugin instance shutdown"); + } + } + + GtkWidget* plug = instanceData->platformData->plug; + if (plug) { + instanceData->platformData->plug = 0; + if (instanceData->cleanupWidget) { + // Default/tidy behavior + gtk_widget_destroy(plug); + } else { + // Flash Player style: let the GtkPlug destroy itself on disconnect. + g_signal_handlers_disconnect_matched(plug, G_SIGNAL_MATCH_DATA, 0, 0, + nullptr, nullptr, instanceData); + } + } + + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; +} + +static void SetCairoRGBA(cairo_t* cairoWindow, uint32_t rgba) { + float b = (rgba & 0xFF) / 255.0; + float g = ((rgba & 0xFF00) >> 8) / 255.0; + float r = ((rgba & 0xFF0000) >> 16) / 255.0; + float a = ((rgba & 0xFF000000) >> 24) / 255.0; + + cairo_set_source_rgba(cairoWindow, r, g, b, a); +} + +static void pluginDrawSolid(InstanceData* instanceData, GdkDrawable* gdkWindow, + int x, int y, int width, int height) { + cairo_t* cairoWindow = gdk_cairo_create(gdkWindow); + + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + + NPRect* clip = &instanceData->window.clipRect; + cairo_rectangle(cairoWindow, clip->left, clip->top, clip->right - clip->left, + clip->bottom - clip->top); + cairo_clip(cairoWindow); + + GdkRectangle windowRect = {x, y, width, height}; + gdk_cairo_rectangle(cairoWindow, &windowRect); + SetCairoRGBA(cairoWindow, instanceData->scriptableObject->drawColor); + + cairo_fill(cairoWindow); + cairo_destroy(cairoWindow); +} + +static void pluginDrawWindow(InstanceData* instanceData, GdkDrawable* gdkWindow, + const GdkRectangle& invalidRect) { + NPWindow& window = instanceData->window; + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + + int x = window.x; + int y = window.y; + int width = window.width; + int height = window.height; + + notifyDidPaint(instanceData); + + if (instanceData->scriptableObject->drawMode == DM_SOLID_COLOR) { + // drawing a solid color for reftests + pluginDrawSolid(instanceData, gdkWindow, invalidRect.x, invalidRect.y, + invalidRect.width, invalidRect.height); + return; + } + + NPP npp = instanceData->npp; + if (!npp) return; + + const char* uaString = NPN_UserAgent(npp); + if (!uaString) return; + + GdkGC* gdkContext = gdk_gc_new(gdkWindow); + if (!gdkContext) return; + + NPRect* clip = &window.clipRect; + GdkRectangle gdkClip = {clip->left, clip->top, clip->right - clip->left, + clip->bottom - clip->top}; + gdk_gc_set_clip_rectangle(gdkContext, &gdkClip); + + // draw a grey background for the plugin frame + GdkColor grey; + grey.red = grey.blue = grey.green = 32767; + gdk_gc_set_rgb_fg_color(gdkContext, &grey); + gdk_draw_rectangle(gdkWindow, gdkContext, TRUE, x, y, width, height); + + // draw a 3-pixel-thick black frame around the plugin + GdkColor black; + black.red = black.green = black.blue = 0; + gdk_gc_set_rgb_fg_color(gdkContext, &black); + gdk_gc_set_line_attributes(gdkContext, 3, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, + GDK_JOIN_MITER); + gdk_draw_rectangle(gdkWindow, gdkContext, FALSE, x + 1, y + 1, width - 3, + height - 3); + + // paint the UA string + PangoContext* pangoContext = gdk_pango_context_get(); + PangoLayout* pangoTextLayout = pango_layout_new(pangoContext); + pango_layout_set_width(pangoTextLayout, (width - 10) * PANGO_SCALE); + pango_layout_set_text(pangoTextLayout, uaString, -1); + gdk_draw_layout(gdkWindow, gdkContext, x + 5, y + 5, pangoTextLayout); + g_object_unref(pangoTextLayout); + + g_object_unref(gdkContext); +} + +static gboolean ExposeWidget(GtkWidget* widget, GdkEventExpose* event, + gpointer user_data) { + InstanceData* instanceData = static_cast(user_data); + pluginDrawWindow(instanceData, event->window, event->area); + return TRUE; +} + +static gboolean MotionEvent(GtkWidget* widget, GdkEventMotion* event, + gpointer user_data) { + InstanceData* instanceData = static_cast(user_data); + instanceData->lastMouseX = event->x; + instanceData->lastMouseY = event->y; + return TRUE; +} + +static gboolean ButtonEvent(GtkWidget* widget, GdkEventButton* event, + gpointer user_data) { + InstanceData* instanceData = static_cast(user_data); + instanceData->lastMouseX = event->x; + instanceData->lastMouseY = event->y; + if (event->type == GDK_BUTTON_RELEASE) { + instanceData->mouseUpEventCount++; + } + return TRUE; +} + +static gboolean DeleteWidget(GtkWidget* widget, GdkEvent* event, + gpointer user_data) { + InstanceData* instanceData = static_cast(user_data); + // Some plugins do not expect the plug to be removed from the socket before + // the plugin instance is destroyed. e.g. bug 485125 + if (instanceData->platformData->plug) g_error("plug removed"); // this aborts + + return FALSE; +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + instanceData->window = *newWindow; + +#ifdef MOZ_X11 + NPSetWindowCallbackStruct* ws_info = + static_cast(newWindow->ws_info); + instanceData->platformData->display = ws_info->display; + instanceData->platformData->visual = ws_info->visual; + instanceData->platformData->colormap = ws_info->colormap; +#endif +} + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { +#ifdef MOZ_X11 + GtkWidget* oldPlug = instanceData->platformData->plug; + if (oldPlug) { + instanceData->platformData->plug = 0; + gtk_widget_destroy(oldPlug); + } + + GdkNativeWindow nativeWinId = + reinterpret_cast(instanceData->window.window); + + /* create a GtkPlug container */ + GtkWidget* plug = gtk_plug_new(nativeWinId); + + // Test for bugs 539138 and 561308 + if (!plug->window) g_error("Plug has no window"); // aborts + + /* make sure the widget is capable of receiving focus */ + GTK_WIDGET_SET_FLAGS(GTK_WIDGET(plug), GTK_CAN_FOCUS); + + /* all the events that our widget wants to receive */ + gtk_widget_add_events(plug, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK); + g_signal_connect(plug, "expose-event", G_CALLBACK(ExposeWidget), + instanceData); + g_signal_connect(plug, "motion_notify_event", G_CALLBACK(MotionEvent), + instanceData); + g_signal_connect(plug, "button_press_event", G_CALLBACK(ButtonEvent), + instanceData); + g_signal_connect(plug, "button_release_event", G_CALLBACK(ButtonEvent), + instanceData); + g_signal_connect(plug, "delete-event", G_CALLBACK(DeleteWidget), + instanceData); + gtk_widget_show(plug); + + instanceData->platformData->plug = plug; +#endif +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { +#ifdef MOZ_X11 + XEvent* nsEvent = (XEvent*)event; + + switch (nsEvent->type) { + case GraphicsExpose: { + const XGraphicsExposeEvent& expose = nsEvent->xgraphicsexpose; + NPWindow& window = instanceData->window; + window.window = (void*)(expose.drawable); + + GdkNativeWindow nativeWinId = reinterpret_cast(window.window); + + GdkDisplay* gdkDisplay = gdk_x11_lookup_xdisplay(expose.display); + if (!gdkDisplay) { + g_warning("Display not opened by GDK"); + return 0; + } + // gdk_pixmap_foreign_new() doesn't check whether a GdkPixmap already + // exists, so check first. + // https://bugzilla.gnome.org/show_bug.cgi?id=590690 + GdkPixmap* gdkDrawable = + GDK_DRAWABLE(gdk_pixmap_lookup_for_display(gdkDisplay, nativeWinId)); + // If there is no existing GdkPixmap or it doesn't have a colormap then + // create our own. + if (gdkDrawable) { + GdkColormap* gdkColormap = gdk_drawable_get_colormap(gdkDrawable); + if (!gdkColormap) { + g_warning("No GdkColormap on GdkPixmap"); + return 0; + } + if (gdk_x11_colormap_get_xcolormap(gdkColormap) != + instanceData->platformData->colormap) { + g_warning("wrong Colormap"); + return 0; + } + if (gdk_x11_visual_get_xvisual(gdk_colormap_get_visual(gdkColormap)) != + instanceData->platformData->visual) { + g_warning("wrong Visual"); + return 0; + } + g_object_ref(gdkDrawable); + } else { + gdkDrawable = GDK_DRAWABLE( + gdk_pixmap_foreign_new_for_display(gdkDisplay, nativeWinId)); + VisualID visualID = instanceData->platformData->visual->visualid; + GdkVisual* gdkVisual = gdk_x11_screen_lookup_visual( + gdk_drawable_get_screen(gdkDrawable), visualID); + GdkColormap* gdkColormap = gdk_x11_colormap_foreign_new( + gdkVisual, instanceData->platformData->colormap); + gdk_drawable_set_colormap(gdkDrawable, gdkColormap); + g_object_unref(gdkColormap); + } + + const NPRect& clip = window.clipRect; + if (expose.x < clip.left || expose.y < clip.top || + expose.x + expose.width > clip.right || + expose.y + expose.height > clip.bottom) { + g_warning( + "expose rectangle (x=%d,y=%d,w=%d,h=%d) not in clip rectangle " + "(l=%d,t=%d,r=%d,b=%d)", + expose.x, expose.y, expose.width, expose.height, clip.left, + clip.top, clip.right, clip.bottom); + return 0; + } + if (expose.x < window.x || expose.y < window.y || + expose.x + expose.width > window.x + int32_t(window.width) || + expose.y + expose.height > window.y + int32_t(window.height)) { + g_warning( + "expose rectangle (x=%d,y=%d,w=%d,h=%d) not in plugin rectangle " + "(x=%d,y=%d,w=%d,h=%d)", + expose.x, expose.y, expose.width, expose.height, window.x, window.y, + window.width, window.height); + return 0; + } + + GdkRectangle invalidRect = {expose.x, expose.y, expose.width, + expose.height}; + pluginDrawWindow(instanceData, gdkDrawable, invalidRect); + g_object_unref(gdkDrawable); + break; + } + case MotionNotify: { + XMotionEvent* motion = &nsEvent->xmotion; + instanceData->lastMouseX = motion->x; + instanceData->lastMouseY = motion->y; + break; + } + case ButtonPress: + case ButtonRelease: { + XButtonEvent* button = &nsEvent->xbutton; + instanceData->lastMouseX = button->x; + instanceData->lastMouseY = button->y; + if (nsEvent->type == ButtonRelease) { + instanceData->mouseUpEventCount++; + } + break; + } + default: + break; + } +#endif + + return 0; +} + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + if (!instanceData->hasWidget) return NPTEST_INT32_ERROR; + + GtkWidget* plug = instanceData->platformData->plug; + if (!plug) return NPTEST_INT32_ERROR; + GdkWindow* plugWnd = plug->window; + if (!plugWnd) return NPTEST_INT32_ERROR; + + GdkWindow* toplevelGdk = 0; +#ifdef MOZ_X11 + Window toplevel = 0; + NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel); + if (!toplevel) return NPTEST_INT32_ERROR; + toplevelGdk = gdk_window_foreign_new(toplevel); +#endif + if (!toplevelGdk) return NPTEST_INT32_ERROR; + + GdkRectangle toplevelFrameExtents; + gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents); + g_object_unref(toplevelGdk); + + gint pluginWidth, pluginHeight; + gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &pluginWidth, &pluginHeight); + gint pluginOriginX, pluginOriginY; + gdk_window_get_origin(plugWnd, &pluginOriginX, &pluginOriginY); + gint pluginX = pluginOriginX - toplevelFrameExtents.x; + gint pluginY = pluginOriginY - toplevelFrameExtents.y; + + switch (edge) { + case EDGE_LEFT: + return pluginX; + case EDGE_TOP: + return pluginY; + case EDGE_RIGHT: + return pluginX + pluginWidth; + case EDGE_BOTTOM: + return pluginY + pluginHeight; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +#ifdef MOZ_X11 +static void intersectWithShapeRects(Display* display, Window window, int kind, + GdkRegion* region) { + int count = -1, order; + XRectangle* shapeRects = + XShapeGetRectangles(display, window, kind, &count, &order); + // The documentation says that shapeRects will be nullptr when the + // extension is not supported. Unfortunately XShapeGetRectangles + // also returns nullptr when the region is empty, so we can't treat + // nullptr as failure. I hope this way is OK. + if (count < 0) return; + + GdkRegion* shapeRegion = gdk_region_new(); + if (!shapeRegion) { + XFree(shapeRects); + return; + } + + for (int i = 0; i < count; ++i) { + XRectangle* r = &shapeRects[i]; + GdkRectangle rect = {r->x, r->y, r->width, r->height}; + gdk_region_union_with_rect(shapeRegion, &rect); + } + XFree(shapeRects); + + gdk_region_intersect(region, shapeRegion); + gdk_region_destroy(shapeRegion); +} +#endif + +static GdkRegion* computeClipRegion(InstanceData* instanceData) { + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + if (!instanceData->hasWidget) return 0; + + GtkWidget* plug = instanceData->platformData->plug; + if (!plug) return 0; + GdkWindow* plugWnd = plug->window; + if (!plugWnd) return 0; + + gint plugWidth, plugHeight; + gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &plugWidth, &plugHeight); + GdkRectangle pluginRect = {0, 0, plugWidth, plugHeight}; + GdkRegion* region = gdk_region_rectangle(&pluginRect); + if (!region) return 0; + + int pluginX = 0, pluginY = 0; + +#ifdef MOZ_X11 + Display* display = GDK_WINDOW_XDISPLAY(plugWnd); + Window window = GDK_WINDOW_XWINDOW(plugWnd); + + Window toplevel = 0; + NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel); + if (!toplevel) return 0; + + for (;;) { + Window root; + int x, y; + unsigned int width, height, border_width, depth; + if (!XGetGeometry(display, window, &root, &x, &y, &width, &height, + &border_width, &depth)) { + gdk_region_destroy(region); + return 0; + } + + GdkRectangle windowRect = {0, 0, static_cast(width), + static_cast(height)}; + GdkRegion* windowRgn = gdk_region_rectangle(&windowRect); + if (!windowRgn) { + gdk_region_destroy(region); + return 0; + } + intersectWithShapeRects(display, window, ShapeBounding, windowRgn); + intersectWithShapeRects(display, window, ShapeClip, windowRgn); + gdk_region_offset(windowRgn, -pluginX, -pluginY); + gdk_region_intersect(region, windowRgn); + gdk_region_destroy(windowRgn); + + // Stop now if we've reached the toplevel. Stopping here means + // clipping performed by the toplevel window is taken into account. + if (window == toplevel) break; + + Window parent; + Window* children; + unsigned int nchildren; + if (!XQueryTree(display, window, &root, &parent, &children, &nchildren)) { + gdk_region_destroy(region); + return 0; + } + XFree(children); + + pluginX += x; + pluginY += y; + + window = parent; + } +#endif + // pluginX and pluginY are now relative to the toplevel. Make them + // relative to the window frame top-left. + GdkWindow* toplevelGdk = gdk_window_foreign_new(window); + if (!toplevelGdk) return 0; + GdkRectangle toplevelFrameExtents; + gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents); + gint toplevelOriginX, toplevelOriginY; + gdk_window_get_origin(toplevelGdk, &toplevelOriginX, &toplevelOriginY); + g_object_unref(toplevelGdk); + + pluginX += toplevelOriginX - toplevelFrameExtents.x; + pluginY += toplevelOriginY - toplevelFrameExtents.y; + + gdk_region_offset(region, pluginX, pluginY); + return region; +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { + GdkRegion* region = computeClipRegion(instanceData); + if (!region) return NPTEST_INT32_ERROR; + + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(region, &rects, &nrects); + gdk_region_destroy(region); + g_free(rects); + return nrects; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) { + GdkRegion* region = computeClipRegion(instanceData); + if (!region) return NPTEST_INT32_ERROR; + + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(region, &rects, &nrects); + gdk_region_destroy(region); + if (rectIndex >= nrects) { + g_free(rects); + return NPTEST_INT32_ERROR; + } + + GdkRectangle rect = rects[rectIndex]; + g_free(rects); + + switch (edge) { + case EDGE_LEFT: + return rect.x; + case EDGE_TOP: + return rect.y; + case EDGE_RIGHT: + return rect.x + rect.width; + case EDGE_BOTTOM: + return rect.y + rect.height; + } + return NPTEST_INT32_ERROR; +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error) {} + +std::string pluginGetClipboardText(InstanceData* instanceData) { + GtkClipboard* cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + // XXX this is a BAD WAY to interact with GtkClipboard. We use this + // deprecated interface only to test nested event loop handling. + gchar* text = gtk_clipboard_wait_for_text(cb); + std::string retText = text ? text : ""; + + g_free(text); + + return retText; +} + +//----------------------------------------------------------------------------- +// NB: this test is quite gross in that it's not only +// nondeterministic, but dependent on the guts of the nested glib +// event loop handling code in PluginModule. We first sleep long +// enough to make sure that the "detection timer" will be pending when +// we enter the nested glib loop, then similarly for the "process browser +// events" timer. Then we "schedule" the crasher thread to run at about the +// same time we expect that the PluginModule "process browser events" task +// will run. If all goes well, the plugin process will crash and generate the +// XPCOM "plugin crashed" task, and the browser will run that task while still +// in the "process some events" loop. + +static void* CrasherThread(void* data) { + // Give the parent thread a chance to send the message. + usleep(200); + + // Exit (without running atexit hooks) rather than crashing with a signal + // so as to make timing more reliable. The process terminates immediately + // rather than waiting for a thread in the parent process to attach and + // generate a minidump. + _exit(1); + + // not reached + return (nullptr); +} + +bool pluginCrashInNestedLoop(InstanceData* instanceData) { + // wait at least long enough for nested loop detector task to be pending ... + sleep(1); + + // Run the nested loop detector by processing all events that are waiting. + bool found_event = false; + while (g_main_context_iteration(nullptr, FALSE)) { + found_event = true; + } + if (!found_event) { + g_warning("DetectNestedEventLoop did not fire"); + return true; // trigger a test failure + } + + // wait at least long enough for the "process browser events" task to be + // pending ... + sleep(1); + + // we'll be crashing soon, note that fact now to avoid messing with + // timing too much + mozilla::NoteIntentionalCrash("plugin"); + + // schedule the crasher thread ... + pthread_t crasherThread; + if (0 != pthread_create(&crasherThread, nullptr, CrasherThread, nullptr)) { + g_warning("Failed to create thread"); + return true; // trigger a test failure + } + + // .. and hope it crashes at about the same time as the "process browser + // events" task (that should run in this loop) is being processed in the + // parent. + found_event = false; + while (g_main_context_iteration(nullptr, FALSE)) { + found_event = true; + } + if (found_event) { + g_warning("Should have crashed in ProcessBrowserEvents"); + } else { + g_warning("ProcessBrowserEvents did not fire"); + } + + // if we get here without crashing, then we'll trigger a test failure + return true; +} + +bool pluginTriggerXError(InstanceData* instanceData) { + mozilla::NoteIntentionalCrash("plugin"); + int num_prop_return; + // Window parameter is None to generate a fatal error, and this function + // should not return. + XListProperties(GDK_DISPLAY(), X11None, &num_prop_return); + + // if we get here without crashing, then we'll trigger a test failure + return true; +} + +static int SleepThenDie(Display* display) { + mozilla::NoteIntentionalCrash("plugin"); + fprintf(stderr, "[testplugin:%d] SleepThenDie: sleeping\n", getpid()); + sleep(1); + + fprintf(stderr, "[testplugin:%d] SleepThenDie: dying\n", getpid()); + _exit(1); +} + +bool pluginDestroySharedGfxStuff(InstanceData* instanceData) { + // Closing the X socket results in the gdk error handler being + // invoked, which exit()s us. We want to give the parent process a + // little while to do whatever it wanted to do, so steal the IO + // handler from gdk and set up our own that delays seppuku. + XSetIOErrorHandler(SleepThenDie); + close(ConnectionNumber(GDK_DISPLAY())); + return true; +} diff --git a/dom/plugins/test/testplugin/nptest_macosx.mm b/dom/plugins/test/testplugin/nptest_macosx.mm new file mode 100644 index 0000000000..191b8f5c6f --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_macosx.mm @@ -0,0 +1,275 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" +#include "nsAlgorithm.h" +#include +#include + +bool pluginSupportsWindowMode() { return false; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { + NPP npp = instanceData->npp; + + NPBool supportsCoreGraphics = false; + if ((NPN_GetValue(npp, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) == NPERR_NO_ERROR) && + supportsCoreGraphics) { + NPN_SetValue(npp, NPPVpluginDrawingModel, (void*)NPDrawingModelCoreGraphics); + } else { + printf("CoreGraphics drawing model not supported, can't create a plugin instance.\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + NPBool supportsCocoaEvents = false; + if ((NPN_GetValue(npp, NPNVsupportsCocoaBool, &supportsCocoaEvents) == NPERR_NO_ERROR) && + supportsCocoaEvents) { + NPN_SetValue(npp, NPPVpluginEventModel, (void*)NPEventModelCocoa); + instanceData->eventModel = NPEventModelCocoa; + } else { + printf("Cocoa event model not supported, can't create a plugin instance.\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + return NPERR_NO_ERROR; +} + +void pluginInstanceShutdown(InstanceData* instanceData) {} + +static bool RectEquals(const NPRect& r1, const NPRect& r2) { + return r1.left == r2.left && r1.top == r2.top && r1.right == r2.right && r1.bottom == r2.bottom; +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + // Ugh. Due to a terrible Gecko bug, we have to ignore position changes + // when the clip rect doesn't change; the position can be wrong + // when set by a path other than nsPluginFrame::FixUpPluginWindow. + int32_t oldX = instanceData->window.x; + int32_t oldY = instanceData->window.y; + bool clipChanged = !RectEquals(instanceData->window.clipRect, newWindow->clipRect); + instanceData->window = *newWindow; + if (!clipChanged) { + instanceData->window.x = oldX; + instanceData->window.y = oldY; + } +} + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { + // Should never be called since we don't support window mode +} + +static void GetColorsFromRGBA(uint32_t rgba, float* r, float* g, float* b, float* a) { + *b = (rgba & 0xFF) / 255.0; + *g = ((rgba & 0xFF00) >> 8) / 255.0; + *r = ((rgba & 0xFF0000) >> 16) / 255.0; + *a = ((rgba & 0xFF000000) >> 24) / 255.0; +} + +static void pluginDraw(InstanceData* instanceData, NPCocoaEvent* event) { + if (!instanceData) return; + + notifyDidPaint(instanceData); + + NPP npp = instanceData->npp; + if (!npp) return; + + const char* uaString = NPN_UserAgent(npp); + if (!uaString) return; + + NPWindow window = instanceData->window; + + CGContextRef cgContext = event->data.draw.context; + + float windowWidth = window.width; + float windowHeight = window.height; + + switch (instanceData->scriptableObject->drawMode) { + case DM_DEFAULT: { + CFStringRef uaCFString = + CFStringCreateWithCString(kCFAllocatorDefault, uaString, kCFStringEncodingASCII); + // save the cgcontext gstate + CGContextSaveGState(cgContext); + + // we get a flipped context + CGContextTranslateCTM(cgContext, 0.0, windowHeight); + CGContextScaleCTM(cgContext, 1.0, -1.0); + + // draw a gray background for the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + CGContextSetGrayFillColor(cgContext, 0.5, 1.0); + CGContextDrawPath(cgContext, kCGPathFill); + + // draw a black frame around the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + CGContextSetGrayStrokeColor(cgContext, 0.0, 1.0); + CGContextSetLineWidth(cgContext, 6.0); + CGContextStrokePath(cgContext); + + // draw the UA string using Core Text + CGContextSetTextMatrix(cgContext, CGAffineTransformIdentity); + + // Initialize a rectangular path. + CGMutablePathRef path = CGPathCreateMutable(); + CGRect bounds = CGRectMake(10.0, 10.0, std::max(0.0, windowWidth - 20.0), + std::max(0.0, windowHeight - 20.0)); + CGPathAddRect(path, NULL, bounds); + + // Initialize an attributed string. + CFMutableAttributedStringRef attrString = + CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), uaCFString); + + // Create a color and add it as an attribute to the string. + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + CGFloat components[] = {0.0, 0.0, 0.0, 1.0}; + CGColorRef red = CGColorCreate(rgbColorSpace, components); + CGColorSpaceRelease(rgbColorSpace); + CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 50), + kCTForegroundColorAttributeName, red); + + // Create the framesetter with the attributed string. + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString); + CFRelease(attrString); + + // Create the frame and draw it into the graphics context + CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); + CFRelease(framesetter); + if (frame) { + CTFrameDraw(frame, cgContext); + CFRelease(frame); + } + + // restore the cgcontext gstate + CGContextRestoreGState(cgContext); + break; + } + case DM_SOLID_COLOR: { + // save the cgcontext gstate + CGContextSaveGState(cgContext); + + // we get a flipped context + CGContextTranslateCTM(cgContext, 0.0, windowHeight); + CGContextScaleCTM(cgContext, 1.0, -1.0); + + // draw a solid background for the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + + float r, g, b, a; + GetColorsFromRGBA(instanceData->scriptableObject->drawColor, &r, &g, &b, &a); + CGContextSetRGBFillColor(cgContext, r, g, b, a); + CGContextDrawPath(cgContext, kCGPathFill); + + // restore the cgcontext gstate + CGContextRestoreGState(cgContext); + break; + } + } +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { + NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event; + if (!cocoaEvent) return kNPEventNotHandled; + + switch (cocoaEvent->type) { + case NPCocoaEventDrawRect: + pluginDraw(instanceData, cocoaEvent); + break; + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseDragged: + instanceData->lastMouseX = (int32_t)cocoaEvent->data.mouse.pluginX; + instanceData->lastMouseY = (int32_t)cocoaEvent->data.mouse.pluginY; + if (cocoaEvent->type == NPCocoaEventMouseUp) { + instanceData->mouseUpEventCount++; + } + break; + case NPCocoaEventWindowFocusChanged: + instanceData->topLevelWindowActivationState = cocoaEvent->data.focus.hasFocus + ? ACTIVATION_STATE_ACTIVATED + : ACTIVATION_STATE_DEACTIVATED; + instanceData->topLevelWindowActivationEventCount = + instanceData->topLevelWindowActivationEventCount + 1; + break; + case NPCocoaEventFocusChanged: + instanceData->focusState = cocoaEvent->data.focus.hasFocus ? ACTIVATION_STATE_ACTIVATED + : ACTIVATION_STATE_DEACTIVATED; + instanceData->focusEventCount = instanceData->focusEventCount + 1; + break; + default: + return kNPEventNotHandled; + } + + return kNPEventHandled; +} + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + NPWindow* w = &instanceData->window; + switch (edge) { + case EDGE_LEFT: + return w->x; + case EDGE_TOP: + return w->y; + case EDGE_RIGHT: + return w->x + w->width; + case EDGE_BOTTOM: + return w->y + w->height; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { return 1; } + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, int32_t rectIndex, RectEdge edge) { + if (rectIndex != 0) return NPTEST_INT32_ERROR; + + // We have to add the Cocoa titlebar height here since the clip rect + // is being returned relative to that + static const int COCOA_TITLEBAR_HEIGHT = 22; + + NPWindow* w = &instanceData->window; + switch (edge) { + case EDGE_LEFT: + return w->clipRect.left; + case EDGE_TOP: + return w->clipRect.top + COCOA_TITLEBAR_HEIGHT; + case EDGE_RIGHT: + return w->clipRect.right; + case EDGE_BOTTOM: + return w->clipRect.bottom + COCOA_TITLEBAR_HEIGHT; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, std::string& error) {} diff --git a/dom/plugins/test/testplugin/nptest_name.cpp b/dom/plugins/test/testplugin/nptest_name.cpp new file mode 100644 index 0000000000..3fc6f1fb3c --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_name.cpp @@ -0,0 +1,8 @@ +const char* sPluginName = "Test Plug-in"; +const char* sPluginDescription = + "Plug-in for testing purposes.\xE2\x84\xA2 " + "(\xe0\xa4\xb9\xe0\xa4\xbf\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xa6\xe0\xa5\x80 " + "\xe4\xb8\xad\xe6\x96\x87 " + "\xd8\xa7\xd9\x84\xd8\xb9\xd8\xb1\xd8\xa8\xd9\x8a\xd8\xa9)"; +const char* sMimeDescription = + "application/x-test:tst:Test \xE2\x84\xA2 mimetype"; diff --git a/dom/plugins/test/testplugin/nptest_platform.h b/dom/plugins/test/testplugin/nptest_platform.h new file mode 100644 index 0000000000..4b9584932d --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_platform.h @@ -0,0 +1,155 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_platform_h_ +#define nptest_platform_h_ + +#include "nptest.h" + +/** + * Returns true if the plugin supports windowed mode + */ +bool pluginSupportsWindowMode(); + +/** + * Returns true if the plugin supports windowless mode. At least one of + * "pluginSupportsWindowMode" and "pluginSupportsWindowlessMode" must + * return true. + */ +bool pluginSupportsWindowlessMode(); + +/** + * Initialize the plugin instance. Returning an error here will cause the + * plugin instantiation to fail. + */ +NPError pluginInstanceInit(InstanceData* instanceData); + +/** + * Shutdown the plugin instance. + */ +void pluginInstanceShutdown(InstanceData* instanceData); + +/** + * Set the instanceData's window to newWindow. + */ +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow); + +/** + * Initialize the window for a windowed plugin. oldWindow is the old + * native window value. This will never be called for windowless plugins. + */ +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow); + +/** + * Handle an event for a windowless plugin. (Windowed plugins are + * responsible for listening for their own events.) + */ +int16_t pluginHandleEvent(InstanceData* instanceData, void* event); + +enum RectEdge { EDGE_LEFT = 0, EDGE_TOP = 1, EDGE_RIGHT = 2, EDGE_BOTTOM = 3 }; + +enum { NPTEST_INT32_ERROR = 0x7FFFFFFF }; + +/** + * Return the coordinate of the given edge of the plugin's area, relative + * to the top-left corner of the toplevel window containing the plugin, + * including window decorations. Only works for window-mode plugins + * and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge); + +/** + * Return the number of rectangles in the plugin's clip region. Only + * works for window-mode plugins and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData); + +/** + * Return the coordinate of the given edge of a rectangle in the plugin's + * clip region, relative to the top-left corner of the toplevel window + * containing the plugin, including window decorations. Only works for + * window-mode plugins and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge); + +/** + * Check that the platform-specific plugin state is internally consistent. + * Just return if everything is OK, otherwise append error messages + * to 'error' separated by \n. + */ +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error); + +/** + * Get the current clipboard item as text. If the clipboard item + * isn't text, the returned value is undefined. + */ +std::string pluginGetClipboardText(InstanceData* instanceData); + +/** + * Crash while in a nested event loop. The goal is to catch the + * browser processing the XPCOM event generated from the plugin's + * crash while other plugin code is still on the stack. + * See https://bugzilla.mozilla.org/show_bug.cgi?id=550026. + */ +bool pluginCrashInNestedLoop(InstanceData* instanceData); + +/** + * Generate an X11 protocol error to terminate the plugin process. + */ +bool pluginTriggerXError(InstanceData* instanceData); + +/** + * Destroy gfx things that might be shared with the parent process + * when we're run out-of-process. It's not expected that this + * function will be called when the test plugin is loaded in-process, + * and bad things will happen if it is called. + * + * This call leaves the plugin subprocess in an undefined state. It + * must not be used after this call or weird things will happen. + */ +bool pluginDestroySharedGfxStuff(InstanceData* instanceData); + +/** + * Checks to see if the native widget is marked as visible. Works + * in e10s and non-e10s. Useful in testing e10s related compositor + * plugin window functionality. Supported on Windows. + */ +bool pluginNativeWidgetIsVisible(InstanceData* instanceData); + +#endif // nptest_platform_h_ diff --git a/dom/plugins/test/testplugin/nptest_utils.cpp b/dom/plugins/test/testplugin/nptest_utils.cpp new file mode 100644 index 0000000000..39a3f4b7b7 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_utils.cpp @@ -0,0 +1,100 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_utils.h" + +#include +#include +#include + +NPUTF8* createCStringFromNPVariant(const NPVariant* variant) { + size_t length = NPVARIANT_TO_STRING(*variant).UTF8Length; + NPUTF8* result = (NPUTF8*)malloc(length + 1); + memcpy(result, NPVARIANT_TO_STRING(*variant).UTF8Characters, length); + result[length] = '\0'; + return result; +} + +NPIdentifier variantToIdentifier(NPVariant variant) { + if (NPVARIANT_IS_STRING(variant)) + return stringVariantToIdentifier(variant); + else if (NPVARIANT_IS_INT32(variant)) + return int32VariantToIdentifier(variant); + else if (NPVARIANT_IS_DOUBLE(variant)) + return doubleVariantToIdentifier(variant); + return 0; +} + +NPIdentifier stringVariantToIdentifier(NPVariant variant) { + assert(NPVARIANT_IS_STRING(variant)); + NPUTF8* utf8String = createCStringFromNPVariant(&variant); + NPIdentifier identifier = NPN_GetStringIdentifier(utf8String); + free(utf8String); + return identifier; +} + +NPIdentifier int32VariantToIdentifier(NPVariant variant) { + assert(NPVARIANT_IS_INT32(variant)); + int32_t integer = NPVARIANT_TO_INT32(variant); + return NPN_GetIntIdentifier(integer); +} + +NPIdentifier doubleVariantToIdentifier(NPVariant variant) { + assert(NPVARIANT_IS_DOUBLE(variant)); + double value = NPVARIANT_TO_DOUBLE(variant); + // sadly there is no "getdoubleidentifier" + int32_t integer = static_cast(value); + return NPN_GetIntIdentifier(integer); +} + +/* + * Parse a color in hex format, #AARRGGBB or AARRGGBB. + */ +uint32_t parseHexColor(const char* color, int len) { + uint8_t bgra[4] = {0, 0, 0, 0xFF}; + int i = 0; + + // Ignore unsupported formats. + if (len != 9 && len != 8) return 0; + + // start from the right and work to the left + while (len >= 2) { // we have at least #AA or AA left. + char byte[3]; + // parse two hex digits + byte[0] = color[len - 2]; + byte[1] = color[len - 1]; + byte[2] = '\0'; + + bgra[i] = (uint8_t)(strtoul(byte, nullptr, 16) & 0xFF); + i++; + len -= 2; + } + return (bgra[3] << 24) | (bgra[2] << 16) | (bgra[1] << 8) | bgra[0]; +} diff --git a/dom/plugins/test/testplugin/nptest_utils.h b/dom/plugins/test/testplugin/nptest_utils.h new file mode 100644 index 0000000000..cb2ca5a803 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_utils.h @@ -0,0 +1,45 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_utils_h_ +#define nptest_utils_h_ + +#include "nptest.h" + +NPUTF8* createCStringFromNPVariant(const NPVariant* variant); + +NPIdentifier variantToIdentifier(NPVariant variant); +NPIdentifier stringVariantToIdentifier(NPVariant variant); +NPIdentifier int32VariantToIdentifier(NPVariant variant); +NPIdentifier doubleVariantToIdentifier(NPVariant variant); + +uint32_t parseHexColor(const char* color, int len); + +#endif // nptest_utils_h_ diff --git a/dom/plugins/test/testplugin/nptest_windows.cpp b/dom/plugins/test/testplugin/nptest_windows.cpp new file mode 100644 index 0000000000..0490d23367 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_windows.cpp @@ -0,0 +1,797 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas + * Jim Mathies + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" + +#include +#include +#include + +#include +#include + +void SetSubclass(HWND hWnd, InstanceData* instanceData); +void ClearSubclass(HWND hWnd); +LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam); + +struct _PlatformData { + HWND childWindow; + IDXGIAdapter1* adapter; + ID3D10Device1* device; + ID3D10Texture2D* frontBuffer; + ID3D10Texture2D* backBuffer; + ID2D1Factory* d2d1Factory; +}; + +bool pluginSupportsWindowMode() { return true; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { + instanceData->platformData = + static_cast(NPN_MemAlloc(sizeof(PlatformData))); + if (!instanceData->platformData) return NPERR_OUT_OF_MEMORY_ERROR; + + instanceData->platformData->childWindow = nullptr; + instanceData->platformData->device = nullptr; + instanceData->platformData->frontBuffer = nullptr; + instanceData->platformData->backBuffer = nullptr; + instanceData->platformData->adapter = nullptr; + instanceData->platformData->d2d1Factory = nullptr; + return NPERR_NO_ERROR; +} + +static inline bool openSharedTex2D(ID3D10Device* device, HANDLE handle, + ID3D10Texture2D** out) { + HRESULT hr = device->OpenSharedResource(handle, __uuidof(ID3D10Texture2D), + (void**)out); + if (FAILED(hr) || !*out) { + return false; + } + return true; +} + +// This is overloaded in d2d1.h so we can't use decltype(). +typedef HRESULT(WINAPI* D2D1CreateFactoryFunc)( + D2D1_FACTORY_TYPE factoryType, REFIID iid, + CONST D2D1_FACTORY_OPTIONS* pFactoryOptions, void** factory); + +static IDXGIAdapter1* FindDXGIAdapter(NPP npp, IDXGIFactory1* factory) { + DXGI_ADAPTER_DESC preferred; + if (NPN_GetValue(npp, NPNVpreferredDXGIAdapter, &preferred) != + NPERR_NO_ERROR) { + return nullptr; + } + + UINT index = 0; + for (;;) { + IDXGIAdapter1* adapter = nullptr; + if (FAILED(factory->EnumAdapters1(index, &adapter)) || !adapter) { + return nullptr; + } + + DXGI_ADAPTER_DESC desc; + if (SUCCEEDED(adapter->GetDesc(&desc)) && + desc.AdapterLuid.LowPart == preferred.AdapterLuid.LowPart && + desc.AdapterLuid.HighPart == preferred.AdapterLuid.HighPart && + desc.VendorId == preferred.VendorId && + desc.DeviceId == preferred.DeviceId) { + return adapter; + } + + adapter->Release(); + index++; + } +} + +// Note: we leak modules since we need them anyway. +bool setupDxgiSurfaces(NPP npp, InstanceData* instanceData) { + HMODULE dxgi = LoadLibraryA("dxgi.dll"); + if (!dxgi) { + return false; + } + decltype(CreateDXGIFactory1)* createDXGIFactory1 = + (decltype(CreateDXGIFactory1)*)GetProcAddress(dxgi, "CreateDXGIFactory1"); + if (!createDXGIFactory1) { + return false; + } + + IDXGIFactory1* factory1 = nullptr; + HRESULT hr = createDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory1); + if (FAILED(hr) || !factory1) { + return false; + } + + instanceData->platformData->adapter = FindDXGIAdapter(npp, factory1); + if (!instanceData->platformData->adapter) { + return false; + } + + HMODULE d3d10 = LoadLibraryA("d3d10_1.dll"); + if (!d3d10) { + return false; + } + + decltype(D3D10CreateDevice1)* createDevice = + (decltype(D3D10CreateDevice1)*)GetProcAddress(d3d10, + "D3D10CreateDevice1"); + if (!createDevice) { + return false; + } + + hr = createDevice( + instanceData->platformData->adapter, D3D10_DRIVER_TYPE_HARDWARE, nullptr, + D3D10_CREATE_DEVICE_BGRA_SUPPORT | + D3D10_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS, + D3D10_FEATURE_LEVEL_10_1, D3D10_1_SDK_VERSION, + &instanceData->platformData->device); + if (FAILED(hr) || !instanceData->platformData->device) { + return false; + } + + if (!openSharedTex2D(instanceData->platformData->device, + instanceData->frontBuffer->sharedHandle, + &instanceData->platformData->frontBuffer)) { + return false; + } + if (!openSharedTex2D(instanceData->platformData->device, + instanceData->backBuffer->sharedHandle, + &instanceData->platformData->backBuffer)) { + return false; + } + + HMODULE d2d1 = LoadLibraryA("D2d1.dll"); + if (!d2d1) { + return false; + } + auto d2d1CreateFactory = + (D2D1CreateFactoryFunc)GetProcAddress(d2d1, "D2D1CreateFactory"); + if (!d2d1CreateFactory) { + return false; + } + + D2D1_FACTORY_OPTIONS options; + options.debugLevel = D2D1_DEBUG_LEVEL_NONE; + + hr = d2d1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, + __uuidof(ID2D1Factory), &options, + (void**)&instanceData->platformData->d2d1Factory); + if (FAILED(hr) || !instanceData->platformData->d2d1Factory) { + return false; + } + + return true; +} + +void drawDxgiBitmapColor(InstanceData* instanceData) { + NPP npp = instanceData->npp; + + HRESULT hr; + + IDXGISurface* surface = nullptr; + hr = instanceData->platformData->backBuffer->QueryInterface( + __uuidof(IDXGISurface), (void**)&surface); + if (FAILED(hr) || !surface) { + return; + } + + D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED)); + + ID2D1RenderTarget* target = nullptr; + hr = instanceData->platformData->d2d1Factory->CreateDxgiSurfaceRenderTarget( + surface, &props, &target); + if (FAILED(hr) || !target) { + surface->Release(); + return; + } + + IDXGIKeyedMutex* mutex = nullptr; + hr = instanceData->platformData->backBuffer->QueryInterface( + __uuidof(IDXGIKeyedMutex), (void**)&mutex); + if (mutex) { + mutex->AcquireSync(0, 0); + } + + target->BeginDraw(); + + unsigned char subpixels[4]; + memcpy(subpixels, &instanceData->scriptableObject->drawColor, + sizeof(subpixels)); + + auto rect = D2D1::RectF(0, 0, instanceData->backBuffer->size.width, + instanceData->backBuffer->size.height); + auto color = D2D1::ColorF(float(subpixels[3] * subpixels[2]) / 0xFF, + float(subpixels[3] * subpixels[1]) / 0xFF, + float(subpixels[3] * subpixels[0]) / 0xFF, + float(subpixels[3]) / 0xff); + + ID2D1SolidColorBrush* brush = nullptr; + hr = target->CreateSolidColorBrush(color, &brush); + if (SUCCEEDED(hr) && brush) { + target->FillRectangle(rect, brush); + brush->Release(); + brush = nullptr; + } + hr = target->EndDraw(); + + if (mutex) { + mutex->ReleaseSync(0); + mutex->Release(); + mutex = nullptr; + } + + target->Release(); + surface->Release(); + target = nullptr; + surface = nullptr; + + NPN_SetCurrentAsyncSurface(npp, instanceData->backBuffer, NULL); + std::swap(instanceData->backBuffer, instanceData->frontBuffer); + std::swap(instanceData->platformData->backBuffer, + instanceData->platformData->frontBuffer); +} + +void pluginInstanceShutdown(InstanceData* instanceData) { + PlatformData* pd = instanceData->platformData; + if (pd->frontBuffer) { + pd->frontBuffer->Release(); + } + if (pd->backBuffer) { + pd->backBuffer->Release(); + } + if (pd->d2d1Factory) { + pd->d2d1Factory->Release(); + } + if (pd->device) { + pd->device->Release(); + } + if (pd->adapter) { + pd->adapter->Release(); + } + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; + ClearSubclass((HWND)instanceData->window.window); +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + instanceData->window = *newWindow; +} + +#define CHILD_WIDGET_SIZE 10 + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { + HWND hWnd = (HWND)instanceData->window.window; + if (oldWindow) { + // chrashtests/539897-1.html excercises this code + HWND hWndOld = (HWND)oldWindow; + ClearSubclass(hWndOld); + if (instanceData->platformData->childWindow) { + ::DestroyWindow(instanceData->platformData->childWindow); + } + } + + SetSubclass(hWnd, instanceData); + + instanceData->platformData->childWindow = ::CreateWindowW( + L"SCROLLBAR", L"Dummy child window", WS_CHILD, 0, 0, CHILD_WIDGET_SIZE, + CHILD_WIDGET_SIZE, hWnd, nullptr, nullptr, nullptr); +} + +static void drawToDC(InstanceData* instanceData, HDC dc, int x, int y, + int width, int height) { + switch (instanceData->scriptableObject->drawMode) { + case DM_DEFAULT: { + const RECT fill = {x, y, x + width, y + height}; + + int oldBkMode = ::SetBkMode(dc, TRANSPARENT); + HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); + if (brush) { + ::FillRect(dc, &fill, brush); + ::DeleteObject(brush); + } + if (width > 6 && height > 6) { + brush = ::CreateSolidBrush(RGB(192, 192, 192)); + if (brush) { + RECT inset = {x + 3, y + 3, x + width - 3, y + height - 3}; + ::FillRect(dc, &inset, brush); + ::DeleteObject(brush); + } + } + + const char* uaString = NPN_UserAgent(instanceData->npp); + if (uaString && width > 10 && height > 10) { + HFONT font = ::CreateFontA(20, 0, 0, 0, 400, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, 5, // CLEARTYPE_QUALITY + DEFAULT_PITCH, "Arial"); + if (font) { + HFONT oldFont = (HFONT)::SelectObject(dc, font); + RECT inset = {x + 5, y + 5, x + width - 5, y + height - 5}; + ::DrawTextA(dc, uaString, -1, &inset, + DT_LEFT | DT_TOP | DT_NOPREFIX | DT_WORDBREAK); + ::SelectObject(dc, oldFont); + ::DeleteObject(font); + } + } + ::SetBkMode(dc, oldBkMode); + } break; + + case DM_SOLID_COLOR: { + HDC offscreenDC = ::CreateCompatibleDC(dc); + if (!offscreenDC) return; + + const BITMAPV4HEADER bitmapheader = { + sizeof(BITMAPV4HEADER), + width, + height, + 1, // planes + 32, // bits + BI_BITFIELDS, + 0, // unused size + 0, + 0, // unused metrics + 0, + 0, // unused colors used/important + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000, // ARGB masks + }; + uint32_t* pixelData; + HBITMAP offscreenBitmap = ::CreateDIBSection( + dc, reinterpret_cast(&bitmapheader), 0, + reinterpret_cast(&pixelData), 0, 0); + if (!offscreenBitmap) return; + + uint32_t rgba = instanceData->scriptableObject->drawColor; + unsigned int alpha = ((rgba & 0xFF000000) >> 24); + BYTE r = ((rgba & 0xFF0000) >> 16); + BYTE g = ((rgba & 0xFF00) >> 8); + BYTE b = (rgba & 0xFF); + + // Windows expects premultiplied + r = BYTE(float(alpha * r) / 0xFF); + g = BYTE(float(alpha * g) / 0xFF); + b = BYTE(float(alpha * b) / 0xFF); + uint32_t premultiplied = (alpha << 24) + (r << 16) + (g << 8) + b; + + for (uint32_t* lastPixel = pixelData + width * height; + pixelData < lastPixel; ++pixelData) + *pixelData = premultiplied; + + ::SelectObject(offscreenDC, offscreenBitmap); + BLENDFUNCTION blendFunc; + blendFunc.BlendOp = AC_SRC_OVER; + blendFunc.BlendFlags = 0; + blendFunc.SourceConstantAlpha = 255; + blendFunc.AlphaFormat = AC_SRC_ALPHA; + ::AlphaBlend(dc, x, y, width, height, offscreenDC, 0, 0, width, height, + blendFunc); + + ::DeleteObject(offscreenDC); + ::DeleteObject(offscreenBitmap); + } break; + } +} + +void pluginDraw(InstanceData* instanceData) { + NPP npp = instanceData->npp; + if (!npp) return; + + HDC hdc = nullptr; + PAINTSTRUCT ps; + + notifyDidPaint(instanceData); + + if (instanceData->hasWidget) + hdc = ::BeginPaint((HWND)instanceData->window.window, &ps); + else + hdc = (HDC)instanceData->window.window; + + if (hdc == nullptr) return; + + // Push the browser's hdc on the resource stack. If this test plugin is + // windowless, we share the drawing surface with the rest of the browser. + int savedDCID = SaveDC(hdc); + + // When we have a widget, window.x/y are meaningless since our widget + // is always positioned correctly and we just draw into it at 0,0. + int x = instanceData->hasWidget ? 0 : instanceData->window.x; + int y = instanceData->hasWidget ? 0 : instanceData->window.y; + int width = instanceData->window.width; + int height = instanceData->window.height; + drawToDC(instanceData, hdc, x, y, width, height); + + // Pop our hdc changes off the resource stack + RestoreDC(hdc, savedDCID); + + if (instanceData->hasWidget) + ::EndPaint((HWND)instanceData->window.window, &ps); +} + +/* script interface */ + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + if (!instanceData || !instanceData->hasWidget) return NPTEST_INT32_ERROR; + + // Get the plugin client rect in screen coordinates + RECT rect = {0}; + if (!::GetClientRect((HWND)instanceData->window.window, &rect)) + return NPTEST_INT32_ERROR; + ::MapWindowPoints((HWND)instanceData->window.window, nullptr, (LPPOINT)&rect, + 2); + + // Get the toplevel window frame rect in screen coordinates + HWND rootWnd = ::GetAncestor((HWND)instanceData->window.window, GA_ROOT); + if (!rootWnd) return NPTEST_INT32_ERROR; + RECT rootRect; + if (!::GetWindowRect(rootWnd, &rootRect)) return NPTEST_INT32_ERROR; + + switch (edge) { + case EDGE_LEFT: + return rect.left - rootRect.left; + case EDGE_TOP: + return rect.top - rootRect.top; + case EDGE_RIGHT: + return rect.right - rootRect.left; + case EDGE_BOTTOM: + return rect.bottom - rootRect.top; + } + + return NPTEST_INT32_ERROR; +} + +static BOOL getWindowRegion(HWND wnd, HRGN rgn) { + if (::GetWindowRgn(wnd, rgn) != ERROR) return TRUE; + + RECT clientRect; + if (!::GetClientRect(wnd, &clientRect)) return FALSE; + return ::SetRectRgn(rgn, 0, 0, clientRect.right, clientRect.bottom); +} + +static RGNDATA* computeClipRegion(InstanceData* instanceData) { + HWND wnd = (HWND)instanceData->window.window; + HRGN rgn = ::CreateRectRgn(0, 0, 0, 0); + if (!rgn) return nullptr; + HRGN ancestorRgn = ::CreateRectRgn(0, 0, 0, 0); + if (!ancestorRgn) { + ::DeleteObject(rgn); + return nullptr; + } + if (!getWindowRegion(wnd, rgn)) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return nullptr; + } + + HWND ancestor = wnd; + for (;;) { + ancestor = ::GetAncestor(ancestor, GA_PARENT); + if (!ancestor || ancestor == ::GetDesktopWindow()) { + ::DeleteObject(ancestorRgn); + + DWORD size = ::GetRegionData(rgn, 0, nullptr); + if (!size) { + ::DeleteObject(rgn); + return nullptr; + } + + HANDLE heap = ::GetProcessHeap(); + RGNDATA* data = static_cast(::HeapAlloc(heap, 0, size)); + if (!data) { + ::DeleteObject(rgn); + return nullptr; + } + DWORD result = ::GetRegionData(rgn, size, data); + ::DeleteObject(rgn); + if (!result) { + ::HeapFree(heap, 0, data); + return nullptr; + } + + return data; + } + + if (!getWindowRegion(ancestor, ancestorRgn)) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return 0; + } + + POINT pt = {0, 0}; + ::MapWindowPoints(ancestor, wnd, &pt, 1); + if (::OffsetRgn(ancestorRgn, pt.x, pt.y) == ERROR || + ::CombineRgn(rgn, rgn, ancestorRgn, RGN_AND) == ERROR) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return 0; + } + } +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { + RGNDATA* data = computeClipRegion(instanceData); + if (!data) return NPTEST_INT32_ERROR; + + int32_t result = data->rdh.nCount; + ::HeapFree(::GetProcessHeap(), 0, data); + return result; +} + +static int32_t addOffset(LONG coord, int32_t offset) { + if (offset == NPTEST_INT32_ERROR) return NPTEST_INT32_ERROR; + return coord + offset; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) { + RGNDATA* data = computeClipRegion(instanceData); + if (!data) return NPTEST_INT32_ERROR; + + HANDLE heap = ::GetProcessHeap(); + if (rectIndex >= int32_t(data->rdh.nCount)) { + ::HeapFree(heap, 0, data); + return NPTEST_INT32_ERROR; + } + + RECT rect = reinterpret_cast(data->Buffer)[rectIndex]; + ::HeapFree(heap, 0, data); + + switch (edge) { + case EDGE_LEFT: + return addOffset(rect.left, pluginGetEdge(instanceData, EDGE_LEFT)); + case EDGE_TOP: + return addOffset(rect.top, pluginGetEdge(instanceData, EDGE_TOP)); + case EDGE_RIGHT: + return addOffset(rect.right, pluginGetEdge(instanceData, EDGE_LEFT)); + case EDGE_BOTTOM: + return addOffset(rect.bottom, pluginGetEdge(instanceData, EDGE_TOP)); + } + + return NPTEST_INT32_ERROR; +} + +static void createDummyWindowForIME(InstanceData* instanceData) { + WNDCLASSW wndClass; + wndClass.style = 0; + wndClass.lpfnWndProc = DefWindowProcW; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hInstance = GetModuleHandleW(NULL); + wndClass.hIcon = nullptr; + wndClass.hCursor = nullptr; + wndClass.hbrBackground = (HBRUSH)COLOR_WINDOW; + wndClass.lpszMenuName = NULL; + wndClass.lpszClassName = L"SWFlash_PlaceholderX"; + RegisterClassW(&wndClass); + + instanceData->placeholderWnd = static_cast( + CreateWindowW(L"SWFlash_PlaceholderX", L"", WS_CHILD, 0, 0, 0, 0, + HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL)); +} + +/* windowless plugin events */ + +static bool handleEventInternal(InstanceData* instanceData, NPEvent* pe, + LRESULT* result) { + switch ((UINT)pe->event) { + case WM_PAINT: + pluginDraw(instanceData); + return true; + + case WM_MOUSEACTIVATE: + if (instanceData->hasWidget) { + ::SetFocus((HWND)instanceData->window.window); + *result = MA_ACTIVATEANDEAT; + return true; + } + return false; + + case WM_MOUSEWHEEL: + return true; + + case WM_WINDOWPOSCHANGED: { + WINDOWPOS* pPos = (WINDOWPOS*)pe->lParam; + instanceData->winX = instanceData->winY = 0; + if (pPos) { + instanceData->winX = pPos->x; + instanceData->winY = pPos->y; + return true; + } + return false; + } + + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: { + int x = instanceData->hasWidget ? 0 : instanceData->winX; + int y = instanceData->hasWidget ? 0 : instanceData->winY; + instanceData->lastMouseX = GET_X_LPARAM(pe->lParam) - x; + instanceData->lastMouseY = GET_Y_LPARAM(pe->lParam) - y; + if ((UINT)pe->event == WM_LBUTTONUP) { + instanceData->mouseUpEventCount++; + } + return true; + } + + case WM_KEYDOWN: + instanceData->lastKeyText.erase(); + *result = 0; + return true; + + case WM_CHAR: { + *result = 0; + wchar_t uniChar = static_cast(pe->wParam); + if (!uniChar) { + return true; + } + char utf8Char[6]; + int len = ::WideCharToMultiByte(CP_UTF8, 0, &uniChar, 1, utf8Char, 6, + nullptr, nullptr); + if (len == 0 || len > 6) { + return true; + } + instanceData->lastKeyText.append(utf8Char, len); + return true; + } + + case WM_IME_STARTCOMPOSITION: + instanceData->lastComposition.erase(); + if (!instanceData->placeholderWnd) { + createDummyWindowForIME(instanceData); + } + return true; + + case WM_IME_ENDCOMPOSITION: + instanceData->lastComposition.erase(); + return true; + + case WM_IME_COMPOSITION: { + if (pe->lParam & GCS_COMPSTR) { + HIMC hIMC = ImmGetContext((HWND)instanceData->placeholderWnd); + if (!hIMC) { + return false; + } + WCHAR compStr[256]; + LONG len = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, compStr, + 256 * sizeof(WCHAR)); + CHAR buffer[256]; + len = ::WideCharToMultiByte(CP_UTF8, 0, compStr, len / sizeof(WCHAR), + buffer, 256, nullptr, nullptr); + instanceData->lastComposition.append(buffer, len); + ::ImmReleaseContext((HWND)instanceData->placeholderWnd, hIMC); + } + return true; + } + + default: + return false; + } +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { + NPEvent* pe = (NPEvent*)event; + + if (pe == nullptr || instanceData == nullptr || + instanceData->window.type != NPWindowTypeDrawable) + return 0; + + LRESULT result = 0; + return handleEventInternal(instanceData, pe, &result); +} + +/* windowed plugin events */ + +LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + WNDPROC wndProc = (WNDPROC)GetProp(hWnd, "MozillaWndProc"); + if (!wndProc) return 0; + InstanceData* pInstance = (InstanceData*)GetProp(hWnd, "InstanceData"); + if (!pInstance) return 0; + + NPEvent event = {static_cast(uMsg), wParam, lParam}; + + LRESULT result = 0; + if (handleEventInternal(pInstance, &event, &result)) return result; + + if (uMsg == WM_CLOSE) { + ClearSubclass((HWND)pInstance->window.window); + } + + return CallWindowProc(wndProc, hWnd, uMsg, wParam, lParam); +} + +void ClearSubclass(HWND hWnd) { + if (GetProp(hWnd, "MozillaWndProc")) { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, + (LONG_PTR)GetProp(hWnd, "MozillaWndProc")); + RemoveProp(hWnd, "MozillaWndProc"); + RemoveProp(hWnd, "InstanceData"); + } +} + +void SetSubclass(HWND hWnd, InstanceData* instanceData) { + // Subclass the plugin window so we can handle our own windows events. + SetProp(hWnd, "InstanceData", (HANDLE)instanceData); + WNDPROC origProc = + (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PluginWndProc); + SetProp(hWnd, "MozillaWndProc", (HANDLE)origProc); +} + +static void checkEquals(int a, int b, const char* msg, std::string& error) { + if (a == b) { + return; + } + + error.append(msg); + char buf[100]; + sprintf(buf, " (got %d, expected %d)\n", a, b); + error.append(buf); +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error) { + if (instanceData->platformData->childWindow) { + RECT childRect; + ::GetWindowRect(instanceData->platformData->childWindow, &childRect); + RECT ourRect; + HWND hWnd = (HWND)instanceData->window.window; + ::GetWindowRect(hWnd, &ourRect); + checkEquals(childRect.left, ourRect.left, "Child widget left", error); + checkEquals(childRect.top, ourRect.top, "Child widget top", error); + checkEquals(childRect.right, childRect.left + CHILD_WIDGET_SIZE, + "Child widget width", error); + checkEquals(childRect.bottom, childRect.top + CHILD_WIDGET_SIZE, + "Child widget height", error); + } +} + +bool pluginNativeWidgetIsVisible(InstanceData* instanceData) { + HWND hWnd = (HWND)instanceData->window.window; + wchar_t className[60]; + if (::GetClassNameW(hWnd, className, sizeof(className) / sizeof(char16_t)) && + !wcsicmp(className, L"GeckoPluginWindow")) { + return ::IsWindowVisible(hWnd); + } + // something isn't right, fail the check + return false; +} diff --git a/dom/plugins/test/testplugin/secondplugin/Info.plist b/dom/plugins/test/testplugin/secondplugin/Info.plist new file mode 100644 index 0000000000..afa83a63ce --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + libnpsecondtest.dylib + CFBundleIdentifier + org.mozilla.SecondTestPlugin + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BRPL + CFBundleShortVersionString + 1.0.0.0 + CFBundleSignature + SECONDTEST + CFBundleVersion + 1.0.0.0 + WebPluginName + Second Test Plug-in + WebPluginDescription + Second plug-in for testing purposes. + WebPluginMIMETypes + + application/x-Second-Test + + WebPluginExtensions + + ts2 + + WebPluginTypeDescription + Second test type + + + + diff --git a/dom/plugins/test/testplugin/secondplugin/moz.build b/dom/plugins/test/testplugin/secondplugin/moz.build new file mode 100644 index 0000000000..29c12260b4 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SharedLibrary("npsecondtest") + +relative_path = "secondplugin" +cocoa_name = "SecondTest" +include("../testplugin.mozbuild") diff --git a/dom/plugins/test/testplugin/secondplugin/nptest.def b/dom/plugins/test/testplugin/secondplugin/nptest.def new file mode 100644 index 0000000000..c6584387d2 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPSECONDTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/secondplugin/nptest.rc b/dom/plugins/test/testplugin/secondplugin/nptest.rc new file mode 100644 index 0000000000..835906d0cb --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest.rc @@ -0,0 +1,42 @@ +#include + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Second plug-in for testing purposes." + VALUE "FileExtents", "ts2" + VALUE "FileOpenName", "Second test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npsecondtest" + VALUE "MIMEType", "application/x-Second-Test" + VALUE "OriginalFilename", "npsecondtest.dll" + VALUE "ProductName", "Second Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp b/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp new file mode 100644 index 0000000000..23b821ae61 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const char* sPluginName = "Second Test Plug-in"; +const char* sPluginDescription = "Second plug-in for testing purposes."; +const char* sMimeDescription = "application/x-Second-Test:ts2:Second test type"; diff --git a/dom/plugins/test/testplugin/testplugin.mozbuild b/dom/plugins/test/testplugin/testplugin.mozbuild new file mode 100644 index 0000000000..2c466409ea --- /dev/null +++ b/dom/plugins/test/testplugin/testplugin.mozbuild @@ -0,0 +1,64 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'nptest.cpp', + 'nptest_utils.cpp', +] + +UNIFIED_SOURCES += [ + '%s/nptest_name.cpp' % relative_path, +] + +toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] +if toolkit == 'cocoa': + UNIFIED_SOURCES += [ + 'nptest_macosx.mm' + ] +elif toolkit == 'gtk': + UNIFIED_SOURCES += [ + 'nptest_gtk2.cpp', + ] +elif toolkit == 'windows': + UNIFIED_SOURCES += [ + 'nptest_windows.cpp', + ] + OS_LIBS += [ + 'msimg32', + 'imm32' + ] + +# must link statically with the CRT; nptest isn't Gecko code +USE_STATIC_LIBS = True + +# Don't use STL wrappers; nptest isn't Gecko code +DisableStlWrapping() + +NO_PGO = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + RCFILE = 'nptest.rc' + DEFFILE = 'nptest.def' + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' and CONFIG['TARGET_CPU'] == 'x86_64': + OS_LIBS += ['-framework Carbon'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk': + CXXFLAGS += CONFIG['MOZ_GTK2_CFLAGS'] + CFLAGS += CONFIG['MOZ_GTK2_CFLAGS'] + OS_LIBS += CONFIG['MOZ_GTK2_LIBS'] + OS_LIBS += CONFIG['XLDFLAGS'] + OS_LIBS += CONFIG['XLIBS'] + OS_LIBS += CONFIG['XEXT_LIBS'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + FINAL_TARGET = 'dist/plugins/%s.plugin/Contents/MacOS' % cocoa_name + OBJDIR_FILES.dist.plugins['%s.plugin' % cocoa_name].Contents += ['%s/Info.plist' % relative_path] +else: + FINAL_TARGET = 'dist/plugins' + +if CONFIG['CC_TYPE'] in ('clang', 'gcc'): + CXXFLAGS += ['-Wno-error=shadow'] -- cgit v1.2.3