Bug 1279699: Use temporary files instead of shared memory to store the page recordings when printing via parent. r=bas, r=froydnj, r=jimm
authorBob Owen <bobowencode@gmail.com>
Tue, 22 Nov 2016 14:06:46 +0000
changeset 325157 d614a623d6817e0055d184c49876804e39c5a514
parent 325135 d277102b35f954c38255d2fc683b7bbb2a2a3024
child 325158 331ca1a40ca24f3f8dfeece611f05754e2735a5e
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersbas, froydnj, jimm
bugs1279699
milestone53.0a1
Bug 1279699: Use temporary files instead of shared memory to store the page recordings when printing via parent. r=bas, r=froydnj, r=jimm
gfx/2d/DrawEventRecorder.cpp
gfx/2d/DrawEventRecorder.h
layout/printing/ipc/PRemotePrintJob.ipdl
layout/printing/ipc/RemotePrintJobChild.cpp
layout/printing/ipc/RemotePrintJobChild.h
layout/printing/ipc/RemotePrintJobParent.cpp
layout/printing/ipc/RemotePrintJobParent.h
widget/nsDeviceContextSpecProxy.cpp
widget/nsDeviceContextSpecProxy.h
xpcom/io/nsAppDirectoryServiceDefs.h
--- a/gfx/2d/DrawEventRecorder.cpp
+++ b/gfx/2d/DrawEventRecorder.cpp
@@ -31,17 +31,17 @@ DrawEventRecorderPrivate::RecordEvent(co
   WriteElement(*mOutputStream, aEvent.mType);
 
   aEvent.RecordToStream(*mOutputStream);
 
   Flush();
 }
 
 DrawEventRecorderFile::DrawEventRecorderFile(const char *aFilename)
-  : DrawEventRecorderPrivate(nullptr) 
+  : DrawEventRecorderPrivate(nullptr)
   , mOutputFile(aFilename, ofstream::binary)
 {
   mOutputStream = &mOutputFile;
 
   WriteHeader();
 }
 
 DrawEventRecorderFile::~DrawEventRecorderFile()
@@ -50,16 +50,39 @@ DrawEventRecorderFile::~DrawEventRecorde
 }
 
 void
 DrawEventRecorderFile::Flush()
 {
   mOutputFile.flush();
 }
 
+bool
+DrawEventRecorderFile::IsOpen()
+{
+  return mOutputFile.is_open();
+}
+
+void
+DrawEventRecorderFile::OpenNew(const char *aFilename)
+{
+  MOZ_ASSERT(!mOutputFile.is_open());
+
+  mOutputFile.open(aFilename, ofstream::binary);
+  WriteHeader();
+}
+
+void
+DrawEventRecorderFile::Close()
+{
+  MOZ_ASSERT(mOutputFile.is_open());
+
+  mOutputFile.close();
+}
+
 DrawEventRecorderMemory::DrawEventRecorderMemory()
   : DrawEventRecorderPrivate(nullptr)
 {
   mOutputStream = &mMemoryStream;
 
   WriteHeader();
 }
 
--- a/gfx/2d/DrawEventRecorder.h
+++ b/gfx/2d/DrawEventRecorder.h
@@ -73,16 +73,35 @@ protected:
 
 class DrawEventRecorderFile : public DrawEventRecorderPrivate
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderFile)
   explicit DrawEventRecorderFile(const char *aFilename);
   ~DrawEventRecorderFile();
 
