Backed out changeset 999aa7b6683d (bug 1352559)
authorSebastian Hengst <archaeopteryx@coole-files.de>
Wed, 12 Jul 2017 19:10:50 +0200
changeset 607714 28f351ecb49b91cba62b4be6ed222657a4b01143
parent 607713 da31f44d2f8a27bfe0d97905346dcf891a5c170f
child 607715 f239f27f8a3b764b76cf1ac394e74e52cc953c44
push id68095
push userbmo:rbarker@mozilla.com
push dateWed, 12 Jul 2017 20:01:47 +0000
bugs1352559
milestone56.0a1
backs out999aa7b6683d7f0cf481f8dd0e8a9ba3dade4a05
Backed out changeset 999aa7b6683d (bug 1352559)
dom/plugins/base/nsNPAPIPlugin.cpp
dom/plugins/base/nsNPAPIPlugin.h
dom/plugins/base/nsNPAPIPluginInstance.cpp
dom/plugins/base/nsNPAPIPluginStreamListener.cpp
dom/plugins/base/nsNPAPIPluginStreamListener.h
dom/plugins/ipc/PPluginInstance.ipdl
dom/plugins/ipc/PPluginStream.ipdl
dom/plugins/ipc/PluginInstanceChild.cpp
dom/plugins/ipc/PluginInstanceChild.h
dom/plugins/ipc/PluginInstanceParent.cpp
dom/plugins/ipc/PluginInstanceParent.h
dom/plugins/ipc/PluginModuleChild.cpp
dom/plugins/ipc/PluginModuleParent.cpp
dom/plugins/ipc/PluginStreamChild.cpp
dom/plugins/ipc/PluginStreamChild.h
dom/plugins/ipc/PluginStreamParent.cpp
dom/plugins/ipc/PluginStreamParent.h
dom/plugins/ipc/moz.build
dom/plugins/test/testplugin/nptest.cpp
ipc/ipdl/sync-messages.ini
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -114,19 +114,19 @@ using namespace mozilla::plugins::parent
 
 // We should make this const...
 static NPNetscapeFuncs sBrowserFuncs = {
   sizeof(sBrowserFuncs),
   (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR,
   _geturl,
   _posturl,
   _requestread,
-  nullptr,
-  nullptr,
-  nullptr,
+  _newstream,
+  _write,
+  _destroystream,
   _status,
   _useragent,
   _memalloc,
   _memfree,
   _memflush,
   _reloadplugins,
   _getJavaEnv,
   _getJavaPeer,
@@ -771,16 +771,137 @@ NPError
 
   PluginDestructionGuard guard(npp);
 
   return MakeNewNPAPIStreamInternal(npp, relativeURL, target,
                                     eNPPStreamTypeInternal_Post, false, nullptr,
                                     len, buf);
 }
 
+NPError
+_newstream(NPP npp, NPMIMEType type, const char* target, NPStream* *result)
+{
+  if (!NS_IsMainThread()) {
+    NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_newstream called from the wrong thread\n"));
+    return NPERR_INVALID_PARAM;
+  }
+  NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+  ("NPN_NewStream: npp=%p, type=%s, target=%s\n", (void*)npp,
+   (const char *)type, target));
+
+  NPError err = NPERR_INVALID_INSTANCE_ERROR;
+  if (npp && npp->ndata) {
+    nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance*)npp->ndata;
+
+    PluginDestructionGuard guard(inst);
+
+    nsCOMPtr<nsIOutputStream> stream;
+    if (NS_SUCCEEDED(inst->NewStreamFromPlugin((const char*) type, target,
+                                               getter_AddRefs(stream)))) {
+      auto* wrapper = new nsNPAPIStreamWrapper(stream, nullptr);
+      if (wrapper) {
+        (*result) = &wrapper->mNPStream;
+        err = NPERR_NO_ERROR;
+      } else {
+        err = NPERR_OUT_OF_MEMORY_ERROR;
+      }
+    } else {
+      err = NPERR_GENERIC_ERROR;
+    }
+  }
+  return err;
+}
+
+int32_t
+_write(NPP npp, NPStream *pstream, int32_t len, void *buffer)
+{
+  if (!NS_IsMainThread()) {
+    NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_write called from the wrong thread\n"));
+    return 0;
+  }
+  NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+                 ("NPN_Write: npp=%p, url=%s, len=%d, buffer=%s\n", (void*)npp,
+                  pstream->url, len, (char*)buffer));
+
+  // negative return indicates failure to the plugin
+  if (!npp)
+    return -1;
+
+  PluginDestructionGuard guard(npp);
+
+  nsNPAPIStreamWrapper* wrapper = static_cast<nsNPAPIStreamWrapper*>(pstream->ndata);
+  if (!wrapper) {
+    return -1;
+  }
+
+  nsIOutputStream* stream = wrapper->GetOutputStream();
+  if (!stream) {
+    return -1;
+  }
+
+  uint32_t count = 0;
+  nsresult rv = stream->Write((char *)buffer, len, &count);
+
+  if (NS_FAILED(rv)) {
+    return -1;
+  }
+
+  return (int32_t)count;
+}
+
+NPError
+_destroystream(NPP npp, NPStream *pstream, NPError reason)
+{
+  if (!NS_IsMainThread()) {
+    NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_destroystream called from the wrong thread\n"));
+    return NPERR_INVALID_PARAM;
+  }
+  NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+                 ("NPN_DestroyStream: npp=%p, url=%s, reason=%d\n", (void*)npp,
+                  pstream->url, (int)reason));
+
+  if (!npp)
+    return NPERR_INVALID_INSTANCE_ERROR;
+
+  PluginDestructionGuard guard(npp);
+
+  nsNPAPIStreamWrapper *streamWrapper = static_cast<nsNPAPIStreamWrapper*>(pstream->ndata);
+  if (!streamWrapper) {
+    return NPERR_INVALID_PARAM;
+  }
+
+  nsNPAPIPluginStreamListener *listener = streamWrapper->GetStreamListener();
+  if (listener) {
+    // This type of stream is going from the browser to the plugin. It's either the
+    // initial src/data stream or another stream resulting from NPN_GetURL* or
+    // NPN_PostURL*.
+    //
+    // Calling OnStopBinding on the listener may cause it to be deleted due to the
+    // releasing of its last references.
+    listener->OnStopBinding(nullptr, NS_BINDING_ABORTED);
+  } else {
+    // This type of stream (NPStream) was created via NPN_NewStream. The plugin holds
+    // the reference until it is to be deleted here. Deleting the wrapper will
+    // release the wrapped nsIOutputStream.
+    //
+    // The NPStream the plugin references should always be a sub-object of its own
+    // 'ndata', which is our nsNPAPIStramWrapper. See bug 548441.
+    NS_ASSERTION((char*)streamWrapper <= (char*)pstream &&
+                 ((char*)pstream) + sizeof(*pstream)
+                     <= ((char*)streamWrapper) + sizeof(*streamWrapper),
+                 "pstream is not a subobject of wrapper");
+    delete streamWrapper;
+  }
+
+  // 'listener' and/or 'streamWrapper' may be invalid (deleted) at this point. Don't
+  // touch them again!
+
+  return NPERR_NO_ERROR;
+}
+
 void
 _status(NPP npp, const char *message)
 {
   // NPN_Status is no longer supported.
 }
 
 void
 _memfree (void *ptr)
