Bug 1580838 - Add option to CloudFile API to get files as File objects instead of ArrayBuffer; r=mkmelin
authorGeoff Lankow <geoff@darktrojan.net> and Diego Casorran <dcasorran@gmail.com>
Fri, 27 Sep 2019 11:36:21 +1200
changeset 36966 b1480206231d72b6cffbd0a4101f031a6bdeda1c
parent 36965 0e193af944ebe11fb4504e743333537fb9769647
child 36967 8ab0fc5740aaddb22fdafbd97886ba30f5728621
push id395
push userclokep@gmail.com
push dateMon, 02 Dec 2019 19:38:57 +0000
reviewersmkmelin
bugs1580838
Bug 1580838 - Add option to CloudFile API to get files as File objects instead of ArrayBuffer; r=mkmelin Patch originally by Diego Casorran
mail/components/extensions/parent/ext-cloudFile.js
mail/components/extensions/schemas/cloudFile.json
mail/components/extensions/test/xpcshell/test_ext_cloudFile.js
--- a/mail/components/extensions/parent/ext-cloudFile.js
+++ b/mail/components/extensions/parent/ext-cloudFile.js
@@ -14,33 +14,37 @@ var { cloudFileAccounts } = ChromeUtils.
 // eslint-disable-next-line mozilla/reject-importGlobalProperties
 Cu.importGlobalProperties(["File", "FileReader"]);
 
 async function promiseFileRead(nsifile) {
   let blob = await File.createFromNsIFile(nsifile);
 
   return new Promise((resolve, reject) => {
     let reader = new FileReader();
-    reader.addEventListener("loadend", () => {
-      resolve(reader.result);
+    reader.addEventListener("loadend", event => {
+      if (event.target.error) {
+        reject(event.target.error);
+      } else {
+        resolve(event.target.result);
+      }
     });
-    reader.addEventListener("onerror", reject);
 
     reader.readAsArrayBuffer(blob);
   });
 }
 
 class CloudFileAccount {
   constructor(accountKey, extension) {
     this.accountKey = accountKey;
     this.extension = extension;
     this._configured = false;
     this.lastError = "";
     this.settingsURL = this.extension.manifest.cloud_file.settings_url;
     this.managementURL = this.extension.manifest.cloud_file.management_url;
+    this.dataFormat = this.extension.manifest.cloud_file.data_format;
     this.quota = {
       uploadSizeLimit: -1,
       spaceRemaining: -1,
       spaceUsed: -1,
     };
 
     this._nextId = 1;
     this._uploads = new Map();
@@ -96,22 +100,31 @@ class CloudFileAccount {
       leafName: file.leafName,
       path: file.path,
       size: file.fileSize,
     };
     this._uploads.set(id, upload);
     let results;
 
     try {
-      let buffer = await promiseFileRead(file);
-      results = await this.extension.emit("uploadFile", this, {
-        id,
-        name: file.leafName,
-        data: buffer,
-      });
+      if (this.dataFormat == "File") {
+        let blob = await File.createFromNsIFile(file);
+        results = await this.extension.emit("uploadFile", this, {
+          id,
+          name: file.leafName,
+          data: blob,
+        });
+      } else {
+        let buffer = await promiseFileRead(file);
+        results = await this.extension.emit("uploadFile", this, {
+          id,
+          name: file.leafName,
+          data: buffer,
+        });
+      }
     } catch (ex) {
       this._uploads.delete(id);
       if (ex.result == 0x80530014) {
         // NS_ERROR_DOM_ABORT_ERR
         throw cloudFileAccounts.constants.uploadCancelled;
       } else {
         console.error(ex);
         throw cloudFileAccounts.constants.uploadErr;
--- a/mail/components/extensions/schemas/cloudFile.json
+++ b/mail/components/extensions/schemas/cloudFile.json
@@ -35,16 +35,23 @@
                 "deprecated": true,
                 "description": "A page for configuring accounts, this is obsolete after Thunderbird 60."
               },
               "management_url": {
                 "type": "string",
                 "format": "relativeUrl",
                 "preprocess": "localize",
                 "description": "A page for configuring accounts, to be displayed in the preferences UI."
+              },
+              "data_format": {
+                "type": "string",
+                "optional": true,
+                "default": "ArrayBuffer",
+                "description": "Determines the format of the ``data`` argument in ``onFileUpload``.",
+                "enum": ["ArrayBuffer", "File"]
               }
             },
             "optional": true
           }
         }
       }
     ]
   },
