Bug 982874 - Import / export API for apps : Part 1, temporary blobs r=bent
authorFabrice Desré <fabrice@mozilla.com>
Tue, 14 Oct 2014 22:55:14 -0700
changeset 210400 56ba1d2ec26445891f0cb3fed60d68bca103250c
parent 210399 92d0490723fbfdcaf3d3344fd15663b243da1de9
child 210401 e1ed7edd0166b2c5dbb599ef45903d10ad9ffe5c
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbent
bugs982874
milestone36.0a1
Bug 982874 - Import / export API for apps : Part 1, temporary blobs r=bent
content/base/public/File.h
content/base/src/File.cpp
content/base/src/MultipartFileImpl.cpp
content/base/test/chrome/chrome.ini
content/base/test/chrome/test_fileconstructor_tempfile.xul
dom/webidl/File.webidl
--- a/content/base/public/File.h
+++ b/content/base/public/File.h
@@ -101,17 +101,17 @@ public:
                    const nsAString& aContentType);
 
   static already_AddRefed<File>
   CreateTemporaryFileBlob(nsISupports* aParent, PRFileDesc* aFD,
                           uint64_t aStartPos, uint64_t aLength,
                           const nsAString& aContentType);
 
   static already_AddRefed<File>
-  CreateFromFile(nsISupports* aParent, nsIFile* aFile);
+  CreateFromFile(nsISupports* aParent, nsIFile* aFile, bool aTemporary = false);
 
   static already_AddRefed<File>
   CreateFromFile(nsISupports* aParent, const nsAString& aContentType,
                  uint64_t aLength, nsIFile* aFile,
                  indexedDB::FileInfo* aFileInfo);
 
   static already_AddRefed<File>
   CreateFromFile(nsISupports* aParent, const nsAString& aName,
@@ -626,33 +626,35 @@ private:
 };
 
 class FileImplFile : public FileImplBase
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   // Create as a file
-  explicit FileImplFile(nsIFile* aFile)
+  explicit FileImplFile(nsIFile* aFile, bool aTemporary = false)
     : FileImplBase(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(false)
+    , mIsTemporary(aTemporary)
   {
     NS_ASSERTION(mFile, "must have file");
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
     mFile->GetLeafName(mName);
   }
 
   FileImplFile(nsIFile* aFile, indexedDB::FileInfo* aFileInfo)
     : FileImplBase(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(true)
+    , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
     NS_ASSERTION(aFileInfo, "must have file info");
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
     mFile->GetLeafName(mName);
 
     mFileInfos.AppendElement(aFileInfo);
@@ -660,76 +662,82 @@ public:
 
   // Create as a file
   FileImplFile(const nsAString& aName, const nsAString& aContentType,
                uint64_t aLength, nsIFile* aFile)
     : FileImplBase(aName, aContentType, aLength, UINT64_MAX)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(false)
+    , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
   }
 
   FileImplFile(const nsAString& aName, const nsAString& aContentType,
                uint64_t aLength, nsIFile* aFile,
                uint64_t aLastModificationDate)
     : FileImplBase(aName, aContentType, aLength, aLastModificationDate)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(false)
+    , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
   }
 
   // Create as a file with custom name
   FileImplFile(nsIFile* aFile, const nsAString& aName,
                const nsAString& aContentType)
     : FileImplBase(aName, aContentType, UINT64_MAX, UINT64_MAX)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(false)
+    , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
     if (aContentType.IsEmpty()) {
       // Lazily get the content type and size
       mContentType.SetIsVoid(true);
     }
   }
 
   // Create as a stored file
   FileImplFile(const nsAString& aName, const nsAString& aContentType,
                uint64_t aLength, nsIFile* aFile,
                indexedDB::FileInfo* aFileInfo)
     : FileImplBase(aName, aContentType, aLength, UINT64_MAX)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(true)
+    , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
     mFileInfos.AppendElement(aFileInfo);
   }
 
   // Create as a stored blob
   FileImplFile(const nsAString& aContentType, uint64_t aLength,
                nsIFile* aFile, indexedDB::FileInfo* aFileInfo)
     : FileImplBase(aContentType, aLength)
     , mFile(aFile)
     , mWholeFile(true)
     , mStoredFile(true)
