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 325124 d614a623d6817e0055d184c49876804e39c5a514
parent 325102 d277102b35f954c38255d2fc683b7bbb2a2a3024
child 325125 331ca1a40ca24f3f8dfeece611f05754e2735a5e
push id31032
push userphilringnalda@gmail.com
push dateSat, 03 Dec 2016 06:17:44 +0000
treeherdermozilla-central@557548714db5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbas, froydnj, jimm
bugs1279699
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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___