--- a/dom/plugins/base/nsNPAPIPlugin.h
+++ b/dom/plugins/base/nsNPAPIPlugin.h
@@ -266,16 +266,25 @@ NPError
 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);
 
+NPError
+_newstream(NPP npp, NPMIMEType type, const char* window, NPStream** pstream);
+
+int32_t
+_write(NPP npp, NPStream *pstream, int32_t len, void *buffer);
+
+NPError
+_destroystream(NPP npp, NPStream *pstream, NPError reason);
+
 void
 _status(NPP npp, const char *message);
 
 void
 _memfree (void *ptr);
 
 uint32_t
 _memflush(uint32_t size);
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -507,16 +507,24 @@ nsresult nsNPAPIPluginInstance::SetWindo
     ("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::NewStreamFromPlugin(const char* type, const char* target,
+                                           nsIOutputStream* *result)
+{
+  nsPluginStreamToFile* stream = new nsPluginStreamToFile(target, mOwner);
+  return stream->QueryInterface(kIOutputStreamIID, (void**)result);
+}
+
+nsresult
 nsNPAPIPluginInstance::NewStreamListener(const char* aURL, void* notifyData,
                                          nsNPAPIPluginStreamListener** listener)
 {
   RefPtr<nsNPAPIPluginStreamListener> sl = new nsNPAPIPluginStreamListener(this, notifyData, aURL);
 
   mStreamListeners.AppendElement(sl);
 
   sl.forget(listener);
--- a/dom/plugins/base/nsNPAPIPluginStreamListener.cpp
+++ b/dom/plugins/base/nsNPAPIPluginStreamListener.cpp
@@ -29,17 +29,108 @@ nsNPAPIStreamWrapper::nsNPAPIStreamWrapp
 
 nsNPAPIStreamWrapper::~nsNPAPIStreamWrapper()
 {
   if (mOutputStream) {
     mOutputStream->Close();
   }
 }
 
+NS_IMPL_ISUPPORTS(nsPluginStreamToFile, nsIOutputStream)
+
+nsPluginStreamToFile::nsPluginStreamToFile(const char* target,
+                                           nsIPluginInstanceOwner* owner)
+: mTarget(PL_strdup(target)),
+mOwner(owner)
+{
+  nsresult rv;
+  nsCOMPtr<nsIFile> pluginTmp;
+  rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(pluginTmp));
+  if (NS_FAILED(rv)) return;
+
+  mTempFile = do_QueryInterface(pluginTmp, &rv);
+  if (NS_FAILED(rv)) return;
+
+  // need to create a file with a unique name - use target as the basis
+  rv = mTempFile->AppendNative(nsDependentCString(target));
+  if (NS_FAILED(rv)) return;
+
+  // Yes, make it unique.
+  rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700);
+  if (NS_FAILED(rv)) return;
+
+  // create the file
+  rv = NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), mTempFile, -1, 00600);
+  if (NS_FAILED(rv))
+    return;
+
+  // construct the URL we'll use later in calls to GetURL()
+  NS_GetURLSpecFromFile(mTempFile, mFileURL);
+
+#ifdef DEBUG
+  printf("File URL = %s\n", mFileURL.get());
+#endif
+}
+
+nsPluginStreamToFile::~nsPluginStreamToFile()
+{
+  // should we be deleting mTempFile here?
+  if (nullptr != mTarget)
+    PL_strfree(mTarget);
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::Flush()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::Write(const char* aBuf, uint32_t aCount,
+                            uint32_t *aWriteCount)
+{
+  mOutputStream->Write(aBuf, aCount, aWriteCount);
+  mOutputStream->Flush();
+  mOwner->GetURL(mFileURL.get(), mTarget, nullptr, nullptr, 0, false);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::WriteFrom(nsIInputStream *inStr, uint32_t count,
+                                uint32_t *_retval)
+{
+  NS_NOTREACHED("WriteFrom");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::WriteSegments(nsReadSegmentFun reader, void * closure,
+                                    uint32_t count, uint32_t *_retval)
+{
+  NS_NOTREACHED("WriteSegments");
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::IsNonBlocking(bool *aNonBlocking)
+{
+  *aNonBlocking = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::Close(void)
+{
+  mOutputStream->Close();
+  mOwner->GetURL(mFileURL.get(), mTarget, nullptr, nullptr, 0, false);
+  return NS_OK;
+}
+
 // nsNPAPIPluginStreamListener Methods
+
 NS_IMPL_ISUPPORTS(nsNPAPIPluginStreamListener,
                   nsITimerCallback, nsIHTTPHeaderListener)
 
 nsNPAPIPluginStreamListener::nsNPAPIPluginStreamListener(nsNPAPIPluginInstance* inst,
                                                          void* notifyData,
                                                          const char* aURL)
   : mStreamBuffer(nullptr)
   , mNotifyURL(aURL ? PL_strdup(aURL) : nullptr)
--- a/dom/plugins/base/nsNPAPIPluginStreamListener.h
+++ b/dom/plugins/base/nsNPAPIPluginStreamListener.h
@@ -35,16 +35,35 @@ public:
   nsNPAPIPluginStreamListener* GetStreamListener() { return mStreamListener; }
 
   NPStream                              mNPStream;
 protected:
   nsCOMPtr<nsIOutputStream>             mOutputStream; // only valid if not browser initiated
   nsNPAPIPluginStreamListener*          mStreamListener; // only valid if browser initiated
 };
 
+// Used to handle NPN_NewStream() - writes the stream as received by the plugin
+// to a file and at completion (NPN_DestroyStream), tells the browser to load it into
+// a plugin-specified target
+class nsPluginStreamToFile : public nsIOutputStream
+{
+public:
+  nsPluginStreamToFile(const char* target, nsIPluginInstanceOwner* owner);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOUTPUTSTREAM
+protected:
+  virtual ~nsPluginStreamToFile();
+  char* mTarget;
+  nsCString mFileURL;
+  nsCOMPtr<nsIFile> mTempFile;
+  nsCOMPtr<nsIOutputStream> mOutputStream;
+  nsIPluginInstanceOwner* mOwner;
+};
+
 class nsNPAPIPluginStreamListener : public nsITimerCallback,
                                     public nsIHTTPHeaderListener
 {
 private:
   typedef mozilla::PluginLibrary PluginLibrary;
 
 public:
   NS_DECL_ISUPPORTS
--- a/dom/plugins/ipc/PPluginInstance.ipdl
+++ b/dom/plugins/ipc/PPluginInstance.ipdl
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.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 PPluginStream;
 include protocol PStreamNotify;
 include protocol PPluginSurface;
 
 include "mozilla/GfxMessageUtils.h";
 include "mozilla/layers/LayersMessageUtils.h";
 
 using NPError from "npapi.h";
 using struct mozilla::plugins::NPRemoteWindow from "mozilla/plugins/PluginMessageUtils.h";
@@ -62,16 +63,17 @@ union OptionalShmem {
 
 intr protocol PPluginInstance
 {
   manager PPluginModule;
 
   manages PPluginBackgroundDestroyer;
   manages PPluginScriptableObject;
   manages PBrowserStream;
+  manages PPluginStream;
   manages PStreamNotify;
   manages PPluginSurface;
 
 child:
   intr __delete__();
 
   // This is only used on Windows and, for windowed plugins, must be called
   // before the first call to NPP_SetWindow.
@@ -284,16 +286,22 @@ child:
 
   // 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:
+  /* NPN_NewStream */
+  intr PPluginStream(nsCString mimeType,
+                    nsCString target)
+    returns (NPError result);
+
+parent:
   intr PluginFocusChange(bool gotFocus);
 
 child:
   intr SetPluginFocus();
   intr UpdateWindow();
 
   async PPluginBackgroundDestroyer();
 };
new file mode 100644
--- /dev/null
+++ b/dom/plugins/ipc/PPluginStream.ipdl
@@ -0,0 +1,37 @@
+/* -*- 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 NPError from "npapi.h";
+using NPReason from "npapi.h";
+
+namespace mozilla {
+namespace plugins {
+
+/**
+ * PPluginStream represents an NPStream sent from the plugin to the browser.
+ */
+
+intr protocol PPluginStream
+{
+  manager PPluginInstance;
+
+parent:
+  intr NPN_Write(Buffer data) returns (int32_t written);
+
+both:
+  /**
+   * ~PPluginStream is for both NPN_DestroyStream and NPP_DestroyStream.
+   * @param artificial True when the stream is closed as a by-product of
+   *                        some other call (such as a failure in NPN_Write).
+   */
+  intr __delete__(NPReason reason, bool artificial);
+};
+
+} // namespace plugins
+} // namespace mozilla
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -3,16 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.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 "PluginStreamChild.h"
 #include "StreamNotifyChild.h"
 #include "PluginProcessChild.h"
 #include "gfxASurface.h"
 #include "gfxPlatform.h"
 #include "gfx2DGlue.h"
 #include "nsNPAPIPluginInstance.h"
 #include "mozilla/gfx/2D.h"
 #ifdef MOZ_X11
@@ -2523,16 +2524,32 @@ PluginInstanceChild::AllocPBrowserStream
 bool
 PluginInstanceChild::DeallocPBrowserStreamChild(PBrowserStreamChild* stream)
 {
     AssertPluginThread();
     delete stream;
     return true;
 }
 
+PPluginStreamChild*
+PluginInstanceChild::AllocPPluginStreamChild(const nsCString& mimeType,
+                                             const nsCString& target,
+                                             NPError* result)
+{
+    MOZ_CRASH("not callable");
+    return nullptr;
+}
+
+bool
+PluginInstanceChild::DeallocPPluginStreamChild(PPluginStreamChild* 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)
@@ -2638,16 +2655,38 @@ PluginInstanceChild::GetActorForNPObject
         NS_ERROR("Failed to send constructor message!");
         return nullptr;
     }
 
     actor->InitializeLocal(aObject);
     return actor;
 }
 
+NPError
+PluginInstanceChild::NPN_NewStream(NPMIMEType aMIMEType, const char* aWindow,
+                                   NPStream** aStream)
+{
+    AssertPluginThread();
+    AutoStackHelper guard(this);
+
+    auto* ps = new PluginStreamChild();
+
+    NPError result;
+    CallPPluginStreamConstructor(ps, nsDependentCString(aMIMEType),
+                                 NullableString(aWindow), &result);
+    if (NPERR_NO_ERROR != result) {
+        *aStream = nullptr;
+        PPluginStreamChild::Call__delete__(ps, NPERR_GENERIC_ERROR, true);
+        return result;
+    }
+
+    *aStream = &ps->mStream;
+    return NPERR_NO_ERROR;
+}
+
 void
 PluginInstanceChild::NPN_URLRedirectResponse(void* notifyData, NPBool allow)
 {
     if (!notifyData) {
         return;
     }
 
     InfallibleTArray<PStreamNotifyChild*> notifyStreams;
--- a/dom/plugins/ipc/PluginInstanceChild.h
+++ b/dom/plugins/ipc/PluginInstanceChild.h
@@ -170,16 +170,24 @@ protected:
                              const uint32_t& length,
                              const uint32_t& lastmodified,
                              PStreamNotifyChild* notifyData,
                              const nsCString& headers) override;
 
     virtual bool
     DeallocPBrowserStreamChild(PBrowserStreamChild* stream) override;
 
+    virtual PPluginStreamChild*
+    AllocPPluginStreamChild(const nsCString& mimeType,
+                            const nsCString& target,
+                            NPError* result) override;
+
+    virtual bool
+    DeallocPPluginStreamChild(PPluginStreamChild* stream) override;
+
     virtual PStreamNotifyChild*
     AllocPStreamNotifyChild(const nsCString& url, const nsCString& target,
                             const bool& post, const nsCString& buffer,
                             const bool& file,
                             NPError* result) override;
 
     virtual bool
     DeallocPStreamNotifyChild(PStreamNotifyChild* notifyData) override;
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -9,16 +9,17 @@
 
 #include "mozilla/BasicEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "PluginInstanceParent.h"
 #include "BrowserStreamParent.h"
 #include "PluginBackgroundDestroyer.h"
 #include "PluginModuleParent.h"
+#include "PluginStreamParent.h"
 #include "StreamNotifyParent.h"
 #include "npfunctions.h"
 #include "nsAutoPtr.h"
 #include "gfxASurface.h"
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 #include "gfxSharedImageSurface.h"
 #include "nsNetUtil.h"
@@ -235,16 +236,30 @@ PluginInstanceParent::AllocPBrowserStrea
 
 bool
 PluginInstanceParent::DeallocPBrowserStreamParent(PBrowserStreamParent* stream)
 {
     delete stream;
     return true;
 }
 
+PPluginStreamParent*
+PluginInstanceParent::AllocPPluginStreamParent(const nsCString& mimeType,
+                                               const nsCString& target,
+                                               NPError* result)
+{
+    return new PluginStreamParent(this, mimeType, target, result);
+}
+
+bool
+PluginInstanceParent::DeallocPPluginStreamParent(PPluginStreamParent* 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)
@@ -1770,23 +1785,34 @@ PluginInstanceParent::NPP_DestroyStream(
 
     AStream* s = static_cast<AStream*>(stream->pdata);
     if (!s) {
         // The stream has already been deleted by other means.
         // With async plugin init this could happen if async NPP_NewStream
         // returns an error code.
         return NPERR_NO_ERROR;
     }
-    MOZ_ASSERT(s->IsBrowserStream());
-    BrowserStreamParent* sp =
-        static_cast<BrowserStreamParent*>(s);
-    if (sp->mNPP != this)
-        MOZ_CRASH("Mismatched plugin data");
-    sp->NPP_DestroyStream(reason);
-    return NPERR_NO_ERROR;
+    if (s->IsBrowserStream()) {
+        BrowserStreamParent* sp =
+            static_cast<BrowserStreamParent*>(s);
+        if (sp->mNPP != this)
+            MOZ_CRASH("Mismatched plugin data");
+
+        sp->NPP_DestroyStream(reason);
+        return NPERR_NO_ERROR;
+    }
+    else {
+        PluginStreamParent* sp =
+            static_cast<PluginStreamParent*>(s);
+        if (sp->mInstance != this)
+            MOZ_CRASH("Mismatched plugin data");
+
+        return PPluginStreamParent::Call__delete__(sp, reason, false) ?
+            NPERR_NO_ERROR : NPERR_GENERIC_ERROR;
+    }
 }
 
 void
 PluginInstanceParent::NPP_Print(NPPrint* platformPrint)
 {
     // TODO: implement me
     NS_ERROR("Not implemented");
 }
--- a/dom/plugins/ipc/PluginInstanceParent.h
+++ b/dom/plugins/ipc/PluginInstanceParent.h
@@ -40,16 +40,17 @@ namespace plugins {
 class PBrowserStreamParent;
 class PluginModuleParent;
 class D3D11SurfaceHolder;
 
 class PluginInstanceParent : public PPluginInstanceParent
 {
     friend class PluginModuleParent;
     friend class BrowserStreamParent;
+    friend class PluginStreamParent;
     friend class StreamNotifyParent;
 
 #if defined(XP_WIN)
 public:
     /**
      * Helper method for looking up instances based on a supplied id.
      */
     static PluginInstanceParent*
@@ -84,16 +85,23 @@ public:
     AllocPBrowserStreamParent(const nsCString& url,
                               const uint32_t& length,
                               const uint32_t& lastmodified,
                               PStreamNotifyParent* notifyData,
                               const nsCString& headers) override;
     virtual bool
     DeallocPBrowserStreamParent(PBrowserStreamParent* stream) override;
 
+    virtual PPluginStreamParent*
+    AllocPPluginStreamParent(const nsCString& mimeType,
+                             const nsCString& target,
+                             NPError* result) override;
+    virtual bool
+    DeallocPPluginStreamParent(PPluginStreamParent* stream) override;
+
     virtual mozilla::ipc::IPCResult
     AnswerNPN_GetValue_NPNVnetscapeWindow(NativeWindowHandle* value,
                                           NPError* result) override;
     virtual mozilla::ipc::IPCResult
     AnswerNPN_GetValue_NPNVWindowNPObject(
                                        PPluginScriptableObjectParent** value,
                                        NPError* result) override;
     virtual mozilla::ipc::IPCResult
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -26,16 +26,17 @@
 #ifdef MOZ_X11
 # include "nsX11ErrorHandler.h"
 # include "mozilla/X11Util.h"
 #endif
 #include "mozilla/ipc/ProcessChild.h"
 #include "mozilla/plugins/PluginInstanceChild.h"
 #include "mozilla/plugins/StreamNotifyChild.h"
 #include "mozilla/plugins/BrowserStreamChild.h"
+#include "mozilla/plugins/PluginStreamChild.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/Unused.h"
 
 #include "nsNPAPIPlugin.h"
 
 #ifdef XP_WIN
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/widget/AudioSession.h"
@@ -835,16 +836,25 @@ static NPError
 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 NPError
+_newstream(NPP aNPP, NPMIMEType type, const char* window, NPStream** pstream);
+
+static int32_t
+_write(NPP aNPP, NPStream *pstream, int32_t len, void *buffer);
+
+static NPError
+_destroystream(NPP aNPP, NPStream *pstream, NPError reason);
+
 static void
 _status(NPP aNPP, const char *message);
 
 static void
 _memfree (void *ptr);
 
 static uint32_t
 _memflush(uint32_t size);
@@ -969,19 +979,19 @@ static void
 } /* namespace mozilla */
 
 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,
-    nullptr,
-    nullptr,
+    mozilla::plugins::child::_newstream,
+    mozilla::plugins::child::_write,
+    mozilla::plugins::child::_destroystream,
     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,
@@ -1222,16 +1232,65 @@ NPError
     // FIXME what should happen when |aBuffer| is null?
     InstCast(aNPP)->CallNPN_PostURL(NullableString(aRelativeURL),
                                     NullableString(aTarget),
                                     nsDependentCString(aBuffer, aLength),
                                     aIsFile, &err);
     return err;
 }
 
+NPError
+_newstream(NPP aNPP,
+           NPMIMEType aMIMEType,
+           const char* aWindow,
+           NPStream** aStream)
+{
+    PLUGIN_LOG_DEBUG_FUNCTION;
+    ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM);
+    return InstCast(aNPP)->NPN_NewStream(aMIMEType, aWindow, aStream);
+}
+
+int32_t
+_write(NPP aNPP,
+       NPStream* aStream,
+       int32_t aLength,
+       void* aBuffer)
+{
+    PLUGIN_LOG_DEBUG_FUNCTION;
+    ENSURE_PLUGIN_THREAD(0);
+
+    PluginStreamChild* ps =
+        static_cast<PluginStreamChild*>(static_cast<AStream*>(aStream->ndata));
+    ps->EnsureCorrectInstance(InstCast(aNPP));
+    ps->EnsureCorrectStream(aStream);
+    return ps->NPN_Write(aLength, aBuffer);
+}
+
+NPError
+_destroystream(NPP aNPP,
+               NPStream* aStream,
+               NPError aReason)
+{
+    PLUGIN_LOG_DEBUG_FUNCTION;
+    ENSURE_PLUGIN_THREAD(NPERR_INVALID_PARAM);
+
+    PluginInstanceChild* p = InstCast(aNPP);
+    AStream* s = static_cast<AStream*>(aStream->ndata);
+    if (s->IsBrowserStream()) {
+        BrowserStreamChild* bs = static_cast<BrowserStreamChild*>(s);
+        bs->EnsureCorrectInstance(p);
+        bs->NPN_DestroyStream(aReason);
+    }
+    else {
+        PluginStreamChild* ps = static_cast<PluginStreamChild*>(s);
+        ps->EnsureCorrectInstance(p);
+        PPluginStreamChild::Call__delete__(ps, aReason, false);
+    }
+    return NPERR_NO_ERROR;
+}
 
 void
 _status(NPP aNPP,
         const char* aMessage)
 {
     // NPN_Status is no longer supported.
 }
 
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -944,16 +944,21 @@ PluginModuleChromeParent::GetManagingIns
                 static_cast<PPluginScriptableObjectParent*>(aProtocol);
             return static_cast<PluginInstanceParent*>(actor->Manager());
         }
         case PBrowserStreamMsgStart: {
             PBrowserStreamParent* actor =
                 static_cast<PBrowserStreamParent*>(aProtocol);
             return static_cast<PluginInstanceParent*>(actor->Manager());
         }