+    , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
     mFileInfos.AppendElement(aFileInfo);
   }
 
   // Create as a file to be later initialized
   FileImplFile()
     : FileImplBase(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX)
     , mWholeFile(true)
     , mStoredFile(false)
+    , mIsTemporary(false)
   {
     // Lazily get the content type and size
     mContentType.SetIsVoid(true);
     mName.SetIsVoid(true);
   }
 
   // Overrides
   virtual uint64_t GetSize(ErrorResult& aRv) MOZ_OVERRIDE;
@@ -737,26 +745,37 @@ public:
   virtual int64_t GetLastModified(ErrorResult& aRv) MOZ_OVERRIDE;
   virtual void GetMozFullPathInternal(nsAString& aFullPath,
                                       ErrorResult& aRv) MOZ_OVERRIDE;
   virtual nsresult GetInternalStream(nsIInputStream**) MOZ_OVERRIDE;
 
   void SetPath(const nsAString& aFullPath);
 
 protected:
-  virtual ~FileImplFile() {}
+  virtual ~FileImplFile() {
+    if (mFile && mIsTemporary) {
+      // Ignore errors if any, not much we can do. Clean-up will be done by
+      // https://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsAnonymousTemporaryFile.cpp?rev=6c1c7e45c902#127
+#ifdef DEBUG
+      nsresult rv =
+#endif
+      mFile->Remove(false);
+      NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to remove temporary DOMFile.");
+    }
+  }
 
 private:
   // Create slice
   FileImplFile(const FileImplFile* aOther, uint64_t aStart,
                uint64_t aLength, const nsAString& aContentType)
     : FileImplBase(aContentType, aOther->mStart + aStart, aLength)
     , mFile(aOther->mFile)
     , mWholeFile(false)
     , mStoredFile(aOther->mStoredFile)