+  /**
+   * Returns whether a recording file is currently open.
+   */
+  bool IsOpen();
+
+  /**
+   * Opens new file with the provided name. The recorder does NOT forget which
+   * objects it has recorded. This can be used with Close, so that a recording
+   * can be processed in chunks. The file must not already be open.
+   */
+  void OpenNew(const char *aFilename);
+
+  /**
+   * Closes the file so that it can be processed. The recorder does NOT forget
+   * which objects it has recorded. This can be used with OpenNew, so that a
+   * recording can be processed in chunks. The file must be open.
+   */
+  void Close();
+
 private:
   virtual void Flush();
 
   std::ofstream mOutputFile;
 };
 
 class DrawEventRecorderMemory final : public DrawEventRecorderPrivate
 {
--- a/layout/printing/ipc/PRemotePrintJob.ipdl
+++ b/layout/printing/ipc/PRemotePrintJob.ipdl
@@ -19,18 +19,17 @@ both:
 
 parent:
   // Initialize the real print device with the given information.
   async InitializePrint(nsString aDocumentTitle, nsString aPrintToFile,
                         int32_t aStartPage, int32_t aEndPage);
 
   // Translate the stored page recording and play back the events to the real
   // print device.
-  // This will always deallocate the shared memory.
-  async ProcessPage(Shmem aStoredPage);
+  async ProcessPage(nsCString aPageFileName);
 
   // This informs the real print device that we've finished, so it can trigger
   // the actual print.
   async FinalizePrint();
 
   // Report a state change to listeners in the parent process.
   async StateChange(long aStateFlags,
                     nsresult aStatus);
--- a/layout/printing/ipc/RemotePrintJobChild.cpp
+++ b/layout/printing/ipc/RemotePrintJobChild.cpp
@@ -42,22 +42,22 @@ mozilla::ipc::IPCResult
 RemotePrintJobChild::RecvPrintInitializationResult(const nsresult& aRv)
 {
   mPrintInitialized = true;
   mInitializationResult = aRv;
   return IPC_OK();
 }
 
 void
-RemotePrintJobChild::ProcessPage(Shmem& aStoredPage)
+RemotePrintJobChild::ProcessPage(const nsCString& aPageFileName)
 {
   MOZ_ASSERT(mPagePrintTimer);
 
   mPagePrintTimer->WaitForRemotePrint();
-  Unused << SendProcessPage(aStoredPage);
+  Unused << SendProcessPage(aPageFileName);
 }
 
 mozilla::ipc::IPCResult
 RemotePrintJobChild::RecvPageProcessed()
 {
   MOZ_ASSERT(mPagePrintTimer);
 
   mPagePrintTimer->RemotePrintFinished();
--- a/layout/printing/ipc/RemotePrintJobChild.h
+++ b/layout/printing/ipc/RemotePrintJobChild.h
@@ -31,17 +31,17 @@ public:
 
   nsresult InitializePrint(const nsString& aDocumentTitle,
                            const nsString& aPrintToFile,
                            const int32_t& aStartPage,
                            const int32_t& aEndPage);
 
   mozilla::ipc::IPCResult RecvPrintInitializationResult(const nsresult& aRv) final;
 
-  void ProcessPage(Shmem& aStoredPage);
+  void ProcessPage(const nsCString& aPageFileName);
 
   mozilla::ipc::IPCResult RecvPageProcessed() final;
 
   mozilla::ipc::IPCResult RecvAbortPrint(const nsresult& aRv) final;
 
   void SetPagePrintTimer(nsPagePrintTimer* aPagePrintTimer);
 
   void SetPrintEngine(nsPrintEngine* aPrintEngine);
--- a/layout/printing/ipc/RemotePrintJobParent.cpp
+++ b/layout/printing/ipc/RemotePrintJobParent.cpp
@@ -1,22 +1,24 @@
 /* -*- 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 "RemotePrintJobParent.h"
 
-#include <istream>
+#include <fstream>
 
 #include "gfxContext.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Unused.h"
+#include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
 #include "nsDeviceContext.h"
 #include "nsIDeviceContextSpec.h"
 #include "nsIPrintSettings.h"
 #include "nsIWebProgressListener.h"
 #include "PrintTranslator.h"
 
 namespace mozilla {
 namespace layout {
@@ -76,56 +78,73 @@ RemotePrintJobParent::InitializePrintDev
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 mozilla::ipc::IPCResult
-RemotePrintJobParent::RecvProcessPage(Shmem&& aStoredPage)
+RemotePrintJobParent::RecvProcessPage(const nsCString& aPageFileName)
 {
-  nsresult rv = PrintPage(aStoredPage);
-
-  // Always deallocate the shared memory no matter what the result.
-  if (!DeallocShmem(aStoredPage)) {
-    NS_WARNING("Failed to deallocated shared memory, remote print will abort.");
-    rv = NS_ERROR_FAILURE;
-  }
+  nsresult rv = PrintPage(aPageFileName);
 
   if (NS_FAILED(rv)) {
     Unused << SendAbortPrint(rv);
   } else {
     Unused << SendPageProcessed();
   }
 
   return IPC_OK();
 }
 
 nsresult
-RemotePrintJobParent::PrintPage(const Shmem& aStoredPage)
+RemotePrintJobParent::PrintPage(const nsCString& aPageFileName)
 {
   MOZ_ASSERT(mPrintDeviceContext);
 
   nsresult rv = mPrintDeviceContext->BeginPage();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  std::istringstream recording(std::string(aStoredPage.get<char>(),
-                                           aStoredPage.Size<char>()));
+  nsCOMPtr<nsIFile> recordingFile;
+  rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
+                              getter_AddRefs(recordingFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = recordingFile->AppendNative(aPageFileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoCString recordingPath;
+  rv = recordingFile->GetNativePath(recordingPath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  std::ifstream recording(recordingPath.get(), std::ifstream::binary);
   if (!mPrintTranslator->TranslateRecording(recording)) {
     return NS_ERROR_FAILURE;
   }
 
   rv = mPrintDeviceContext->EndPage();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  recording.close();
+  rv = recordingFile->Remove(/* recursive= */ false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   return NS_OK;
 }
 
 mozilla::ipc::IPCResult
 RemotePrintJobParent::RecvFinalizePrint()
 {
   // EndDocument is sometimes called in the child even when BeginDocument has
   // not been called. See bug 1223332.
--- a/layout/printing/ipc/RemotePrintJobParent.h
+++ b/layout/printing/ipc/RemotePrintJobParent.h
@@ -29,17 +29,17 @@ public:
 
   void ActorDestroy(ActorDestroyReason aWhy) final;
 
   mozilla::ipc::IPCResult RecvInitializePrint(const nsString& aDocumentTitle,
                                               const nsString& aPrintToFile,
                                               const int32_t& aStartPage,
                                               const int32_t& aEndPage) final;
 
-  mozilla::ipc::IPCResult RecvProcessPage(Shmem&& aStoredPage) final;
+  mozilla::ipc::IPCResult RecvProcessPage(const nsCString& aPageFileName) final;
 
   mozilla::ipc::IPCResult RecvFinalizePrint() final;
 
   mozilla::ipc::IPCResult RecvAbortPrint(const nsresult& aRv) final;
 
   mozilla::ipc::IPCResult RecvStateChange(const long& aStateFlags,
                                           const nsresult& aStatus) final;
 
@@ -65,17 +65,17 @@ public:
 private:
   ~RemotePrintJobParent() final;
 
   nsresult InitializePrintDevice(const nsString& aDocumentTitle,
                                  const nsString& aPrintToFile,
                                  const int32_t& aStartPage,
                                  const int32_t& aEndPage);
 
-  nsresult PrintPage(const Shmem& aStoredPage);
+  nsresult PrintPage(const nsCString& aPageFileName);
 
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
   RefPtr<nsDeviceContext> mPrintDeviceContext;
   UniquePtr<PrintTranslator> mPrintTranslator;
   nsCOMArray<nsIWebProgressListener> mPrintProgressListeners;
 };
 
 } // namespace layout
