Bug 1045845 - Box.com attachment upload stopped working in Thunderbird. r=Fallen a=rkent
authorPatrick Cloke <clokep@gmail.com>
Tue, 13 Oct 2015 08:48:42 -0400
changeset 22257 3911bc2095254f8d37a08c6a83305347293ec6b5
parent 22256 23315287d83f0de90d1d8f149b78a4a027b1466d
child 22258 72d1dfcc6d465fdd197874667cf2cc5ed289ed91
push id101
push userkent@caspia.com
push dateFri, 29 Apr 2016 20:46:04 +0000
treeherdercomm-esr38@66db30aed2d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersFallen, rkent
bugs1045845
Bug 1045845 - Box.com attachment upload stopped working in Thunderbird. r=Fallen a=rkent
mail/components/cloudfile/content/addAccountDialog.js
mail/components/cloudfile/content/addAccountDialog.xul
mail/components/cloudfile/nsBox.js
--- a/mail/components/cloudfile/content/addAccountDialog.js
+++ b/mail/components/cloudfile/content/addAccountDialog.js
@@ -11,16 +11,17 @@ const kFormId = "provider-form";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/cloudFileAccounts.js");
 
 function createAccountObserver() {};
 
 createAccountObserver.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver]),
   onStartRequest: function(aRequest, aContext) {},
   onStopRequest: function(aRequest, aContext, aStatusCode) {
     if (aStatusCode == Cr.NS_OK
         && aContext instanceof Ci.nsIMsgCloudFileProvider) {
       let accountKey = aContext.accountKey;
 
       // For now, we'll just set the display name to be the name of the service
       cloudFileAccounts.setDisplayName(accountKey, aContext.displayName);
@@ -66,39 +67,66 @@ let addAccountDialog = {
     this._authSpinner = document.getElementById("authorizing");
     this._error = document.getElementById("error");
     this._createAccountText = document.getElementById("createAccountText");
 
     this.removeTitleMenuItem();
     this.addAccountTypes();
 
     // Hook up our onInput event handler
-    this._settings.addEventListener("DOMContentLoaded",
-                                    this.onIFrameLoaded.bind(this),
-                                    false);
+    this._settings.addEventListener("DOMContentLoaded", this, false);
+
+    this._settings.addEventListener("overflow", this);
 
-    this._settings.addEventListener("overflow", function(e) {
-      addAccountDialog.fitIFrame();
-    });
+    // Hook up the selection handler.
+    this._accountType.addEventListener("select", this);
+    // Also call it to run it for the default selection.
+    addAccountDialog.accountTypeSelected();
 
     // Hook up the default "Learn More..." link to the appropriate link.
     let learnMore = this._settings
                         .contentDocument
                         .querySelector('#learn-more > a[href=""]');
     if (learnMore)
       learnMore.href = Services.prefs
                                .getCharPref("mail.cloud_files.learn_more_url");
     // The default emptySettings.xhtml is already loaded into the IFrame
     // at this point, before we could attach our DOMContentLoaded event
     // listener, so we'll call the function here manually.
     this.onIFrameLoaded(null);
 
     addAccountDialog.fitIFrame();
   },
 
+  onUnInit: function() {
+    // Clean-up the event listeners.
+    this._settings.removeEventListener("DOMContentLoaded", this, false);
+    this._settings.removeEventListener("overflow", this);
+    this._accountType.removeEventListener("select", this);
+
+    return false;
+  },
+
+  handleEvent: function(aEvent) {
+    switch (aEvent.type) {
+      case "DOMContentLoaded": {
+        this.onIFrameLoaded();
+        break;
+      }
+      case "overflow": {
+        this.fitIFrame();
+        break;
+      }
+      case "select": {
+        this.accountTypeSelected();
+        break;
+      }
+    }
+  },
+
   onIFrameLoaded: function AAD_onIFrameLoaded(aEvent) {
     let doc = this._settings.contentDocument;
 
     let links = doc.getElementsByTagName("a");
 
     for (let [, link] in Iterator(links))
       link.addEventListener("click", this.onClickLink);
 
@@ -180,17 +208,18 @@ let addAccountDialog = {
 
     let extras = this.getExtraArgs();
 
     let provider = cloudFileAccounts.createAccount(accountType, obs, extras);
     this._accept.disabled = true;
 
     this._messages.selectedPanel = this._authSpinner;
 
-    return false;
+    // Uninitialize the dialog before closing.
+    return this.onUnInit()
   },
 
   getExtraArgs: function AAD_getExtraArgs() {
     if (!this._settings)
       return {};
 
     let func = this._settings.contentWindow
                    .wrappedJSObject
--- a/mail/components/cloudfile/content/addAccountDialog.xul
+++ b/mail/components/cloudfile/content/addAccountDialog.xul
@@ -15,30 +15,31 @@
 
 <dialog id="addCloudFileAccount"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         buttons="accept,cancel"
         buttonlabelaccept="&addAccountDialog.acceptButton.label;"
         buttondisabledaccept="true"
         onload="return addAccountDialog.onInit();"
         ondialogaccept="return addAccountDialog.onOK();"
+        ondialogcancel="return addAccountDialog.onUnInit();"
         title="&addAccountDialog.title;"
         style="&addAccountDialog.style;"
         persist="screenX screenY">
 
   <script type="text/javascript;version=1.8"
           src="chrome://messenger/content/cloudfile/addAccountDialog.js"/>
 
 #ifdef XP_MACOSX
   <description id="createAccountTitle">&addAccountDialog.title;</description>
 #endif
   <description id="createAccountText">&addAccountDialog.createAccountText2;</description>
   <description id="noAccountText" hidden="true">&addAccountDialog.noAccountText;</description>
 
-  <menulist id="accountType" class="indent" onselect="return addAccountDialog.accountTypeSelected();">
+  <menulist id="accountType" class="indent">
     <menupopup>
       <menuitem label="&addAccountDialog.menuTitle;" value=""/>
     </menupopup>
   </menulist>
 
   <iframe id="accountSettings" class="indent" flex="1" src="chrome://messenger/content/cloudfile/emptySettings.xhtml"/>
   <deck id="messages" class="indent" selectedIndex="-1">
     <hbox id="authorizing">
--- a/mail/components/cloudfile/nsBox.js
+++ b/mail/components/cloudfile/nsBox.js
@@ -12,16 +12,18 @@ const {classes: Cc, interfaces: Ci, util
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/gloda/log4moz.js");
 Cu.import("resource:///modules/cloudFileAccounts.js");
 Cu.import("resource:///modules/OAuth2.jsm");
 Cu.import("resource://gre/modules/Http.jsm");
 
+Cu.importGlobalProperties(["File"]);
+
 var gServerUrl = "https://api.box.com/2.0/";
 var gUploadUrl = "https://upload.box.com/api/2.0/";
 
 const kAuthBaseUrl = "https://www.box.com/api/";
 const kAuthUrl = "oauth2/authorize";
 
 XPCOMUtils.defineLazyServiceGetter(this, "gProtocolService",
                                    "@mozilla.org/uriloader/external-protocol-service;1",
@@ -110,36 +112,38 @@ nsBox.prototype = {
   init: function nsBox_init(aAccountKey) {
     this._accountKey = aAccountKey;
     this._prefBranch = Services.prefs.getBranch("mail.cloud_files.accounts." +
                                                 aAccountKey + ".");
   },
 
   /**
    * Private function for assigning the folder id from a cached version
-   * If the folder doesn't exist, set in motion the creation
+   * If the folder doesn't exist, check if it exists on the server. If it
+   * doesn't, set in motion the creation.
    *
    * @param aCallback called if folder is ready.
    */
   _initFolder: function nsBox__initFolder(aCallback) {
     this.log.info('_initFolder, cached folder id  = ' + this._cachedFolderId);
 
-    let saveFolderId = function(aFolderId) {
+    let saveFolderId = (aFolderId) => {
       this.log.info('saveFolderId : ' + aFolderId);
       this._cachedFolderId = this._folderId = aFolderId;
       if (aCallback)
         aCallback();
-    }.bind(this);
+    };
 
-    let createThunderbirdFolder = function() {
+    let createThunderbirdFolder = () => {
       this._createFolder("Thunderbird", saveFolderId);
-    }.bind(this);
+    };
 
+    // If there's no cached folder, try to get one, otherwise create one.
     if (this._cachedFolderId == "")
-      createThunderbirdFolder();
+      this._getFolder("Thunderbird", saveFolderId, createThunderbirdFolder);
     else {
       this._folderId = this._cachedFolderId;
       if (aCallback)
         aCallback();
     }
   },
 
   /**
@@ -409,16 +413,88 @@ nsBox.prototype = {
    */
   createNewAccount: function nsBox_createNewAccount(aEmailAddress,
                                                           aPassword, aFirstName,
                                                           aLastName) {
     return Cr.NS_ERROR_NOT_IMPLEMENTED;
   },
 
   /**
+   * Private function to get the ID of an already existing folder on the Box
+   * website.
+   *
+   * @param aName name of folder
+   * @param aSuccessCallback called if the folder exists
+   * @param aFailureCallback called if the folder cannot be found
+   */
+  _getFolder: function nsBox__getFolder(aName,
+                                        aSuccessCallback,
+                                        aFailureCallback) {
+    this.log.info("Getting folder: " + aName);
+    if (Services.io.offline)
+      throw Ci.nsIMsgCloudFileProvider.offlineErr;
+
+    // There's no API to search by name and we don't know the ID. Get the root
+    // folder and search for this name inside of it.
+    const ROOT_ID = "0";
+    let requestUrl = gServerUrl + "folders/" + ROOT_ID;
+    this.log.info("get_folder requestUrl = " + requestUrl);
+
+    let getSuccess = (aResponseText, aRequest) => {
+      this.log.info("get_folder request response = " + aResponseText);
+
+      let folderId = null;
+      try {
+        let result = JSON.parse(aResponseText);
+
+        // Ensure the JSON is somewhat valid.
+        if (!result || !result.item_collection) {
+          this._lastErrorText = "Get folder failure";
+          this._lastErrorStatus = docStatus;
+          return;
+        }
+
+        // Search the paths for the folder.
+        for (let item of result.item_collection.entries) {
+          // Found it!
+          if (item.type == "folder" && item.name == aName) {
+            folderId = item.id;
+            break;
+          }
+        }
+      }
+      catch(e) {
+        // most likely bad JSON
+        this.log.error("Failed to get the folder:\n" + e);
+      }
+
+      // Return outside of the try-catch.
+      if (folderId) {
+        this.log.info("folder id = " + folderId);
+        aSuccessCallback(folderId)
+      }
+      else {
+        // Didn't find any item.
+        aFailureCallback();
+      }
+    };
+    let getFailure = (aException, aResponseText, aRequest) => {
+      this.log.error("Failed to get an existing folder: " + aRequest.status);
+    };
+
+    // Request to create the folder
+    httpRequest(requestUrl, {
+                  onLoad: getSuccess,
+                  onError: getFailure,
+                  method: "GET",
+                  headers: [["Authorization", "Bearer " + this._oauth.accessToken]]
+                });
+  },
+
+  /**
    * Private function for creating folder on the Box website.
    *
    * @param aName name of folder
    * @param aSuccessCallback called when folder is created
    */
   _createFolder: function nsBox__createFolder(aName,
                                               aSuccessCallback) {
     this.log.info("Creating folder: " + aName);
@@ -472,24 +548,24 @@ nsBox.prototype = {
    * If a the user associated with this account key already has an account,
    * allows them to log in.
    *
    * @param aRequestObserver an nsIRequestObserver for monitoring the start and
    *                         stop states of the login procedure.
    */
   createExistingAccount: function nsBox_createExistingAccount(aRequestObserver) {
      // XXX: replace this with a better function
-    let successCb = function(aResponseText, aRequest) {
+    let successCb = () => {
       aRequestObserver.onStopRequest(null, this, Cr.NS_OK);
-    }.bind(this);
+    };
 
-    let failureCb = function(aResponseText, aRequest) {
+    let failureCb = (aResponseText) => {
       aRequestObserver.onStopRequest(null, this,
                                      Ci.nsIMsgCloudFileProvider.authErr);
-    }.bind(this);
+    };
 
     this.logon(successCb, failureCb, true);
   },
 
   /**
    * Returns an appropriate provider-specific URL for dealing with a particular
    * error type.
    *