@@ -205,20 +212,30 @@
             "minimum": 1,
             "description": "An identifier for this file"
           },
           "name": {
             "type": "string",
             "description": "Filename of the file to be transferred"
           },
           "data": {
-            "type": "object",
-            "isInstanceOf": "ArrayBuffer",
-            "additionalProperties": true,
-            "description": "Contents of the file to be transferred"
+            "choices": [
+              {
+                "type": "object",
+                "isInstanceOf": "ArrayBuffer",
+                "additionalProperties": true,
+                "description": "Contents of the file to be transferred"
+              },
+              {
+                "type": "object",
+                "isInstanceOf": "File",
+                "additionalProperties": true,
+                "description": "Contents of the file to be transferred"
+              }
+            ]
           }
         }
       }
     ],
     "functions": [
       {
         "name": "getAccount",
         "type": "function",
--- a/mail/components/extensions/test/xpcshell/test_ext_cloudFile.js
+++ b/mail/components/extensions/test/xpcshell/test_ext_cloudFile.js
@@ -141,16 +141,17 @@ add_task(async () => {
       browser.test.log("test_upload_delete");
       let createdAccount = await createCloudfileAccount();
 
       let fileId = await new Promise(resolve => {
         function fileListener(account, { id, name, data }) {
           browser.cloudFile.onFileUpload.removeListener(fileListener);
           browser.test.assertEq(account.id, createdAccount.id);
           browser.test.assertEq(name, "cloudFile1.txt");
+          browser.test.assertTrue(data instanceof ArrayBuffer);
           browser.test.assertEq(
             new TextDecoder("utf-8").decode(data),
             "you got the moves!\n"
           );
           setTimeout(() => resolve(id));
           return { url: "https://example.com/" + name };
         }
 
@@ -280,8 +281,62 @@ add_task(async () => {
   await extension.awaitFinish("cloudFile");
   await extension.unload();
 
   Assert.ok(!cloudFileAccounts.getProviderForType("ext-cloudfile@xpcshell"));
   Assert.equal(cloudFileAccounts.accounts.length, 0);
 
   ExtensionTestUtils.failOnSchemaWarnings(true);
 });
+
+add_task(async () => {
+  async function background() {
+    await new Promise(resolve => {
+      function fileListener(account, { id, name, data }) {
+        browser.cloudFile.onFileUpload.removeListener(fileListener);
+        browser.test.assertEq(name, "cloudFile1.txt");
+        browser.test.assertTrue(data instanceof File);
+        let reader = new FileReader();
+        reader.addEventListener("loadend", () => {
+          browser.test.assertEq(reader.result, "you got the moves!\n");
+          setTimeout(() => resolve(id));
+        });
+        reader.readAsText(data);
+        return { url: "https://example.com/" + name };
+      }
+
+      browser.cloudFile.onFileUpload.addListener(fileListener);
+      browser.test.sendMessage("uploadFile");
+    });
+
+    browser.test.notifyPass("cloudFile");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      cloud_file: {
+        name: "xpcshell",
+        settings_url: "/content/settings.html",
+        management_url: "/content/management.html",
+        data_format: "File",
+      },
+      applications: { gecko: { id: "cloudfile@xpcshell" } },
+    },
+  });
+
+  // Deprecated property "settings_url". See bug 1581496 for removal.
+  ExtensionTestUtils.failOnSchemaWarnings(false);
+
+  await extension.startup();
+
+  await extension.awaitMessage("uploadFile");
+  let id = "ext-cloudfile@xpcshell";
+  let account = cloudFileAccounts.createAccount(id);
+  await account.uploadFile(do_get_file("data/cloudFile1.txt"));
+
+  await extension.awaitFinish("cloudFile");
+  cloudFileAccounts.removeAccount(account);
+
+  await extension.unload();
+
+  ExtensionTestUtils.failOnSchemaWarnings(true);
+});