--- a/widget/nsDeviceContextSpecProxy.cpp
+++ b/widget/nsDeviceContextSpecProxy.cpp
@@ -9,18 +9,21 @@
 #include "gfxASurface.h"
 #include "gfxPlatform.h"
 #include "mozilla/gfx/DrawEventRecorder.h"
 #include "mozilla/gfx/PrintTargetThebes.h"
 #include "mozilla/layout/RemotePrintJobChild.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Unused.h"
 #include "nsComponentManagerUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
 #include "nsIPrintSession.h"
 #include "nsIPrintSettings.h"
+#include "nsIUUIDGenerator.h"
 
 using mozilla::Unused;
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 NS_IMPL_ISUPPORTS(nsDeviceContextSpecProxy, nsIDeviceContextSpec)
 
@@ -57,16 +60,27 @@ nsDeviceContextSpecProxy::Init(nsIWidget
   }
 
   rv = mPrintSession->GetRemotePrintJob(getter_AddRefs(mRemotePrintJob));
   if (NS_FAILED(rv) || !mRemotePrintJob) {
     NS_WARNING("We can't print via the parent without a RemotePrintJobChild.");
     return NS_ERROR_FAILURE;
   }
 
+  rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR,
+                              getter_AddRefs(mRecordingDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mUuidGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   return NS_OK;
 }
 
 already_AddRefed<PrintTarget>
 nsDeviceContextSpecProxy::MakePrintTarget()
 {
   MOZ_ASSERT(mRealDeviceContextSpec);
 
@@ -124,22 +138,58 @@ nsDeviceContextSpecProxy::GetDPI()
 float
 nsDeviceContextSpecProxy::GetPrintingScale()
 {
   MOZ_ASSERT(mRealDeviceContextSpec);
 
   return mRealDeviceContextSpec->GetPrintingScale();
 }
 
+nsresult
+nsDeviceContextSpecProxy::CreateUniqueTempPath(nsACString& aFilePath)
+{
+  MOZ_ASSERT(mRecordingDir);
+  MOZ_ASSERT(mUuidGenerator);
+
+  nsID uuid;
+  nsresult rv = mUuidGenerator->GenerateUUIDInPlace(&uuid);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  char uuidChars[NSID_LENGTH];
+  uuid.ToProvidedString(uuidChars);
+  mRecordingFileName.AssignASCII(uuidChars);
+
+  nsCOMPtr<nsIFile> recordingFile;
+  rv = mRecordingDir->Clone(getter_AddRefs(recordingFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = recordingFile->AppendNative(mRecordingFileName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return recordingFile->GetNativePath(aFilePath);
+}
+
 NS_IMETHODIMP
 nsDeviceContextSpecProxy::BeginDocument(const nsAString& aTitle,
                                         const nsAString& aPrintToFileName,
                                         int32_t aStartPage, int32_t aEndPage)
 {
-  mRecorder = new mozilla::gfx::DrawEventRecorderMemory();
+  nsAutoCString recordingPath;
+  nsresult rv = CreateUniqueTempPath(recordingPath);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  mRecorder = new mozilla::gfx::DrawEventRecorderFile(recordingPath.get());
   return mRemotePrintJob->InitializePrint(nsString(aTitle),
                                           nsString(aPrintToFileName),
                                           aStartPage, aEndPage);
 }
 
 NS_IMETHODIMP
 nsDeviceContextSpecProxy::EndDocument()
 {
@@ -152,39 +202,31 @@ nsDeviceContextSpecProxy::AbortDocument(
 {
   Unused << mRemotePrintJob->SendAbortPrint(NS_OK);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDeviceContextSpecProxy::BeginPage()
 {
+  // Reopen the file, if necessary, ready for the next page.
+  if (!mRecorder->IsOpen()) {
+    nsAutoCString recordingPath;
+    nsresult rv = CreateUniqueTempPath(recordingPath);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    mRecorder->OpenNew(recordingPath.get());
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDeviceContextSpecProxy::EndPage()
 {
-  // Save the current page recording to shared memory.
-  mozilla::ipc::Shmem storedPage;
-  size_t recordingSize = mRecorder->RecordingSize();
-  if (!mRemotePrintJob->AllocShmem(recordingSize,
-                                   mozilla::ipc::SharedMemory::TYPE_BASIC,
-                                   &storedPage)) {
-    NS_WARNING("Failed to create shared memory for remote printing.");
-    return NS_ERROR_FAILURE;
-  }
-
-  bool success = mRecorder->CopyRecording(storedPage.get<char>(), recordingSize);
-  if (!success) {
-    NS_WARNING("Copying recording to shared memory was not succesful.");
-    return NS_ERROR_FAILURE;
-  }
-
-  // Wipe the recording to free memory. The recorder does not forget which data
-  // backed objects that it has stored.
-  mRecorder->WipeRecording();
-
   // Send the page recording to the parent.
-  mRemotePrintJob->ProcessPage(storedPage);
+  mRecorder->Close();
+  mRemotePrintJob->ProcessPage(mRecordingFileName);
 
   return NS_OK;
 }
--- a/widget/nsDeviceContextSpecProxy.h
+++ b/widget/nsDeviceContextSpecProxy.h
@@ -4,22 +4,25 @@
  * License, v. 2.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 nsDeviceContextSpecProxy_h
 #define nsDeviceContextSpecProxy_h
 
 #include "nsIDeviceContextSpec.h"
 #include "nsCOMPtr.h"
+#include "nsString.h"
 
+class nsIFile;
 class nsIPrintSession;
+class nsIUUIDGenerator;
 
 namespace mozilla {
 namespace gfx {
-class DrawEventRecorderMemory;
+class DrawEventRecorderFile;
 }
 
 namespace layout {
 class RemotePrintJobChild;
 }
 }
 
 class nsDeviceContextSpecProxy final : public nsIDeviceContextSpec
@@ -33,31 +36,37 @@ public:
   virtual already_AddRefed<PrintTarget> MakePrintTarget() final;
 
   NS_IMETHOD GetDrawEventRecorder(mozilla::gfx::DrawEventRecorder** aDrawEventRecorder) final;
 
   float GetDPI() final;
 
   float GetPrintingScale() final;
 
+
   NS_IMETHOD BeginDocument(const nsAString& aTitle,
                            const nsAString& aPrintToFileName,
                            int32_t aStartPage, int32_t aEndPage) final;
 
   NS_IMETHOD EndDocument() final;
 
   NS_IMETHOD AbortDocument() final;
 
   NS_IMETHOD BeginPage() final;
 
   NS_IMETHOD EndPage() final;
 
 private:
   ~nsDeviceContextSpecProxy() {}
 
+  nsresult CreateUniqueTempPath(nsACString& aFilePath);
+
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
   nsCOMPtr<nsIPrintSession> mPrintSession;
   nsCOMPtr<nsIDeviceContextSpec> mRealDeviceContextSpec;
   RefPtr<mozilla::layout::RemotePrintJobChild> mRemotePrintJob;
-  RefPtr<mozilla::gfx::DrawEventRecorderMemory> mRecorder;
+  RefPtr<mozilla::gfx::DrawEventRecorderFile> mRecorder;
+  nsCOMPtr<nsIFile> mRecordingDir;
+  nsCOMPtr<nsIUUIDGenerator> mUuidGenerator;
+  nsCString mRecordingFileName;
 };
 
 #endif // nsDeviceContextSpecProxy_h
--- a/xpcom/io/nsAppDirectoryServiceDefs.h
+++ b/xpcom/io/nsAppDirectoryServiceDefs.h
@@ -105,11 +105,14 @@
 //
 // New code should avoid writing to the filesystem from the content process
 // and should instead proxy through the parent process whenever possible.
 //
 // At present, all sandboxed content processes use the same directory for
 // NS_APP_CONTENT_PROCESS_TEMP_DIR, but that should not be relied upon.
 //
 #define NS_APP_CONTENT_PROCESS_TEMP_DIR         "ContentTmpD"
+#else
+// Otherwise NS_APP_CONTENT_PROCESS_TEMP_DIR must match NS_OS_TEMP_DIR.
+#define NS_APP_CONTENT_PROCESS_TEMP_DIR         "TmpD"
 #endif // (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
 
 #endif // nsAppDirectoryServiceDefs_h___