+        case PPluginStreamMsgStart: {
+            PPluginStreamParent* actor =
+                static_cast<PPluginStreamParent*>(aProtocol);
+            return static_cast<PluginInstanceParent*>(actor->Manager());
+        }
         case PStreamNotifyMsgStart: {
             PStreamNotifyParent* actor =
                 static_cast<PStreamNotifyParent*>(aProtocol);
             return static_cast<PluginInstanceParent*>(actor->Manager());
         }
 #ifdef XP_WIN
         case PPluginSurfaceMsgStart: {
             PPluginSurfaceParent* actor =
new file mode 100644
--- /dev/null
+++ b/dom/plugins/ipc/PluginStreamChild.cpp
@@ -0,0 +1,64 @@
+/* -*- 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 "PluginStreamChild.h"
+#include "mozilla/plugins/PluginInstanceChild.h"
+
+namespace mozilla {
+namespace plugins {
+
+PluginStreamChild::PluginStreamChild()
+  : mClosed(false)
+{
+  memset(&mStream, 0, sizeof(mStream));
+  mStream.ndata = static_cast<AStream*>(this);
+}
+
+mozilla::ipc::IPCResult
+PluginStreamChild::Answer__delete__(const NPReason& reason,
+                                    const bool& artificial)
+{
+  AssertPluginThread();
+  if (!artificial)
+    NPP_DestroyStream(reason);
+  return IPC_OK();
+}
+
+int32_t
+PluginStreamChild::NPN_Write(int32_t length, void* buffer)
+{
+  AssertPluginThread();
+
+  int32_t written = 0;
+  CallNPN_Write(nsCString(static_cast<char*>(buffer), length),
+                &written);
+  if (written < 0)
+    PPluginStreamChild::Call__delete__(this, NPERR_GENERIC_ERROR, true);
+  // careful after here! |this| just got deleted
+
+  return written;
+}
+
+void
+PluginStreamChild::NPP_DestroyStream(NPError reason)
+{
+  AssertPluginThread();
+
+  if (mClosed)
+    return;
+
+  mClosed = true;
+  Instance()->mPluginIface->destroystream(
+    &Instance()->mData, &mStream, reason);
+}
+
+PluginInstanceChild*
+PluginStreamChild::Instance()
+{
+  return static_cast<PluginInstanceChild*>(Manager());
+}
+
+} // namespace plugins
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/plugins/ipc/PluginStreamChild.h
@@ -0,0 +1,55 @@
+/* -*- 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_PluginStreamChild_h
+#define mozilla_plugins_PluginStreamChild_h
+
+#include "mozilla/plugins/PPluginStreamChild.h"
+#include "mozilla/plugins/AStream.h"
+
+namespace mozilla {
+namespace plugins {
+
+class PluginInstanceChild;
+
+class PluginStreamChild : public PPluginStreamChild, public AStream
+{
+  friend class PluginInstanceChild;
+
+public:
+  PluginStreamChild();
+  virtual ~PluginStreamChild() { }
+
+  virtual bool IsBrowserStream() override { return false; }
+
+  virtual mozilla::ipc::IPCResult Answer__delete__(const NPReason& reason,
+                                                   const bool& artificial) override;
+
+  int32_t NPN_Write(int32_t length, void* buffer);
+  void NPP_DestroyStream(NPError reason);
+
+  void EnsureCorrectInstance(PluginInstanceChild* i)
+  {
+    if (i != Instance())
+      MOZ_CRASH("Incorrect stream instance");
+  }
+  void EnsureCorrectStream(NPStream* s)
+  {
+    if (s != &mStream)
+      MOZ_CRASH("Incorrect stream data");
+  }
+
+private:
+  PluginInstanceChild* Instance();
+
+  NPStream mStream;
+  bool mClosed;
+};
+
+
+} // namespace plugins
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/plugins/ipc/PluginStreamParent.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "PluginStreamParent.h"
+#include "PluginInstanceParent.h"
+
+namespace mozilla {
+namespace plugins {
+
+PluginStreamParent::PluginStreamParent(PluginInstanceParent* npp,
+                                       const nsCString& mimeType,
+                                       const nsCString& target,
+                                       NPError* result)
+  : mInstance(npp)
+  , mClosed(false)
+{
+  *result = mInstance->mNPNIface->newstream(mInstance->mNPP,
+                                            const_cast<char*>(mimeType.get()),
+                                            NullableStringGet(target),
+                                            &mStream);
+  if (*result == NPERR_NO_ERROR)
+    mStream->pdata = static_cast<AStream*>(this);
+  else
+    mStream = nullptr;
+}
+
+void
+PluginStreamParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  // Implement me! Bug 1005166
+}
+
+mozilla::ipc::IPCResult
+PluginStreamParent::AnswerNPN_Write(const Buffer& data, int32_t* written)
+{
+  if (mClosed) {
+    *written = -1;
+    return IPC_OK();
+  }
+
+  *written = mInstance->mNPNIface->write(mInstance->mNPP, mStream,
+                                         data.Length(),
+                                         const_cast<char*>(data.get()));
+  if (*written < 0)
+    mClosed = true;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+PluginStreamParent::Answer__delete__(const NPError& reason,
+                                     const bool& artificial)
+{
+  if (!artificial)
+    this->NPN_DestroyStream(reason);
+  return IPC_OK();
+}
+
+void
+PluginStreamParent::NPN_DestroyStream(NPReason reason)
+{
+  if (mClosed)
+    return;
+
+  mInstance->mNPNIface->destroystream(mInstance->mNPP, mStream, reason);
+  mClosed = true;
+}
+
+} // namespace plugins
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/plugins/ipc/PluginStreamParent.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 mozilla_plugins_PluginStreamParent_h
+#define mozilla_plugins_PluginStreamParent_h
+
+#include "mozilla/plugins/PPluginStreamParent.h"
+#include "mozilla/plugins/AStream.h"
+
+namespace mozilla {
+namespace plugins {
+
+class PluginInstanceParent;
+
+class PluginStreamParent : public PPluginStreamParent, public AStream
+{
+  friend class PluginModuleParent;
+  friend class PluginInstanceParent;
+
+public:
+  PluginStreamParent(PluginInstanceParent* npp, const nsCString& mimeType,
+                     const nsCString& target, NPError* result);
+  virtual ~PluginStreamParent() { }
+
+  virtual bool IsBrowserStream() override { return false; }
+
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual mozilla::ipc::IPCResult AnswerNPN_Write(const Buffer& data, int32_t* written) override;
+
+  virtual mozilla::ipc::IPCResult Answer__delete__(const NPError& reason, const bool& artificial) override;
+
+private:
+  void NPN_DestroyStream(NPReason reason);
+
+  PluginInstanceParent* mInstance;
+  NPStream* mStream;
+  bool mClosed;
+};
+
+} // namespace plugins
+} // namespace mozilla
+
+#endif
--- a/dom/plugins/ipc/moz.build
+++ b/dom/plugins/ipc/moz.build
@@ -29,16 +29,18 @@ EXPORTS.mozilla.plugins += [
     'PluginModuleParent.h',
     'PluginProcessChild.h',
     'PluginProcessParent.h',
     'PluginQuirks.h',
     'PluginScriptableObjectChild.h',
     'PluginScriptableObjectParent.h',
     'PluginScriptableObjectUtils-inl.h',
     'PluginScriptableObjectUtils.h',
+    'PluginStreamChild.h',
+    'PluginStreamParent.h',
     'PluginUtilsOSX.h',
     'StreamNotifyChild.h',
     'StreamNotifyParent.h',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS.mozilla.plugins += [
         'PluginSurfaceParent.h',
@@ -70,16 +72,18 @@ UNIFIED_SOURCES += [
     'PluginInstanceParent.cpp',
     'PluginMessageUtils.cpp',
     'PluginModuleParent.cpp',
     'PluginProcessChild.cpp',
     'PluginProcessParent.cpp',
     'PluginQuirks.cpp',
     'PluginScriptableObjectChild.cpp',
     'PluginScriptableObjectParent.cpp',
+    'PluginStreamChild.cpp',
+    'PluginStreamParent.cpp',
 ]
 
 SOURCES += [
     'PluginInstanceChild.cpp', # 'PluginThreadCallback' : ambiguous symbol
     'PluginModuleChild.cpp',   # Redefinition of mozilla::WindowsDllInterceptor sUser32Intercept
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
@@ -104,16 +108,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wind
 
 IPDL_SOURCES += [
     'PBrowserStream.ipdl',
     'PluginTypes.ipdlh',
     'PPluginBackgroundDestroyer.ipdl',
     'PPluginInstance.ipdl',
     'PPluginModule.ipdl',
     'PPluginScriptableObject.ipdl',
+    'PPluginStream.ipdl',
     'PPluginSurface.ipdl',
     'PStreamNotify.ipdl',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
--- a/dom/plugins/test/testplugin/nptest.cpp
+++ b/dom/plugins/test/testplugin/nptest.cpp
@@ -473,43 +473,78 @@ static void sendBufferToFrame(NPP instan
   }
   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;
+  if (instanceData->npnNewStream &&
+      instanceData->err.str().length() == 0) {
+    char typeHTML[] = "text/html";
+    NPStream* stream;
+    printf("calling NPN_NewStream...");
+    NPError err = NPN_NewStream(instance, typeHTML,
+        instanceData->frame.c_str(), &stream);
+    printf("return value %d\n", err);
+    if (err != NPERR_NO_ERROR) {
+      instanceData->err << "NPN_NewStream returned " << err;
+      return;
     }
-    else if (outbuf[i] == '\r') {
-      outbuf.replace(i, 1, "");
-      i -= 1;
+
+    int32_t bytesToWrite = outbuf.length();
+    int32_t bytesWritten = 0;
+    while ((bytesToWrite - bytesWritten) > 0) {
+      int32_t numBytes = (bytesToWrite - bytesWritten) <
+          instanceData->streamChunkSize ?
+          bytesToWrite - bytesWritten : instanceData->streamChunkSize;
+      int32_t written = NPN_Write(instance, stream,
+          numBytes, (void*)(outbuf.c_str() + bytesWritten));
+      if (written <= 0) {
+        instanceData->err << "NPN_Write returned " << written;
+        break;
+      }
+      bytesWritten += numBytes;
+      printf("%d bytes written, total %d\n", written, bytesWritten);
     }
-    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);
+    err = NPN_DestroyStream(instance, stream, NPRES_DONE);
+    if (err != NPERR_NO_ERROR) {
+      instanceData->err << "NPN_DestroyStream returned " << err;
+    }
+  }
+  else {
+    // 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;
+
+    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
@@ -812,16 +847,17 @@ NPP_New(NPMIMEType pluginType, NPP insta
   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->closeStream = false;
   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;
@@ -943,16 +979,19 @@ NPP_New(NPMIMEType pluginType, NPP insta
     // "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], "closestream")) {
+      instanceData->closeStream = true;
+    }
     if (strcmp(argn[i], "bugmode") == 0) {
       instanceData->bugMode = atoi(argv[i]);
     }
     // Try to emulate java's codebase handling: Use the last seen codebase
     // value, regardless of whether it is in attributes or params.
     if (strcasecmp(argn[i], "codebase") == 0) {
       instanceData->javaCodebase = argv[i];
     }
@@ -1374,17 +1413,25 @@ NPP_Write(NPP instance, NPStream* stream
 
   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;
   }
-  if (instanceData->streamMode == NP_SEEK &&
+
+  if (instanceData->closeStream) {
+    instanceData->closeStream = false;
+    if (instanceData->testrange != nullptr) {
+      NPN_RequestRead(stream, instanceData->testrange);
+    }
+    NPN_DestroyStream(instance, stream, NPRES_USER_BREAK);
+  }
+  else if (instanceData->streamMode == NP_SEEK &&
       stream->end != 0 &&
       stream->end == ((uint32_t)instanceData->streamBufSize + len)) {
     // If the complete stream has been written, and we're doing a seek test,
     // then call NPN_RequestRead.
     // prevent recursion
     instanceData->streamMode = NP_NORMAL;
 
     if (instanceData->testrange != nullptr) {
@@ -1410,16 +1457,22 @@ NPP_Write(NPP instance, NPStream* stream
     while(range != nullptr) {
       if (offset == range->offset &&
         (uint32_t)len == range->length) {
         range->waiting = false;
       }
       if (range->waiting) stillwaiting = true;
       range = reinterpret_cast<TestRange*>(range->next);
     }
+    if (!stillwaiting) {
+      NPError err = NPN_DestroyStream(instance, stream, NPRES_DONE);
+      if (err != NPERR_NO_ERROR) {
+        instanceData->err << "Error: NPN_DestroyStream returned " << err;
+      }
+    }
   }
   else {
     if (instanceData->streamBufSize == 0) {
       instanceData->streamBuf = malloc(len + 1);
       streamBuf = reinterpret_cast<char *>(instanceData->streamBuf);
     }
     else {
       instanceData->streamBuf =
@@ -1851,17 +1904,39 @@ NPN_PostURLNotify(NPP instance, const ch
 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);
 }
 
-
+NPError
+NPN_DestroyStream(NPP instance, NPStream* stream, NPError reason)
+{
+  return sBrowserFuncs->destroystream(instance, stream, reason);
+}
+
+NPError
+NPN_NewStream(NPP instance,
+              NPMIMEType  type,
+              const char* target,
+              NPStream**  stream)
+{
+  return sBrowserFuncs->newstream(instance, type, target, stream);
+}
+
+int32_t
+NPN_Write(NPP instance,
+          NPStream* stream,
+          int32_t len,
+          void* buf)
+{
+  return sBrowserFuncs->write(instance, stream, len, buf);
+}
 
 bool
 NPN_Enumerate(NPP instance,
               NPObject *npobj,
               NPIdentifier **identifiers,
               uint32_t *identifierCount)
 {
   return sBrowserFuncs->enumerate(instance, npobj, identifiers,
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -742,16 +742,18 @@ description =
 [PPluginInstance::NPN_SetValueForURL]
 description =
 [PPluginInstance::NPN_ConvertPoint]
 description =
 [PPluginInstance::GetCompositionString]
 description =
 [PPluginInstance::NPP_NewStream]
 description =
+[PPluginInstance::PPluginStream]
+description =
 [PPluginInstance::PluginFocusChange]
 description =
 [PPluginInstance::SetPluginFocus]
 description =
 [PPluginInstance::UpdateWindow]
 description =
 [PPluginModule::ModuleSupportsAsyncRender]
 description =