Bug 1014609 - Part 5: Remove jquery in accountProvisioner.js. r=mkmelin,ui-r=Paenglab
authoraleth <aleth@instantbird.org>
Wed, 30 Sep 2015 23:01:57 +0200
changeset 18470 32eb613ad2eb74dab38722910b4e4e2b08ee780e
parent 18469 3ddf3a3eaf07d5e6c1a98bfbe184dab32841ed03
child 18471 c5401915af893260262edddd66921570370fe3c3
push id11305
push useraleth@instantbird.org
push dateWed, 30 Sep 2015 21:06:54 +0000
treeherdercomm-central@180bafc8f18c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin, Paenglab
bugs1014609
Bug 1014609 - Part 5: Remove jquery in accountProvisioner.js. r=mkmelin,ui-r=Paenglab
mail/components/newmailaccount/content/accountProvisioner.js
mail/components/newmailaccount/content/accountProvisioner.xhtml
mail/locales/en-US/chrome/messenger/newmailaccount/accountProvisioner.dtd
mail/themes/shared/mail/accountProvisioner.css
--- a/mail/components/newmailaccount/content/accountProvisioner.js
+++ b/mail/components/newmailaccount/content/accountProvisioner.js
@@ -105,115 +105,109 @@ var EmailAccountProvisioner = {
   get userLanguage() {
     return Services.prefs.getCharPref("general.useragent.locale");
   },
 
   /**
    * A helper function to enable or disable the Search button.
    */
   searchButtonEnabled: function EAP_searchButtonEnabled(aVal) {
-    if (aVal) {
-      $("#searchSubmit").removeAttr("disabled");
-    } else {
-      $("#searchSubmit").attr("disabled", "true");
-    }
+    document.getElementById("searchSubmit").disabled = !aVal;
   },
 
   /**
    * A setter for enabling / disabling the search fields.
    */
   searchEnabled: function EAP_searchEnabled(aVal) {
-    if (aVal) {
-      $("#name").removeAttr("disabled");
-      $(".providerCheckbox").removeAttr("disabled");
-    } else {
-      $("#name").attr("disabled", "true");
-      $(".providerCheckbox").attr("disabled", "true");
+    document.getElementById("name").disabled = !aVal;
+    for (let node of document.querySelectorAll(".providerCheckbox")) {
+      node.disabled = !aVal;
     }
     this.searchButtonEnabled(aVal);
   },
 
   /**
    * If aVal is true, show the spinner, else hide.
    */
   spinning: function EAP_spinning(aVal) {
-    if (aVal) {
-      $("#notifications .spinner").css('display', 'block');
-    } else {
-      $("#notifications .spinner").css('display', 'none');
+    let display = aVal ? "block" : "none";
+    for (let node of document.querySelectorAll("#notifications .spinner")) {
+      node.style.display = display;
     }
   },
 
   /**
    * Sets the current window state to display the "success" page, with options
    * for composing messages, setting a signature, finding add-ons, etc.
    */
   showSuccessPage: function EAP_showSuccessPage() {
     gLog.info("Showing the success page");
     let engine = Services.search.getEngineByName(window.arguments[0].search_engine);
     let account = window.arguments[0].account;
 
     if (engine && Services.search.defaultEngine != engine) {
       // Expose the search engine checkbox
-      $("#search_engine_wrap").show()
-                              .click(function(event) {
-        $("#search_engine_check").click();
+      let searchEngineWrap = document.getElementById("search_engine_wrap");
+      let searchEngineCheck = document.getElementById("search_engine_check");
+      searchEngineWrap.style.display = "block";
+      searchEngineWrap.addEventListener("click", function() {
+        searchEngineCheck.click();
         return false;
       });
 
-      $("#search_engine_check").click(function(event) {
+      searchEngineCheck.addEventListener("click", function(event) {
         event.stopPropagation();
       });
 
       // Set up the fields...
-      $("#search_engine_check").prop("checked", true);
-      $("#search_engine_desc").html(stringBundle.get("searchDesc", [engine.name]));
+      searchEngineCheck.checked = true;
+      document.getElementById("search_engine_desc").innerHTML =
+        stringBundle.get("searchDesc", [engine.name]);
     }
 
-    $("#success-compose").click(function() {
+    document.getElementById("success-compose").addEventListener("click", function() {
       MailServices.compose.OpenComposeWindow(null, null, null,
                                              Ci.nsIMsgCompType.New,
                                              Ci.nsIMsgCompFormat.Default,
                                              account.defaultIdentity, null);
     });
 
-    $("#success-addons").click(function() {
+    document.getElementById("success-addons").addEventListener("click", function() {
       EmailAccountProvisioner.openAddonsMgr();
     });
 
-    $("#success-signature").click(function() {
+    document.getElementById("success-signature").addEventListener("click", function() {
       var existingAccountManager =
         Services.wm.getMostRecentWindow("mailnews:accountmanager");
 
       if (existingAccountManager)
         existingAccountManager.focus();
       else
         window.openDialog("chrome://messenger/content/AccountManager.xul",
                           "AccountManager", "chrome,centerscreen,modal,titlebar",
                           {server: account.incomingServer});
     });
 
-    $("#window").hide();
-    $("#successful_account").show();
+    document.getElementById("window").style.display = "none";
+    document.getElementById("successful_account").style.display = "block";
   },
 
   /**
    * Save the name inputted in the search field to localstorage, so we can
    * reconstitute it on respawn later.
    */
   saveName: function EAP_saveName() {
-    var name = String.trim($("#name").val());
+    let name = document.getElementById("name").value.trim();
     this.storage.setItem("name", name);
   },
 
   onSearchInputOrProvidersChanged: function EAP_onSearchInputOrProvidersChanged(event) {
-    let emptyName = $("#name").val() == "";
-    EmailAccountProvisioner.searchButtonEnabled(!emptyName
-                                                && EmailAccountProvisioner
-                                                   .someProvidersChecked);
+    let emptyName = document.getElementById("name").value == "";
+    EmailAccountProvisioner.searchButtonEnabled(!emptyName &&
+      EmailAccountProvisioner.someProvidersChecked);
   },
 
   /**
    * Hook up our events, populate the DOM, set our hooks, do all of our
    * prep work.  Since this is called via jQuery on document ready,
    * the value for "this" is the actual window document, hence the need
    * to explicitly refer to EmailAccountProvisioner.
    */
@@ -224,216 +218,308 @@ var EmailAccountProvisioner = {
 
     gLog.info("Initializing Email Account Provisioner");
 
     // For any anchor element that gets the "external" class, make it so that
     // when we click on that element, instead of loading up the href in the
     // window itself, we open up the link in the default browser.
     let opener = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
                            .getService(Ci.nsIExternalProtocolService);
-    $("a.external").live("click", function (e) {
-      e.preventDefault();
-      opener.loadUrl(Services.io.newURI($(e.target).attr("href"), "UTF-8", null));
+    document.addEventListener("click", function(e) {
+      if (e.target.tagName == "a" &&
+          e.target.classList.contains("external")) {
+        e.preventDefault();
+        let uri = e.target.getAttribute("href");
+        opener.loadUrl(Services.io.newURI(uri, "UTF-8", null));
+      }
     });
 
     // Throw the disclaimer into the window.  In the future, this should probably
     // be done in the actual XHTML page, instead of injected via JS.
-    let commentary = $(".commentary")
-      .append($("<span>" + stringBundle.get("disclaimer",
-      ["https://www.mozilla.org/thunderbird/legal/privacy/"]) + "</span>"));
+    let commentary = document.querySelector(".commentary");
+    commentary.innerHTML =
+      commentary.innerHTML +
+      "<span>" +
+      stringBundle.get("disclaimer", ["https://www.mozilla.org/thunderbird/legal/privacy/"]) +
+      "</span>";
 
     EmailAccountProvisioner.tryToPopulateProviderList();
 
     // Link the keypress function to the name field so that we can enable and
     // disable the search button.
-    $("#name").keyup(EmailAccountProvisioner.onSearchInputOrProvidersChanged);
+    let nameElement = document.getElementById("name");
+    nameElement.addEventListener("keyup",
+      EmailAccountProvisioner.onSearchInputOrProvidersChanged);
 
     // If we have a name stored in local storage from an earlier session,
     // populate the search field with it.
     let name = EmailAccountProvisioner.storage.getItem("name") ||
-               $("#name").text();
+               nameElement.value;
     if (!name) {
       try {
         let userInfo = Cc["@mozilla.org/userinfo;1"].getService(Ci.nsIUserInfo);
         name = userInfo.fullname;
       } catch(e) {
         // nsIUserInfo may not be implemented on all platforms, and name might
         // not be avaialble even if it is.
       }
     }
-    $("#name").val(name);
+    nameElement.value = name;
     EmailAccountProvisioner.saveName();
 
     // Pretend like we've typed something into the search input to set the
     // initial enabled/disabled state of the search button.
     EmailAccountProvisioner.onSearchInputOrProvidersChanged();
 
-    $("#window").css("height", window.innerHeight - 1);
+    document.getElementById("window").style.height = window.innerHeight - 1;
 
-    $("button.existing").click(function() {
+    document.querySelector("button.existing").addEventListener("click", function() {
       EmailAccountProvisioner.saveName();
       EmailAccountProvisioner.NewMailAccount(EmailAccountProvisioner.msgWindow,
                                              null,
                                              window.arguments[0]);
       window.close();
     });
 
     // Handle Ctrl-W and Esc
-    $(window).keypress(function(event) {
+    window.addEventListener("keypress", function(event) {
       if ((event.which == "119" && isAccel(event))
           || event.keyCode == 27) {
         window.close();
       }
     });
 
-    $("#search").submit(EmailAccountProvisioner.onSearchSubmit);
+    document.getElementById("search").addEventListener("submit",
+      EmailAccountProvisioner.onSearchSubmit);
 
-    $("#notifications").delegate("button.create", "click",
-                                 EmailAccountProvisioner.onAddressSelected);
+    let notifications = document.getElementById("notifications");
+    notifications.addEventListener("click", function(event) {
+      if (event.target.tagName == "button" &&
+          event.target.classList.contains("create")) {
+        EmailAccountProvisioner.onAddressSelected(event.target);
+      }
+    });
 
     // Handle clicking on both email address suggestions, as well
     // as the headers for the providers of those suggestions.
-    $("#results").delegate("div.selection", "click", function() {
-      let self = $(this);
-      let resultsGroup = self.closest(".resultsGroup");
+    let results = document.getElementById("results");
+    results.addEventListener("click", event => {
+      // Find the resultsGroup this click was in.
+      let resultsGroup = event.target;
+      while (resultsGroup) {
+        if (resultsGroup.classList.contains("resultsGroup")) {
+          break;
+        }
+        resultsGroup = resultsGroup.parentElement;
+      }
+      if (!resultsGroup)
+        throw("Unexpected error finding resultsGroup.");
 
       // Return if we're already expanded
-      if (resultsGroup.hasClass("expanded"))
+      if (resultsGroup.classList.contains("expanded"))
         return;
 
-      resultsGroup.siblings().removeClass("expanded");
-      resultsGroup.addClass("expanded");
-
-      // Hide the other boxes.
-      resultsGroup.siblings().children(".extra").slideUp();
-      resultsGroup.siblings().find(".more").show();
-      resultsGroup.siblings().find(".pricing").fadeOut("fast");
-      resultsGroup.siblings().find(".price").fadeIn("fast");
-
-      // And show this box.
-      resultsGroup.find(".more").hide();
-      resultsGroup.children().find(".pricing").fadeIn("fast");
-      resultsGroup.children().find(".price").fadeOut("fast");
-      self.siblings(".extra").slideDown();
+      for (let child of resultsGroup.parentElement.children) {
+        if (child != resultsGroup) {
+          child.classList.remove("expanded");
+          // Hide the other boxes.
+          for (let node of child.querySelectorAll(".extra")) {
+            node.classList.add("slideUp");
+            for (let address of node.querySelectorAll(".address")) {
+              address.classList.remove("showWithFade");
+              address.classList.add("hideWithFade");
+            }
+          }
+          let more = child.querySelector(".more");
+          let makeListener = (aNode, aMore) => {
+            let listener = () => {
+              if (aMore)
+                aMore.style.display = "block";
+              aNode.querySelector("button").disabled = true;
+              aNode.removeEventListener("transitionend", listener);
+            };
+            return listener;
+          };
+          for (let node of child.querySelectorAll(".pricing")) {
+            node.classList.remove("showWithFade");
+            // Disable the pricing button and show the "more" text
+            // after the transition is complete.
+            node.addEventListener("transitionend", makeListener(node, more));
+            node.classList.add("hideWithFade");
+          }
+          for (let node of child.querySelectorAll(".price")) {
+            node.classList.remove("hideWithFade");
+            node.classList.add("showWithFade");
+          }
+        } else {
+          child.classList.add("expanded");
+          // And show this box.
+          let more = child.querySelector(".more");
+          if (more)
+            more.style.display = "none";
+          for (let node of child.querySelectorAll(".pricing")) {
+            node.classList.remove("hideWithFade");
+            node.classList.add("showWithFade");
+            node.querySelector("button").disabled = false;
+          }
+          for (let node of child.querySelectorAll(".price")) {
+            node.classList.remove("showWithFade");
+            node.classList.add("hideWithFade");
+          }
+          for (let node of child.querySelectorAll(".extra")) {
+            node.classList.remove("slideUp");
+            for (let address of node.querySelectorAll(".address")) {
+              address.classList.remove("hideWithFade");
+              address.classList.add("showWithFade");
+            }
+          }
+        }
+      }
     });
 
-    $("button.close").click(function() {
-      window.close();
-    });
+    for (let node of document.querySelectorAll("button.close")) {
+      node.addEventListener("click", () => window.close());
+    }
 
-    $(window).unload(function() {
-      if (window.arguments[0].search_engine
-          && $("#search_engine_check").prop("checked")) {
+    window.addEventListener("unload", function() {
+      let searchEngineCheck = document.getElementById("search_engine_check");
+      if (window.arguments[0].search_engine && searchEngineCheck.checked) {
         let engine = Services.search.getEngineByName(window.arguments[0].search_engine);
         Services.search.currentEngine = engine;
       }
     });
 
     if (window.arguments[0].success) {
       // Show the success page which lets a user compose mail, find add-ons,
       // set a signature, etc.
       gLog.info("Looks like we just finished ordering an address - showing the success page...");
       EmailAccountProvisioner.showSuccessPage();
     } else {
       // The default mode, where we display the search input, providers, etc
-      $("#window").show();
-      $("#successful_account").hide();
+      document.getElementById("window").style.display = "block";
+      document.getElementById("successful_account").style.display = "none";
     }
 
     gLog.info("Email Account Provisioner init complete.");
 
     EmailAccountProvisioner._inited = true;
   },
 
   /**
    * Event handler for when the user submits the search request for their
    * name to the suggestFromName service.
    */
   onSearchSubmit: function EAP_onSearchSubmit() {
-    $("#notifications").children().hide();
-    $("#instructions").fadeOut();
+    for (let node of document.getElementById("notifications").children) {
+      node.style.display = "none";
+    }
+    document.getElementById("instructions").classList.add("hide");
     EmailAccountProvisioner.saveName();
+
     // Here's where we do some kind of hack-y client-side sanitization.
     // Believe it or not, this is how you sanitize stuff to HTML elements
     // via jQuery.
-    let name = String.trim($("<div></div>").text($("#name").val()).html());
-    if (name.length <= 0) {
-      $("#name").select().focus();
+    // let name = String.trim($("<div></div>").text($("#name").val()).html());
+    // Not quite sure what this was for, but here's the hack converted
+    // to vanilla JS.
+    let nameElement = document.getElementById("name");
+    let div = document.createElement("div");
+    div.textContent = nameElement.value;
+    let name = div.innerHTML.trim();
+    if (!name) {
+      nameElement.select();
+      nameElement.focus();
       return;
     }
 
     EmailAccountProvisioner.searchEnabled(false);
     EmailAccountProvisioner.spinning(true);
     let [firstname, lastname] = splitName(name);
-    let providerList = $(".provider input:checked").map(function() {
-      return $(this).val();
-    }).get().join(',');
+    let selectedProviderList =
+      [...document.querySelectorAll(".provider input:checked")];
+    let providerList = selectedProviderList.map(node => node.value).join(',');
 
-    $.ajax({
-      url: EmailAccountProvisioner.suggestFromName,
-      dataType: 'json',
-      data: {"first_name": firstname,
-             "last_name": lastname,
-             "providers": providerList,
-             "version": 2},
-      timeout: CONNECTION_TIMEOUT,
-      success: EmailAccountProvisioner.onSearchResults})
-      .error(EmailAccountProvisioner.showSearchError)
-      .complete(function() {
-        $("#FirstAndLastName").html(String.trim(firstname + " " + lastname));
-        EmailAccountProvisioner.searchEnabled(true);
-        EmailAccountProvisioner.spinning(false);
-      });
+    let request = new XMLHttpRequest();
+    request.open("GET", EmailAccountProvisioner.suggestFromName +
+      "?first_name=" + encodeURIComponent(firstname) +
+      "&last_name=" + encodeURIComponent(lastname) +
+      "&providers=" + encodeURIComponent(providerList) +
+      "&version=2");
+    request.onload = function () {
+      let data;
+      try {
+        data = JSON.parse(request.responseText);
+      } catch(e) {};
+      EmailAccountProvisioner.onSearchResults(data);
+    };
+    request.onerror = () => {
+      gLog.info("Error response of XMLHttpRequest fetching address data.");
+      EmailAccountProvisioner.showSearchError();
+    };
+    request.ontimeout = () => {
+      gLog.info("Timeout of XMLHttpRequest fetching address data.");
+      EmailAccountProvisioner.showSearchError();
+    }
+    request.onloadend = function() {
+      // Also called if we timeout.
+      let firstAndLastName = document.getElementById("FirstAndLastName");
+      firstAndLastName.innerHTML = String.trim(firstname + " " + lastname);
+      EmailAccountProvisioner.searchEnabled(true);
+      EmailAccountProvisioner.spinning(false);
+    };
+    request.timeout = CONNECTION_TIMEOUT;
+    request.send(null);
   },
 
   /**
    * Event handler for when the user selects an address by clicking on
    * the price button for that address.  This function spawns the content
    * tab for the address order form, and then closes the Account Provisioner
    * window.
    */
-  onAddressSelected: function EAP_onAddressSelected() {
+  onAddressSelected: function EAP_onAddressSelected(aTarget) {
     gLog.info("An address was selected by the user.");
-    let provider = EmailAccountProvisioner.providers[$(this).data("provider")];
+    let provider = EmailAccountProvisioner.providers[aTarget.dataset["provider"]];
 
     // Replace the variables in the url.
     let url = provider.api;
-    let [firstName, lastName] = splitName(String.trim($("#name").val()));
-    let email = $(this).attr("address");
+    let [firstName, lastName] = splitName(document.getElementById("name").value.trim());
+    let email = aTarget.getAttribute("address");
     url = url.replace("{firstname}", firstName);
     url = url.replace("{lastname}", lastName);
     url = url.replace("{email}", email);
 
     // And add the extra data.
     let data = storedData[provider.id];
     delete data.provider;
     for (let name in data) {
       url += (url.indexOf("?") == -1 ? "?" : "&") +
               name + "=" + encodeURIComponent(data[name]);
     }
 
     gLog.info("Opening up a contentTab with the order form.");
     // Then open a content tab.
     let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane");
-
     let tabmail = mail3Pane.document.getElementById("tabmail");
     tabmail.openTab("accountProvisionerTab", {
       contentPage: url,
       realName: String.trim(firstName + " " + lastName),
       email: email,
       searchEngine: provider.search_engine,
       onLoad: function (aEvent, aBrowser) {
         window.close();
       },
     });
 
     // Wait for the handler to close us.
     EmailAccountProvisioner.spinning(true);
     EmailAccountProvisioner.searchEnabled(false);
-    $("#notifications").children().not(".spinner").hide();
+    for (let node of document.querySelectorAll("#notifications > :not(.spinner)")) {
+      node.style.display = "none";
+    }
   },
 
   /**
    * Attempt to fetch the provider list from the server.  If it fails,
    * display an error message, and queue for retry.
    */
   tryToPopulateProviderList: function EAP_tryToPopulateProviderList() {
     // If we're already in the middle of getting the provider list, or
@@ -444,45 +530,53 @@ var EmailAccountProvisioner = {
     gLog.info("Trying to populate provider list...");
 
     // If there's a timeout ID for waking the account provisioner, clear it.
     if (this._loadProviderRetryId) {
       window.clearTimeout(this._loadProviderRetryId)
       this._loadProviderRetryId = null;
     }
 
-    var self = this;
-
-    self.searchEnabled(false);
-    self.spinning(true);
+    this.searchEnabled(false);
+    this.spinning(true);
 
     let providerListUrl = Services.prefs.getCharPref("mail.provider.providerList");
 
-    $.ajax({
-      url: providerListUrl,
-      dataType: 'json',
-      data: '',
-      timeout: CONNECTION_TIMEOUT,
-      success: EmailAccountProvisioner.populateProviderList,
-      }).error(function() {
-        // Ugh, we couldn't get the JSON file.  Maybe we're not online.  Or maybe
-        // the server is down, or the file isn't being served.  Regardless, if
-        // we get here, none of this stuff is going to work.
-        EmailAccountProvisioner._loadProviderRetryId = window.setTimeout(EmailAccountProvisioner.tryToPopulateProviderList
-                                                                                                .bind(self),
-                                                                         RETRY_TIMEOUT);
-        EmailAccountProvisioner._loadingProviders = false;
-        EmailAccountProvisioner.beOffline();
-        gLog.error("Something went wrong loading the provider list JSON file. "
-                   + "Going into offline mode.");
-      }).complete(function() {
-        EmailAccountProvisioner._loadingProviders = false;
-        EmailAccountProvisioner.spinning(false);
-        gLog.info("Got provider list JSON.");
-      });
+    let request = new XMLHttpRequest();
+    request.open("GET", providerListUrl);
+    request.onload = function() {
+      let data;
+      try {
+        data = JSON.parse(request.responseText);
+      } catch(e) {};
+      EmailAccountProvisioner.populateProviderList(data);
+    };
+    request.onerror = () => {
+      // Ugh, we couldn't get the JSON file. Maybe we're not online. Or maybe
+      // the server is down, or the file isn't being served. Regardless, if
+      // we get here, none of this stuff is going to work.
+      EmailAccountProvisioner._loadProviderRetryId =
+        window.setTimeout(() => EmailAccountProvisioner.tryToPopulateProviderList(),
+                          RETRY_TIMEOUT);
+      EmailAccountProvisioner._loadingProviders = false;
+      EmailAccountProvisioner.beOffline();
+      gLog.error("Something went wrong loading the provider list JSON file. " +
+                 "Going into offline mode.");
+    };
+    request.onloadend = function() {
+      EmailAccountProvisioner._loadingProviders = false;
+      EmailAccountProvisioner.spinning(false);
+      gLog.info("Got provider list JSON.");
+    };
+    request.timeout = CONNECTION_TIMEOUT;
+    request.ontimeout = () => {
+      glog.info("Timeout of XMLHttpRequest fetching provider list.");
+      request.onError();
+    };
+    request.send(null);
 
     EmailAccountProvisioner._loadingProviders = true;
     gLog.info("We've kicked off a request for the provider list JSON file...");
   },
 
   providerHasCorrectFields: function EAP_providerHasCorrectFields(provider) {
     let result = true;
 
@@ -509,216 +603,207 @@ var EmailAccountProvisioner = {
     gLog.info("Populating the provider list");
 
     if (!data || !data.length) {
       gLog.error("The provider list we got back from the server was empty!");
       EmailAccountProvisioner.beOffline();
       return;
     }
 
-    let providerList = $("#providerList");
+    let providerList = document.getElementById("providerList");
     let otherLangProviders = [];
 
     EmailAccountProvisioner.providers = {};
 
     data.forEach(function(provider) {
-
       if (!(EmailAccountProvisioner.providerHasCorrectFields(provider))) {
         gLog.error("A provider had incorrect fields, and has been skipped");
         return;
       }
 
       EmailAccountProvisioner.providers[provider.id] = provider;
 
       // Let's go through the array of languages for this provider, and
       // check to see if at least one of them matches general.useragent.locale.
       // If so, we'll show / select this provider by default.
-      let supportsSomeUserLang = provider
-                                 .languages
-                                 .some(function (x) {
-                                   return x == "*" ||
-                                          x == EmailAccountProvisioner.userLanguage
-                                 });
+      let supportsSomeUserLang = provider.languages.some(function (x) {
+        return x == "*" || x == EmailAccountProvisioner.userLanguage;
+      });
 
       let checkboxId = provider.id + "-check";
 
-      let providerCheckbox = $('<input type="checkbox" />')
-                             .val(provider.id)
-                             .addClass("providerCheckbox")
-                             .attr("id", checkboxId);
+      let providerCheckbox = document.createElement("input");
+      providerCheckbox.setAttribute("type", "checkbox");
+      providerCheckbox.setAttribute("value", provider.id);
+      providerCheckbox.className = "providerCheckbox";
+      providerCheckbox.setAttribute("id", checkboxId);
 
-      let providerEntry = $('<li class="provider" />')
-                          .append(providerCheckbox);
+      let providerEntry = document.createElement("li");
+      providerEntry.className = "provider";
+      providerEntry.appendChild(providerCheckbox);
 
-      let labelSpan = $('<label class="providerLabel" />')
-                      .append(provider.label)
-                      .appendTo(providerEntry)
-                      .attr("for", checkboxId);
+      let icon = document.createElement("img");
+      icon.className = "icon";
+      // We add this even if there is no icon, so that the alignment with
+      // providers without icons isn't broken.
+      providerEntry.appendChild(icon);
+      if (provider.icon) {
+        // Note this favicon must be fetched, which takes a noticeable
+        // time the first time it happens.
+        icon.setAttribute("src", provider.icon);
+      }
 
-      if (provider.icon)
-        providerCheckbox.after('<img class="icon" src="' + provider.icon + '"/>');
+      let labelSpan = document.createElement("label");
+      labelSpan.className = "providerLabel";
+      labelSpan.setAttribute("for", checkboxId);
+      labelSpan.innerHTML = provider.label;
+      providerEntry.appendChild(labelSpan);
 
-      providerCheckbox.change(function() {
-        EmailAccountProvisioner.populateTermsAndPrivacyLinks();
-      });
+      providerCheckbox.addEventListener("change",
+        EmailAccountProvisioner.populateTermsAndPrivacyLinks);
 
       if (supportsSomeUserLang) {
-        providerCheckbox.attr('checked', 'checked');
-        providerEntry.css('display', 'inline-block');
-        providerList.append(providerEntry);
+        providerCheckbox.setAttribute("checked", "true");
+        providerEntry.style.display = "inline-block";
+        providerList.appendChild(providerEntry);
       }
       else {
-        providerEntry.addClass("otherLanguage");
+        providerEntry.classList.add("otherLanguage");
         otherLangProviders.push(providerEntry);
       }
     });
 
-    for (let provider of otherLangProviders) {
-      providerList.append(provider);
-    }
+    if (otherLangProviders.length) {
+      for (let provider of otherLangProviders) {
+        providerList.appendChild(provider);
+      }
 
-    if (otherLangProviders.length) {
       let otherLangDesc = document.getElementById("otherLangDesc");
       otherLangDesc.classList.remove("fadeOut");
       otherLangDesc.classList.add("fadeIn");
-      $("#otherLangDesc").click(function() {
+      otherLangDesc.addEventListener("click", function() {
         otherLangDesc.classList.remove("fadeIn");
         otherLangDesc.classList.add("fadeOut");
-        $(".otherLanguage").fadeIn().css("display", "inline-block");
+        for (let node of document.querySelectorAll(".otherLanguage")) {
+          node.style.display = "inline-block";
+          node.classList.add("showWithFade");
+        }
       });
     }
 
     EmailAccountProvisioner.populateTermsAndPrivacyLinks();
     EmailAccountProvisioner.beOnline();
     EmailAccountProvisioner._loadedProviders = true;
     EmailAccountProvisioner.onSearchInputOrProvidersChanged();
   },
 
   /**
    * Go through each of the checked providers, and add the appropriate
    * ToS and privacy links to the disclaimer.
    */
   populateTermsAndPrivacyLinks: function EAP_populateTOSandPrivacyLinks() {
     gLog.info("Refreshing terms and privacy links");
     // Empty the Terms of Service and Privacy links placeholder.
-    let commentary = $(".commentary");
-    let placeholder = commentary.find(".placeholder");
-    placeholder.empty();
-
-    let selectedProviders = $(".provider input:checked");
-
-    EmailAccountProvisioner.someProvidersChecked = selectedProviders.length > 0;
+    let placeholder = document.querySelector(".commentary .placeholder");
+    placeholder.innerHTML = "";
 
-    let termsAndPrivacyLinks = [];
-    selectedProviders.each(function(i, checkbox) {
-      let providerId = $(checkbox).val();
-      let provider = EmailAccountProvisioner.providers[providerId];
-      let providerLinks = $("<span />").text(provider.label + " (")
-        .append($("<a />")
-          .attr("href", provider.privacy_url)
-          .text(stringBundle.get("privacyPolicy"))
-          .addClass("privacy").addClass("external").addClass(provider.id)
-        )
-        .append($("<span />").text(stringBundle.get("sepComma")))
-        .append($("<a />")
-          .attr("href", provider.tos_url)
-          .text(stringBundle.get("tos"))
-          .addClass("tos").addClass("external").addClass(provider.id)
-        ).append($("<span />").text(")"));
-      termsAndPrivacyLinks.push(providerLinks);
-    });
+    let selectedProviders =
+      [...document.querySelectorAll(".provider input:checked")];
+    let len = selectedProviders.length;
 
-    if (termsAndPrivacyLinks.length <= 0) {
+    EmailAccountProvisioner.someProvidersChecked = len > 0;
+    if (!len) {
       // Something went really wrong - we shouldn't have gotten here. Bail out.
       return;
-    } else if (termsAndPrivacyLinks.length == 1) {
-      placeholder.append(termsAndPrivacyLinks[0]);
-      return;
-    } else {
-      // Pop off the last terms and privacy links...
-      let lastTermsAndPrivacyLink = termsAndPrivacyLinks.pop();
-      // Join the remaining terms and privacy links with the comma separator...
-      $(termsAndPrivacyLinks).each(function(i, termsAndPrivacyLink) {
-        placeholder.append(termsAndPrivacyLink);
-        if (i < termsAndPrivacyLinks.length - 1)
-          placeholder.append($("<span />").text(stringBundle.get("sepComma")));
-      });
-      placeholder.append($("<span />").text(stringBundle.get("sepAnd")));
-      placeholder.append(lastTermsAndPrivacyLink);
     }
-  },
+
+    let innerHTML = "";
+    selectedProviders.forEach((checkbox, i) => {
+      let providerId = checkbox.value;
+      let provider = EmailAccountProvisioner.providers[providerId];
+
+      innerHTML += '<span>' + provider.label + ' (</span>';
+      innerHTML += '<a href="' + provider.privacy_url + '" ';
+      innerHTML += 'class="privary external ' + provider.id + '">';
+      innerHTML += stringBundle.get("privacyPolicy") + '</a>';
+
+      innerHTML += '<span>' + stringBundle.get("sepComma") + '</span>';
 
-  /**
-   * Make the search pane a little bit taller, and the existing account
-   * pane a little bit shorter.
-   */
-  expandSearchPane: function() {
-    // Don't expand twice.
-    if ($("#existing").data("expanded"))
-      return;
+      innerHTML += '<a href="' + provider.tos_url + '" ';
+      innerHTML += 'class="tos external ' + provider.id + '">';
+      innerHTML += stringBundle.get('tos') + '</a>';
+
+      innerHTML += '<span>)</span>';
 
-    $("#existing").animate({"height": "50px",
-                            "font-size": "10pt"}, "fast",
-      function() {
-        $("#providers").fadeIn();
-        $("#content .description").fadeIn();
-        $("#existing .header").hide();
-        $(".tinyheader .title").css({"opacity": "1.0"}).fadeIn("fast");
-        $("#existing").data("expanded", true);
-      });
+      if (len != 1) {
+        if (i < len - 2) {
+          innerHTML += '<span>' + stringBundle.get("sepComma") + '</span>';
+        } else if (i == len - 2) {
+          innerHTML += '<span>' + stringBundle.get("sepAnd") + '</span>';
+        }
+      }
+    });
+
+    placeholder.innerHTML = innerHTML;
   },
 
   /**
    * Something went wrong during search.  Show a generic error.  In the future,
    * we might want to show something a bit more descriptive.
    */
   showSearchError: function() {
-    $("#notifications").children().hide();
-    $("#notifications .error").fadeIn();
+    for (let node of document.getElementById("notifications").children) {
+      node.style.display = "none";
+    }
+    for (let node of document.querySelectorAll("#notifications .error")) {
+      node.style.display = "block"
+      node.getBoundingClientRect();
+      node.classList.add("showWithFade");
+    }
   },
 
   /**
    * Once we've received search results from the server, create some
    * elements to display those results, and inject them into the DOM.
    */
   onSearchResults: function(data) {
     gLog.info("Got back search results");
-    // Expand the search pane if it hasn't been expanded yet.
-    EmailAccountProvisioner.expandSearchPane();
 
     // Empty any old results.
-    let results = $("#results").empty();
+    let results = document.getElementById("results");
+    results.innerHTML = "";
 
     if (!data || !data.length) {
       // If we've gotten back nonsense, display the generic
       // error message, and bail out.
       gLog.error("We got nothing back from the server for search results!");
       EmailAccountProvisioner.showSearchError();
       return;
     }
 
     // Get a list of the providers that the user checked - we'll
     // check against these to make sure the server didn't send any
     // back from a provider that the user did not select.
-    let selectedProviders = $(".provider input:checked").map(function() {
-      return $(this).val();
-    });
+    let selectedProviderList =
+      [...document.querySelectorAll(".provider input:checked")];
+    let selectedProviders = selectedProviderList.map(node => node.value);
+    gLog.info(selectedProviders.length + " selected providers.");
 
     // Filter out any results that don't match our requirements...
     let returnedProviders = data.filter(function(aResult) {
       // We require that the search succeeded for a provider, that we
       // got at least one result, and that the provider is actually in
       // the list of providers that we care about.
       let providerInList = (aResult.provider in EmailAccountProvisioner.providers);
 
       if (!providerInList)
         gLog.error("Got a result back for a provider that was not "
                    + "in the original providerList: " + aResult.provider);
 
-      let providerSelected = $.inArray(aResult.provider, selectedProviders) != -1;
+      let providerSelected = selectedProviders.indexOf(aResult.provider) != -1;
 
       if (!providerSelected)
         gLog.error("Got a result back for a provider that the user did "
                    + "not select: " + aResult.provider);
 
       return (aResult.succeeded
               && aResult.addresses.length > 0
               && providerInList
@@ -728,31 +813,35 @@ var EmailAccountProvisioner = {
     if (returnedProviders.length == 0) {
       gLog.info("There weren't any results for the selected providers.");
       // Display the generic error message, and bail out.
       EmailAccountProvisioner.showSearchError();
       return;
     }
 
     for (let provider of returnedProviders) {
-      let group = $("<div class='resultsGroup'></div>");
-      let header = $("#resultsHeader")
-                   .clone()
-                   .removeClass("displayNone")
-                   .addClass("selection");
+      let group = document.createElement("div");
+      group.className = "resultsGroup";
+
+      let header = document.getElementById("resultsHeader").cloneNode(true);
+      header.classList.remove("displayNone");
+      header.classList.add("selection");
 
-      header.children(".provider")
-            .text(EmailAccountProvisioner.providers[provider.provider].label);
+      let providerLabel =
+        document.createTextNode(EmailAccountProvisioner.providers[provider.provider].label);
+      header.querySelector(".provider").appendChild(providerLabel);
 
+      let providerPrice;
       if (provider.price && provider.price != "0")
-        header.children(".price").text(provider.price);
+        providerPrice = document.createTextNode(provider.price);
       else
-        header.children(".price").text(stringBundle.get("free"));
+        providerPrice = document.createTextNode(stringBundle.get("free"));
+      header.querySelector(".price").appendChild(providerPrice);
 
-      group.append(header);
+      group.appendChild(header);
 
       let renderedAddresses = 0;
       let addrIndex = 0;
       for (let address of provider.addresses) {
         addrIndex++;
 
         // Figure out the price to display on the address button, as so:
         // If there is a per-address price of > 0, use that.
@@ -772,71 +861,93 @@ var EmailAccountProvisioner = {
 
         let templateElement = document.querySelector("#result_tmpl");
         let result = document.importNode(templateElement.content, true).children[0];
         result.innerHTML =
           result.innerHTML.replace(/\${address}/g,
                                    address.address ? address.address : address)
                           .replace(/\${priceStr}/g, priceStr);
 
-        group.append(result);
+        group.appendChild(result);
         // Keep a count of the rendered addresses for the "More" buttons, etc.
         renderedAddresses++;
 
-        if (addrIndex >= MAX_SMALL_ADDRESSES) {
+        if (addrIndex > MAX_SMALL_ADDRESSES) {
           result.classList.add("extra");
-          result.style.display = "none";
+          for (let address of result.querySelectorAll(".address")) {
+            address.classList.add("hideWithFade");
+          }
+          result.classList.add("slideUp");
         }
       }
+      gLog.info("Added " + renderedAddresses + " addresses, showing at most " +
+        MAX_SMALL_ADDRESSES + ".");
 
       if (renderedAddresses > MAX_SMALL_ADDRESSES) {
         let more = renderedAddresses - MAX_SMALL_ADDRESSES;
         let moreStr = PluralForm.get(more, stringBundle.get("moreOptions")).replace("#1", more);
-        let last = group.children(".row:nth-child(" + (MAX_SMALL_ADDRESSES + 1) + ")");
-        last.append('<div class="more">' + moreStr + '</div>');
+        let last = group.querySelector(".row:nth-child(" + (MAX_SMALL_ADDRESSES + 1) + ")");
+        last.innerHTML += '<div class="more">' + moreStr + '</div>';
+      }
+      for (let node of group.querySelectorAll("button.create")) {
+        node.dataset.provider = provider.provider;
       }
-      group.find("button.create").data("provider", provider.provider);
-      group.append($("#resultsFooter").clone().removeClass("displayNone"));
-      results.append(group);
+
+      // There doesn't seem to be a #resultsFooter anywhere.
+      // let footer = document.getElementById("resultsFooter").cloneNode(true);
+      // footer.classList.remove("displayNone");
+      // group.append(footer);
+
+      results.appendChild(group);
     }
 
-    $("#notifications").children().hide();
-    $("#notifications .success").show();
-
+    for (let node of document.getElementById("notifications").children) {
+      if (node.classList.contains("success")) {
+        node.style.display = "block";
+      } else {
+        node.style.display = "none";
+      }
+    }
     for (let provider of data) {
       delete provider.succeeded;
       delete provider.addresses;
       delete provider.price;
       storedData[provider.provider] = provider;
     }
   },
 
   /**
    * If we cannot retrieve the provider list from the server, display a
    * message about connection problems, and disable the search fields.
    */
   beOffline: function EAP_beOffline() {
     let offlineMsg = stringBundle.get("cannotConnect");
-    $('#cannotConnectMessage').text(offlineMsg).show();
+    let element = document.getElementById("cannotConnectMessage");
+    element.appendChild(document.createTextNode(offlineMsg));
+    element.style.display = "block";
+    element.style.opacity = 1;
     this.searchEnabled(false);
     gLog.info("Email Account Provisioner is in offline mode.");
   },
 
   /**
    * If we're suddenly able to get the provider list, hide the connection
    * error message and re-enable the search fields.
    */
   beOnline: function EAP_beOnline() {
-    $('#cannotConnectMessage').hide().text('');
+    let element = document.getElementById("cannotConnectMessage");
+    element.style.display = "none";
+    element.innerHTML = "";
     this.searchEnabled(true);
     gLog.info("Email Account Provisioner is in online mode.");
   }
 }
 
 
 XPCOMUtils.defineLazyGetter(EmailAccountProvisioner, "storage", function() {
   return getLocalStorage("accountProvisioner");
 });
 
 window.addEventListener("online",
                         EmailAccountProvisioner.tryToPopulateProviderList);
 
-$(EmailAccountProvisioner.init);
+document.addEventListener("DOMContentLoaded",
+                          EmailAccountProvisioner.init);
--- a/mail/components/newmailaccount/content/accountProvisioner.xhtml
+++ b/mail/components/newmailaccount/content/accountProvisioner.xhtml
@@ -16,22 +16,16 @@
 
   <title>&window.title;</title>
 
   <link rel="stylesheet"
         type="text/css"
         href="chrome://messenger/skin/newmailaccount/accountProvisioner.css" />
 
   <script type="text/javascript;version=1.8"
-          src="chrome://messenger/content/jquery.js">
-  </script>
-  <script type="text/javascript;version=1.8"
-          src="chrome://messenger/content/jquery-ui.js">
-  </script>
-  <script type="text/javascript;version=1.8"
           src="chrome://messenger/content/accountcreation/util.js">
   </script>
   <script type="text/javascript;version=1.8"
           src="chrome://messenger/content/accountcreation/accountConfig.js">
   </script>
   <script type="text/javascript;version=1.8"
           src="chrome://messenger/content/accountcreation/sanitizeDatatypes.js">
   </script>
@@ -150,18 +144,18 @@
     </div>
 
     <button id="closeWindow" class="close">&successful.close;</button>
   </div>
 
   <template id="result_tmpl">
     <div class="hbox row selection">
       <div class="boxFlex address">${address}</div>
-      <div class="pricing">
-        <button class="create"
+      <div class="pricing hideWithFade">
+        <button class="create" disabled="true"
           address="${address}">${priceStr}</button>
         <span class="create"
           address="${address}">${priceStr}</span>
       </div>
     </div>
   </template>
 
 </body>
--- a/mail/locales/en-US/chrome/messenger/newmailaccount/accountProvisioner.dtd
+++ b/mail/locales/en-US/chrome/messenger/newmailaccount/accountProvisioner.dtd
@@ -1,17 +1,17 @@
 <!-- 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/. -->
 
 <!ENTITY window.title "Welcome to &brandShortName;">
 <!ENTITY header2.label "Would you like a new email address?">
 <!ENTITY other.languages "We are only displaying the providers offering addresses in your area.  Click here to show all providers.">
 <!ENTITY error.line1 "Sorry, we could not find any suggested email addresses.">
-<!ENTITY error.line2 "You try can search for nicknames or any other term to find more emails">
+<!ENTITY error.line2 "You can try to search for nicknames or any other term to find more emails.">
 
 <!-- LOCALIZATION NOTE(error.suggest.before, error.suggest.middle, error.suggest.after):
      error.suggest.before, error.suggest.middle, and error.suggest.after all go into
      one line with error.suggest.middle that links to a site which provides download
      of free account alternatives. -->
 <!ENTITY error.suggest.before "Also you might try one of the ">
 <!ENTITY error.suggest.middle "free email account alternatives">
 <!ENTITY error.suggest.after ".">
--- a/mail/themes/shared/mail/accountProvisioner.css
+++ b/mail/themes/shared/mail/accountProvisioner.css
@@ -66,16 +66,25 @@ input[type="submit"][disabled="true"], i
   display: none;
 }
 
 #instructions {
   font-size: smaller;
   color: #444;
   margin-bottom: 19px;
   margin-top: 6px;
+  max-height: 80px;
+  transition: all 0.4s;
+}
+
+#instructions.hide {
+  max-height: 0;
+  margin: 0;
+  opacity: 0;
+  pointer-events: none;
 }
 
 #content .description {
   font-size: smaller;
   color: gray;
 }
 
 .header .commentary {
@@ -87,26 +96,29 @@ input[type="submit"][disabled="true"], i
   list-style-type: none;
   padding: 0;
   margin: 0;
 }
 
 #providers .icon {
   margin-left: 5px;
   margin-right: 5px;
+  min-width: 16px;
+  max-width: 16px;
+  max-height: 16px;
   vertical-align: middle;
 }
 
 #providers .provider {
   width: 280px;
 }
 
 #providers .providerLabel {
   vertical-align: top;
-  width: 240px;
+  width: 240px; /* as #providers.provider, minus space for icon and checkbox */
   display: inline-block;
   word-wrap: break-word;
 }
 
 #providers input[type="checkbox"] {
   vertical-align: middle;
 }
 
@@ -117,16 +129,20 @@ input[type="submit"][disabled="true"], i
 
 #otherLangDesc {
   text-decoration: underline;
   cursor: pointer;
   font-size: x-small;
   margin-top: 9px;
 }
 
+.otherLanguage {
+  opacity: 0;
+}
+
 /* External things are links that open in a browser, so underline them. */
 .external {
   text-decoration: underline;
 }
 
 #content,
 #existing {
   background-color: rgba(255,255,255,0.9);
@@ -202,18 +218,18 @@ input[type="submit"].search {
   margin-left: auto;
   margin-right: auto;
   margin-top: 25px;
   margin-bottom: 25px;
   display: block;
 }
 
 .error {
+  opacity: 0;
   color: red;
-  display: block;
 }
 
 .success .title {
   color: #333;
 }
 
 #FirstAndLastName {
   font-weight: bold;
@@ -234,19 +250,22 @@ input[type="submit"].search {
 #results .provider {
   font-weight: bold;
 }
 
 #results .row {
   width: 100%;
   padding: 7px;
   background-color: #fff;
-  -moz-box-align: center;
-  -moz-box-pack: center;
   border-bottom: 1px solid #ccc;
+  align-content: center;
+  align-items: center;
+  max-height: 100px;
+  overflow-y: hidden;
+  transition: all 0.4s;
 }
 
 #results :-moz-any(div.selection) {
   cursor: pointer;
 }
 
 #results .row:last-child {
   border-bottom: none;
@@ -259,20 +278,16 @@ input[type="submit"].search {
 #results .row.th:hover {
   background-color: #f2f2f2;
 }
 
 #results .row.th {
   background-color: #f2f2f2;
 }
 
-#results .row.more {
-  text-align: right;
-}
-
 #results:not(.showAll) .noUserLang {
   display: none;
 }
 .footer {
   margin-bottom: 10px;
 }
 
 .row .address.th {
@@ -287,37 +302,41 @@ input[type="submit"].search {
 
 .row .address {
   font-weight: normal;
 }
 
 .row .pricing {
   text-align: right;
   min-width: 200px;
-  display: none;
 }
 
 .pricing button.create {
   border-radius: 12px;
+  transition: min-height 0.2s;
+}
+
+.pricing button:disabled.create {
+  min-height: 0px;
 }
 
 .pricing span.create {
   display: none;
 }
 
 #existing {
   width: 100%;
   color: graytext;
   font-weight: bold;
   text-align: center;
   padding: 15px;
 }
 
 #existing .header {
-    text-align: left;
+  text-align: left;
 }
 
 .contentPadded {
   padding: 15px;
 }
 
 /* === NEW ACCOUNT FORM === */
 
@@ -365,29 +384,18 @@ input[type="submit"].search {
 #search_engine_wrap {
   display: none;
 }
 
 /* START hbox/vbox normalization from http://alex.dojotoolkit.org/2009/08/css-3-progress/ */
 /* hbox and vbox classes for Mozilla only */
 
 .hbox {
-  display: -moz-box;
-  -moz-box-orient: horizontal;
-  -moz-box-align: stretch;
-
-  /*display: box;*/
-  /*box-orient: horizontal;*/
-  /*box-align: stretch;*/
-}
-
-.hbox > * {
-  -moz-box-flex: 0;
-  /*box-flex: 0;*/
-  display: block;
+  display: flex;
+  flex-direction: row;
 }
 
 .vbox {
   display: -moz-box;
   -moz-box-orient: vertical;
   -moz-box-align: stretch;
 
   /*display: box;*/
@@ -410,17 +418,21 @@ input[type="submit"].search {
   /*box-direction: reverse;*/
 }
 
 .boxFlex0 {
   -moz-box-flex: 0;
   /*box-flex: 0;*/
 }
 
-.boxFlex1, .boxFlex {
+.boxFlex {
+  flex: 1 1 auto;
+}
+
+.boxFlex1 /*, .boxFlex*/ {
   -moz-box-flex: 1;
   /*box-flex: 1;*/
 }
 
 .boxFlex2 {
   -moz-box-flex: 2;
   /*box-flex: 2;*/
 }
@@ -522,8 +534,25 @@ input[type="submit"].search {
     opacity: 0.0;
     display: none;
   }
   100% {
     opacity: 0.0;
     display: none;
   }
 }
+
+.hideWithFade {
+  opacity: 0;
+  transition: opacity 0.3s;
+}
+
+.showWithFade {
+  opacity: 1;
+  transition: opacity 0.3s;
+}
+
+#results .slideUp {
+  max-height: 0;
+  border: none;
+  padding-top: 0;
+  padding-bottom: 0;
+}