+    , mIsTemporary(false)
   {
     NS_ASSERTION(mFile, "must have file");
     mImmutable = aOther->mImmutable;
 
     if (mStoredFile) {
       indexedDB::FileInfo* fileInfo;
 
       using indexedDB::IndexedDatabaseManager;
@@ -785,16 +804,17 @@ private:
   virtual bool IsWholeFile() const MOZ_OVERRIDE
   {
     return mWholeFile;
   }
 
   nsCOMPtr<nsIFile> mFile;
   bool mWholeFile;
   bool mStoredFile;
+  bool mIsTemporary;
 };
 
 class FileList MOZ_FINAL : public nsIDOMFileList,
                            public nsWrapperCache
 {
   ~FileList() {}
 
 public:
--- a/content/base/src/File.cpp
+++ b/content/base/src/File.cpp
@@ -226,19 +226,19 @@ File::CreateTemporaryFileBlob(nsISupport
                               const nsAString& aContentType)
 {
   nsRefPtr<File> file = new File(aParent,
     new FileImplTemporaryFileBlob(aFD, aStartPos, aLength, aContentType));
   return file.forget();
 }
 
 /* static */ already_AddRefed<File>
-File::CreateFromFile(nsISupports* aParent, nsIFile* aFile)
+File::CreateFromFile(nsISupports* aParent, nsIFile* aFile, bool aTemporary)
 {
-  nsRefPtr<File> file = new File(aParent, new FileImplFile(aFile));
+  nsRefPtr<File> file = new File(aParent, new FileImplFile(aFile, aTemporary));
   return file.forget();
 }
 
 /* static */ already_AddRefed<File>
 File::CreateFromFile(nsISupports* aParent, const nsAString& aContentType,
                      uint64_t aLength, nsIFile* aFile,
                      indexedDB::FileInfo* aFileInfo)
 {
--- a/content/base/src/MultipartFileImpl.cpp
+++ b/content/base/src/MultipartFileImpl.cpp
@@ -305,17 +305,17 @@ MultipartFileImpl::InitializeChromeFile(
     aRv.Throw(NS_ERROR_FILE_IS_DIRECTORY);
     return;
   }
 
   if (mName.IsEmpty()) {
     aFile->GetLeafName(mName);
   }
 
-  nsRefPtr<File> blob = File::CreateFromFile(aWindow, aFile);
+  nsRefPtr<File> blob = File::CreateFromFile(aWindow, aFile, aBag.mTemporary);
 
   // Pre-cache size.
   uint64_t unused;
   aRv = blob->GetSize(&unused);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
--- a/content/base/test/chrome/chrome.ini
+++ b/content/base/test/chrome/chrome.ini
@@ -53,9 +53,10 @@ skip-if = buildapp == 'mulet'
 [test_bug914381.html]
 [test_bug990812.xul]
 [test_bug1063837.xul]
 [test_cpows.xul]
 skip-if = buildapp == 'mulet'
 [test_document_register.xul]
 [test_domparsing.xul]
 [test_fileconstructor.xul]
+[test_fileconstructor_tempfile.xul]
 [test_title.xul]
new file mode 100644
--- /dev/null
+++ b/content/base/test/chrome/test_fileconstructor_tempfile.xul
@@ -0,0 +1,93 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+  href="chrome://mochikit/content/tests/SimpleTest/test.css"
+  type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=982874
+
+Tests building a DOMFile with the "temporary" option and checks that
+the underlying file is removed when the DOMFile is gc'ed.
+-->
+<window title="Mozilla Bug 982874"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript"
+    src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body  xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+   href="https://bugzilla.mozilla.org/show_bug.cgi?id=982874">
+   Mozilla Bug 982874</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 982874 **/
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+SimpleTest.waitForExplicitFinish();
+
+function cleanup(tmp) {
+  // Force cycle and garbage collection and check that we removed the file.
+  for (let i = 0; i < 10; i++) {
+    Cu.forceCC();
+    Cu.forceGC();
+  }
+  if (tmp.exists()) {
+    ok(false, "Failed to remove temporary file!");
+  } else {
+    ok(true, "Temporary file removed when gc-ing the DOMFile");
+  }
+  SimpleTest.finish();
+}
+
+try {
+  // Create a file in $TMPDIR/mozilla-temp-files
+  let tmp = Cc["@mozilla.org/file/directory_service;1"]
+              .getService(Ci.nsIProperties)
+              .get("TmpD", Ci.nsIFile);
+  tmp.append("mozilla-temp-files");
+  tmp.append("test.txt");
+  tmp.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
+
+  // Add some content to the file.
+  let fileData = "I'm a temporary file!";
+  let outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+                    .createInstance(Ci.nsIFileOutputStream);
+  outStream.init(tmp, 0x02 | 0x08 | 0x20, // write, create, truncate
+                 0666, 0);
+  outStream.write(fileData, fileData.length);
+  outStream.close();
+
+  // Create a scoped DOMFile so the gc will happily get rid of it.
+  {
+    let dirfile = new File(tmp, { temporary: true });
+    ok(true, "Temporary File() created");
+    var reader = new FileReader();
+    reader.readAsArrayBuffer(dirfile);
+    reader.onload = function(event) {
+      let buffer = event.target.result;
+      ok(buffer.byteLength > 0,
+         "Blob size should be > 0 : " + buffer.byteLength);
+      cleanup(tmp);
+    }
+  }
+} catch (e) {
+  ok(false, "Unable to create the File() object : " + e);
+  SimpleTest.finish();
+}
+]]>
+</script>
+
+</window>
--- a/dom/webidl/File.webidl
+++ b/dom/webidl/File.webidl
@@ -30,17 +30,17 @@ dictionary FilePropertyBag {
       DOMString type = "";
       long long lastModified;
 
 };
 
 dictionary ChromeFilePropertyBag : FilePropertyBag {
 
       DOMString name = "";
-
+      boolean temporary = false;
 };
 
 // Mozilla extensions
 partial interface File {
 
   [GetterThrows]
   readonly attribute Date lastModifiedDate;