Rewrite account creation wizard UI code (emailWizard.js). Bug 549045, r=bwinton, ui-r=clarkbw, a=Standard8
authorBen Bucksch <ben.bucksch@beonex.com>
Tue, 08 Mar 2011 14:27:43 +0100
changeset 7277 75875cb17fb0a20caabb627fe743b211f8d43eaa
parent 7276 2bb4e4f86e8d83f947818258b4c0f46ede2f3847
child 7278 382b3365a2e9c19f9ac3227d04e0f43cc6c96ddd
push idunknown
push userunknown
push dateunknown
reviewersbwinton, clarkbw, Standard8
bugs549045
Rewrite account creation wizard UI code (emailWizard.js). Bug 549045, r=bwinton, ui-r=clarkbw, a=Standard8 The previous UI code had a bit the taste of spaghetti bolognese. Delicious, but hard to follow. Concrete problem was mostly when you did not want autodetection, but manual config: 1. The Stop button didn't work, it continued for a bit after you pressed Stop 2. When you pressed "Manual Config", we immediately created the account and went into the account manager, instead of going into the inline "Edit" mode. 3. Worse, the account was created with whatever server happened to be tested last, so when pressing "Manual config", you got a litterally random config. You couldn't change IMAP<->POP3 later, either. 4. The inline Edit didn't take the values you gave it as given, but second-guessed them upon testing, and felt free to change them, even if the user explicitly said otherwise. Understandably, that infuriated some users. This change should fix all that. The stop button now actually works, and the "Manual config" button goes into "Edit" mode. This also uses a different philosophy, in a way: After detection, we have a "result" screen, where the user is only *presented* the values that we found, but he cannot *change* them (apart from IMAP vs. POP3). The result screen is only to confirm that the servers are good. It should be very quick and easy to read. If the user wants to change the values, we make that easy with the "manual config" button (formerly "Edit") and going into a different where we present all the values as input fields. There, we take what the user said as a given command. But the default is "Auto", where we can guess, so you have a combination of both user being in command and us supporting the user. Only if the user needs to change some advanced values like IMAP root folder, and wants to do so immediately, we have the "Advanced config" button, which does the same as the old "manual config". This was a lot of work to code, and then even more with a one-year foray to get this reviewed, so I really hope this helps. Permission to commit into CLOSED TREE by Standard8/9 on IRC.
mail/locales/en-US/chrome/messenger/accountCreation.dtd
mail/locales/en-US/chrome/messenger/accountCreation.properties
mail/test/mozmill/account/test-mail-account-setup-wizard.js
mail/test/mozmill/account/test-retest-config.js
mail/test/mozmill/shared-modules/test-account-manager-helpers.js
mail/themes/gnomestripe/mail/accountCreation.css
mail/themes/pinstripe/mail/accountCreation.css
mail/themes/qute/mail/accountCreation.css
mailnews/base/prefs/content/accountUtils.js
mailnews/base/prefs/content/accountcreation/accountConfig.js
mailnews/base/prefs/content/accountcreation/createInBackend.js
mailnews/base/prefs/content/accountcreation/emailWizard.js
mailnews/base/prefs/content/accountcreation/emailWizard.xul
mailnews/base/prefs/content/accountcreation/fetchConfig.js
mailnews/base/prefs/content/accountcreation/fetchhttp.js
mailnews/base/prefs/content/accountcreation/guessConfig.js
mailnews/base/prefs/content/accountcreation/readFromXML.js
mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js
mailnews/base/prefs/content/accountcreation/util.js
mailnews/base/prefs/content/accountcreation/verifyConfig.js
--- a/mail/locales/en-US/chrome/messenger/accountCreation.dtd
+++ b/mail/locales/en-US/chrome/messenger/accountCreation.dtd
@@ -5,52 +5,56 @@
 <!ENTITY name.text                       "Your name, as shown to others">
 <!ENTITY email.label                     "Email address:">
 <!ENTITY email.accesskey                 "l">
 <!ENTITY email.placeholder               "email@example.com">
 <!ENTITY password.label                  "Password:">
 <!ENTITY password.accesskey              "P">
 <!ENTITY password.placeholder            "Password">
 <!ENTITY password.text                   "Optional, will only be used to validate the username">
+<!ENTITY rememberPassword.label          "Remember password">
+<!ENTITY rememberPassword.accesskey      "m">
 
-<!ENTITY accountInformation.label        "Account Information">
-<!ENTITY username2.label                 "Username:">
+<!ENTITY imapLong.label                  "IMAP (remote folders)">
+<!ENTITY pop3Long.label                  "POP3 (keep mail on your computer)">
+
 <!ENTITY incoming.label                  "Incoming:">
 <!ENTITY outgoing.label                  "Outgoing:">
+<!ENTITY username.label                  "Username:">
+<!ENTITY hostname.label                  "Server hostname">
+<!ENTITY port.label                      "Port">
+<!ENTITY ssl.label                       "SSL">
+<!ENTITY auth.label                      "Authentication">
+<!ENTITY imap.label                      "IMAP">
+<!ENTITY pop3.label                       "POP3">
+<!ENTITY smtp.label                      "SMTP">
+<!ENTITY autodetect.label                "Autodetect">
+<!-- LOCALIZATION NOTE(noEncryption.label): Neither SSL/TLS nor STARTTLS.
+     Transmission of emails in cleartext over the Internet. -->
 <!ENTITY noEncryption.label              "None">
 <!ENTITY starttls.label                  "STARTTLS">
 <!ENTITY sslTls.label                    "SSL/TLS">
 
-<!ENTITY imap.label                      "IMAP">
-<!ENTITY pop.label                       "POP">
-<!ENTITY smtp.label                      "SMTP">
-<!ENTITY imap.description                "IMAP - Access folders and messages from multiple computers">
-<!ENTITY pop.description                 "POP - Download all messages onto this computer, folders are local only">
-<!ENTITY recommended-appendix.label      "(recommended)">
-
-<!ENTITY manualSetup.label               "Manual Setup…">
-<!ENTITY manualSetup.accesskey           "S">
+<!ENTITY advancedSetup.label             "Advanced config">
+<!ENTITY advancedSetup.accesskey         "A">
 <!ENTITY cancel.label                    "Cancel">
 <!ENTITY cancel.accesskey                "a">
 <!ENTITY continue.label                  "Continue">
 <!ENTITY continue.accesskey              "C">
-<!ENTITY startOver.label                 "Start over">
-<!ENTITY startOver.accesskey             "o">
 <!ENTITY stop.label                      "Stop">
 <!ENTITY stop.accesskey                  "S">
-<!-- LOCALIZATION NOTE (retest.label): This is the text that is
-     displayed on the button which will re-guess the account configuration,
-     taking into account the settings the user has changed. -->
-<!ENTITY retest.label                    "Re-test Configuration">
-<!ENTITY retest.accesskey                "R">
-<!ENTITY edit.label                      "Edit">
-<!ENTITY edit.accesskey                  "E">
+<!-- LOCALIZATION NOTE (half-manual-test.label): This is the text that is
+     displayed on the button in manual edit mode which will re-guess
+     the account configuration, taking into account the settings that
+     the user has manually changed. -->
+<!ENTITY half-manual-test.label          "Re-test">
+<!ENTITY half-manual-test.accesskey      "t">
+<!ENTITY manual-edit.label               "Manual config">
+<!ENTITY manual-edit.accesskey           "M">
 
-<!ENTITY rememberPassword.label          "Remember password">
-<!ENTITY rememberPassword.accesskey      "m">
 
 <!ENTITY warning.label                   "Warning!">
 <!ENTITY incomingSettings.label          "Incoming settings:">
 <!ENTITY outgoingSettings.label          "Outgoing settings:">
 <!ENTITY technicaldetails.label          "Technical Details">
 <!-- LOCALIZATION NOTE (confirmWarning.label): If there is a security
      warning on the outgoing server, then the user will need to check a
      checkbox beside this text before continuing. -->
--- a/mail/locales/en-US/chrome/messenger/accountCreation.properties
+++ b/mail/locales/en-US/chrome/messenger/accountCreation.properties
@@ -4,32 +4,38 @@
 cleartext_warning=%1$S does not use encryption.
 # LOCALIZATION NOTE(selfsigned_warning): %1$S will be the hostname of the server the user was trying to connect to.
 selfsigned_warning=%1$S does not use a trusted certificate.
 selfsigned_details=Normally, a secure mail server will present a trusted certificate to prove that it is really the server it claims to be. The connection to the mail server will be encrypted but cannot be validated as being the correct server.
 cleartext_details=Insecure mail servers do not use encrypted connections to protect your passwords and private information. By connecting to this server you could expose your password and private information.
 
 # LOCALIZATION NOTE(default_server_tag): Used to indicate the default smtp server in the server dropdown list.
 default_server_tag= (default)
+# LOCALIZATION NOTE(port_auto): It must be short (4-5 characters max.).
+# Content of server port field (usually a number), used when the user didn't
+# enter anything yet and we'll automatically detect it later.
+port_auto=Auto
 
 # config titles
 # LOCALIZATION NOTE(looking_up_settings_disk): Referring to Thunderbird installation folder on user's harddisk. %1$S will be the brandShortName.
 looking_up_settings_disk=Looking up configuration: %1$S installation
 looking_up_settings_isp=Looking up configuration: Email provider
 # LOCALIZATION NOTE(looking_up_settings_db): Do not translate or replace Mozilla. It stands for the public project mozilla.org, not Mozilla Messaging. The database is a generic, public domain facility usable by any client.
 looking_up_settings_db=Looking up configuration: Mozilla ISP database
 # LOCALIZATION NOTE(looking_up_settings_guess): We are checking common server names like pop., pop3., smtp., mail., without knowing whether they exist or really serve this email account. If a server responds, we try to talk to it via POP/IMAP/SMTP protocols and query its capabilities. If that succeeds, we assume we found a configuration. Of course, it may still be wrong, but it often works.
 looking_up_settings_guess=Looking up configuration: Trying common server names
+looking_up_settings_halfmanual=Looking up configuration: Probing server
 # LOCALIZATION NOTE(found_settings_disk): Referring to Thunderbird installation folder on user's harddisk. %1$S will be the brandShortName.
-found_settings_disk=The following settings were found on: %1$S installation
-found_settings_isp=The following settings were found from: Email provider
+found_settings_disk=Configuration found on %1$S installation
+found_settings_isp=Configuration found at email provider
 # LOCALIZATION NOTE(found_settings_db): Do not translate or replace Mozilla. It stands for the public project mozilla.org, not Mozilla Messaging. The database is a generic, public domain facility usable by any client.
-found_settings_db=The following settings were found from: Mozilla ISP database
+found_settings_db=Configuration found in Mozilla ISP database
 # LOCALIZATION NOTE(found_settings_guess): We tried common mail server names and we found a mail server and talked to it and it responded properly, so we think we found a suitable configuration, but we are only about 80% certain that it is the correct setting for this email address. There's a chance that email address may not actually be served by this server and it won't work, or that there is a better server.
-found_settings_guess=The following settings were found by trying common server names
+found_settings_guess=Configuration found by trying common server names
+found_settings_halfmanual=The following settings were found by probing the given server
 # LOCALIZATION NOTE(failed_to_find_settings): %1$S will be the brandShortName.
 failed_to_find_settings=%1$S failed to find the settings for your email account.
 manually_edit_config=Editing Config
 
 # config subtitles
 check_preconfig=checking for pre-configuration…
 found_preconfig=found pre-configuration
 checking_config=checking configuration…
@@ -50,15 +56,42 @@ password_ok=Password ok!
 user_pass_invalid=Username or password invalid
 check_server_details=Checking server details
 check_in_server_details=Checking incoming server details
 check_out_server_details=Checking outgoing server details
 
 error_creating_account=Error Creating Account
 incoming_server_exists=Incoming server already exists.
 
-# LOCALIZATION NOTE(no_encryption): Neither SSL/TLS nor STARTTLS. Transmission of emails in cleartext over the Internet.
-no_encryption=No encryption
-ssl_tls=SSL/TLS
-starttls=STARTTLS
-
 please_enter_name=Please enter your name.
 double_check_email=Double check this email address!
+
+#config result display
+# LOCALIZATION NOTE(resultUnknown): Displayed instead of resultIncoming,
+# resultOutgoing or resultUsername when we don't have a proper value.
+resultUnknown=Unknown
+# LOCALIZATION NOTE(resultIncoming):
+# %1$S will be replaced with either resultIMAP, resultPOP3 or resultSMTP.
+# %2$S will be replaced with the server hostname
+#   with possibly a port appended as ":"+port.
+#   The domain part may be made bold.
+# %3$S will be replaced with either resultNoEncryption or resultSSL or
+#    resultSTARTTLS.
+# %4$S will be replaced with either resultSSLCertWeak or resultSSLCertOK
+#    (which should normally be empty)
+# You may adjust the strings to be a real sentence.
+resultIncoming=%1$S, %2$S, %3$S%4$S
+# LOCALIZATION NOTE(resultOutgoing): see resultIncoming
+resultOutgoing=%1$S, %2$S, %3$S%4$S
+resultOutgoingExisting=Use existing outgoing SMTP server
+resultIMAP=IMAP
+resultPOP3=POP3
+resultSMTP=SMTP
+# LOCALIZATION NOTE(resultNoEncryption): Neither SSL/TLS nor STARTTLS. Transmission of emails in cleartext over the Internet.
+resultNoEncryption=No Encryption
+resultSSL=SSL
+resultSTARTTLS=STARTTLS
+# LOCALIZATION NOTE(resultSSLCertWeak): \u0020 is just a space
+resultSSLCertWeak=\u0020(Warning: Could not verify server)
+resultSSLCertOK=
+resultSTARTTLS=STARTTLS
+resultUsernameBoth=%1$S
+resultUsernameDifferent=Incoming: %1$S, Outgoing: %2$S
--- a/mail/test/mozmill/account/test-mail-account-setup-wizard.js
+++ b/mail/test/mozmill/account/test-mail-account-setup-wizard.js
@@ -118,19 +118,19 @@ function test_mail_account_setup() {
   input_value(awc, user.password);
 
   // Load the autoconfig file from http://localhost:433**/autoconfig/example.com
   awc.e("next_button").click();
 
   let config = null;
 
   // XXX: This should probably use a notification, once we fix bug 561143.
-  awc.waitForEval("subject._currentConfigFilledIn != null", 8000, 600,
+  awc.waitForEval("subject._currentConfig != null", 8000, 600,
                   awc.window.gEmailConfigWizard);
-  config = awc.window.gEmailConfigWizard._currentConfigFilledIn;
+  config = awc.window.gEmailConfigWizard.getConcreteConfig();
 
   // Open the advanced settings (Account Manager) to create the account
   // immediately.  We use an invalid email/password so the setup will fail
   // anyway.
   open_advanced_settings_from_account_wizard(subtest_verify_account, awc);
 
   // Clean up
   pref.clearUserPref(pref_name);
@@ -143,17 +143,17 @@ function subtest_verify_account(amc) {
   incoming = account.incomingServer;
   outgoing = amc.window.smtpService.getServerByKey(identity.smtpServerKey);
 
   let config = {
     "incoming server username": {
       actual: incoming.username, expected: user.email.split("@")[0]
     },
     "outgoing server username": {
-      actual: outgoing.username, expected: user.email
+      actual: outgoing.username, expected: user.email.split("@")[0]
     },
     "incoming server hostname": {
       // Note: N in the hostName is uppercase
       actual: incoming.hostName, expected: user.incomingHost
     },
     "outgoing server hostname": {
       // And this is lowercase
       actual: outgoing.hostname, expected: user.outgoingHost
@@ -186,17 +186,17 @@ function test_bad_password_uses_old_sett
   // Set the pref to load a local autoconfig file.
   let pref = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   let pref_name = "mailnews.auto_config_url";
   let url = collector.addHttpResource("../account/xml", "autoconfig");
   try {
     pref.setCharPref(pref_name, url);
 
     // Force .com MIME-Type to text/xml
-   collector.httpd.registerContentType("com", "text/xml");
+    collector.httpd.registerContentType("com", "text/xml");
 
     mc.sleep(0);
     awc = open_mail_account_setup_wizard();
 
     // Input user's account information
     awc.e("realname").focus();
     input_value(awc, user.name);
     awc.keypress(null, "VK_TAB", {});
@@ -204,29 +204,30 @@ function test_bad_password_uses_old_sett
     awc.keypress(null, "VK_TAB", {});
     input_value(awc, user.password);
 
     // Load the autoconfig file from http://localhost:433**/autoconfig/example.com
     awc.e("next_button").click();
 
     let config = null;
 
-    awc.waitForEval("subject.disabled == false", 8000, 600,
-                    awc.e("create_button"));
+    awc.waitForEval("subject.disabled == false && subject.hidden == false",
+                    8000, 600, awc.e("create_button"));
     awc.e("create_button").click();
 
     awc.waitForEval("subject.disabled == false", 8000, 600,
                     awc.e("create_button"));
     awc.e("create_button").click();
+    awc.e("manual-edit_button").click();
 
     // Make sure all the values are the same as in the user object.
     awc.sleep(1000);
-    assert_equals(awc.e("outgoing_server").value, user.outgoingHost,
+    assert_equals(awc.e("outgoing_hostname").value, user.outgoingHost,
                   "Outgoing server changed!");
-    assert_equals(awc.e("incoming_server").value, user.incomingHost,
+    assert_equals(awc.e("incoming_hostname").value, user.incomingHost,
                   "incoming server changed!");
   }
   finally {
     // Clean up
     pref.clearUserPref(pref_name);
     awc.e("cancel_button").click();
   }
 }
@@ -249,50 +250,36 @@ function remember_password_test(aPrefVal
 
   pref.setBoolPref("signon.rememberSignons", aPrefValue);
 
   // without this, it breaks the test, don't know why
   mc.sleep(0);
   awc = open_mail_account_setup_wizard();
 
   try {
-  let password = new elementslib.ID(awc.window.document, "password");
-  let rememberPassword =
-      new elementslib.ID(awc.window.document, "remember_password");
-
-  // password field is empty and the checkbox is disabled initially
-  // -> uncheck checkbox
+    let password = new elementslib.ID(awc.window.document, "password");
+    let rememberPassword =
+        new elementslib.ID(awc.window.document, "remember_password");
 
-  awc.assertProperty(rememberPassword, "disabled", true);
-  awc.assertNotChecked(rememberPassword);
-
-  // type something in the password field
-  awc.e("password").focus();
-  input_value(awc, "testing");
+    // type something in the password field
+    awc.e("password").focus();
+    input_value(awc, "testing");
 
-  awc.assertProperty(rememberPassword, "disabled", !aPrefValue);
-  if (aPrefValue) {
-    // password field is not empty any more
-    // -> enable and check checkbox
-    awc.assertChecked(rememberPassword);
-  }
-  else {
-    // password field is not empty any more, but aPrefValue is false
-    // -> disable and uncheck checkbox
-    awc.assertNotChecked(rememberPassword);
-  }
+    awc.assertProperty(rememberPassword, "disabled", !aPrefValue);
+    if (aPrefValue) {
+      awc.assertChecked(rememberPassword);
+    }
+    else {
+      awc.assertNotChecked(rememberPassword);
+    }
 
-  // empty the password field
-  awc.keypress(password, 'a', {accelKey: true});
-  awc.keypress(password, 'VK_DELETE', {});
+    // empty the password field
+    awc.keypress(password, 'a', {accelKey: true});
+    awc.keypress(password, 'VK_DELETE', {});
 
-  // password field is empty -> disable and uncheck checkbox
-  awc.assertProperty(rememberPassword, "disabled", true);
-  awc.assertNotChecked(rememberPassword);
-
-  // restore the saved signon.rememberSignons value
-  pref.setBoolPref("signon.rememberSignons", rememberSignons_pref_save);
+    // restore the saved signon.rememberSignons value
+    pref.setBoolPref("signon.rememberSignons", rememberSignons_pref_save);
   }
   finally {
     // close the wizard
     awc.e("cancel_button").click();
   }
 }
--- a/mail/test/mozmill/account/test-retest-config.js
+++ b/mail/test/mozmill/account/test-retest-config.js
@@ -33,35 +33,37 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 var MODULE_NAME = "test-retest-config";
 
 var RELATIVE_ROOT = "../shared-modules";
-var MODULE_REQUIRES = ["window-helpers"];
+var MODULE_REQUIRES = ["window-helpers", "folder-display-helpers"];
 
 var mozmill = {};
 Components.utils.import("resource://mozmill/modules/mozmill.js", mozmill);
 var controller = {};
 Components.utils.import("resource://mozmill/modules/controller.js", controller);
 var elib = {};
 Components.utils.import("resource://mozmill/modules/elementslib.js", elib);
 
-var wh, mc, awc, account, incoming, outgoing;
+var wh, awc, account, incoming, outgoing;
 
 var user = {
   name: "test",
-  email: "test@yahoo.com"
+  email: "test@yahoo.com",
+  altEmail: "test2@yahoo.com",
 };
 
 function setupModule(module) {
+  let fdh = collector.getModule("folder-display-helpers");
+  fdh.installInto(module);
   wh = collector.getModule("window-helpers");
-  mc = wh.wait_for_existing_window("mail:3pane");
   wh.installInto(module);
 }
 
 // Select File > New > Mail Account to open the Mail Account Setup Wizard
 function open_mail_account_setup_wizard() {
   wh.plan_for_new_window("mail:autoconfig");
   mc.click(new elib.Elem(mc.menus.menu_File.menu_New.newMailAccountMenuItem));
   return wh.wait_for_new_window("mail:autoconfig");
@@ -86,42 +88,45 @@ function test_re_test_config() {
   input_value(user.name);
   awc.keypress(null, "VK_TAB", {});
   input_value(user.email);
 
   // Click "continue" button
   awc.e("next_button").click();
 
   // Wait for 'edit' button to be enabled
-  awc.waitForEval("subject.hidden == false", 100000, 600,
-                  awc.e("edit_button"));
+  awc.waitForEval("subject.disabled == false && subject.hidden == false",
+                  8000, 600, awc.e("create_button"));
 
-  awc.e("edit_button").click();
-
-  awc.waitForEval("subject.hidden == false", 20000, 600,
-                  awc.e("go_button"));
+  awc.e("manual-edit_button").click();
+  mc.sleep(0);
 
   // Click "re-test" button
-  awc.e("go_button").click();
+  awc.e("half-manual-test_button").click();
+
+  awc.waitForEval("subject.disabled == false", 20000, 600,
+                  awc.e("half-manual-test_button"));
 
-  awc.waitForEval("subject.hidden == false", 20000, 600,
-                  awc.e("stop_button"));
+  // There used to be a "start over" button (line commented out below). Now just
+  // changing the value of the email field does the trick. Line left out for
+  // posterity.
+  //   awc.e("back_button").click();
+  awc.e("realname").focus();
+  awc.keypress(null, "VK_TAB", {});
+  input_value(user.altEmail);
+  awc.keypress(null, "VK_TAB", {});
 
-  // Click 'start over' button
-  awc.e("back_button").click();
-
+  // Wait for the "continue" button to be back, which means we're back to the
+  // original state.
   awc.waitForEval("subject.hidden == false", 20000, 600,
                   awc.e("next_button"));
 
   awc.e("next_button").click();
 
-  var incoming_server = awc.e("incoming_server");
-
-  var wizard_window = awc.e("autoconfigWizard");
-
-  var right = incoming_server.boxObject.y+incoming_server.boxObject.height;
-  var bottom = incoming_server.boxObject.x+incoming_server.boxObject.width;
-
-  if (right > wizard_window.boxObject.height ||
-      bottom > wizard_window.boxObject.width)
-    throw new Error("The start over button didn't collapse the window.");
+  // Previously, we'd switched to the manual editing state. Now we've started
+  // over, we should make sure the information is presented back in its original
+  // "automatic" mode.
+  assert_true(!awc.e("manual-edit_button").hidden,
+    "We're not back to the original state!");  
+  assert_true(awc.e("advanced-setup_button").hidden,
+    "We're not back to the original state!");  
 }
 
--- a/mail/test/mozmill/shared-modules/test-account-manager-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-account-manager-helpers.js
@@ -72,23 +72,27 @@ function installInto(module) {
  *
  * @param callback Callback for the modal dialog that is opened.
  */
 function open_advanced_settings(aCallback, aController) {
   if (aController === undefined)
     aController = mc;
 
   wh.plan_for_modal_dialog("mailnews:accountmanager", aCallback);
-  aController.click(new elib.Elem(mc.menus.tasksMenu.menu_accountmgr));
+  if (mc.isLinux)
+    aController.click(new elib.Elem(mc.menus.menu_Edit.menu_accountmgr));
+  else
+    aController.click(new elib.Elem(mc.menus.tasksMenu.menu_accountmgr));
   return wh.wait_for_modal_dialog("mailnews:accountmanager");
 }
 
 /**
  * Opens the Account Manager from the mail account setup wizard.
  *
  * @param callback Callback for the modal dialog that is opened.
  */
 function open_advanced_settings_from_account_wizard(aCallback, aController) {
   wh.plan_for_modal_dialog("mailnews:accountmanager", aCallback);
-  aController.e("advanced_settings").click();
+  aController.e("manual-edit_button").click();
+  aController.e("advanced-setup_button").click();
   return wh.wait_for_modal_dialog("mailnews:accountmanager");
 }
 
--- a/mail/themes/gnomestripe/mail/accountCreation.css
+++ b/mail/themes/gnomestripe/mail/accountCreation.css
@@ -19,17 +19,17 @@
 
 .errordescription {
   background-color: InfoBackground;
   border-radius: 4px;
   margin-top: 3px;
   -moz-padding-start: 3px;
 }
 
-.initialDesc {
+.initialDesc, .columnHeader {
   margin-top: 2px;
   color: GrayText;
 }
 
 menulist[disabled="true"] {
   -moz-appearance: menulist;
   color: -moz-DialogText;
 }
@@ -40,23 +40,18 @@ menulist[disabled="true"] > .menulist-dr
   -moz-appearance: none;
   color: -moz-DialogText;
 }
 
 #outgoing_protocol {
   padding: 6px;
 }
 
-#config_status_title {
-  font-weight: bold;
-}
-
 window {
   -moz-appearance: none;
-  background-color: #fff;
 }
 
 vbox.icon {
   width: 22px;
   height: 22px;
   background-repeat: no-repeat;
 }
 
@@ -225,19 +220,68 @@ textbox[disabled="true"],
 input[disabled="true"],
 menulist[disabled="true"] {
   -moz-appearance: none;
   border: 0;
   background-color: dialog;
   color: -moz-DialogText;
 }
 
+
+/* status area */
+
+#status_area {
+  -moz-box-pack: center;
+}
+
+#status_area[status=loading] #status_msg {
+  font-weight: bold;
+}
+
+#status_area[status=error] #status_msg {
+  font-weight: bold;
+}
+
+#status_area[status=error] #status_img_before {
+  background: url("moz-icon://stock/gtk-dialog-warning?size=menu") no-repeat;
+  width: 16px;
+  height: 16px;
+}
+
+#status_area[status=loading] #status_img_after {
+  background: url("chrome://global/skin/icons/loading_16.png") no-repeat;
+  width: 16px;
+  height: 16px;
+}
+
+
 /* Missing:
  * .menulist-dropmarker[disabled="true"]
  */
 
 textbox.port[disabled="true"] {
   padding-top: 4px;
 }
 
 #incoming_protocol[disabled="true"] {
   padding-left: 5px;
 }
+
+#initialSettings, #status_area {
+  margin-bottom: 1em;
+}
+#result_area, #result_imappop {
+  margin-bottom: 1.5em;
+}
+#manual-edit_area {
+  margin-bottom: 2em;
+}
+
+#status_msg {
+  min-height: 2em;
+}
+
+#incoming_hostname {
+  width: 15em;
+}
+#incoming_username {
+  width: 10em;
+}
--- a/mail/themes/pinstripe/mail/accountCreation.css
+++ b/mail/themes/pinstripe/mail/accountCreation.css
@@ -12,17 +12,17 @@
   cursor: pointer;
 }
 
 .errordescription {
   -moz-padding-start: 3px;
   margin-top: 3px;
 }
 
-.initialDesc {
+.initialDesc, .columnHeader {
   margin-top: 2px;
   color: GrayText;
 }
 
 /* Missing:
  * menulist[disabled="true"]
  * menulist[disabled="true"] > .menulist-editable-box
  * menulist[disabled="true"] > .menulist-editable-box > .menulist-editable-input
@@ -208,19 +208,65 @@ textbox[disabled="true"],
 input[disabled="true"],
 menulist[disabled="true"] {
   -moz-appearance: none;
   border: 0;
   background-color: #ececec;
   color: #000000 !important;
 }
 
+
+/* status area */
+
+#status_area[status=loading] #status_msg {
+  font-weight: bold;
+}
+
+#status_area[status=error] #status_msg {
+  font-weight: bold;
+}
+
+#status_area[status=error] #status_img_before {
+  background: url("chrome://global/skin/icons/warning-16.png") no-repeat;
+  width: 16px;
+  height: 16px;
+  margin-right: 1em;
+}
+
+#status_area[status=loading] #status_img_after {
+  background: url("chrome://global/skin/icons/loading_16.png") no-repeat;
+  width: 16px;
+  height: 16px;
+}
+
+
 /* Missing:
  * .menulist-dropmarker[disabled="true"]
  */
 
 textbox.port[disabled="true"] {
   padding-top: 4px;
 }
 
 #incoming_protocol[disabled="true"] {
   padding-left: 5px;
 }
+
+#initialSettings, #status_area {
+  margin-bottom: 1em;
+}
+#result_area, #result_imappop {
+  margin-bottom: 1.5em;
+}
+#manual-edit_area {
+  margin-bottom: 2em;
+}
+
+#status_msg {
+  min-height: 2em;
+}
+
+#incoming_hostname {
+  width: 15em;
+}
+#incoming_username {
+  width: 10em;
+}
--- a/mail/themes/qute/mail/accountCreation.css
+++ b/mail/themes/qute/mail/accountCreation.css
@@ -17,17 +17,17 @@
   cursor: pointer;
 }
 
 .errordescription {
   -moz-padding-start: 3px;
   margin-top: 3px;
 }
 
-.initialDesc {
+.initialDesc, .columnHeader {
   margin-top: 2px;
   color: GrayText;
 }
 
 menulist[disabled="true"] {
   -moz-appearance: none;
   border: none;
   background-color: #fff;
@@ -222,19 +222,64 @@ textbox[disabled="true"],
 input[disabled="true"],
 menulist[disabled="true"] {
   -moz-appearance: none;
   border: 0;
   background-color: -moz-dialog;
   color: -moz-DialogText;
 }
 
+
+/* status area */
+
+#status_area[status=loading] #status_msg {
+  font-weight: bold;
+}
+
+#status_area[status=error] #status_msg {
+  font-weight: bold;
+}
+
+#status_area[status=error] #status_img_before {
+  background: url("chrome://global/skin/icons/warning-16.png") no-repeat;
+  width: 16px;
+  height: 16px;
+}
+
+#status_area[status=loading] #status_img_after {
+  background: url("chrome://global/skin/icons/loading_16.png") no-repeat;
+  width: 16px;
+  height: 16px;
+}
+
+
 .menulist-dropmarker[disabled="true"] {
   visibility: hidden;
 }
 
 textbox.port[disabled="true"] {
   padding-top: 6px;
 }
 
 #incoming_protocol[disabled="true"] {
   padding-left: 6px;
 }
+
+#initialSettings, #status_area {
+  margin-bottom: 1em;
+}
+#result_area, #result_imappop {
+  margin-bottom: 1.5em;
+}
+#manual-edit_area {
+  margin-bottom: 2em;
+}
+
+#status_msg {
+  min-height: 2em;
+}
+
+#incoming_hostname {
+  width: 15em;
+}
+#incoming_username {
+  width: 10em;
+}
--- a/mailnews/base/prefs/content/accountUtils.js
+++ b/mailnews/base/prefs/content/accountUtils.js
@@ -345,17 +345,17 @@ function msgNewMailAccount(msgWindow, ok
   let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                      .getService()
                      .QueryInterface(Components.interfaces.nsIWindowMediator);
   let existingWindow = wm.getMostRecentWindow("mail:autoconfig");
   if (existingWindow)
     existingWindow.focus();
   else
     window.openDialog("chrome://messenger/content/accountcreation/emailWizard.xul",
-                      "AccountSetup", "chrome,titlebar,centerscreen",
+                      "AccountSetup", "chrome,titlebar,modal,centerscreen",
                       {msgWindow:msgWindow,
                        okCallback:okCallback,
                        extraData:extraData});
 
   // If we started with no servers at all and "smtp servers" list selected,
   // refresh display somehow. Bug 58506.
   // TODO Better fix: select newly created account (in all cases)
   if (typeof(getCurrentAccount) == "function" && // in AccountManager, not menu
--- a/mailnews/base/prefs/content/accountcreation/accountConfig.js
+++ b/mailnews/base/prefs/content/accountcreation/accountConfig.js
@@ -42,17 +42,18 @@
  *
  * Several AccountConfig objects may co-exist, e.g. for autoconfig.
  * One AccountConfig object is used to prefill and read the widgets
  * in the Wizard UI.
  * When we autoconfigure, we autoconfig writes the values into a
  * new object and returns that, and the caller can copy these
  * values into the object used by the UI.
  *
- * See also <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
+ * See also
+ * <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
  * for values stored.
  */
 
 function AccountConfig()
 {
   this.incoming = this.createNewIncoming();
   this.incomingAlternatives = [];
   this.outgoing = this.createNewOutgoing();
@@ -64,46 +65,55 @@ function AccountConfig()
     // email address of user, as shown in From of outgoing mails
     emailAddress : "%EMAILADDRESS%",
   };
   this.inputFields = [];
   this.domains = [];
 };
 AccountConfig.prototype =
 {
-  incoming : null, // see |createNewIncoming()|
-  outgoing : null, // see |createNewOutgoing()|
+  // @see createNewIncoming()
+  incoming : null,
+  // @see createNewOutgoing()
+  outgoing : null,
   /**
-   * {Array of |incoming|/|createNewIncoming|}
    * Other servers which can be used instead of |incoming|,
    * in order of decreasing preference.
    * (|incoming| itself should not be included here.)
+   * { Array of incoming/createNewIncoming() }
    */
   incomingAlternatives : null,
   outgoingAlternatives : null,
-  id : null, // just an internal string to refer to this. Do not show to user.
-  source : 0, // who created the config. kSource*
+  // just an internal string to refer to this. Do not show to user.
+  id : null,
+  // who created the config.
+  // { one of kSource* }
+  source : 0,
   displayName : null,
-  // Array of Objects with properties varname (value without %), displayName, exampleValue
+  // { Array of { varname (value without %), displayName, exampleValue } }
   inputFields : null,
-  // Array of Strings - email address domains for which this config is applicable
+  // email address domains for which this config is applicable
+  // { Array of Strings }
   domains : null,
 
   /**
    * Factory function for incoming and incomingAlternatives
    */
   createNewIncoming : function()
   {
     return {
-      type : null, // string-enum: "pop3", "imap", "nntp"
+      // { String-enum: "pop3", "imap", "nntp" }
+      type : null,
       hostname : null,
-      port : null, // Integer
-      username : null, // String. May be a placeholder (starts and ends with %).
+      // { Integer }
+      port : null,
+      // May be a placeholder (starts and ends with %). { String }
+      username : null,
       password : null,
-      // enum: 1 = plain, 2 = SSL/TLS, 3 = STARTTLS always, 0 = not inited
+      // { enum: 1 = plain, 2 = SSL/TLS, 3 = STARTTLS always, 0 = not inited }
       // ('TLS when available' is insecure and not supported here)
       socketType : 0,
       /**
        * true when the cert is invalid (and thus SSL useless), because it's
        * 1) not from an accepted CA (including self-signed certs)
        * 2) for a different hostname or
        * 3) expired.
        * May go back to false when user explicitly accepted the cert.
@@ -111,26 +121,28 @@ AccountConfig.prototype =
       badCert : false,
       /**
        * How to log in to the server: plaintext or encrypted pw, GSSAPI etc.
        * Defined by Ci.nsMsgAuthMethod
        * Same as server pref "authMethod".
        */
       auth : 0,
       /**
-       * {Array of Ci.nsMsgAuthMethod} (same as .auth)
        * Other auth methods that we think the server supports.
        * They are ordered by descreasing preference.
        * (|auth| itself is not included in |authAlternatives|)
+       * {Array of Ci.nsMsgAuthMethod} (same as .auth)
        */
       authAlternatives : null,
-      checkInterval : 10, // Integer, in minutes
+      // in minutes { Integer }
+      checkInterval : 10,
       loginAtStartup : true,
       // POP3 only:
-      useGlobalInbox : false, // boolean. Not yet implemented.
+      // Not yet implemented. { Boolean }
+      useGlobalInbox : false,
       leaveMessagesOnServer : true,
       daysToLeaveMessagesOnServer : 14,
       deleteByAgeFromServer : true,
       // When user hits delete, delete from local store and from server
       deleteOnServerWhenLocalDelete : true,
       downloadOnBiff : true,
     };
   },
@@ -144,20 +156,21 @@ AccountConfig.prototype =
       hostname : null,
       port : null, // see incoming
       username : null, // see incoming. may be null, if auth is 0.
       password : null, // see incoming. may be null, if auth is 0.
       socketType : 0, // see incoming
       badCert : false, // see incoming
       auth : 0, // see incoming
       authAlternatives : null, // see incoming
-      addThisServer : true, // if we already have an SMTP server, add this or not.
+      addThisServer : true, // if we already have an SMTP server, add this
       // if we already have an SMTP server, use it.
       useGlobalPreferredServer : false,
-      // we should reuse an already configured SMTP server. This is nsISmtpServer.key.
+      // we should reuse an already configured SMTP server.
+      // nsISmtpServer.key
       existingServerKey : null,
       // user display value for existingServerKey
       existingServerLabel : null,
     };
   },
 
   /**
    * Returns a deep copy of this object,
@@ -192,17 +205,17 @@ AccountConfig.kSourceUser = 1; // user m
 AccountConfig.kSourceXML = 2; // config from XML from ISP or Mozilla DB
 AccountConfig.kSourceGuess = 3; // guessConfig()
 
 
 /**
  * Some fields on the account config accept placeholders (when coming from XML).
  *
  * These are the predefined ones
- * * %EMAILADDRESS% (full email address of the user, usually entered by the user)
+ * * %EMAILADDRESS% (full email address of the user, usually entered by user)
  * * %EMAILLOCALPART% (email address, part before @)
  * * %EMAILDOMAIN% (email address, part after @)
  * * %REALNAME%
  * as well as those defined in account.inputFields.*.varname, with % added
  * before and after.
  *
  * These must replaced with real values, supplied by the user or app,
  * before the account is created. This is done here. You call this function once
@@ -245,24 +258,24 @@ function replaceVariables(account, realn
 
   account.incoming.password = password;
   account.outgoing.password = password; // set member only if auth required?
   account.incoming.username = _replaceVariable(account.incoming.username,
                                                otherVariables);
   account.outgoing.username = _replaceVariable(account.outgoing.username,
                                                otherVariables);
   account.incoming.hostname =
-    _replaceVariable(account.incoming.hostname, otherVariables);
+      _replaceVariable(account.incoming.hostname, otherVariables);
   if (account.outgoing.hostname) // will be null if user picked existing server.
     account.outgoing.hostname =
-      _replaceVariable(account.outgoing.hostname, otherVariables);
+        _replaceVariable(account.outgoing.hostname, otherVariables);
   account.identity.realname =
-    _replaceVariable(account.identity.realname, otherVariables);
+      _replaceVariable(account.identity.realname, otherVariables);
   account.identity.emailAddress =
-    _replaceVariable(account.identity.emailAddress, otherVariables);
+      _replaceVariable(account.identity.emailAddress, otherVariables);
   account.displayName = _replaceVariable(account.displayName, otherVariables);
 }
 
 function _replaceVariable(variable, values)
 {
   let str = variable;
   if (typeof(str) != "string")
     return str;
--- a/mailnews/base/prefs/content/accountcreation/createInBackend.js
+++ b/mailnews/base/prefs/content/accountcreation/createInBackend.js
@@ -32,27 +32,25 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
- * Takes an AccountConfig JS object and creates that account in the
+ * Takes an |AccountConfig| JS object and creates that account in the
  * Thunderbird backend (which also writes it to prefs).
  *
  * @param config {AccountConfig} The account to create
  *
- * @ret - the account created.
+ * @return - the account created.
  */
 function createAccountInBackend(config)
 {
-  const Cc = Components.classes;
-  const Ci = Components.interfaces;
   var accountManager = Cc["@mozilla.org/messenger/account-manager;1"]
                        .getService(Ci.nsIMsgAccountManager);
   var smtpManager = Cc["@mozilla.org/messengercompose/smtp;1"]
                     .getService(Ci.nsISmtpService);
 
   // incoming server
   var inServer = accountManager.createIncomingServer(
       config.incoming.username,
@@ -196,17 +194,18 @@ function createAccountInBackend(config)
   } catch (ex) {
     ddump("Could not write out prefs: " + ex);
   }
   return account;
 }
 
 function setFolders(identity, server)
 {
-  // TODO: support for local folders for global inbox (or use smart search folder instead)
+  // TODO: support for local folders for global inbox (or use smart search
+  // folder instead)
 
   var baseURI = server.serverURI + "/";
 
   // Names will be localized in UI, not in folder names on server/disk
   // TODO allow to override these names in the XML config file,
   // in case e.g. Google or AOL use different names?
   // Workaround: Let user fix it :)
   var fccName = "Sent";
@@ -238,18 +237,81 @@ function rememberPassword(server, passwo
   login.init(passwordURI, null, passwordURI, server.username, password, "", "");
   try {
     lm.addLogin(login);
   } catch (e if e.message.indexOf("This login already exists") != -1) {
     // TODO modify
   }
 }
 
-// Check if there already is a "Local Folders". If not, create it. This routine
-//  is copied from AccountWizard.js with minor updates.
+/**
+ * Check whether the user's setup already has an incoming server
+ * which matches (hostname, port, username) the primary one
+ * in the config.
+ * (We also check the email address as username.)
+ *
+ * @param config {AccountConfig} filled in (no placeholders)
+ * @return {nsIMsgIncomingServer} If it already exists, the server
+ *     object is returned.
+ *     If it's a new server, |null| is returned.
+ */
+function checkIncomingServerAlreadyExists(config)
+{
+  assert(config instanceof AccountConfig);
+  var accountManager = Cc["@mozilla.org/messenger/account-manager;1"]
+                       .getService(Ci.nsIMsgAccountManager);
+  var incoming = config.incoming;
+  var existing = accountManager.findRealServer(incoming.username,
+        incoming.hostname,
+        sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
+        incoming.port);
+
+  // if username does not have an '@', also check the e-mail
+  // address form of the name.
+  if (!existing && incoming.username.indexOf("@") == -1)
+    existing = accountManager.findRealServer(config.identity.emailAddress,
+          incoming.hostname,
+          sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
+          incoming.port);
+  return existing;
+};
+
+/**
+ * Check whether the user's setup already has an outgoing server
+ * which matches (hostname, port, username) the primary one
+ * in the config.
+ *
+ * @param config {AccountConfig} filled in (no placeholders)
+ * @return {nsISmtpServer} If it already exists, the server
+ *     object is returned.
+ *     If it's a new server, |null| is returned.
+ */
+function checkOutgoingServerAlreadyExists(config)
+{
+  assert(config instanceof AccountConfig);
+  var smtpManager = Cc["@mozilla.org/messengercompose/smtp;1"]
+                    .getService(Ci.nsISmtpService);
+  var smtpServers = smtpManager.smtpServers;
+  while (smtpServers.hasMoreElements())
+  {
+    let existingServer = smtpServers.getNext()
+        .QueryInterface(Ci.nsISmtpServer);
+    // TODO check username with full email address, too, like for incoming
+    if (existingServer.hostname == config.outgoing.hostname &&
+        existingServer.port == config.outgoing.port &&
+        existingServer.username == config.outgoing.username)
+      return existingServer;
+  }
+  return null;
+};
+
+/**
+ * Check if there already is a "Local Folders". If not, create it.
+ * Copied from AccountWizard.js with minor updates.
+ */
 function verifyLocalFoldersAccount(am) 
 {
   let localMailServer;
   try {
     localMailServer = am.localFoldersServer;
   }
   catch (ex) {
     localMailServer = null;
@@ -259,15 +321,15 @@ function verifyLocalFoldersAccount(am)
     if (!localMailServer) 
     {
       // creates a copy of the identity you pass in
       am.createLocalMailAccount();
       try {
         localMailServer = am.localFoldersServer;
       }
       catch (ex) {
-        dump("error!  we should have found the local mail server after we created it.\n");
+        ddump("Error! we should have found the local mail server " +
+              "after we created it.");
       }
     }
   }
-  catch (ex) {dump("Error in verifyLocalFoldersAccount " + ex + "\n");  }
-
+  catch (ex) { ddump("Error in verifyLocalFoldersAccount " + ex); }
 }
--- a/mailnews/base/prefs/content/accountcreation/emailWizard.js
+++ b/mailnews/base/prefs/content/accountcreation/emailWizard.js
@@ -11,18 +11,18 @@
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is mozilla.org code.
  *
  * The Initial Developer of the Original Code is
  * David Ascher <davida@mozilla.com> and
- * Ben Bucksch <mozilla bucksch.org>
- * Portions created by the Initial Developer are Copyright (C) 2008-2009
+ * Ben Bucksch <ben.bucksch beonex.com>
+ * Portions created by the Initial Developer are Copyright (C) 2008-2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
@@ -61,1531 +61,1788 @@
 
 
 // from http://xyfer.blogspot.com/2005/01/javascript-regexp-email-validator.html
 var emailRE = /^[-_a-z0-9\'+*$^&%=~!?{}]+(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*@(?:[-a-z0-9.]+\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d+)?$/i;
 var domainRE = /^((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$|(\[?(\d{1,3}\.){3}\d{1,3}\]?)$/i
 const kHighestPort = 65535;
 
 Cu.import("resource:///modules/gloda/log4moz.js");
+let gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
 
-let gSmtpManager = Cc["@mozilla.org/messengercompose/smtp;1"]
-                   .getService(Ci.nsISmtpService);
-let gAccountMgr = Cc["@mozilla.org/messenger/account-manager;1"]
-                  .getService(Ci.nsIMsgAccountManager);
-let gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
-let gStringsBundle;
-let gBrandBundle;
+var gStringsBundle;
+var gMessengerBundle;
+var gBrandShortName;
+
+/*********************
+TODO for bug 549045
+- autodetect protocol
+Polish
+- reformat code style to match
+<https://developer.mozilla.org/En/Mozilla_Coding_Style_Guide#Control_Structures>
+- bold status
+- remove status when user edited in manual edit
+- add and adapt test from bug 534588
+Bugs
+- SSL cert errors
+  - invalid cert (hostname mismatch) doesn't trigger warning dialog as it should
+  - accept self-signed cert (e.g. imap.mail.ru) doesn't work
+    (works without my patch),
+    verifyConfig.js line 124 has no inServer, for whatever reason,
+    although I didn't change verifyConfig.js at all
+    (the change you see in that file is irrelevant: that was an attempt to fix
+    the bug and clean up the code).
+- Set radio IMAP vs. POP3, see TODO in code
+Things to test (works for me):
+- state transitions, buttons enable, status msgs
+  - stop button
+    - showes up again after stopping detection and restarting it
+    - when stopping [retest]: buttons proper?
+  - enter nonsense domain. guess fails, (so automatically) manual,
+    change domain to real one (not in DB), guess succeeds.
+    former bug: goes to manual first shortly, then to result
+**********************/
+
+// To debug, set mail.wizard.logging.dump (or .console)="All" and kDebug = true
+
+function e(elementID)
+{
+  return document.getElementById(elementID);
+};
 
 function _hide(id)
 {
-  document.getElementById(id).hidden = true;
+  e(id).hidden = true;
 }
+
 function _show(id)
 {
-  document.getElementById(id).hidden = false;
+  e(id).hidden = false;
+}
+
+function _enable(id)
+{
+  e(id).disabled = false;
+}
+
+function _disable(id)
+{
+  e(id).disabled = true;
 }
+
+function setText(id, value)
+{
+  var element = e(id);
+  assert(element, "setText() on non-existant element ID");
+
+  if (element.localName == "textbox" || element.localName == "label") {
+    element.value = value;
+  } else if (element.localName == "description") {
+    element.textContent = value;
+  } else {
+    throw new NotReached("XUL element type not supported");
+  }
+}
+
+function setLabelFromStringBundle(elementID, stringName)
+{
+  e(elementID).label = gMessengerBundle.getString(stringName);
+};
+
 function EmailConfigWizard()
 {
   this._init();
 }
 EmailConfigWizard.prototype =
 {
   _init : function EmailConfigWizard__init()
   {
     gEmailWizardLogger.info("Initializing setup wizard");
-    this._probeAbortable = null;
+    this._abortable = null;
   },
 
   onLoad : function()
   {
+    /**
+     * this._currentConfig is the config we got either from the XML file or
+     * from guessing or from the user. Unless it's from the user, it contains
+     * placeholders like %EMAILLOCALPART% in username and other fields.
+     *
+     * The config here must retain these placeholders, to be able to
+     * adapt when the user enters a different realname, or password or
+     * email local part. (A change of the domain name will trigger a new
+     * detection anyways.)
+     * That means, before you actually use the config (e.g. to create an
+     * account or to show it to the user), you need to run replaceVariables().
+     */
+    this._currentConfig = null;
     this._domain = "";
     this._email = "";
     this._realname = "";
     this._password = "";
-    this._verifiedConfig = false;
-    this._userChangedIncomingServer = false;
-    this._userChangedIncomingProtocol = false;
-    this._userChangedIncomingPort = false;
-    this._userChangedIncomingSocketType = false;
-    this._userChangedOutgoingPort = false;
-    this._userChangedOutgoingSocketType = false;
-    this._userChangedPassword = false;
-
-    this._incomingWarning = 'cleartext';
-    this._outgoingWarning = 'cleartext';
-    this._userPickedOutgoingServer = false;
     this._okCallback = null;
 
     if (window.arguments && window.arguments[0]) {
-      if (window.arguments[0].msgWindow)
+      if (window.arguments[0].msgWindow) {
         this._parentMsgWindow = window.arguments[0].msgWindow;
-      if (window.arguments[0].okCallback)
+      }
+      if (window.arguments[0].okCallback) {
         this._okCallback = window.arguments[0].okCallback;
+      }
     }
 
-    gStringsBundle = document.getElementById("strings");
-    gBrandBundle = document.getElementById("bundle_brand");
+    gStringsBundle = e("strings");
+    gMessengerBundle = e("bundle_messenger");
+    gBrandShortName = e("bundle_brand").getString("brandShortName");
+
+    setLabelFromStringBundle("in-authMethod-password-cleartext",
+        "authPasswordCleartextViaSSL"); // will warn about insecure later
+    setLabelFromStringBundle("in-authMethod-password-encrypted",
+        "authPasswordEncrypted");
+    setLabelFromStringBundle("in-authMethod-kerberos", "authKerberos");
+    setLabelFromStringBundle("in-authMethod-ntlm", "authNTLM");
+    setLabelFromStringBundle("out-authMethod-no", "authNo");
+    setLabelFromStringBundle("out-authMethod-password-cleartext",
+        "authPasswordCleartextViaSSL"); // will warn about insecure later
+    setLabelFromStringBundle("out-authMethod-password-encrypted",
+        "authPasswordEncrypted");
+    setLabelFromStringBundle("out-authMethod-kerberos", "authKerberos");
+    setLabelFromStringBundle("out-authMethod-ntlm", "authNTLM");
+
+    e("incoming_port").value = gStringsBundle.getString("port_auto");
+    this.fillPortDropdown("smtp");
 
     // Populate SMTP server dropdown with already configured SMTP servers from
     // other accounts.
-    let gSmtpManager = Cc["@mozilla.org/messengercompose/smtp;1"]
-                       .getService(Ci.nsISmtpService);
-    this._smtpServers = gSmtpManager.smtpServers;
-    var menupopup = document.getElementById("smtp_menupopup");
-    this._smtpServerCount = 0;
-    while (this._smtpServers.hasMoreElements())
-    {
-      var server = this._smtpServers.getNext().QueryInterface(Ci.nsISmtpServer);
-      this._smtpServerCount++;
-      var menuitem = document.createElement("menuitem");
-      var label = server.displayname;
-      if (server.key == gSmtpManager.defaultServer.key)
+    var menulist = e("outgoing_hostname");
+    var smtpManager = Cc["@mozilla.org/messengercompose/smtp;1"]
+        .getService(Ci.nsISmtpService);
+    var smtpServers = smtpManager.smtpServers;
+    while (smtpServers.hasMoreElements()) {
+      let server = smtpServers.getNext().QueryInterface(Ci.nsISmtpServer);
+      let label = server.displayname;
+      let key = server.key;
+      if (smtpManager.defaultServer &&
+          smtpManager.defaultServer.key == key) {
         label += " " + gStringsBundle.getString("default_server_tag");
+      }
+      let menuitem = menulist.appendItem(label, key, ""); // label,value,descr
+      menuitem.serverKey = key;
+    }
+    // Add the entry for the new host to the menulist
+    let menuitem = menulist.insertItemAt(0, "", "-new-"); // pos,label,value
+    menuitem.serverKey = null;
 
-      menuitem.setAttribute("label", label);
-      menuitem.setAttribute("value", server.key);
-      menuitem.hostname = server.hostname;
-      menupopup.appendChild(menuitem);
+    // admin-locked prefs hurray
+    if (!Application.prefs.getValue("signon.rememberSignons", true)) {
+      let rememberPasswordE = e("remember_password");
+      rememberPasswordE.checked = false;
+      rememberPasswordE.disabled = true;
     }
-    var menulist = document.getElementById("outgoing_server");
-    menulist.addEventListener("command",
-      function(event) { gEmailConfigWizard.userChangedOutgoing(event); }, true);
 
-    // Store the size of the bottom half of the window in the fullspacer
-    // element, so that we don't resize wildly when we show and hide it.
+    // First, unhide the main window areas, and store the width,
+    // so that we don't resize wildly when we unhide areas.
+    // switchToMode() will then hide the unneeded parts again.
+    // We will add some leeway of 10px, in case some of the <description>s wrap,
+    // e.g. outgoing username != incoming username.
+    _show("status_area");
+    _show("result_area");
+    _hide("manual-edit_area");
     window.sizeToContent();
-    document.getElementById("settingsbox").hidden = true;
-    document.getElementById("fullspacer").width = document.width;
-    window.sizeToContent();
+    e("mastervbox").setAttribute("style",
+        "min-width: " + document.width + "px; " +
+        "min-height: " + (document.height + 10) + "px;");
+
+    this.switchToMode("start");
+    e("realname").focus();
   },
 
-  /* Correct the behavior of remember_password checkbox in case we change
-   * signon.rememberSignons = true and come back
-   */
-  onFocus: function() {
-    let passwordElt = document.getElementById("password");
-    let rememberPasswordElt = document.getElementById("remember_password");
-    let rememberSignons_pref =
-      Application.prefs.getValue("signon.rememberSignons", true);
-
-    rememberPasswordElt.disabled = !rememberSignons_pref ||
-                                  passwordElt.value.length < 1;
-    rememberPasswordElt.checked = rememberSignons_pref &&
-                                  !this._userChangedPassword &&
-                                  passwordElt.value.length >= 1;
-  },
-
-  /* When the next button is clicked we've moved from the initial account
-   * information stage to using that information to configure account details.
+  /**
+   * Changes the window configuration to the different modes we have.
+   * Shows/hides various window parts and buttons.
+   * @param modename {String-enum}
+   *    "start" : Just the realname, email address, password fields
+   *    "find-config" : detection step, adds the progress message/spinner
+   *    "result" : We found a config and display it to the user.
+   *       The user may create the account.
+   *    "manual-edit" : The user wants (or needs) to manually enter their
+   *       the server hostname and other settings. We'll use them as provided.
+   * Additionally, there are the following sub-modes which can be entered after
+   * you entered the main mode:
+   *    "manual-edit-have-hostname" : user entered a hostname for both servers
+   *        that we can use
+   *    "manual-edit-testing" : User pressed the [Re-test] button and
+   *         we're currently detecting the "Auto" values
+   *    "manual-edit-complete" : user entered (or we tested) all necessary
+   *         values, and we're ready to create to account
+   * Currently, this doesn't cover the warning dialogs etc.. It may later.
    */
-  onNext : function()
+  switchToMode : function(modename)
   {
-    // change the inputs to a flat look
-    this._accountInfoInputs(true);
+    if (modename == this._currentModename) {
+      return;
+    }
+    this._currentModename = modename;
+    gEmailWizardLogger.info("switching to UI mode " + modename)
+
+    //_show("initialSettings"); always visible
+    //_show("cancel_button"); always visible
+    if (modename == "start") {
+      _hide("status_area");
+      _hide("result_area");
+      _hide("manual-edit_area");
 
-    this._email = document.getElementById("email").value;
-    this._realname = document.getElementById("realname").value;
-    this._password = document.getElementById("password").value;
+      _show("next_button");
+      _disable("next_button"); // will be enabled by code
+      _hide("half-manual-test_button");
+      _hide("create_button");
+      _hide("stop_button");
+      _hide("manual-edit_button");
+      _hide("advanced-setup_button");
+    } else if (modename == "find-config") {
+      _show("status_area");
+      _hide("result_area");
+      _hide("manual-edit_area");
 
-    this.showConfigDetails();
+      _show("next_button");
+      _disable("next_button");
+      _hide("half-manual-test_button");
+      _hide("create_button");
+      _show("stop_button");
+      this.onStop = this.onStopFindConfig;
+      _show("manual-edit_button");
+      _hide("advanced-setup_button");
+    } else if (modename == "result") {
+      _show("status_area");
+      _show("result_area");
+      _hide("manual-edit_area");
+
+      _hide("next_button");
+      _hide("half-manual-test_button");
+      _show("create_button");
+      _enable("create_button");
+      _hide("stop_button");
+      _show("manual-edit_button");
+      _hide("advanced-setup_button");
+    } else if (modename == "manual-edit") {
+      _show("status_area");
+      _hide("result_area");
+      _show("manual-edit_area");
 
-    this._domain = this._email.split("@")[1].toLowerCase();
-    this.findConfig(this._domain, this._email);
+      _hide("next_button");
+      _show("half-manual-test_button");
+      _disable("half-manual-test_button");
+      _show("create_button");
+      _disable("create_button");
+      _hide("stop_button");
+      _hide("manual-edit_button");
+      _show("advanced-setup_button");
+      _disable("advanced-setup_button");
+    } else if (modename == "manual-edit-have-hostname") {
+      _show("status_area");
+      _hide("result_area");
+      _show("manual-edit_area");
+      _hide("manual-edit_button");
+      _hide("next_button");
+      _show("create_button");
 
-    // swap out buttons
-    _hide("next_button");
-    _show("back_button");
-    _show("stop_button");
-    _hide("edit_button");
-    _hide("go_button");
+      _show("half-manual-test_button");
+      _enable("half-manual-test_button");
+      _disable("create_button");
+      _hide("stop_button");
+      _show("advanced-setup_button");
+      _disable("advanced-setup_button");
+    } else if (modename == "manual-edit-testing") {
+      _show("status_area");
+      _hide("result_area");
+      _show("manual-edit_area");
+      _hide("manual-edit_button");
+      _hide("next_button");
+      _show("create_button");
 
+      _show("half-manual-test_button");
+      _disable("half-manual-test_button");
+      _disable("create_button");
+      _show("stop_button");
+      this.onStop = this.onStopHalfManualTesting;
+      _show("advanced-setup_button");
+      _disable("advanced-setup_button");
+    } else if (modename == "manual-edit-complete") {
+      _show("status_area");
+      _hide("result_area");
+      _show("manual-edit_area");
+      _hide("manual-edit_button");
+      _hide("next_button");
+      _show("create_button");
+
+      _show("half-manual-test_button");
+      _enable("half-manual-test_button");
+      _enable("create_button");
+      _hide("stop_button");
+      _show("advanced-setup_button");
+      _enable("advanced-setup_button");
+    } else {
+      throw new NotReached("unknown mode");
+    }
     window.sizeToContent();
   },
 
-  /* The back button can be clicked at anytime and should stop all probing of
-   * account details.  This allows the person to return to their account
-   * information details in case anything was incorrect.
-   *
-   * Any account details that were manually entered should be remembered even
-   * when this back button is clicked
+  /**
+   * Start from beginning with possibly new email address.
    */
-  onBack : function()
+  onStartOver : function()
   {
-    this._disableConfigDetails(true);
-    _hide("popimap_radio_area");
-    this.hideConfigDetails();
-    this.clearConfigDetails();
-
-    // change the inputs back to regular
-    this._accountInfoInputs(false);
-
-    // swap buttons back
-    _show("next_button");
-    _hide("back_button");
-    document.getElementById("advanced_settings").disabled = true;
-    _hide("advanced_settings");
-    window.sizeToContent();
-  },
-
-  /* Helper method that enables or disabled all the account information inputs
-   */
-  _accountInfoInputs : function(disabled)
-  {
-    let ids = ["realname", "email", "password", "remember_password"];
-    if (disabled && document.getElementById("password").getAttribute("empty")) {
-      document.getElementById("password").value = " ";
-      document.getElementById("password").setAttribute("empty", true);
-      document.getElementById("remember_password").checked = false;
+    if (this._abortable) {
+      this.onStop();
     }
-
-    for ( let i = 0; i < ids.length; i++ )
-      document.getElementById(ids[i]).disabled = disabled;
-
-    if (!disabled &&
-        document.getElementById("password").getAttribute("empty")) {
-      document.getElementById("password").value = "";
-      document.getElementById("password").setAttribute("empty", true);
-      document.getElementById("remember_password").checked = false;
-    }
+    this.switchToMode("start");
   },
 
-  userChangedOutgoing : function(event)
+  getConcreteConfig : function()
   {
-    let menulist = document.getElementById("outgoing_server");
-
-    let selectedIndex = menulist.getIndexOfItem(event.target);
-
-    if (selectedIndex != -1) {
-      document.getElementById("outgoing_port").value =
-        this._currentConfig.outgoing.port;
-      document.getElementById("outgoing_security").value =
-        this._currentConfig.outgoing.socketType;
-    }
-
-    if (selectedIndex == -1 || selectedIndex == 0) {
-      this._outgoingState = '';
-      this._userPickedOutgoingServer = false;
-      _show('outgoing_protocol');
-      _show('outgoing_port');
-      _show('outgoing_security');
-      menulist.setAttribute("editable", true);
-      return;
-    }
-    menulist.setAttribute("editable", false);
-    this._outgoingState = 'done';
-    this._outgoingWarning = '';
-
-    if (this._probeAbortable)
-      this._probeAbortable.cancel('outgoing');
-
-    // Re-set the outgoing port and security.
-    this._userPickedOutgoingServer = true;
-    _hide('outgoing_protocol');
-    _hide('outgoing_port');
-    _hide('outgoing_security');
-
-    if (this._incomingState == 'done')
-      this.foundConfig(this.getUserConfig());
-  },
-
-  /* This does very little other than to check that a name was entered at all
-   * Since this is such an insignificant test we should be using a very light
-   * or even jovial warning.
-   */
-  validateRealname : function()
-  {
-    let realname = document.getElementById("realname");
-    if (realname.value.length > 0) {
-      this.clearError("nameerror");
-      _show("nametext");
-      realname.removeAttribute("error");
-    }
-    else {
-      _hide("nametext");
-      this.setError("nameerror", "please_enter_name");
-      realname.setAttribute("error", "true");
-    }
+    var result = this._currentConfig.copy();
+    replaceVariables(result, this._realname, this._email, this._password);
+    result.rememberPassword = e("remember_password").checked &&
+                              !!this._password;
+    return result;
   },
 
   /*
    * This checks if the email address is at least possibly valid, meaning it
    * has an '@' before the last char.
    */
-  emailAddrValidish : function()
+  validateEmailMinimally : function(emailAddr)
   {
-    let emailAddr = document.getElementById('email').value;
     let atPos = emailAddr.lastIndexOf("@");
-    return  atPos > 0 && atPos + 1 < emailAddr.length;
+    return atPos > 0 && atPos + 1 < emailAddr.length;
   },
 
   /*
-   * onEmailInput and onRealnameInput are called onInput, and just handle
-   * hiding/showing the next button based on whether there's a semi-reasonable
-   * e-mail address and nonblank realname to start with.
+   * This checks if the email address is syntactically valid,
+   * as far as we can determine. We try hard to make full checks.
+   *
+   * OTOH, we have a very small chance of false negatives,
+   * because the RFC822 address spec is insanely complicated,
+   * but rarely needed, so when this here fails, we show an error message,
+   * but don't stop the user from continuing.
+   * In contrast, if validateEmailMinimally() fails, we stop the user.
    */
-  onEmailInput : function()
+  validateEmail : function(emailAddr)
   {
-    if (document.getElementById("realname").value.length > 0 &&
-        this.emailAddrValidish())
-      document.getElementById("next_button").disabled = false;
-    else
-      document.getElementById("next_button").disabled = true;
-   },
+    return emailRE.test(emailAddr);
+  },
 
-  onRealnameInput : function()
+  /**
+   * onInputEmail and onInputRealname are called on input = keypresses, and
+   * enable/disable the next button based on whether there's a semi-proper
+   * e-mail address and non-blank realname to start with.
+   *
+   * A change to the email address also automatically restarts the
+   * whole process.
+   */
+  onInputEmail : function()
   {
-    if (document.getElementById('realname').value.length > 0 &&
-        this.emailAddrValidish())
-      document.getElementById("next_button").disabled = false;
-    else
-      document.getElementById("next_button").disabled = true;
-   },
+    this._email = e("email").value;
+    this.onStartOver();
+    this.checkStartDone();
+  },
+  onInputRealname : function()
+  {
+    this._realname = e("realname").value;
+    this.checkStartDone();
+  },
 
-  /* This check is done on blur and is only done as an informative warning
-   * we don't want to block the person if they've entered and email that
-   * doesn't conform to our regex
+  onInputPassword : function()
+  {
+    this._password = e("password").value;
+  },
+
+  /**
+   * This does very little other than to check that a name was entered at all
+   * Since this is such an insignificant test we should be using a very light
+   * or even jovial warning.
    */
-  validateEmail : function()
+  onBlurRealname : function()
   {
-    let email = document.getElementById("email");
-    if (email.value.length <= 0)
-      return;
-
-    if (emailRE.test(email.value)) {
-      this.clearError("emailerror");
-      email.removeAttribute("error");
-    }
-    else {
-      this.setError("emailerror", "double_check_email");
-      email.setAttribute("error", "true");
+    let realnameEl = e("realname");
+    if (this._realname) {
+      this.clearError("nameerror");
+      _show("nametext");
+      realnameEl.removeAttribute("error");
+    // bug 638790: don't show realname error until user enter an email address
+    } else if (this.validateEmailMinimally(this._email)) {
+      _hide("nametext");
+      this.setError("nameerror", "please_enter_name");
+      realnameEl.setAttribute("error", "true");
     }
   },
 
-  // We use this to  prevent probing from forgetting the user's choice.
-  setSecurity : function(eltId)
+  /**
+   * This check is only done as an informative warning.
+   * We don't want to block the person, if they've entered an email address
+   * that doesn't conform to our regex.
+   */
+  onBlurEmail : function()
   {
-    switch (eltId) {
-      case 'incoming_security':
-        this._userChangedIncomingSocketType = true;
-        this._incomingState = "";
-        break;
-      case 'outgoing_security':
-        this._userChangedOutgoingSocketType = true;
-        this._outgoingState = "";
-        break;
+    if (!this._email) {
+      return;
+    }
+    var emailEl = e("email");
+    if (this.validateEmail(this._email)) {
+      this.clearError("emailerror");
+      emailEl.removeAttribute("error");
+      this.onBlurRealname();
+    } else {
+      this.setError("emailerror", "double_check_email");
+      emailEl.setAttribute("error", "true");
     }
   },
 
-  setIncomingServer : function()
-  {
-    this._userChangedIncomingServer = true;
-    this._incomingState = "";
-  },
-
-  // The IMAP vs. POP dropdown (not radio), settable during manual edit.
-  // In the normal result screen, it's not editable and people use the
-  // radios instead.
-  setIncomingProtocol : function()
-  {
-    this._userChangedIncomingProtocol = true;
-    this._incomingState = "";
-  },
-
-  // IMAP vs. POP3 radio (not *dropdown*) changed
-  setIncomingProtocolRadio : function()
-  {
-    let newTypeSelected = document.getElementById("popimap_radiogroup")
-                                  .selectedItem.value;
-    var config = this._currentConfig;
-    // this is also called when we set the fields programmatically
-    if (newTypeSelected == config.incoming.type ||
-        !config.incomingAlternatives || !config.incomingAlternatives.length)
-      return;
-    let alternates = config.incomingAlternatives.filter(function(c) {
-      return c.type != config.incoming.type;
-    });
-    if (alternates.length == 0)
-      return;
-
-    // re-add current incoming server to alternatives
-    config.incomingAlternatives.unshift(config.incoming);
-    // find alternative config which matches newly selected protocol
-    for each (let alt in config.incomingAlternatives) {
-      if (alt.type == newTypeSelected) {
-        config.incoming = alt;
-        break;
-      }
-    }
-    this.foundConfig(config);
-  },
-
-  setPort : function(eltId)
-  {
-    switch (eltId) {
-      case 'incoming_port':
-        this._userChangedIncomingPort = true;
-        this._incomingState = "";
-        break;
-      case 'outgoing_port':
-        this._userChangedOutgoingPort = true;
-        this._outgoingState = "";
-        break;
-    }
-  },
-
-  oninputPassword : function()
-  {
-    let passwordElt = document.getElementById("password");
-    let rememberPasswordElt = document.getElementById("remember_password");
-    let rememberSignons_pref =
-        Application.prefs.getValue("signon.rememberSignons", true);
-
-    rememberPasswordElt.disabled = !rememberSignons_pref ||
-                                   passwordElt.value.length < 1;
-    rememberPasswordElt.checked = rememberSignons_pref &&
-                                  !this._userChangedPassword &&
-                                  passwordElt.value.length >= 1;
-  },
-
-  /* If the user just tabbed through the password input without entering
+  /**
+   * If the user just tabbed through the password input without entering
    * anything, set the type back to text so we don't wind up showing the
    * emptytext as bullet characters.
    */
-  onblurPassword : function()
+  onBlurPassword : function()
   {
-    let passwordElt = document.getElementById("password");
-    let rememberPasswordElt = document.getElementById("remember_password");
-    let rememberSignons_pref =
-        Application.prefs.getValue("signon.rememberSignons", true);
+    if (!this._password) {
+      e("password").type = "text";
+    }
+  },
 
-    if (passwordElt.value.length < 1) {
-      rememberPasswordElt.disabled = true;
-      passwordElt.type = "text";
-    }
-    else if (rememberSignons_pref)
-      rememberPasswordElt.disabled = false;
+  /**
+   * @see onBlurPassword()
+   */
+  onFocusPassword : function()
+  {
+    e("password").type = "password";
   },
 
+  /**
+   * Check whether the user entered the minimum of information
+   * needed to leave the "start" mode (entering of name, email, pw)
+   * and is allowed to proceed to detection step.
+   */
+  checkStartDone : function()
+  {
+    if (this.validateEmailMinimally(this._email) &&
+        this._realname) {
+      this._domain = this._email.split("@")[1].toLowerCase();
+      _enable("next_button");
+    } else {
+      _disable("next_button");
+    }
+  },
+
+  /**
+   * When the [Continue] button is clicked, we move from the initial account
+   * information stage to using that information to configure account details.
+   */
+  onNext : function()
+  {
+    this.findConfig(this._domain, this._email);
+  },
+
+
+  /////////////////////////////////////////////////////////////////
+  // Detection step
+
+  /**
+   * Try to find an account configuration for this email address.
+   * This is the function which runs the autoconfig.
+   */
   findConfig : function(domain, email)
   {
     gEmailWizardLogger.info("findConfig()");
-    if (this._probeAbortable)
-    {
-      gEmailWizardLogger.info("aborting existing config search");
-      this._probeAbortable.cancel();
+    if (this._abortable) {
+      this.onStop();
     }
-    this.startSpinner("all", "looking_up_settings_disk");
-    var me = this;
-    this._probeAbortable = fetchConfigFromDisk(domain,
+    this.switchToMode("find-config");
+    this.startSpinner("looking_up_settings_disk");
+    var self = this;
+    this._abortable = fetchConfigFromDisk(domain,
       function(config) // success
       {
-        me.foundConfig(config);
-        me.stopSpinner("found_settings_disk");
-        me._probeAbortable = null;
+        self._abortable = null;
+        self.foundConfig(config);
+        self.stopSpinner("found_settings_disk");
       },
       function(e) // fetchConfigFromDisk failed
       {
+        if (e instanceof CancelledException) {
+          return;
+        }
         gEmailWizardLogger.info("fetchConfigFromDisk failed: " + e);
-        me.startSpinner("all", "looking_up_settings_isp");
-        me._probeAbortable = fetchConfigFromISP(domain, email,
+        self.startSpinner("looking_up_settings_isp");
+        self._abortable = fetchConfigFromISP(domain, email,
           function(config) // success
           {
-            me.foundConfig(config);
-            me.stopSpinner("found_settings_isp");
-            me.showEditButton();
-            me._probeAbortable = null;
+            self._abortable = null;
+            self.foundConfig(config);
+            self.stopSpinner("found_settings_isp");
           },
           function(e) // fetchConfigFromISP failed
           {
+            if (e instanceof CancelledException) {
+              return;
+            }
             gEmailWizardLogger.info("fetchConfigFromISP failed: " + e);
-            me.startSpinner("all", "looking_up_settings_db");
-            me._probeAbortable = fetchConfigFromDB(domain,
+            logException(e);
+            self.startSpinner("looking_up_settings_db");
+            self._abortable = fetchConfigFromDB(domain,
               function(config) // success
               {
-                me.foundConfig(config);
-                me.stopSpinner("found_settings_db");
-                me.showEditButton();
-                me._probeAbortable = null;
+                self._abortable = null;
+                self.foundConfig(config);
+                self.stopSpinner("found_settings_db");
               },
               function(e) // fetchConfigFromDB failed
               {
+                if (e instanceof CancelledException) {
+                  return;
+                }
+                logException(e);
                 gEmailWizardLogger.info("fetchConfigFromDB failed: " + e);
-                me.startSpinner("all", "looking_up_settings_db");
-                me._probeAbortable = fetchConfigForMX(domain,
-                  function(config) // success
-                  {
-                    me.foundConfig(config);
-                    me.stopSpinner("found_settings_db");
-                    me.showEditButton();
-                    me._probeAbortable = null;
-                  },
-                  function(e) // fetchConfigForMX failed
-                  {
-                    gEmailWizardLogger.info("fetchConfigForMX failed: " + e);
-                    var initialConfig = new AccountConfig();
-                    me._prefillConfig(initialConfig);
-                    me.startSpinner("all", "looking_up_settings_guess")
-                    me._guessConfig(domain, initialConfig, "both");
-                  });
+                var initialConfig = new AccountConfig();
+                self._prefillConfig(initialConfig);
+                self._guessConfig(domain, initialConfig);
               });
           });
       });
   },
 
-  _guessConfig : function(domain, initialConfig, which)
+  /**
+   * Just a continuation of findConfig()
+   */
+  _guessConfig : function(domain, initialConfig)
   {
-    let me = this;
-    // guessConfig takes several callback functions, which we define inline.
-    me._probeAbortable = guessConfig(domain,
-          function(type, hostname, port, ssl, done, config) // progress
-          {
-            gEmailWizardLogger.info("progress callback host " + hostname +
-                                    " port " +  port + " type " + type);
-            if (type == "imap" || type == "pop3")
-            {
-              config.incoming.type = type;
-              config.incoming.hostname = hostname;
-              config.incoming.port = port;
-              config.incoming.socketType = ssl;
-              config.incoming._inprogress = !done; // XXX not nice to change the AccountConfig object
-            }
-            else if (type == "smtp" && !me._userPickedOutgoingServer)
-            {
-              config.outgoing.hostname = hostname;
-              config.outgoing.port = port;
-              config.outgoing.socketType = ssl;
-              config.outgoing._inprogress = !done;
-            }
-            me.updateConfig(config);
-          },
-          function(config) // success
-          {
-            me.foundConfig(config);
-            gEmailWizardLogger.info("in success, incomingState = " +
-                                    me._incomingState + " outgoingState = " +
-                                    me._outgoingState);
-            if (me._incomingState == 'done' && me._outgoingState == 'done')
-            {
-              me.stopSpinner("found_settings_guess");
-              _hide("stop_button");
-              _show("edit_button");
-            }
-            else if (me._incomingState == 'done' && me._outgoingState != 'probing')
-            {
-              if (me._outgoingState == "failed")
-                me.stopSpinner("failed_to_find_settings");
-              else
-                me.stopSpinner("found_settings_guess");
-              me.editConfigDetails();
-            }
-            else if (me._outgoingState == 'done' && me._incomingState != 'probing')
-            {
-              if (me._incomingState == "failed")
-                me.stopSpinner("failed_to_find_settings");
-              else
-                me.stopSpinner("found_settings_guess");
-              me.editConfigDetails();
-            }
-            if (me._outgoingState != 'probing' &&
-                me._incomingState != 'probing')
-              me._probeAbortable = null;
-
-          },
-          function(e, config) // guessconfig failed
-          {
-            gEmailWizardLogger.info("guessConfig failed: " + e);
-            me.updateConfig(config);
-            me.stopSpinner("failed_to_find_settings");
-            me._probeAbortable = null;
-            me.editConfigDetails();
-          },
-          function(e, config) // guessconfig failed for incoming
-          {
-            gEmailWizardLogger.info("guessConfig failed for incoming: " + e);
-            me._setIconAndTooltip("incoming", "failed", "");
-            me._incomingState = "failed";
-            config.incoming.hostname = -1;
-            me.updateConfig(config);
-          },
-          function(e, config) // guessconfig failed for outgoing
-          {
-            gEmailWizardLogger.info("guessConfig failed for outgoing: " + e);
-            me._setIconAndTooltip("outgoing", "failed", "");
-            me._outgoingState = "failed";
-            if (!me._userPickedOutgoingServer)
-              config.outgoing.hostname = -1;
-
-            me.updateConfig(config);
-          },
-    initialConfig, which);
+    this.startSpinner("looking_up_settings_guess")
+    var self = this;
+    self._abortable = guessConfig(domain,
+      function(type, hostname, port, ssl, done, config) // progress
+      {
+        gEmailWizardLogger.info("progress callback host " + hostname +
+                                " port " +  port + " type " + type);
+      },
+      function(config) // success
+      {
+        self._abortable = null;
+        self.foundConfig(config);
+        self.stopSpinner("found_settings_guess");
+      },
+      function(e, config) // guessconfig failed
+      {
+        if (e instanceof CancelledException) {
+          return;
+        }
+        self._abortable = null;
+        gEmailWizardLogger.info("guessConfig failed: " + e);
+        self.showErrorStatus("failed_to_find_settings");
+        self.editConfigDetails();
+      },
+      initialConfig, "both");
   },
 
+  /**
+   * When findConfig() was successful, it calls this.
+   * This displays the config to the user.
+   */
   foundConfig : function(config)
   {
     gEmailWizardLogger.info("foundConfig()");
-    assert(config instanceof AccountConfig, "BUG: Arg 'config' needs to be an AccountConfig object");
+    assert(config instanceof AccountConfig,
+        "BUG: Arg 'config' needs to be an AccountConfig object");
 
-    if (!config)
-      config = new AccountConfig();
-
-    this._currentConfig = config;
     this._haveValidConfigForDomain = this._email.split("@")[1];;
 
-    if (!this._realname || !this._email)
+    if (!this._realname || !this._email) {
       return;
-
+    }
     return this._foundConfig2(config);
   },
+
   // Continuation of foundConfig2() after custom fields.
   _foundConfig2 : function(config)
   {
-    this._currentConfigFilledIn = config.copy();
-    _show("advanced_settings");
-    document.getElementById("advanced_settings").disabled = false;
-    replaceVariables(this._currentConfigFilledIn, this._realname, this._email,
-                     this._password);
+    this.displayConfigResult(config);
+  },
+
+  /**
+   * [Stop] button click handler.
+   * This allows the user to abort any longer operation, esp. network activity.
+   * We currently have 3 such cases here:
+   * 1. findConfig(), i.e. fetch config from DB, guessConfig etc.
+   * 2. onHalfManualTest(), i.e. the [Retest] button in manual config.
+   * 3. verifyConfig() - We can't stop this yet, so irrelevant here currently.
+   * Given that these need slightly different actions, this function will be set
+   * to a function (i.e. overwritten) by whoever enables the stop button.
+   *
+   * We also call this from the code when the user started a different action
+   * without explicitly clicking [Stop] for the old one first.
+   */
+  onStop : function()
+  {
+    throw new NotReached("onStop should be overridden by now");
+  },
+  _onStopCommon : function()
+  {
+    if (!this._abortable) {
+      throw new NotReached("onStop called although there's nothing to stop");
+    }
+    gEmailWizardLogger.info("onStop cancelled _abortable");
+    this._abortable.cancel(new UserCancelledException());
+    this._abortable = null;
+    this.stopSpinner();
+  },
+  onStopFindConfig : function()
+  {
+    this._onStopCommon();
+    this.switchToMode("start");
+    this.checkStartDone();
+  },
+  onStopHalfManualTesting : function()
+  {
+    this._onStopCommon();
+    this.validateManualEditComplete();
+  },
+
+
+
+  ///////////////////////////////////////////////////////////////////
+  // status area
+
+  startSpinner : function(actionStrName)
+  {
+    e("status_area").setAttribute("status", "loading");
+    gEmailWizardLogger.warn("spinner start " + actionStrName);
+    this._showStatusTitle(actionStrName);
+  },
+
+  stopSpinner : function(actionStrName)
+  {
+    e("status_area").setAttribute("status", "result");
+    _hide("stop_button");
+    this._showStatusTitle(actionStrName);
+    gEmailWizardLogger.warn("all spinner stop " + actionStrName);
+  },
+
+  showErrorStatus : function(actionStrName)
+  {
+    e("status_area").setAttribute("status", "error");
+    gEmailWizardLogger.warn("status error " + actionStrName);
+    this._showStatusTitle(actionStrName);
+  },
+
+  _showStatusTitle : function(msgName)
+  {
+    let msg = " "; // assure height. Do via min-height in CSS, for 2 lines?
+    try {
+      if (msgName) {
+        msg = gStringsBundle.getFormattedString(msgName, [gBrandShortName]);
+      }
+    } catch(ex) {
+      gEmailWizardLogger.error("missing string for " + msgName);
+      msg = msgName + " (missing string in translation!)";
+    }
+
+    e("status_msg").textContent = msg;
+    gEmailWizardLogger.info("status msg: " + msg);
+  },
+
+
+
+  /////////////////////////////////////////////////////////////////
+  // Result area
+
+  /**
+   * Displays a (probed) config to the user,
+   * in the result config details area.
+   *
+   * @param config {AccountConfig} The config to present to user
+   */
+  displayConfigResult : function(config)
+  {
+    assert(config instanceof AccountConfig);
+    this._currentConfig = config;
+    var configFilledIn = this.getConcreteConfig();
+
+    var unknownString = gStringsBundle.getString("resultUnknown");
+
+    function _makeHostDisplayString(server, stringName)
+    {
+      let type = gStringsBundle.getString(sanitize.translate(server.type,
+          { imap : "resultIMAP", pop3 : "resultPOP3", smtp : "resultSMTP" }),
+          unknownString);
+      let host = server.hostname +
+          (isStandardPort(server.port) ? "" : ":" + server.port);
+      let ssl = gStringsBundle.getString(sanitize.translate(server.socketType,
+          { 1 : "resultNoEncryption", 2 : "resultSSL", 3 : "resultSTARTTLS" }),
+          unknownString);
+      let certStatus = gStringsBundle.getString(server.badCert ?
+          "resultSSLCertWeak" : "resultSSLCertOK");
+      return gStringsBundle.getFormattedString(stringName,
+          [ type, host, ssl, certStatus ]);
+    };
+
+    var incomingResult = unknownString;
+    if (configFilledIn.incoming.hostname) {
+      incomingResult = _makeHostDisplayString(configFilledIn.incoming,
+          "resultIncoming");
+    }
+
+    var outgoingResult = unknownString;
+    if (!config.outgoing.existingServerKey) {
+      if (configFilledIn.outgoing.hostname) {
+        outgoingResult = _makeHostDisplayString(configFilledIn.outgoing,
+            "resultOutgoing");
+      }
+    } else {
+      outgoingResult = gStringsBundle.getString("resultOutgoingExisting");
+    }
+
+    var usernameResult;
+    if (configFilledIn.incoming.username == configFilledIn.outgoing.username) {
+      usernameResult = gStringsBundle.getFormattedString("resultUsernameBoth",
+            [ configFilledIn.incoming.username || unknownString ]);
+    } else {
+      usernameResult = gStringsBundle.getFormattedString(
+            "resultUsernameDifferent",
+            [ configFilledIn.incoming.username || unknownString,
+              configFilledIn.outgoing.username || unknownString ]);
+    }
+
+    setText("result-incoming", incomingResult);
+    setText("result-outgoing", outgoingResult);
+    setText("result-username", usernameResult);
+
+    gEmailWizardLogger.info(debugObject(config, "config"));
+    // IMAP / POP dropdown
+    var lookForAltType =
+        configFilledIn.incoming.type == "imap" ? "pop3" : "imap";
+    var alternative = null;
+    for (let i = 0; i < configFilledIn.incomingAlternatives.length; i++) {
+      let alt = configFilledIn.incomingAlternatives[i];
+      if (alt.type == lookForAltType) {
+        alternative = alt;
+        break;
+      }
+    }
+    if (alternative) {
+      _show("result_imappop");
+      // TODO breaks, no idea why
+      //e("result_imappop").value =
+      //    configFilledIn.incoming.type == "imap" ? 1 : 2;
+      e("result_select_" + alternative.type).configIncoming = alternative;
+      e("result_select_" + configFilledIn.incoming.type).configIncoming =
+                                                configFilledIn.incoming;
+    } else {
+      _hide("result_imappop");
+    }
+
+    this.switchToMode("result");
+  },
 
-    this.updateConfig(this._currentConfigFilledIn);
+  onResultIMAPOrPOP3 : function()
+  {
+    var config = this._currentConfig;
+    if (!config) { // <radiogroup>.onselect is also called on window open
+      return;
+    }
+    var radiogroup = e("result_imappop");
+    // add current server as best alternative to start of array
+    config.incomingAlternatives.unshift(config.incoming);
+    // use selected server (stored as special property on the <radio> node)
+    config.incoming = radiogroup.selectedItem.configIncoming;
+    // remove newly selected server from list of alternatives
+    config.incomingAlternatives = config.incomingAlternatives.filter(
+        function(e) { return e != config.incoming; }); // TODO doesn't work
+    this.displayConfigResult(config);
+  },
+
+
+
+  /////////////////////////////////////////////////////////////////
+  // Manual Edit area
+
+  /**
+   * Gets the values from the user in the manual edit area.
+   *
+   * Realname and password are not part of that area and still
+   * placeholders, but hostname and username are concrete and
+   * no placeholders anymore.
+   */
+  getUserConfig : function()
+  {
+    var config = this.getConcreteConfig();
+    if (!config) {
+      config = new AccountConfig();
+    }
+    config.source = AccountConfig.kSourceUser;
+
+    // Incoming server
+    try {
+      var inHostnameField = e("incoming_hostname");
+      config.incoming.hostname = sanitize.hostname(inHostnameField.value);
+      inHostnameField.value = config.incoming.hostname;
+    } catch (e) { gEmailWizardLogger.warn(e); }
+    try {
+      config.incoming.port = sanitize.integerRange(e("incoming_port").value,
+                                                   1, kHighestPort);
+    } catch (e) {
+      config.incoming.port = undefined; // incl. default "Auto"
+    }
+    config.incoming.type = sanitize.translate(e("incoming_protocol").value,
+        { 1: "imap", 2 : "pop3", 0 : null });
+    config.incoming.socketType = parseInt(e("incoming_ssl").value);
+    config.incoming.auth = parseInt(e("incoming_authMethod").value);
+    config.incoming.username = e("incoming_username").value;
+
+    // Outgoing server
+
+    // Did the user select one of the already configured SMTP servers from the
+    // drop-down list? If so, use it.
+    var outHostnameCombo = e("outgoing_hostname");
+    var outMenuitem = outHostnameCombo.selectedItem;
+    if (outMenuitem && outMenuitem.serverKey) {
+      config.outgoing.existingServerKey = outMenuitem.serverKey;
+      config.outgoing.existingServerLabel = outMenuitem.label;
+      config.outgoing.addThisServer = false;
+      config.outgoing.useGlobalPreferredServer = false;
+    } else {
+      config.outgoing.existingServerKey = null;
+      config.outgoing.addThisServer = true;
+      config.outgoing.useGlobalPreferredServer = false;
+
+      try {
+        config.outgoing.hostname = sanitize.hostname(
+              outHostnameCombo.inputField.value);
+        outHostnameCombo.inputField.value = config.outgoing.hostname;
+      } catch (e) { gEmailWizardLogger.warn(e); }
+      try {
+        config.outgoing.port = sanitize.integerRange(e("outgoing_port").value,
+              1, kHighestPort);
+      } catch (e) {
+        config.outgoing.port = undefined; // incl. default "Auto"
+      }
+      config.outgoing.socketType = e("outgoing_ssl").value;
+      config.outgoing.auth = e("outgoing_authMethod").value;
+      config.outgoing.username = config.incoming.username;
+    }
+
+    return config;
+  },
 
-    document.getElementById('create_button').disabled = false;
-    document.getElementById('create_button').hidden = false;
+  /**
+   * [Manual Config] button click handler. This turns the config details area
+   * into an editable form and makes the (Go) button appear. The edit button
+   * should only be available after the config probing is completely finished,
+   * replacing what was the (Stop) button.
+   */
+  onManualEdit : function()
+  {
+    if (this._abortable) {
+      this.onStop();
+    }
+    this.editConfigDetails();
+  },
+
+  /**
+   * Setting the config details form so it can be edited. We also disable
+   * (and hide) the create button during this time because we don't know what
+   * might have changed. The function called from the button that restarts
+   * the config check should be enabling the config button as needed.
+   */
+  editConfigDetails : function()
+  {
+    gEmailWizardLogger.info("manual edit");
+
+    if (!this._currentConfig) {
+      this._currentConfig = new AccountConfig();
+      this._currentConfig.incoming.type = "imap";
+      this._currentConfig.incoming.username = "%EMAILLOCALPART%";
+      this._currentConfig.outgoing.username = "%EMAILLOCALPART%";
+      this._currentConfig.incoming.hostname = ".%EMAILDOMAIN%";
+      this._currentConfig.outgoing.hostname = ".%EMAILDOMAIN%";
+    }
+    // Although we go manual, and we need to display the concrete username,
+    // however the realname and password is not part of manual config and
+    // must stay a placeholder in _currentConfig. @see getUserConfig()
+
+    this._fillManualEditFields(this.getConcreteConfig());
+
+    // _fillManualEditFields() indirectly calls validateManualEditComplete(),
+    // but it's important to not forget it in case the code is rewritten,
+    // so calling it explicitly again. Doesn't do harm, speed is irrelevant.
+    this.validateManualEditComplete();
+  },
+
+  /**
+   * Fills the manual edit textfields with the provided config.
+   * @param config {AccountConfig} The config to present to user
+   */
+  _fillManualEditFields : function(config)
+  {
+    assert(config instanceof AccountConfig);
+
+    // incoming server
+    e("incoming_protocol").value = sanitize.translate(config.incoming.type,
+                                                { "imap" : 1, "pop3" : 2 }, 1);
+    e("incoming_hostname").value = config.incoming.hostname;
+    e("incoming_ssl").value = sanitize.enum(config.incoming.socketType,
+                                            [ 0, 1, 2, 3 ], 0);
+    e("incoming_authMethod").value = sanitize.enum(config.incoming.auth,
+                                                   [ 0, 3, 4, 5, 6 ], 0);
+    e("incoming_username").value = config.incoming.username;
+    if (config.incoming.port) {
+      e("incoming_port").value = config.incoming.port;
+    } else {
+      this.adjustIncomingPortToSSLAndProtocol(config);
+    }
+    this.fillPortDropdown(config.incoming.type);
+
+    // outgoing server
+    e("outgoing_hostname").value = config.outgoing.hostname;
+    e("outgoing_ssl").value = sanitize.enum(config.outgoing.socketType,
+                                            [ 0, 1, 2, 3 ], 0);
+    e("outgoing_authMethod").value = sanitize.enum(config.outgoing.auth,
+                                                   [ 0, 1, 3, 4, 5, 6 ], 0);
+    if (config.outgoing.port) {
+      e("outgoing_port").value = config.outgoing.port;
+    } else {
+      this.adjustOutgoingPortToSSLAndProtocol(config);
+    }
+    // populate fields even if existingServerKey, in case user changes back
+
+    if (config.outgoing.existingServerKey) {
+      let menulist = e("outgoing_hostname");
+      // We can't use menulist.value = config.outgoing.existingServerKey
+      // because would overwrite the text field, so have to do it manually:
+      for each (let menuitem in e("outgoing_hostname_popup").childNodes) {
+        if (menuitem.serverKey == config.outgoing.existingServerKey) {
+          menulist.selectedItem = menuitem;
+          break;
+        }
+      }
+    }
+    this.onChangedOutgoingDropdown(); // show/hide outgoing port, SSL, ...
   },
 
-  /*
-   * Returns either the nsISmtpServer.key for an existing account that matches
-   * our discovered hostname + port + username, or false if no match is found.
+  /**
+   * Automatically fill port field in manual edit,
+   * unless user entered a non-standard port.
+   * @param config {AccountConfig}
+   */
+  adjustIncomingPortToSSLAndProtocol : function(config)
+  {
+    var autoPort = gStringsBundle.getString("port_auto");
+    var incoming = config.incoming;
+    // we could use getHostEntry() here, but that API is bad, so don't bother
+    var newInPort = undefined;
+    if (!incoming.port || isStandardPort(incoming.port)) {
+      if (incoming.type == "imap") {
+        if (incoming.socketType == 1 || incoming.socketType == 3) {
+          newInPort = 143;
+        } else if (incoming.socketType == 2) { // Normal SSL
+          newInPort = 993;
+        } else { // auto
+          newInPort = autoPort;
+        }
+      } else if (incoming.type == "pop3") {
+        if (incoming.socketType == 1 || incoming.socketType == 3) {
+          newInPort = 110;
+        } else if (incoming.socketType == 2) { // Normal SSLs
+          newInPort = 995;
+        } else { // auto
+          newInPort = autoPort;
+        }
+      }
+    }
+    if (newInPort != undefined) {
+      e("incoming_port").value = newInPort;
+      e("incoming_authMethod").value = 0; // auto
+    }
+  },
+
+  /**
+   * @see adjustIncomingPortToSSLAndProtocol()
+   */
+  adjustOutgoingPortToSSLAndProtocol : function(config)
+  {
+    var autoPort = gStringsBundle.getString("port_auto");
+    var outgoing = config.outgoing;
+    var newOutPort = undefined;
+    if (!outgoing.port || isStandardPort(outgoing.port)) {
+      if (outgoing.socketType == 1 || outgoing.socketType == 3) {
+        // standard port is 587 *or* 25, so set to auto
+        // unless user or config already entered one of these two ports.
+        if (outgoing.port != 25 && outgoing.port != 587) {
+          newOutPort = autoPort;
+        }
+      } else if (outgoing.socketType == 2) { // Normal SSL
+        newOutPort = 465;
+      } else { // auto
+        newOutPort = autoPort;
+      }
+    }
+    if (newOutPort != undefined) {
+      e("outgoing_port").value = newOutPort;
+      e("outgoing_authMethod").value = 0; // auto
+    }
+  },
+
+  /**
+   * If the user changed the port manually, adjust the SSL value,
+   * (only) if the new port is impossible with the old SSL value.
+   * @param config {AccountConfig}
    */
-  keyForExistingOutgoingAccount : function()
+  adjustIncomingSSLToPort : function(config)
+  {
+    var incoming = config.incoming;
+    var newInSocketType = undefined;
+    if (!incoming.port || // auto
+        !isStandardPort(incoming.port)) {
+      return;
+    }
+    if (incoming.type == "imap") {
+      // normal SSL impossible
+      if (incoming.port == 143 && incoming.socketType == 2) {
+        newInSocketType = 0; // auto
+      // must be normal SSL
+      } else if (incoming.port == 993 && incoming.socketType != 2) {
+        newInSocketType = 2;
+      }
+    } else if (incoming.type == "pop3") {
+      // normal SSL impossible
+      if (incoming.port == 110 && incoming.socketType == 2) {
+        newInSocketType = 0; // auto
+      // must be normal SSL
+      } else if (incoming.port == 995 && incoming.socketType != 2) {
+        newInSocketType = 2;
+      }
+    }
+    if (newInSocketType != undefined) {
+      e("incoming_ssl").value = newInSocketType;
+      e("incoming_authMethod").value = 0; // auto
+    }
+  },
+
+  /**
+   * @see adjustIncomingSSLToPort()
+   */
+  adjustOutgoingSSLToPort : function(config)
+  {
+    var outgoing = config.outgoing;
+    var newOutSocketType = undefined;
+    if (!outgoing.port || // auto
+        !isStandardPort(outgoing.port)) {
+      return;
+    }
+    // normal SSL impossible
+    if ((outgoing.port == 587 || outgoing.port == 25) &&
+        outgoing.socketType == 2) {
+      newOutSocketType = 0; // auto
+    // must be normal SSL
+    } else if (outgoing.port == 465 && outgoing.socketType != 2) {
+      newOutSocketType = 2;
+    }
+    if (newOutSocketType != undefined) {
+      e("outgoing_ssl").value = newOutSocketType;
+      e("outgoing_authMethod").value = 0; // auto
+    }
+  },
+
+  /**
+   * Sets the prefilled values of the port fields.
+   * Filled statically with the standard ports for the given protocol,
+   * plus "Auto".
+   */
+  fillPortDropdown : function(protocolType)
+  {
+    var menu = e(protocolType == "smtp" ? "outgoing_port" : "incoming_port");
+
+    // menulist.removeAllItems() is nice, but nicely clears the user value, too
+    var popup = menu.menupopup;
+    while (popup.hasChildNodes())
+      popup.removeChild(popup.firstChild);
+
+    // add standard ports
+    var autoPort = gStringsBundle.getString("port_auto");
+    menu.appendItem(autoPort, autoPort, ""); // label,value,descr
+    for each (let port in getStandardPorts(protocolType)) {
+      menu.appendItem(port, port, ""); // label,value,descr
+    }
+  },
+
+  onChangedProtocolIncoming : function()
+  {
+    var config = this.getUserConfig();
+    this.adjustIncomingPortToSSLAndProtocol(config);
+    this.fillPortDropdown(config.incoming.type);
+    this.onChangedManualEdit();
+  },
+  onChangedPortIncoming : function()
+  {
+    gEmailWizardLogger.info("incoming port changed");
+    this.adjustIncomingSSLToPort(this.getUserConfig());
+    this.onChangedManualEdit();
+  },
+  onChangedPortOutgoing : function()
+  {
+    gEmailWizardLogger.info("outgoing port changed");
+    this.adjustOutgoingSSLToPort(this.getUserConfig());
+    this.onChangedManualEdit();
+  },
+  onChangedSSLIncoming : function()
+  {
+    this.adjustIncomingPortToSSLAndProtocol(this.getUserConfig());
+    this.onChangedManualEdit();
+  },
+  onChangedSSLOutgoing : function()
+  {
+    this.adjustOutgoingPortToSSLAndProtocol(this.getUserConfig());
+    this.onChangedManualEdit();
+  },
+  onChangedAuth : function()
+  {
+    this.onChangedManualEdit();
+  },
+  onInputUsername : function()
+  {
+    this.onChangedManualEdit();
+  },
+  onInputHostname : function()
+  {
+    this.onChangedManualEdit();
+  },
+
+  /**
+   * Sets the label of the first entry of the dropdown which represents
+   * the new outgoing server.
+   */
+  onOpenOutgoingDropdown : function()
   {
-    var smtpServers = gSmtpManager.smtpServers;
-    while (smtpServers.hasMoreElements())
-    {
-      let existingServer = smtpServers.getNext().QueryInterface(Components.interfaces.nsISmtpServer);
-      if (existingServer.hostname == this._currentConfigFilledIn.outgoing.hostname &&
-          existingServer.port == this._currentConfigFilledIn.outgoing.port &&
-          existingServer.username == this._currentConfigFilledIn.outgoing.username)
-        return existingServer.key;
+    var menulist = e("outgoing_hostname");
+    var menuitem = menulist.getItemAtIndex(0);
+    assert(!menuitem.serverKey, "I wanted the special item for the new host");
+    menuitem.label = menulist.inputField.value;
+  },
+
+  /**
+   * User selected an existing SMTP server (or deselected it).
+   * This changes only the UI. The values are read in getUserConfig().
+   */
+  onChangedOutgoingDropdown : function()
+  {
+    var menulist = e("outgoing_hostname");
+    var menuitem = menulist.selectedItem;
+    if (menuitem && menuitem.serverKey) {
+      // an existing server has been selected from the dropdown
+      menulist.setAttribute("editable", false);
+      _hide("outgoing_port");
+      _hide("outgoing_ssl");
+      _hide("outgoing_authMethod");
+    } else {
+      // new server, with hostname, port etc.
+      menulist.setAttribute("editable", true);
+      _show("outgoing_port");
+      _show("outgoing_ssl");
+      _show("outgoing_authMethod");
+    }
+
+    this.onChangedManualEdit();
+  },
+
+  onChangedManualEdit : function()
+  {
+    if (this._abortable) {
+      this.onStop();
+    }
+    this.validateManualEditComplete();
+  },
+
+  /**
+   * This enables the buttons which allow the user to proceed
+   * once he has entered enough information.
+   *
+   * We can easily and faily surely autodetect everything apart from the
+   * hostname (and username). So, once the user has entered
+   * proper hostnames, change to "manual-edit-have-hostname" mode
+   * which allows to press [Re-test], which starts the detection
+   * of the other values.
+   * Once the user has entered (or we detected) all values, he may
+   * do [Create Account] (tests login and if successful creates the account)
+   * or [Advanced Setup] (goes to Account Manager). Esp. in the latter case,
+   * we will not second-guess his setup and just to as told, so here we make
+   * sure that he at least entered all values.
+   */
+  validateManualEditComplete : function()
+  {
+    // getUserConfig() is expensive, but still OK, not a problem
+    var manualConfig = this.getUserConfig();
+    this._currentConfig = manualConfig;
+    if (manualConfig.isComplete()) {
+      this.switchToMode("manual-edit-complete");
+    } else if (!!manualConfig.incoming.hostname &&
+               !!manualConfig.outgoing.hostname) {
+      this.switchToMode("manual-edit-have-hostname");
+    } else {
+      this.switchToMode("manual-edit");
+    }
+  },
+
+  /**
+   * [Advanced Setup...] button click handler
+   * Only active in manual edit mode, and goes straight into
+   * Account Settings (pref UI) dialog. Requires a backend account,
+   * which requires proper hostname, port and protocol.
+   */
+  onAdvancedSetup : function()
+  {
+    assert(this._currentConfig instanceof AccountConfig);
+    var configFilledIn = this.getConcreteConfig();
+
+    if (checkIncomingServerAlreadyExists(configFilledIn)) {
+      alertPrompt(gStringsBundle.getString("error_creating_account"),
+                  gStringsBundle.getString("incoming_server_exists"));
+      return;
+    }
+
+    gEmailWizardLogger.info("creating account in backend");
+    var newAccount = createAccountInBackend(configFilledIn);
+
+    var windowManager = Cc["@mozilla.org/appshell/window-mediator;1"]
+        .getService(Ci.nsIWindowMediator);
+    var existingAccountManager = windowManager
+        .getMostRecentWindow("mailnews:accountmanager");
+    if (existingAccountManager) {
+      existingAccountManager.focus();
+    } else {
+      window.openDialog("chrome://messenger/content/AccountManager.xul",
+                        "AccountManager", "chrome,centerscreen,modal,titlebar",
+                        { server: newAccount.incomingServer,
+                          selectPage: "am-server.xul" });
+    }
+    window.close();
+  },
+
+  /**
+   * [Re-test] button click handler.
+   * Restarts the config guessing process after a person editing the server
+   * fields.
+   * It's called "half-manual", because we take the user-entered values
+   * as given and will not second-guess them, to respect the user wishes.
+   * (Yes, Sir! Will do as told!)
+   * The values that the user left empty or on "Auto" will be guessed/probed
+   * here. We will also check that the user-provided values work.
+   */
+  onHalfManualTest : function()
+  {
+    var newConfig = this.getUserConfig();
+    gEmailWizardLogger.info(debugObject(newConfig, "manualConfigToTest"));
+    this.startSpinner("looking_up_settings_halfmanual");
+    this.switchToMode("manual-edit-testing");
+    // if (this._userPickedOutgoingServer) TODO
+    var self = this;
+    this._abortable = guessConfig(this._domain,
+      function(type, hostname, port, ssl, done, config) // progress
+      {
+        gEmailWizardLogger.info("progress callback host " + hostname +
+                                " port " +  port + " type " + type);
+      },
+      function(config) // success
+      {
+        self._abortable = null;
+        self._fillManualEditFields(config);
+        self.switchToMode("manual-edit-complete");
+        self.stopSpinner("found_settings_halfmanual");
+      },
+      function(e, config) // guessconfig failed
+      {
+        if (e instanceof CancelledException) {
+          return;
+        }
+        self._abortable = null;
+        gEmailWizardLogger.info("guessConfig failed: " + e);
+        self.showErrorStatus("failed_to_find_settings");
+        self.switchToMode("manual-edit-have-hostname");
+      },
+      newConfig,
+      newConfig.outgoing.existingServerKey ? "incoming" : "both");
+  },
+
+
+
+  /////////////////////////////////////////////////////////////////
+  // UI helper functions
+
+  _prefillConfig : function(initialConfig)
+  {
+    var emailsplit = this._email.split("@");
+    assert(emailsplit.length > 1);
+    var emaillocal = sanitize.nonemptystring(emailsplit[0]);
+    initialConfig.incoming.username = emaillocal;
+    initialConfig.outgoing.username = emaillocal;
+    return initialConfig;
+  },
+
+  clearError : function(which)
+  {
+    _hide(which);
+    _hide(which + "icon");
+    e(which).textContent = "";
+  },
+
+  setError : function(which, msg_name)
+  {
+    try {
+      _show(which);
+      _show(which + "icon");
+      e(which).textContent = gStringsBundle.getString(msg_name);
+    } catch (ex) { alertPrompt("missing error string", msg_name); }
+  },
+
+
+
+  /////////////////////////////////////////////////////////////////
+  // Finish & dialog close functions
+
+  onKeyDown : function(event)
+  {
+    let key = event.keyCode;
+    if (key == 27) { // Escape key
+      this.onCancel();
+      return true;
+    }
+    if (key == 13) { // OK key
+      let buttons = [
+        { id: "next_button", action: makeCallback(this, this.onNext) },
+        { id: "create_button", action: makeCallback(this, this.onCreate) },
+        { id: "half-manual-test_button",
+          action: makeCallback(this, this.onHalfManualTest) },
+      ];
+      for each (let button in buttons) {
+        button.e = e(button.id);
+        if (button.e.hidden || button.e.disabled) {
+          continue;
+        }
+        button.action();
+        return true;
+      }
     }
     return false;
   },
 
-  checkIncomingAccountIsNew : function()
+  onCancel : function()
   {
-    let incoming = this._currentConfigFilledIn.incoming;
-    let isNew = gAccountMgr.findRealServer(incoming.username,
-                                          incoming.hostname,
-                                          sanitize.enum(incoming.type,
-                                                        ["pop3", "imap", "nntp"]),
-                                          incoming.port) == null;
-
-    // if username does not have an '@', also check the e-mail
-    // address form of the name.
-    if (isNew && incoming.username.indexOf("@") < 0) {
-      return gAccountMgr.findRealServer(this._email,
-                                        incoming.hostname,
-                                        sanitize.enum(incoming.type,
-                                                      ["pop3", "imap", "nntp"]),
-                                        incoming.port) == null;
-    }
-    return isNew;
+    window.close();
+    // The window onclose handler will call onWizardShutdown for us.
   },
 
-  toggleDetails : function (id)
+  onWizardShutdown : function()
   {
-    let tech = document.getElementById(id+"_technical");
-    let details = document.getElementById(id+"_details");
-    if (details.getAttribute("collapsed")) {
-      tech.setAttribute("expanded", true);
-      details.removeAttribute("collapsed");
+    if (this._abortable) {
+      this._abortable.cancel(new UserCancelledException());
     }
-    else {
-      details.setAttribute("collapsed", true);
-      tech.removeAttribute("expanded");
+
+    if (this._okCallback) {
+      this._okCallback();
     }
+    gEmailWizardLogger.info("Shutting down email config dialog");
   },
 
-  toggleAcknowledgeWarning : function()
-  {
-    this._warningAcknowledged =
-      document.getElementById('acknowledge_warning').checked;
-    this.checkEnableIKnow();
-  },
 
-  checkEnableIKnow: function()
-  {
-    if ((!this._incomingWarning && !this._outgoingWarning) ||
-        this._warningAcknowledged)
-      document.getElementById('iknow').disabled = false;
-    else
-      document.getElementById('iknow').disabled = true;
-  },
-
-  onOK : function()
+  onCreate : function()
   {
     try {
-
-      gEmailWizardLogger.info("OK/Create button clicked");
-
-      this._password = document.getElementById("password").value;
-      replaceVariables(this._currentConfigFilledIn, this._realname, this._email,
-                       this._password);
-      this.updateConfig(this._currentConfigFilledIn);
+      gEmailWizardLogger.info("Create button clicked");
 
-      // we can't check if the account already exists here, because
-      // we created it to test the password already.
-      if (this._outgoingWarning || this._incomingWarning)
-      {
-        _hide('mastervbox');
-        _show('warningbox');
-
-        let incomingwarningstring;
-        let outgoingwarningstring;
-        let incoming_details;
-        let outgoing_details;
-        let incoming = this._currentConfigFilledIn.incoming;
-        let outgoing = this._currentConfigFilledIn.outgoing;
-        var brandShortName = gBrandBundle.getString("brandShortName");
-        switch (this._incomingWarning)
+      var configFilledIn = this.getConcreteConfig();
+      var self = this;
+      // If the dialog is not needed, it will go straight to OK callback
+      gSecurityWarningDialog.open(this._currentConfig, configFilledIn, true,
+        function() // on OK
         {
-          case 'cleartext':
-            incomingwarningstring = gStringsBundle.getFormattedString(
-              "cleartext_warning", [incoming.hostname]);
-            incoming_details = gStringsBundle.getString("cleartext_details");
-            setText('warning_incoming', incomingwarningstring);
-            setText('incoming_details', incoming_details);
-            _show('incoming_box');
-            _show('acknowledge_warning');
-            break;
-          case 'selfsigned':
-            incomingwarningstring = gStringsBundle.getFormattedString(
-              "selfsigned_warning", [incoming.hostname]);
-            incoming_details = gStringsBundle.getString("selfsigned_details");
-            setText('warning_incoming', incomingwarningstring);
-            setText('incoming_details', incoming_details);
-            _show('incoming_box');
-            _show('acknowledge_warning');
-            break;
-          case '':
-            _hide('incoming_box');
-            _hide('acknowledge_warning');
-        }
-        switch (this._outgoingWarning)
-        {
-          case 'cleartext':
-            outgoingwarningstring = gStringsBundle.getFormattedString(
-              "cleartext_warning", [outgoing.hostname]);
-            outgoing_details = gStringsBundle.getString("cleartext_details");
-            setText('warning_outgoing', outgoingwarningstring);
-            setText('outgoing_details', outgoing_details);
-            _show('outgoing_box');
-            _show('acknowledge_warning');
-            break;
-          case 'selfsigned':
-            outgoingwarningstring = gStringsBundle.getFormattedString(
-              "selfsigned_warning", [outgoing.hostname]);
-            outgoing_details = gStringsBundle.getString("selfsigned_details");
-            setText('warning_outgoing', outgoingwarningstring);
-            setText('outgoing_details', outgoing_details);
-            _show('outgoing_box');
-            _show('acknowledge_warning');
-            break;
-          case '':
-            _hide('outgoing_box');
-            if (this._incomingWarning == '')
-              _hide('acknowledge_warning');
-        }
-        window.sizeToContent();
-      }
-      else
-      {
-        // no certificate or cleartext issues
-        this.validateAndFinish();
-      }
+          self.validateAndFinish(configFilledIn);
+        },
+        function() {}); // on cancel, do nothing
     } catch (ex) {
       gEmailWizardLogger.error("Error creating account.  ex=" + ex +
                                ", stack=" + ex.stack);
       alertPrompt(gStringsBundle.getString("error_creating_account"), ex);
     }
   },
 
-  getMeOutOfHere : function()
-  {
-    // If we're going backwards, we should reset the acknowledge_warning.
-    document.getElementById('acknowledge_warning').checked = false;
-    this.toggleAcknowledgeWarning();
-    document.getElementById("incoming_technical").removeAttribute("expanded");;
-    document.getElementById("incoming_details").setAttribute("collapsed", true);;
-    document.getElementById("outgoing_technical").removeAttribute("expanded");;
-    document.getElementById("outgoing_details").setAttribute("collapsed", true);;
-    _hide('warningbox');
-    _show('mastervbox');
-    window.sizeToContent();
-  },
-
+  // called by onCreate()
   validateAndFinish : function()
   {
-    // if we're coming from the cert warning dialog
-    _show('mastervbox');
-    _hide('warningbox');
-    window.sizeToContent();
+    var configFilledIn = this.getConcreteConfig();
 
-    if (!this.checkIncomingAccountIsNew())
-    {
+    if (checkIncomingServerAlreadyExists(configFilledIn)) {
       alertPrompt(gStringsBundle.getString("error_creating_account"),
                   gStringsBundle.getString("incoming_server_exists"));
       return;
     }
 
-    // No need to check if we aren't adding it.
-    if (this._currentConfigFilledIn.outgoing.addThisServer)
-    {
-      let existingKey = this.keyForExistingOutgoingAccount();
-      if (existingKey)
-      {
-        this._currentConfigFilledIn.outgoing.addThisServer = false;
-        this._currentConfigFilledIn.outgoing.existingServerKey = existingKey;
+    if (configFilledIn.outgoing.addThisServer) {
+      let existingServer = checkOutgoingServerAlreadyExists(configFilledIn);
+      if (existingServer) {
+        configFilledIn.outgoing.addThisServer = false;
+        configFilledIn.outgoing.existingServerKey = existingServer.key;
       }
     }
 
-    document.getElementById('create_button').disabled = true;
-    var me = this;
-    if (!this._verifiedConfig)
-      this.verifyConfig(function(successfulConfig) // success
-                        {
-                          // the auth might have changed, so we
-                          // should back-port it to the current config.
-                          me._currentConfigFilledIn.incoming.auth =
-                              successfulConfig.incoming.auth;
-                          me._currentConfigFilledIn.outgoing.auth =
-                              successfulConfig.outgoing.auth;
-                          me.finish();
-                        },
-                        function(e) // failure
-                        {
-                          // Enable the username and password fields, and
-                          // the create button to re-check.
-                          document.getElementById("create_button").disabled = false;
-                          document.getElementById("username").disabled = false;
-                          document.getElementById("password").disabled = false;
-                        });
-    else
-      this.finish();
-  },
-
-  verifyConfig : function(successCallback, errorCallback)
-  {
-    var me = this;
-    gEmailWizardLogger.info("Verifying config.");
-    this.startSpinner("username", "checking_password");
-
+    // TODO use a UI mode (switchToMode()) for verfication, too.
+    // But we need to go back to the previous mode, because we might be in
+    // "result" or "manual-edit-complete" mode.
+    _disable("create_button");
+    _disable("half-manual-test_button");
+    _disable("advanced-setup_button");
+    // no stop button: backend has no ability to stop :-(
+    var self = this;
+    this.startSpinner("checking_password");
     // logic function defined in verifyConfig.js
     verifyConfig(
-      this._currentConfigFilledIn,
+      configFilledIn,
       // guess login config?
-      this._currentConfigFilledIn.source != AccountConfig.kSourceXML,
+      configFilledIn.source != AccountConfig.kSourceXML,
+      // TODO Instead, the following line would be correct, but I cannot use it,
+      // because some other code doesn't adhere to the expectations/specs.
+      // Find out what it was and fix it.
+      //concreteConfig.source == AccountConfig.kSourceGuess,
       this._parentMsgWindow,
-      function(successfulServer) // success
+      function(successfulConfig) // success
       {
-        me._verifiedConfig = true;
-        if (me._currentConfigFilledIn.incoming.password)
-          me.stopSpinner("password_ok");
-        if (successCallback)
-          successCallback(successfulServer);
+        self.stopSpinner(successfulConfig.incoming.password ?
+                         "password_ok" : null);
+
+        // the auth might have changed, so we
+        // should back-port it to the current config.
+        self._currentConfig.incoming.auth = successfulConfig.incoming.auth;
+        self._currentConfig.outgoing.auth = successfulConfig.outgoing.auth;
+        self.finish();
       },
       function(e) // failed
       {
-        me.stopSpinner("config_unverifiable");
-        me._updateSpinner("username", true);
-        me.setError('passworderror', 'user_pass_invalid');
-        if (errorCallback)
-          errorCallback(e);
+        self.showErrorStatus("config_unverifiable");
+        // TODO bug 555448: wrong error msg, there may be a 1000 other
+        // reasons why this failed, and this is misleading users.
+        self.setError("passworderror", "user_pass_invalid");
+        // TODO use switchToMode(), see above
+        // give user something to proceed after fixing
+        _enable("create_button");
+        // hidden in non-manual mode, so it's fine to enable
+        _enable("half-manual-test_button");
+        _enable("advanced-setup_button");
       });
   },
 
   finish : function()
   {
     gEmailWizardLogger.info("creating account in backend");
-    this._currentConfigFilledIn.rememberPassword =
-      document.getElementById("remember_password").checked;
-    createAccountInBackend(this._currentConfigFilledIn);
-    window.close();
-  },
-
-  advancedSettings : function()
-  {
-    let shouldEraseConfig = !this._currentConfigFilledIn;
-    let config = this.getUserConfig();
-    this._currentConfigFilledIn = config.copy();
-
-    // call this to set the password
-    replaceVariables(config, this._realname, this._email, this._password);
-
-    if (!this.checkIncomingAccountIsNew()) {
-      alertPrompt(gStringsBundle.getString("error_creating_account"),
-                  gStringsBundle.getString("incoming_server_exists"));
-      if (shouldEraseConfig)
-        this._currentConfigFilledIn = null;
-      return;
-    }
-
-    gEmailWizardLogger.info("creating account in backend");
-    config.rememberPassword =
-      document.getElementById("remember_password").checked;
-    var newAccount = createAccountInBackend(config);
-    var windowManager =
-      Components.classes['@mozilla.org/appshell/window-mediator;1']
-                .getService(Components.interfaces.nsIWindowMediator);
-
-    var existingAccountManager =
-      windowManager.getMostRecentWindow("mailnews:accountmanager");
-
-    if (existingAccountManager)
-      existingAccountManager.focus();
-    else
-      window.openDialog("chrome://messenger/content/AccountManager.xul",
-                        "AccountManager", "chrome,centerscreen,modal,titlebar",
-                        { server: newAccount.incomingServer,
-                          selectPage: 'am-server.xul' });
+    createAccountInBackend(this.getConcreteConfig());
     window.close();
   },
-  /**
-   * Gets the values that the user edited in the right of the dialog.
-   */
-  getUserConfig : function()
-  {
-    var config = this._currentConfig;
-    if (!config)
-      config = new AccountConfig();
-
-    config.source = AccountConfig.kSourceGuess;
-
-    // Did the user select one of the already configured SMTP servers from the
-    // drop-down list? If so, use it.
-    if (this._userPickedOutgoingServer)
-    {
-      config.outgoing.addThisServer = false;
-      config.outgoing.existingServerKey =
-        document.getElementById("outgoing_server").selectedItem.value;
-      config.outgoing.existingServerLabel =
-        document.getElementById("outgoing_server").selectedItem.label;
-    }
-    else
-    {
-      if (!config.outgoing.username)
-        config.outgoing.username = document.getElementById("username").value;
-      config.outgoing.hostname =
-        sanitize.hostname(document.getElementById("outgoing_server").value);
-      document.getElementById("outgoing_server").value =
-        config.outgoing.hostname;
-      config.outgoing.port =
-        sanitize.integerRange(document.getElementById("outgoing_port").value, 1,
-                              kHighestPort);
-      config.outgoing.socketType =
-        parseInt(document.getElementById("outgoing_security").value);
-    }
-    config.incoming.username = document.getElementById("username").value;
-    config.incoming.hostname =
-      sanitize.hostname(document.getElementById("incoming_server").value);
-    document.getElementById("incoming_server").value = config.incoming.hostname;
-    config.incoming.port =
-      sanitize.integerRange(document.getElementById("incoming_port").value, 1,
-                            kHighestPort);
-    config.incoming.type =
-      document.getElementById("incoming_protocol").value == 1 ? "imap" : "pop3";
-    // type is a string, "imap" or "pop3", protocol is a protocol type.
-    config.incoming.protocol = sanitize.translate(config.incoming.type, { "imap" : 0, "pop3" : 1});
-    config.incoming.socketType =
-      parseInt(document.getElementById("incoming_security").value);
-
-    return config;
-  },
-
-  _setIncomingStatus : function(state, details)
-  {
-    if (!details)
-      details = "";
-
-    switch (state)
-    {
-      case 'strong':
-        this._incomingState = 'done';
-        this._incomingWarning = '';
-        // fall through
-      case 'weak':
-        this._incomingWarning = details;
-        this._warningAcknowledged = false;
-        break;
-      default:
-        this._incomingState = state;
-    }
-    // since we look for SSL/TLS first, if we get 'weak', we're
-    // stuck with it, and might as well admit we're done.
-    if (state == 'weak')
-      this._incomingState = 'done';
-
-    this._setIconAndTooltip('incoming', state, details);
-  },
-
-  _setOutgoingStatus: function(state, details)
-  {
-    if (!details)
-      details = '';
-
-    if (this._userPickedOutgoingServer)
-      return;
-
-    switch (state)
-    {
-      case 'strong':
-        this._outgoingState = 'done';
-        this._outgoingWarning = '';
-        // fall through
-      case 'weak':
-        this._outgoingWarning = details;
-        this._warningAcknowledged = false;
-        break
-      default:
-        this._outgoingState = state;
-    }
-    // since we look for SSL/TLS first, if we get 'weak', we're
-    // stuck with it, and might as well admit we're done.
-    if (state == 'weak')
-      this._outgoingState = 'done';
-
-    this._setIconAndTooltip('outgoing', state, details);
-  },
-
-  _setIconAndTooltip : function(id, state, details)
-  {
-    gEmailWizardLogger.warn(id + " setting icon and tooltip " +
-                            state + ":" + details);
-    let icon = document.getElementById(id + "_status");
-    icon.setAttribute("state", state);
-    switch (state)
-    {
-      case "weak":
-        icon.setAttribute("tooltip", "insecureserver-" + details);
-        icon.setAttribute("popup", "insecureserver-" + details + "-panel");
-        this._updateSpinner(id, true);
-        break;
-      case "hidden":
-        icon.removeAttribute("tooltip");
-        icon.removeAttribute("popup");
-        this._updateSpinner(id, false);
-        break;
-      case "strong":
-        icon.setAttribute("tooltip", "secureservertooltip");
-        icon.setAttribute("popup", "secureserver-panel");
-        this._updateSpinner(id, true);
-        break;
-      case "failed":
-        icon.removeAttribute("tooltip");
-        icon.removeAttribute("popup");
-        this._updateSpinner(id, true);
-        break;
-    }
-  },
-
-  showConfigDetails : function()
-  {
-    // show the config details area
-    _show("settingsbox");
-    // also show the create button because it's outside of the area
-    _show("create_button");
-  },
-
-  hideConfigDetails : function()
-  {
-    _hide("settingsbox");
-    document.getElementById("create_button").disabled = true;
-    _hide("create_button");
-  },
-
-  /* Clears out the config details information, this is really only meant to be
-   * called from the (Back) button.
-   * XXX This can destroy user input where it might not be necessary
-   */
-  clearConfigDetails : function()
-  {
-    for (let i = 0; i < this._configDetailTextInputs.length; i++ )
-    {
-      document.getElementById(this._configDetailTextInputs[i]).value = "";
-    }
-
-    this._setIncomingStatus('hidden');
-    this._setOutgoingStatus('hidden');
-  },
-
-  /* Setting the config details form so it can be edited.  We also disable
-   * (and hide) the create button during this time because we don't know what
-   * might have changed.  The function called from the button that restarts
-   * the config check should be enabling the config button as needed.
-   */
-  editConfigDetails : function()
-  {
-    gEmailWizardLogger.info("onEdit");
-    // Add the custom entry to the menulist, if it's not already there.
-    let menulist = document.getElementById("outgoing_server");
-    if (this._smtpServerCount == menulist.itemCount) {
-      var hostname = document.getElementById("outgoing_server").value;
-      let menuitem = menulist.insertItemAt(0, hostname, hostname);
-      menuitem.hostname = hostname;
-    }
-    this._currentConfig.incomingAlternatives = [];
-    this._currentConfig.outgoingAlternatives = [];
-
-    this._disableConfigDetails(false);
-    _hide("popimap_radio_area");
-    this._setIncomingStatus("failed");
-    this._setOutgoingStatus("failed");
-    document.getElementById("advanced_settings").disabled = false;
-    document.getElementById("create_button").disabled = true;
-    _hide("stop_button");
-    _hide("edit_button");
-    _show("go_button");
-  },
-
-  /* This _doesn't_ set create back to enabled, that needs to be done in a
-   * config check.  Only showing the button.
-   * XXX However it seems that a disabled button can still receive click
-   *     events so this needs to be looked into further.
-   */
-  goWithConfigDetails : function()
-  {
-    this._disableConfigDetails(true);
-    _show("create_button");
-  },
-
-  // IDs of <textbox> inputs that can have a .value attr set
-  _configDetailTextInputs : ["username", "incoming_server",
-                             "incoming_port", "incoming_protocol",
-                             "outgoing_port"],
-
-  // IDs of the <menulist> elements that don't accept a .value attr but can
-  // be disabled
-  _configDetailMenulists : ["remember_password", "incoming_security",
-                            "outgoing_server", "outgoing_security"],
-
-  /* Helper function to loop through all config details form elements and
-   * enable or disable each
-   */
-  _disableConfigDetails : function(disabled)
-  {
-    let formElements =
-      this._configDetailTextInputs.concat(this._configDetailMenulists)
-                                  .concat("password");
-    for (let i = 0; i < formElements.length; i++)
-    {
-      document.getElementById(formElements[i]).disabled = disabled;
-    }
-  },
-
-  /**
-   * Updates a (probed) config to the user,
-   * in the config details area
-   *
-   * @param config {AccountConfig} The config to present to user
-   */
-  updateConfig : function(config)
-  {
-    _show("advanced_settings");
-    this._currentConfig = config;
-    // if we have a username, set it.
-    if (config.incoming.username)
-    {
-      document.getElementById("username").value = config.incoming.username;
-    }
-    else
-    {
-      // XXX needs more thought
-    }
-
-    // incoming server
-    if (config.incoming.hostname && config.incoming.hostname != -1)
-    {
-      /* -1 failure needs to be handled in the context of the failure
-        * at this point all we know is there is no hostname, but not why
-        * the error handling behaviour needs to be done above
-        */
-      document.getElementById("incoming_server").value =
-        config.incoming.hostname;
-      document.getElementById("incoming_port").value = config.incoming.port;
-      document.getElementById("incoming_security").value =
-        config.incoming.socketType;
-      document.getElementById("incoming_protocol").value =
-        sanitize.translate(config.incoming.type, { "imap" : 1, "pop3" : 2});
-      document.getElementById("popimap_radiogroup").value =
-        config.incoming.type;
-
-      // en/disable IMAP vs. POP radiogroup
-      let haveOther = false;
-      if (config.incomingAlternatives && config.incomingAlternatives.length)
-      {
-        let alternates = config.incomingAlternatives.filter(function(c) {
-          return c.type != config.incoming.type;
-        });
-        haveOther = alternates.length > 0;
-      }
-      let popImapRadiogroup = document.getElementById("popimap_radio_area");
-      let wasHidden = popImapRadiogroup.hidden;
-      popImapRadiogroup.hidden = !haveOther;
-      // Hide "(recommended)" behind IMAP, if POP3 was first choice from XML,
-      // and not just selected by user later.
-      if (!config._imapRecommendedToggled)
-        document.getElementById("imap_recommended").hidden =
-            config.incoming.type == "pop3";
-      config._imapRecommendedToggled = true;
-      if (wasHidden == haveOther) // hidden changed, so resize
-        window.sizeToContent();
-
-      if (config.incoming._inprogress)
-      {
-        this._setIncomingStatus('probing');
-      }
-      else
-      {
-        // We got an incoming server, so we can enable the advanced settings.
-        document.getElementById("advanced_settings").disabled = false;
-        switch (config.incoming.socketType)
-        {
-          case 2: // SSL / TLS
-          case 3: // STARTTLS
-            if (config.incoming.badCert)
-            {
-              this._setIncomingStatus('weak', 'selfsigned');
-              var params = { exceptionAdded : false };
-              params.prefetchCert = true;
-              params.location = config.incoming.targetSite;
-              window.openDialog('chrome://pippki/content/exceptionDialog.xul',
-                                '','chrome,centerscreen,modal', params);
-              config.incoming.badCert = false;
-            }
-            else
-            {
-              this._setIncomingStatus('strong');
-            }
-            break;
-          case 1: // plain
-            this._setIncomingStatus('weak', 'cleartext');
-            break;
-          default:
-            throw new NotReached("sslType " + config.incoming.socketType + " unknown");
-        }
-      }
-    }
-
-    // outgoing server
-    if (config.outgoing.hostname && config.outgoing.hostname != -1)
-    {
-      /* -1 failure needs to be handled in the context of the failure
-       * at this point all we know is there is no hostname, but not why.
-       * The error handling behaviour needs to be done elsewhere
-       */
-      if (!gEmailConfigWizard._userPickedOutgoingServer)
-      {
-        document.getElementById("outgoing_server").value =
-          config.outgoing.hostname;
-        document.getElementById("outgoing_port").value = config.outgoing.port;
-        document.getElementById("outgoing_security").value =
-          config.outgoing.socketType;
-        _show("outgoing_port");
-        _show("outgoing_security");
-        if (config.outgoing._inprogress) {
-          this._setOutgoingStatus('probing');
-        }
-        else
-        {
-          switch (config.outgoing.socketType)
-          {
-            case 2: // SSL / TLS
-            case 3: // STARTTLS
-              if (config.outgoing.badCert)
-                this._setOutgoingStatus('weak', 'selfsigned');
-              else
-                this._setOutgoingStatus('strong');
-              break;
-            case 1: // plain
-              this._setOutgoingStatus('weak', 'cleartext');
-              break;
-            default:
-              throw new NotReached("sslType " + config.incoming.socketType + " unknown");
-          }
-        }
-      }
-      else
-      {
-        config.outgoing.addThisServer = false;
-        config.outgoing.useGlobalPreferredServer = true;
-      }
-    }
-  },
-
-  /* (Edit) button click handler.  This turns the config details area into an
-   * editable form and makes the (Go) button appear.  The edit button should
-   * only be available after the config probing is completely finished,
-   * replacing what was the (Stop) button.
-   */
-  onEdit : function()
-  {
-    this.editConfigDetails();
-  },
-
-  // short hand so I didn't have to insert the swap functions in various places
-  showEditButton : function() { _show("edit_button"); _hide("stop_button"); },
-
-  /* (Stop) button click handler.  This should stop short any probing or config
-   * guessing progress and changing the config details area into manual edit
-   * mode.  This button should only be available during probing, after which it
-   * is replaced by the (Edit) button.
-   */
-  onStop : function()
-  {
-    if (!this._probeAbortable)
-    {
-      gEmailWizardLogger.info("onStop without a _probeAbortable to cancel");
-    }
-    else
-    {
-      this._probeAbortable.cancel();
-      gEmailWizardLogger.info("onStop cancelled _probeAbortable");
-    }
-    this.stopSpinner('manually_edit_config');
-    this.editConfigDetails();
-  },
-
-  /* (Go) button click handler.  Restarts the config guessing process after a
-   * person possibly editing the fields.  The go button replaces either the
-   * (Stop) or (Edit) button after they have been clicked.
-   */
-  onGo : function()
-  {
-    // swap out go for stop button
-    _hide("go_button");
-    // the stop is naturally not hidden so has no place in the code where it is
-    // told to be shown
-    _show("stop_button");
-    this._password = document.getElementById("password").value;
-    this.goWithConfigDetails();
-    var newConfig = this.getUserConfig();
-    if (this._incomingState != "done") {
-      newConfig.incoming._inprogress = true;
-      gEmailConfigWizard._startProbingIncoming(newConfig);
-    }
-    if (!this._userPickedOutgoingServer && this._outgoingState != "done") {
-      newConfig.outgoing._inprogress = true;
-      gEmailConfigWizard._startProbingOutgoing(newConfig);
-    }
-  },
-
-  onCancel : function()
-  {
-    // The window onclose handler will call onWizardShutdown for us.
-    window.close();
-  },
-
-  // UI helper functions
-
-  startSpinner: function(which, actionStrName)
-  {
-    this._showStatusTitle(false, actionStrName);
-    if (which == "all") {
-      this._setIconAndTooltip("incoming", "hidden", "");
-      this._setIconAndTooltip("outgoing", "hidden", "");
-    }
-    else
-      this._setIconAndTooltip(which, "hidden", "");
-    gEmailWizardLogger.warn(which + "spinner start " + actionStrName);
-  },
-
-  stopSpinner: function(actionStrName)
-  {
-    this._showStatusTitle(true, actionStrName);
-    this._updateSpinner("incoming", true);
-    this._updateSpinner("outgoing", true);
-    gEmailWizardLogger.warn("all spinner stop " + actionStrName);
-  },
-
-  _updateSpinner: function(which, stop)
-  {
-    document.getElementById(which + "_spinner").hidden = stop;
-  },
-
-  _showStatusTitle: function(success, msgName)
-  {
-    let brandShortName = document.getElementById("bundle_brand")
-                                 .getString("brandShortName");
-    let msg;
-    try {
-      msg = msgName
-            ? gStringsBundle.getFormattedString(msgName, [brandShortName])
-            : "";
-    } catch(ex) {
-      gEmailWizardLogger.error("missing string for " + msgName);
-      logException(new Exception("missing string for name: " + msgName));
-    }
-
-    let title = document.getElementById("config_status_title");
-    title.hidden = false;
-    title.textContent = msg;
-    gEmailWizardLogger.info("show status title " + (success ? "success" : "failure") +
-                            ", msg: " + msg);
-  },
-
-  _prefillConfig: function(initialConfig)
-  {
-    var emailsplit = this._email.split("@");
-    if (emailsplit.length != 2)
-      throw new Exception(gStringsBundle.getString("double_check_email"));
-
-    var emaillocal = sanitize.nonemptystring(emailsplit[0]);
-    initialConfig.incoming.username = emaillocal;
-    initialConfig.outgoing.username = emaillocal;
-    initialConfig.outgoing.protocol = 'smtp';
-    return initialConfig;
-  },
-
-  _startProbingIncoming : function(config)
-  {
-    gEmailWizardLogger.info("_startProbingIncoming: " + config.incoming.hostname +
-                            " probe = " + this._probeAbortable);
-    this.startSpinner("incoming", "looking_up_settings_guess");
-
-    config.incoming._inprogress = true;
-    // User entered hostname, we may want to probe port and protocol and socketType
-    if (!this._userChangedIncomingProtocol)
-      config.incoming.protocol = undefined;
-    if (!this._userChangedIncomingPort)
-      config.incoming.port = undefined;
-    if (!this._userChangedIncomingSocketType)
-      config.incoming.socketType = undefined;
-    if (!this._userChangedIncomingServer)
-      config.incoming.hostname = undefined;
-
-    let domain = this._domain;
-    if (config.incoming.hostname)
-      domain = config.incoming.hostname;
-
-    if (this._probeAbortable)
-    {
-      gEmailWizardLogger.info("restarting probe: " + domain);
-      this._probeAbortable.restart(domain, config, "incoming",
-                                   config.incoming.type,
-                                   config.incoming.port,
-                                   config.incoming.socketType);
-    }
-    else
-    {
-      this._guessConfig(domain, config, "incoming");
-    }
-  },
-
-  _startProbingOutgoing : function(config)
-  {
-    gEmailWizardLogger.info("_startProbingOutgoing: " + config.outgoing.hostname +
-                            " probe = " + this._probeAbortable);
-    this.startSpinner("outgoing", "looking_up_settings_guess");
-
-    config.outgoing._inprogress = true;
-    // User entered hostname, we want to probe port and protocol and socketType
-    if (!this._userChangedOutgoingPort)
-      config.outgoing.port = undefined;
-    if (!this._userChangedOutgoingSocketType)
-      config.outgoing.socketType = undefined;
-
-    if (this._probeAbortable)
-    {
-      gEmailWizardLogger.info("restarting probe: " + config.outgoing.hostname);
-      this._probeAbortable.restart(config.outgoing.hostname, config, "outgoing",
-                                   "smtp", config.outgoing.port,
-                                   config.outgoing.socketType);
-    }
-    else
-    {
-      this._guessConfig(config.outgoing.hostname, config, "outgoing");
-    }
-  },
-
-  clearError: function(which) {
-    _hide(which);
-    _hide(which+"icon");
-    document.getElementById(which).textContent = "";
-  },
-
-  setError: function(which, msg_name) {
-    try {
-      _show(which);
-      _show(which+"icon");
-      document.getElementById(which).textContent =
-        gStringsBundle.getString(msg_name);
-    }
-    catch(ex) {
-      alertPrompt("missing error string", msg_name);
-    }
-  },
-
-  onKeyDown : function(event)
-  {
-    let key = event.keyCode;
-    if (key == 27) {
-      this.onCancel();
-      return true;
-    }
-    if (key == 13 && !document.getElementById("go_button").hidden
-                  && !document.getElementById("go_button").disabled) {
-      this.onGo();
-      return true;
-    }
-    if (key == 13 && !document.getElementById("create_button").hidden
-                  && !document.getElementById("create_button").disabled) {
-      this.onOK();
-      return true;
-    }
-    if (key == 13 && !document.getElementById("next_button").hidden
-                  && !document.getElementById("next_button").disabled) {
-      this.onNext();
-      return true;
-    }
-    return false;
-  },
-
-  onWizardShutdown: function EmailConfigWizard_onWizardshutdown() {
-    if (this._probeAbortable)
-      this._probeAbortable.cancel();
-
-    if (this._okCallback)
-      this._okCallback();
-    gEmailWizardLogger.info("Shutting down email config dialog");
-  }
 };
 
 var gEmailConfigWizard = new EmailConfigWizard();
 gEmailWizardLogger.info("email account setup dialog");
 
-function sslLabel(val)
+
+function serverMatches(a, b)
 {
-  switch (val)
-  {
-    case 1:
-      return gStringsBundle.getString("no_encryption");
-    case 2:
-      return gStringsBundle.getString("ssl_tls");
-    case 3:
-      return gStringsBundle.getString("starttls");;
-    default:
-      throw new NotReached("Unknown SSL type");
-  }
+  return a.type == b.type &&
+         a.hostname == b.hostname &&
+         a.port == b.port &&
+         a.socketType == b.socketType &&
+         a.auth == b.auth;
+}
+
+var _gStandardPorts = {};
+_gStandardPorts["imap"] = [ 143, 993 ];
+_gStandardPorts["pop3"] = [ 110, 995 ];
+_gStandardPorts["smtp"] = [ 587, 25, 465 ]; // order matters
+var _gAllStandardPorts = _gStandardPorts["smtp"]
+    .concat(_gStandardPorts["imap"]).concat(_gStandardPorts["pop3"]);
+
+function isStandardPort(port)
+{
+  return _gAllStandardPorts.indexOf(port) != -1;
+}
+
+function getStandardPorts(protocolType)
+{
+  return _gStandardPorts[protocolType];
 }
 
 
-function setText(id, value)
+/**
+ * Warning dialog, warning user about lack of, or inappropriate, encryption.
+ *
+ * This is effectively a separate dialog, but implemented as part of
+ * this dialog. It works by hiding the main dialog part and unhiding
+ * the this part, and vice versa, and resizing the dialog.
+ */
+function SecurityWarningDialog()
+{
+  this._acknowledged = new Array();
+}
+SecurityWarningDialog.prototype =
 {
-  var element = document.getElementById(id);
-  if (!element)
-    return;
+  /**
+   * {Array of {(incoming or outgoing) server part of {AccountConfig}}
+   * A list of the servers for which we already showed this dialog and the
+   * user approved the configs. For those, we won't show the warning again.
+   * (Make sure to store a copy in case the underlying object is changed.)
+   */
+  _acknowledged : null,
+
+  /**
+   * Checks whether we need to warn about this config.
+   *
+   * We (currently) warn if
+   * - the mail travels unsecured (no SSL/STARTTLS)
+   * - the SSL certificate is not proper
+   * - (We don't warn about unencrypted passwords specifically,
+   *   because they'd be encrypted with SSL and without SSL, we'd
+   *   warn anyways.)
+   *
+   * We may not warn despite these conditions if we had shown the
+   * warning for that server before and the user acknowledged it.
+   * (Given that this dialog object is static/global and persistent,
+   * we can store that approval state here in this object.)
+   *
+   * @param configSchema @see open()
+   * @param configFilledIn @see open()
+   * @returns {Boolean}   true when the dialog should be shown
+   *      (call open()). if false, the dialog can and should be skipped.
+   */
+  needed : function(configSchema, configFilledIn)
+  {
+    assert(configSchema instanceof AccountConfig);
+    assert(configFilledIn instanceof AccountConfig);
+    assert(configSchema.isComplete());
+    assert(configFilledIn.isComplete());
+
+    var incomingOK = configFilledIn.incoming.socketType > 1 &&
+        !configFilledIn.incoming.badCert;
+    var outgoingOK = configFilledIn.outgoing.socketType > 1 &&
+        !configFilledIn.outgoing.badCert;
+
+    if (!incomingOK) {
+      incomingOK = this._acknowledged.some(
+          function(ackServer) {
+            return serverMatches(ackServer, configFilledIn.incoming);
+          });
+    }
+    if (!outgoingOK) {
+      outgoingOK = this._acknowledged.some(
+          function(ackServer) {
+            return serverMatches(ackServer, configFilledIn.outgoing);
+          });
+    }
+    return !incomingOK || !outgoingOK;
+  },
+
+  /**
+   * Opens the dialog, fills it with values, and shows it to the user.
+   *
+   * The function is async: it returns immediately, and when the user clicks
+   * OK or Cancel, the callbacks are called. There the callers proceed as
+   * appropriate.
+   *
+   * @param configSchema   The config, with placeholders not replaced yet.
+   *      This object may be modified to store the user's confirmations, but
+   *      currently that's not the case.
+   * @param configFilledIn   The concrete config with placeholders replaced.
+   * @param onlyIfNeeded {Boolean}   If there is nothing to warn about,
+   *     call okCallback() immediately (and sync).
+   * @param okCallback {function(config {AccountConfig})}
+   *      Called when the user clicked OK and approved the config including
+   *      the warnings. |config| is without placeholders replaced.
+   * @param cancalCallback {function()}
+   *      Called when the user decided to heed the warnings and not approve.
+   */
+  open : function(configSchema, configFilledIn, onlyIfNeeded,
+                  okCallback, cancelCallback)
+  {
+    assert(typeof(okCallback) == "function");
+    assert(typeof(cancelCallback) == "function");
+    // needed() also checks the parameters
+    var needed = this.needed(configSchema, configFilledIn);
+    if (!needed && onlyIfNeeded) {
+      okCallback();
+      return;
+    }
+    assert(needed, "security dialog opened needlessly");
+    this._currentConfigFilledIn = configFilledIn;
+    this._okCallback = okCallback;
+    this._cancelCallback = cancelCallback;
+    var incoming = configFilledIn.incoming;
+    var outgoing = configFilledIn.outgoing;
+
+    _hide("mastervbox");
+    _show("warningbox");
+    // reset dialog, in case we've shown it before
+    e("acknowledge_warning").checked = false;
+    _disable("iknow");
+    e("incoming_technical").removeAttribute("expanded");
+    e("incoming_details").setAttribute("collapsed", true);
+    e("outgoing_technical").removeAttribute("expanded");
+    e("outgoing_details").setAttribute("collapsed", true);
+
+    if (incoming.socketType == 1) {
+      setText("warning_incoming", gStringsBundle.getFormattedString(
+          "cleartext_warning", [incoming.hostname]));
+      setText("incoming_details", gStringsBundle.getString(
+          "cleartext_details"));
+      _show("incoming_box");
+      _show("acknowledge_warning");
+    } else if (incoming.badCert) {
+      setText("warning_incoming", gStringsBundle.getFormattedString(
+          "selfsigned_warning", [incoming.hostname]));
+      setText("incoming_details", gStringsBundle.getString(
+          "selfsigned_details"));
+      _show("incoming_box");
+      _show("acknowledge_warning");
+    } else {
+      _hide("incoming_box");
+      _hide("acknowledge_warning");
+    }
 
-  if (element.localName == "textbox" || element.localName == "label")
-    element.value = value;
-  else if (element.localName == "description")
-    element.textContent = value;
-  else
-    throw new NotReached("XUL element type not supported");
+    if (outgoing.socketType == 1) {
+      setText("warning_outgoing", gStringsBundle.getFormattedString(
+          "cleartext_warning", [outgoing.hostname]));
+      setText("outgoing_details", gStringsBundle.getString(
+          "cleartext_details"));
+      _show("outgoing_box");
+      _show("acknowledge_warning");
+    } else if (outgoing.badCert) {
+      setText("warning_outgoing", gStringsBundle.getFormattedString(
+          "selfsigned_warning", [outgoing.hostname]));
+      setText("outgoing_details", gStringsBundle.getString(
+          "selfsigned_details"));
+      _show("outgoing_box");
+      _show("acknowledge_warning");
+    } else {
+      _hide("outgoing_box");
+    }
+    window.sizeToContent();
+  },
+
+  toggleDetails : function (id)
+  {
+    let details = e(id + "_details");
+    let tech = e(id + "_technical");
+    if (details.getAttribute("collapsed")) {
+      details.removeAttribute("collapsed");
+      tech.setAttribute("expanded", true);
+    } else {
+      details.setAttribute("collapsed", true);
+      tech.removeAttribute("expanded");
+    }
+  },
+
+  /**
+   * user checked checkbox that he understood it and wishes
+   * to ignore the warning.
+   */
+  toggleAcknowledge : function()
+  {
+    if (e("acknowledge_warning").checked) {
+      _enable("iknow");
+    } else {
+      _disable("iknow");
+    }
+  },
+
+  /**
+   * [Cancel] button pressed. Get me out of here!
+   */
+  onCancel : function()
+  {
+    _hide("warningbox");
+    _show("mastervbox");
+    window.sizeToContent();
+
+    this._cancelCallback();
+  },
+
+  /**
+   * [OK] button pressed.
+   * Implies that the user toggled the acknowledge checkbox,
+   * i.e. approved the config and ignored the warnings,
+   * otherwise the button would have been disabled.
+   */
+  onOK : function()
+  {
+    assert(e("acknowledge_warning").checked);
+
+    var overrideOK = this.showCertOverrideDialog(this._currentConfigFilledIn);
+    if (!overrideOK) {
+      this.onCancel();
+      return;
+    }
+
+    // need filled in, in case hostname is placeholder
+    var storeConfig = this._currentConfigFilledIn.copy();
+    this._acknowledged.push(storeConfig.incoming);
+    this._acknowledged.push(storeConfig.outgoing);
+
+    _show("mastervbox");
+    _hide("warningbox");
+    window.sizeToContent();
+
+    this._okCallback();
+  },
+
+  /**
+   * Shows a(nother) dialog which allows the user to see and override
+   * (manually accept) a bad certificate. It also optionally adds it
+   * permanently to the "good certs" store of NSS in the profile.
+   * Only shows the dialog, if there are bad certs. Otherwise, it's a no-op.
+   *
+   * The dialog is the standard PSM cert override dialog.
+   *
+   * @param config {AccountConfig} concrete
+   * @returns true, if all certs are fine or the user accepted them.
+   *     false, if the user cancelled.
+   *
+   * static function
+   * sync function: blocks until the dialog is closed.
+   */
+  showCertOverrideDialog : function(config)
+  {
+    if (config.incoming.socketType > 1 && // SSL or STARTTLS
+        config.incoming.badCert) {
+      var params = {
+        exceptionAdded : false,
+        prefetchCert : true,
+        location : config.incoming.targetSite,
+      };
+      window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+                        "","chrome,centerscreen,modal", params);
+      if (params.exceptionAdded) { // set by dialog
+        config.incoming.badCert = false;
+      } else {
+        return false;
+      }
+    }
+    if (!config.outgoing.existingServerKey) {
+      if (config.outgoing.socketType > 1 && // SSL or STARTTLS
+          config.outgoing.badCert) {
+        var params = {
+          exceptionAdded : false,
+          prefetchCert : true,
+          location : config.outgoing.targetSite,
+        };
+        window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+                          "","chrome,centerscreen,modal", params);
+        if (params.exceptionAdded) { // set by dialog
+          config.outgoing.badCert = false;
+        } else {
+          return false;
+        }
+      }
+    }
+    return true;
+  },
 }
+var gSecurityWarningDialog = new SecurityWarningDialog();
--- a/mailnews/base/prefs/content/accountcreation/emailWizard.xul
+++ b/mailnews/base/prefs/content/accountcreation/emailWizard.xul
@@ -31,40 +31,45 @@
    - decision by deleting the provisions above and replace them with the notice
    - and other provisions required by the LGPL or the GPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://messenger/skin/accountCreation.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountCreation.css"
+                                              type="text/css"?>
 
 <!DOCTYPE window [
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
   %brandDTD;
   <!ENTITY % acDTD SYSTEM "chrome://messenger/locale/accountCreation.dtd">
   %acDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         id="autoconfigWizard"
         windowtype="mail:autoconfig"
         title="&autoconfigWizard.title;"
         onload="gEmailConfigWizard.onLoad();"
         onkeypress="gEmailConfigWizard.onKeyDown(event)"
         onclose="gEmailConfigWizard.onWizardShutdown();"
         onunload="gEmailConfigWizard.onWizardShutdown();"
-        onfocus="gEmailConfigWizard.onFocus();"
         >
 
   <stringbundleset>
-    <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
-    <stringbundle id="strings" src="chrome://messenger/locale/accountCreation.properties"/>
-    <stringbundle id="utilstrings" src="chrome://messenger/locale/accountCreationUtil.properties"/>
+    <stringbundle id="bundle_brand"
+          src="chrome://branding/locale/brand.properties"/>
+    <stringbundle id="strings"
+          src="chrome://messenger/locale/accountCreation.properties"/>
+    <stringbundle id="utilstrings"
+          src="chrome://messenger/locale/accountCreationUtil.properties"/>
+    <stringbundle id="bundle_messenger"
+          src="chrome://messenger/locale/messenger.properties"/>
   </stringbundleset>
   <script type="application/javascript"
           src="chrome://messenger/content/accountcreation/util.js"/>
   <script type="application/javascript"
           src="chrome://messenger/content/accountcreation/accountConfig.js"/>
   <script type="application/javascript"
           src="chrome://messenger/content/accountcreation/emailWizard.js"/>
   <script type="application/javascript"
@@ -88,26 +93,28 @@
     <key keycode="VK_ESCAPE" oncommand="window.close();"/>
   </keyset>
 
   <panel id="insecureserver-cleartext-panel" class="popup-panel">
     <hbox>
       <image class="insecureLarry"/>
       <vbox flex="1">
         <description class="title">&insecureServer.tooltip.title;</description>
-        <description class="details">&insecureUnencrypted.description;</description>
+        <description class="details">
+                                &insecureUnencrypted.description;</description>
       </vbox>
     </hbox>
   </panel>
   <panel id="insecureserver-selfsigned-panel" class="popup-panel">
     <hbox>
       <image class="insecureLarry"/>
       <vbox flex="1">
         <description class="title">&insecureServer.tooltip.title;</description>
-        <description class="details">&insecureSelfSigned.description;</description>
+        <description class="details">
+                                 &insecureSelfSigned.description;</description>
       </vbox>
     </hbox>
   </panel>
   <panel id="secureserver-panel" class="popup-panel">
     <hbox>
       <image class="secureLarry"/>
       <vbox flex="1">
         <description class="title">&secureServer.description;</description>
@@ -115,69 +122,71 @@
     </hbox>
   </panel>
 
   <tooltip id="insecureserver-cleartext">
     <hbox>
       <image class="insecureLarry"/>
       <vbox>
         <description class="title">&insecureServer.tooltip.title;</description>
-        <description class="details">&insecureServer.tooltip.details;</description>
+        <description class="details">
+                                 &insecureServer.tooltip.details;</description>
       </vbox>
     </hbox>
   </tooltip>
   <tooltip id="insecureserver-selfsigned">
     <hbox>
       <image class="insecureLarry"/>
       <vbox>
         <description class="title">&insecureServer.tooltip.title;</description>
-        <description class="details">&insecureServer.tooltip.details;</description>
+        <description class="details">
+                                 &insecureServer.tooltip.details;</description>
       </vbox>
     </hbox>
   </tooltip>
   <tooltip id="secureservertooltip">
     <hbox>
       <image class="secureLarry"/>
       <description class="title">&secureServer.description;</description>
     </hbox>
   </tooltip>
   <tooltip id="optional-password">
     <description>&password.text;</description>
   </tooltip>
 
+  <spacer id="fullwidth"/>
 
   <vbox id="mastervbox" class="mastervbox" flex="1">
     <groupbox id="initialSettings">
       <hbox align="center">
         <label accesskey="&name.accesskey;"
                class="autoconfigLabel"
                value="&name.label;"
                control="realname"/>
         <textbox id="realname"
                  class="padded"
-                 value=""
                  placeholder="&name.placeholder;"
-                 oninput="gEmailConfigWizard.onRealnameInput()"
-                 onblur="gEmailConfigWizard.validateRealname();"/>
+                 oninput="gEmailConfigWizard.onInputRealname();"
+                 onblur="gEmailConfigWizard.onBlurRealname();"/>
         <description id="nametext" class="initialDesc">&name.text;</description>
         <image id="nameerroricon"
                hidden="true"
                src="chrome://global/skin/icons/warning-16.png"/>
         <description id="nameerror" class="errordescription" hidden="true"/>
       </hbox>
       <hbox align="center">
         <label accesskey="&email.accesskey;"
                class="autoconfigLabel"
                value="&email.label;"
                control="email"/>
         <textbox id="email"
                  class="padded uri-element"
                  placeholder="&email.placeholder;"
-                 oninput="gEmailConfigWizard.onEmailInput()"
-                 onblur="gEmailConfigWizard.validateEmail();"/>
+                 oninput="gEmailConfigWizard.onInputEmail();"
+                 onblur="gEmailConfigWizard.onBlurEmail();"/>
         <image id="emailerroricon"
                hidden="true"
                src="chrome://global/skin/icons/warning-16.png"/>
         <description id="emailerror" class="errordescription" hidden="true"/>
       </hbox>
       <hbox align="center">
         <!-- this starts out as text so the emptytext shows, but then
              changes to type=password once it's not empty -->
@@ -185,243 +194,243 @@
                class="autoconfigLabel"
                value="&password.label;"
                control="password"
                tooltip="optional-password"/>
         <textbox id="password"
                  class="padded"
                  placeholder="&password.placeholder;"
                  type="text"
-                 oninput="gEmailConfigWizard.oninputPassword();"
-                 onfocus="this.type='password';"
-                 onblur="gEmailConfigWizard.onblurPassword();"/>
+                 oninput="gEmailConfigWizard.onInputPassword();"
+                 onfocus="gEmailConfigWizard.onFocusPassword();"
+                 onblur="gEmailConfigWizard.onBlurPassword();"/>
         <image id="passworderroricon"
                hidden="true"
                src="chrome://global/skin/icons/warning-16.png"/>
         <description id="passworderror" class="errordescription" hidden="true"/>
       </hbox>
-      <hbox align="center">
+      <hbox align="center" pack="start">
         <label class="autoconfigLabel"/>
         <checkbox id="remember_password"
                   label="&rememberPassword.label;"
                   accesskey="&rememberPassword.accesskey;"
-                  disabled="true"
-                  checked="false"
-                  onclick="gEmailConfigWizard._userChangedPassword=true;"/>
-        <spacer flex="1"/>
-        <hbox>
-          <button id="back_button"
-                  label="&startOver.label;"
-                  accesskey="&startOver.accesskey;"
-                  class="larger-button"
-                  hidden="true"
-                  oncommand="gEmailConfigWizard.onBack();"/>
-        </hbox>
+                  checked="true"/>
       </hbox>
-      <hbox id="fullspacer"/>
     </groupbox>
-    <vbox>
-      <groupbox id="popimap_radio_area" hidden="true">
-        <radiogroup id="popimap_radiogroup">
-          <hbox align="center">
-            <radio id="incoming_protocol_imap"
-                   label="&imap.description;" value="imap"
-                   oncommand="gEmailConfigWizard.setIncomingProtocolRadio();"/>
-            <label id="imap_recommended"
-                   value="&recommended-appendix.label;"/>
-          </hbox>
-          <radio id="incoming_protocol_pop3"
-                 label="&pop.description;" value="pop3"
-                 oncommand="gEmailConfigWizard.setIncomingProtocolRadio();"/>
-        </radiogroup>
-      </groupbox>
-      <groupbox id="settingsbox">
-        <vbox>
-          <vbox id="configarea">
-            <hbox>
-              <hbox flex="1">
-                <description id="config_status_title" flex="1" class="title"/>
-              </hbox>
-            </hbox>
-          </vbox>
-          <hbox id="usernamearea" align="center">
-            <vbox id="username_status"
-                  class="icon"
-                  align="center"
-                  pack="center">
-              <image id="username_spinner"
-                     src="chrome://global/skin/icons/loading_16.png"
-                     hidden="true"/>
-            </vbox>
-            <label class="textbox-label"
-                   value="&username2.label;"
-                   control="username"/>
-            <textbox id="username"
-                     value=""
-                     disabled="true"
-                     class="username"/>
-            <hbox flex="1" pack="end">
-              <button id="stop_button"
-                      label="&stop.label;"
-                      accesskey="&stop.accesskey;"
-                      class="smaller-button"
-                      oncommand="gEmailConfigWizard.onStop();"/>
-              <button id="edit_button"
-                      label="&edit.label;"
-                      accesskey="&edit.accesskey;"
-                      class="smaller-button"
-                      hidden="true"
-                      oncommand="gEmailConfigWizard.onEdit();"/>
-              <button id="go_button"
-                      label="&retest.label;"
-                      accesskey="&retest.accesskey;"
-                      class="smaller-button"
-                      hidden="true"
-                      oncommand="gEmailConfigWizard.onGo();"/>
-            </hbox>
-          </hbox>
-          <hbox id="incomingarea" align="center">
-            <vbox id="incoming_status"
-                  class="icon"
-                  align="center"
-                  pack="center">
-              <image id="incoming_spinner"
-                     src="chrome://global/skin/icons/loading_16.png"
-                     hidden="true"/>
-            </vbox>
+    <spacer flex="1" />
+
+     <hbox id="status_area" flex="1">
+      <vbox id="status_img_before" pack="start"/>
+      <description id="status_msg">&#160;</description>
+              <!-- Include 160 = nbsp, to make the element occupy the
+                   full height, for at least one line. With a normal space,
+                   it does not have sufficient height. -->
+      <vbox id="status_img_after" pack="start"/>
+    </hbox>
+
+    <groupbox id="result_area" hidden="true">
+      <radiogroup id="result_imappop" orient="horizontal"
+                  onselect="gEmailConfigWizard.onResultIMAPOrPOP3();">
+        <radio id="result_select_imap" label="&imapLong.label;" value="1"/>
+        <radio id="result_select_pop3" label="&pop3Long.label;" value="2"/>
+      </radiogroup>
+      <hbox>
+        <label class="textbox-label" value="&incoming.label;"/>
+        <description id="result-incoming" flex="1">&#160;</description>
+      </hbox>
+      <hbox>
+        <label class="textbox-label" value="&outgoing.label;"/>
+        <description id="result-outgoing" flex="1">&#160;</description>
+      </hbox>
+      <hbox>
+        <label class="textbox-label" value="&username.label;"/>
+        <description id="result-username" flex="1">&#160;</description>
+      </hbox>
+    </groupbox>
+
+    <groupbox id="manual-edit_area" hidden="true">
+      <grid>
+        <columns>
+          <column/><!-- row label, e.g. "incoming" -->
+          <column/><!-- protocol, e.g. "IMAP" -->
+          <column flex="1"/><!-- hostname / username -->
+          <column/><!-- port -->
+          <column/><!-- SSL -->
+          <column/><!-- auth method -->
+        </columns>
+        <rows>
+          <row id="labels_row" align="center">
+            <spacer/>
+            <spacer/>
+            <label value="&hostname.label;" class="columnHeader"/>
+            <label value="&port.label;" class="columnHeader"/>
+            <label value="&ssl.label;" class="columnHeader"/>
+            <label value="&auth.label;" class="columnHeader"/>
+          </row>
+          <row id="incoming_server_area" align="center">
             <label class="textbox-label"
                    value="&incoming.label;"
-                   control="incoming_server"/>
-            <hbox>
-              <textbox id="incoming_server"
-                       class="host uri-element"
-                       value=""
-                       disabled="true"
-                       oninput="gEmailConfigWizard.setIncomingServer()"/>
-            </hbox>
-            <hbox>
-              <spacer height="30" width="12"/>
-              <hbox class="protocol">
-                <menulist id="incoming_protocol"
-                          value=""
-                          disabled="true"
-                          sizetopopup="always">
-                  <menupopup onpopuphidden="gEmailConfigWizard.setIncomingProtocol()">
-                    <menuitem label="&imap.label;" value="1"/>
-                    <menuitem label="&pop.label;" value="2"/>
-                  </menupopup>
-                </menulist>
-              </hbox>
-              <hbox>
-                <textbox id="incoming_port"
-                         class="port"
-                         value=""
-                         disabled="true"
-                         oninput="gEmailConfigWizard.setPort('incoming_port')"/>
-              </hbox>
-              <hbox>
-                <menulist id="incoming_security"
-                          class="security"
-                          value=""
-                          disabled="true"
-                          sizetopopup="always">
-                  <menupopup onpopuphidden="gEmailConfigWizard.setSecurity('incoming_security')">
-                    <menuitem label="&noEncryption.label;" value="1"/>
-                    <menuitem label="&starttls.label;" value="3"/>
-                    <menuitem label="&sslTls.label;" value="2"/>
-                  </menupopup>
-                </menulist>
-              </hbox>
-            </hbox>
-          </hbox>
-          <hbox id="outgoingarea" align="center">
-            <vbox id="outgoing_status"
-                  class="icon"
-                  align="center"
-                  pack="center">
-              <image id="outgoing_spinner"
-                     src="chrome://global/skin/icons/loading_16.png"
-                     hidden="true"/>
-            </vbox>
+                   control="incoming_hostname"/>
+            <menulist id="incoming_protocol"
+                      sizetopopup="always">
+              <menupopup
+              onpopuphidden="gEmailConfigWizard.onChangedProtocolIncoming();">
+                <menuitem label="&imap.label;" value="1"/>
+                <menuitem label="&pop3.label;" value="2"/>
+              </menupopup>
+            </menulist>
+            <textbox id="incoming_hostname"
+                     oninput="gEmailConfigWizard.onInputHostname();"
+                     class="host uri-element"/>
+            <menulist id="incoming_port"
+                      editable="true"
+                      oninput="gEmailConfigWizard.onChangedPortIncoming();"
+                      class="port">
+              <menupopup
+                    onpopuphidden="gEmailConfigWizard.onChangedPortIncoming();"/>
+            </menulist>
+            <menulist id="incoming_ssl"
+                      class="security"
+                      sizetopopup="always">
+              <menupopup
+                  onpopuphidden="gEmailConfigWizard.onChangedSSLIncoming();">
+                <!-- values defined in nsMsgSocketType -->
+                <menuitem label="&autodetect.label;" value="0"/>
+                <menuitem label="&noEncryption.label;" value="1"/>
+                <menuitem label="&starttls.label;" value="3"/>
+                <menuitem label="&sslTls.label;" value="2"/>
+              </menupopup>
+            </menulist>
+            <menulist id="incoming_authMethod"
+                      class="auth"
+                      sizetopopup="always">
+              <menupopup onpopuphidden="gEmailConfigWizard.onChangedAuth();">
+                <menuitem label="&autodetect.label;" value="0"/>
+                <!-- values defined in nsMsgAuthMethod -->
+                <!-- labels set from messenger.properties
+                     to avoid duplication -->
+                <menuitem id="in-authMethod-password-cleartext" value="3"/>
+                <menuitem id="in-authMethod-password-encrypted" value="4"/>
+                <menuitem id="in-authMethod-kerberos" value="5"/>
+                <menuitem id="in-authMethod-ntlm" value="6"/>
+              </menupopup>
+            </menulist>
+          </row>
+          <row id="outgoing_server_area" align="center">
             <label class="textbox-label"
                    value="&outgoing.label;"
-                   control="outgoing_server"/>
-            <hbox>
-              <!-- oncommand, and onchange and onmousedown of menulist's
-                   inputfield, set in constructor -->
-              <menulist id="outgoing_server"
-                        class="host uri-element"
-                        disabled="true"
-                        sizetopopup="none"
-                        editable="true" >
-                <menupopup id="smtp_menupopup"/>
-              </menulist>
-            </hbox>
-            <hbox>
-              <spacer height="30" width="12"/>
-              <hbox class="protocol" align="center">
-                <label id="outgoing_protocol"
-                       value="&smtp.label;"/>
-              </hbox>
-              <hbox>
-                <textbox id="outgoing_port"
-                         class="port"
-                         value=""
-                         disabled="true"
-                         oninput="gEmailConfigWizard.setPort('outgoing_port')"/>
-              </hbox>
-              <hbox>
-                <menulist id="outgoing_security"
-                          class="security"
-                          value=""
-                          disabled="true"
-                          sizetopopup="always">
-                  <menupopup onpopuphidden="gEmailConfigWizard.setSecurity('outgoing_security')">
-                    <menuitem label="&noEncryption.label;" value="1"/>
-                    <menuitem label="&starttls.label;" value="3"/>
-                    <menuitem label="&sslTls.label;" value="2"/>
-                  </menupopup>
-                </menulist>
-              </hbox>
-            </hbox>
-          </hbox>
-        </vbox>
-      </groupbox>
-    </vbox>
-    <spacer flex="1"/>
-    <hbox id="button_box">
-      <button id="advanced_settings"
-              label="&manualSetup.label;"
-              accesskey="&manualSetup.accesskey;"
-              class="larger-button"
-              disabled="true"
-              hidden="true"
-              oncommand="gEmailConfigWizard.advancedSettings();"/>
+                   control="outgoing_hostname"/>
+            <label id="outgoing_protocol"
+                   value="&smtp.label;"/>
+            <menulist id="outgoing_hostname"
+                editable="true"
+                sizetopopup="none"
+                oninput="gEmailConfigWizard.onInputHostname();"
+                onpopuphidden="gEmailConfigWizard.onChangedOutgoingDropdown();"
+                onpopupshowing="gEmailConfigWizard.onOpenOutgoingDropdown();"
+                class="host uri-element">
+              <menupopup id="outgoing_hostname_popup"/>
+            </menulist>
+            <menulist id="outgoing_port"
+                      editable="true"
+                      oninput="gEmailConfigWizard.onChangedPortOutgoing();"
+                      class="port">
+              <menupopup
+                    onpopuphidden="gEmailConfigWizard.onChangedPortOutgoing();"/>
+            </menulist>
+            <menulist id="outgoing_ssl"
+                      class="security"
+                      sizetopopup="always">
+              <menupopup
+                    onpopuphidden="gEmailConfigWizard.onChangedSSLOutgoing();">
+                <!-- @see incoming -->
+                <menuitem label="&autodetect.label;" value="0"/>
+                <menuitem label="&noEncryption.label;" value="1"/>
+                <menuitem label="&starttls.label;" value="3"/>
+                <menuitem label="&sslTls.label;" value="2"/>
+              </menupopup>
+            </menulist>
+            <menulist id="outgoing_authMethod"
+                      class="auth"
+                      sizetopopup="always">
+              <menupopup onpopuphidden="gEmailConfigWizard.onChangedAuth();">
+                <menuitem label="&autodetect.label;" value="0"/>
+                <!-- @see incoming -->
+                <menuitem id="out-authMethod-no" value="1"/>
+                <menuitem id="out-authMethod-password-cleartext" value="3"/>
+                <menuitem id="out-authMethod-password-encrypted" value="4"/>
+                <menuitem id="out-authMethod-kerberos" value="5"/>
+                <menuitem id="out-authMethod-ntlm" value="6"/>
+              </menupopup>
+            </menulist>
+          </row>
+          <row id="username_area" align="center">
+            <label class="textbox-label"
+                   value="&username.label;"
+                   control="incoming_username"/>
+            <spacer/>
+            <textbox id="incoming_username"
+                     oninput="gEmailConfigWizard.onInputUsername();"
+                     class="username"/>
+            <spacer/>
+            <spacer/>
+            <spacer/>
+          </row>
+        </rows>
+      </grid>
+    </groupbox>
+
+    <spacer flex="1" />
+    <hbox id="buttons_area">
+      <hbox id="left_buttons_area" align="center" pack="start">
+        <button id="manual-edit_button"
+                label="&manual-edit.label;"
+                accesskey="&manual-edit.accesskey;"
+                class="smaller-button"
+                hidden="true"
+                oncommand="gEmailConfigWizard.onManualEdit();"/>
+        <button id="advanced-setup_button"
+                label="&advancedSetup.label;"
+                accesskey="&advancedSetup.accesskey;"
+                class="smaller-button"
+                disabled="true"
+                hidden="true"
+                oncommand="gEmailConfigWizard.onAdvancedSetup();"/>
+      </hbox>
       <spacer flex="1"/>
-      <hbox align="center">
+      <hbox id="right_buttons_area" align="center" pack="end">
+        <button id="stop_button"
+                label="&stop.label;"
+                accesskey="&stop.accesskey;"
+                class="smaller-button"
+                hidden="true"
+                oncommand="gEmailConfigWizard.onStop();"/>
         <button id="cancel_button"
                 label="&cancel.label;"
+                class="smaller-button"
                 accesskey="&cancel.accesskey;"
                 oncommand="gEmailConfigWizard.onCancel();"/>
-      </hbox>
-      <button id="create_button"
-              label="&createAccount.label;"
-              accesskey="&createAccount.accesskey;"
-              class="larger-button"
-              hidden="true"
-              disabled="true"
-              oncommand="gEmailConfigWizard.onOK();"/>
-      <hbox pack="end">
+        <button id="half-manual-test_button"
+                label="&half-manual-test.label;"
+                accesskey="&half-manual-test.accesskey;"
+                class="larger-button"
+                hidden="true"
+                oncommand="gEmailConfigWizard.onHalfManualTest();"/>
         <button id="next_button"
                 label="&continue.label;"
                 accesskey="&continue.accesskey;"
                 class="larger-button"
                 hidden="false"
-                disabled="true"
                 oncommand="gEmailConfigWizard.onNext();"/>
+        <button id="create_button"
+                label="&createAccount.label;"
+                accesskey="&createAccount.accesskey;"
+                class="larger-button"
+                hidden="true"
+                oncommand="gEmailConfigWizard.onCreate();"/>
       </hbox>
     </hbox>
   </vbox>
 
 
   <vbox id="warningbox" hidden="true" flex="1">
     <hbox class="warning" flex="1">
       <vbox class="larrybox">
@@ -432,46 +441,47 @@
         <vbox id="incoming_box">
           <hbox>
             <label class="warning_settings" value="&incomingSettings.label;"/>
             <description id="warning_incoming"/>
           </hbox>
           <label id="incoming_technical"
                  class="technical_details"
                  value="&technicaldetails.label;"
-                 onclick="gEmailConfigWizard.toggleDetails('incoming');"/>
+                 onclick="gSecurityWarningDialog.toggleDetails('incoming');"/>
           <description id="incoming_details" collapsed="true"/>
         </vbox>
         <vbox id="outgoing_box">
           <hbox>
             <label class="warning_settings" value="&outgoingSettings.label;"/>
             <description id="warning_outgoing"/>
           </hbox>
           <label id="outgoing_technical"
                  class="technical_details"
                  value="&technicaldetails.label;"
-                 onclick="gEmailConfigWizard.toggleDetails('outgoing');"/>
+                 onclick="gSecurityWarningDialog.toggleDetails('outgoing');"/>
           <description id="outgoing_details" collapsed="true"/>
         </vbox>
         <spacer flex="10"/>
-        <description id="findoutmore">&contactYourProvider.description;</description>
+        <description id="findoutmore">
+            &contactYourProvider.description;</description>
         <spacer flex="100"/>
         <checkbox id="acknowledge_warning"
                   label="&confirmWarning.label;"
                   accesskey="&confirmWarning.accesskey;"
                   class="acknowledge_checkbox"
-                  oncommand="gEmailConfigWizard.toggleAcknowledgeWarning()"/>
+                  oncommand="gSecurityWarningDialog.toggleAcknowledge()"/>
         <hbox>
           <button id="getmeoutofhere"
                   label="&changeSettings.label;"
                   accesskey="&changeSettings.accesskey;"
-                  oncommand="gEmailConfigWizard.getMeOutOfHere()"/>
+                  oncommand="gSecurityWarningDialog.onCancel()"/>
           <spacer flex="1"/>
           <button id="iknow"
                   label="&createAccount.label;"
                   accesskey="&createAccount.accesskey;"
                   disabled="true"
-                  oncommand="gEmailConfigWizard.validateAndFinish()"/>
+                  oncommand="gSecurityWarningDialog.onOK()"/>
         </hbox>
       </vbox>
     </hbox>
   </vbox>
 </window>
--- a/mailnews/base/prefs/content/accountcreation/fetchConfig.js
+++ b/mailnews/base/prefs/content/accountcreation/fetchConfig.js
@@ -67,17 +67,18 @@ function fetchConfigFromDisk(domain, suc
  * @param domain {String}   The domain part of the user's email address
  * @param emailAddress {String}   The user's email address
  * @param successCallback {Function(config {AccountConfig}})}   A callback that
  *         will be called when we could retrieve a configuration.
  *         The AccountConfig object will be passed in as first parameter.
  * @param errorCallback {Function(ex)}   A callback that
  *         will be called when we could not retrieve a configuration,
  *         for whatever reason. This is expected (e.g. when there's no config
- *         for this domain at this location), so do not unconditionally show this to the user.
+ *         for this domain at this location),
+ *         so do not unconditionally show this to the user.
  *         The first paramter will be an exception object or error string.
  */
 function fetchConfigFromISP(domain, emailAddress, successCallback,
                             errorCallback)
 {
   let url1 = "http://autoconfig." + sanitize.hostname(domain) +
              "/mail/config-v1.1.xml";
   // .well-known/ <http://tools.ietf.org/html/draft-nottingham-site-meta-04>
@@ -91,27 +92,35 @@ function fetchConfigFromISP(domain, emai
     {
       successCallback(readFromXML(result));
     },
     function(e1) // fetch1 failed
     {
       ddump("fetchisp 1 <" + url1 + "> took " + (Date.now() - time) +
           "ms and failed with " + e1);
       time = Date.now();
+      if (e1 instanceof CancelledException)
+      {
+        errorCallback(e1);
+        return;
+      }
+
       let fetch2 = new FetchHTTP(
         url2, { emailaddress: emailAddress }, false,
         function(result)
         {
           successCallback(readFromXML(result));
         },
         function(e2)
         {
           ddump("fetchisp 2 <" + url2 + "> took " + (Date.now() - time) +
               "ms and failed with " + e2);
-          errorCallback(e1); // return error for primary call
+          // return the error for the primary call,
+          // unless the fetch was cancelled
+          errorCallback(e2 instanceof CancelledException ? e2 : e1);
         });
       sucAbortable.current = fetch2;
       fetch2.start();
     });
   sucAbortable.current = fetch1;
   fetch1.start();
   return sucAbortable;
 }
@@ -119,27 +128,29 @@ function fetchConfigFromISP(domain, emai
 /**
  * Tries to get a configuration for this ISP from a central database at
  * Mozilla servers.
  * Params @see fetchConfigFromISP()
  */
 
 function fetchConfigFromDB(domain, successCallback, errorCallback)
 {
-  let pref = Components.classes["@mozilla.org/preferences-service;1"]
-                               .getService(Components.interfaces.nsIPrefBranch);
+  let pref = Cc["@mozilla.org/preferences-service;1"]
+             .getService(Ci.nsIPrefBranch);
   let url = pref.getCharPref("mailnews.auto_config_url");
   let domain = sanitize.hostname(domain);
 
   // If we don't specify a place to put the domain, put it at the end.
   if (url.indexOf("{{domain}}") == -1)
     url = url + domain;
   else
     url = url.replace("{{domain}}", domain);
-  url = url.replace("{{accounts}}", gAccountMgr.accounts.Count());
+  var accountManager = Cc["@mozilla.org/messenger/account-manager;1"]
+                       .getService(Ci.nsIMsgAccountManager);
+  url = url.replace("{{accounts}}", accountManager.accounts.Count());
 
   if (!url.length)
     return errorCallback("no fetch url set");
   let fetch = new FetchHTTP(url, null, false,
                             function(result)
                             {
                               successCallback(readFromXML(result));
                             },
--- a/mailnews/base/prefs/content/accountcreation/fetchhttp.js
+++ b/mailnews/base/prefs/content/accountcreation/fetchhttp.js
@@ -178,60 +178,78 @@ FetchHTTP.prototype =
           //ddump("mimetype: " + mimetype + " only supported as text");
           this.result = this._request.responseText;
         }
         //ddump("result:\n" + this.result);
       }
       catch (e)
       {
         success = false;
-        let stringBundle = getStringBundle("chrome://messenger/locale/accountCreationUtil.properties");
-        errorStr = stringBundle.GetStringFromName("bad_response_content.error");
+        errorStr = getStringBundle(
+                   "chrome://messenger/locale/accountCreationUtil.properties")
+                   .GetStringFromName("bad_response_content.error");
         errorCode = -4;
       }
     }
     else
     {
       success = false;
       try
       {
         errorCode = this._request.status;
         errorStr = this._request.statusText;
       } catch (e) {
         // If we can't resolve the hostname in DNS etc., .statusText throws
         errorCode = -2;
-        let stringBundle = getStringBundle("chrome://messenger/locale/accountCreationUtil.properties")
-        errorStr = stringBundle.GetStringFromName("cannot_contact_server.error");
+        errorStr = getStringBundle(
+                   "chrome://messenger/locale/accountCreationUtil.properties")
+                   .GetStringFromName("cannot_contact_server.error");
         ddump(errorStr);
       }
     }
 
     // Callbacks
     if (success)
-      this._successCallback(this.result);
+    {
+      try {
+        this._successCallback(this.result);
+      } catch (e) {
+        logException(e);
+        this._error(e);
+      }
+    }
     else if (exStored)
-      this._errorCallback(exStored);
+      this._error(exStored);
     else
-      this._errorCallback(new ServerException(errorStr, errorCode, this._url));
+      this._error(new ServerException(errorStr, errorCode, this._url));
 
     if (this._finishedCallback)
-      this._finishedCallback(this);
+    {
+      try {
+        this._finishedCallback(this);
+      } catch (e) {
+        logException(e);
+        this._error(e);
+      }
+    }
 
     } catch (e) {
-      // error in callback or our fetchhttp._response() code
-      try
-      {
-        ddump("Error in errorCallback or _response(): " + e);
-        this._errorCallback(e);
-      } catch (e) {
-        //ddump("Error in errorCallback: " + e);
-        // XXX TODO FIXME BOGUS
-        alert(e); // error in errorCallback, too!
-        throw(e); // to error console
-      }
+      // error in our fetchhttp._response() code
+      logException(e);
+      this._error(e);
+    }
+  },
+  _error : function(e)
+  {
+    try {
+      this._errorCallback(e);
+    } catch (e) {
+      // error in errorCallback, too!
+      logException(e);
+      alertPrompt("Error in errorCallback for fetchhttp", e);
     }
   },
   /**
    * Call this between start() and finishedCallback fired.
    */
   cancel : function(ex)
   {
     assert(!this.result, "Call already returned");
@@ -249,28 +267,38 @@ FetchHTTP.prototype =
    */
   setFinishedCallback : function(finishedCallback)
   {
     this._finishedCallback = finishedCallback;
   }
 }
 extend(FetchHTTP, Abortable);
 
+
+function CancelledException(msg)
+{
+  Exception.call(this, msg);
+}
+CancelledException.prototype =
+{
+}
+extend(CancelledException, Exception);
+
 function UserCancelledException(msg)
 {
   // The user knows they cancelled so I don't see a need
   // for a message to that effect.
   if (!msg)
-    msg = "";
-  Exception.call(this, msg);
+    msg = "User cancelled";
+  CancelledException.call(this, msg);
 }
 UserCancelledException.prototype =
 {
 }
-extend(UserCancelledException, Exception);
+extend(UserCancelledException, CancelledException);
 
 function ServerException(msg, code, uri)
 {
   Exception.call(this, msg);
   this.code = code;
   this.uri = uri;
 }
 ServerException.prototype =
--- a/mailnews/base/prefs/content/accountcreation/guessConfig.js
+++ b/mailnews/base/prefs/content/accountcreation/guessConfig.js
@@ -76,50 +76,35 @@ var outgoingDone = false;
  *   param accountConfig {AccountConfig} The guessed account config.
  *       username, password, realname, emailaddress etc. are not filled out,
  *       but placeholders to be filled out via replaceVariables().
  * @param errorCallback function(ex)
  *   Called when we could guess not the config, either
  *   because we have not found anything or
  *   because there was an error (e.g. no network connection).
  *   The ex.message will contain a user-presentable message.
- * @param incomingErrorCallback {function(ex, config {AccountConfig})}
- *   Like errorCallback, just that we do have a config for the
- *   outgoing server, just not for the incoming server.
- *   This is not terribly useful, because we may have guessed
- *   the MX server (SMTP server for incoming mail from other SMTP servers),
- *   not the outbound SMTP for users.
- *   Showing MX will highly mislead users, so better to treat that as total error.
- * @param outgoingErrorCallback {function(ex, config {AccountConfig})}
- *   Like errorCallback, just that we do have a config for the
- *   incoming server, just not for the outgoing server.
  * @param resultConfig {AccountConfig} (optional)
  *   A config which may be partially filled in. If so, it will be used as base
  *   for the guess.
  * @param which {String-enum} (optional)  "incoming", "outgoing", or "both".
  *   Default "both". Whether to guess only the incoming or outgoing server.
  * @result {Abortable} Allows you to cancel the guess
  */
 function guessConfig(domain, progressCallback, successCallback, errorCallback,
-                     incomingErrorCallback, outgoingErrorCallback,
                      resultConfig, which)
 {
   assert(typeof(progressCallback) == "function", "need progressCallback");
   assert(typeof(successCallback) == "function", "need successCallback");
   assert(typeof(errorCallback) == "function", "need errorCallback");
-  assert(typeof(incomingErrorCallback) == "function",
-    "need incomingErrorCallback");
-  assert(typeof(outgoingErrorCallback) == "function",
-    "need outgoingErrorCallback");
   if (!resultConfig)
     resultConfig = new AccountConfig();
   resultConfig.source = AccountConfig.kSourceGuess;
 
+  var incomingHostDetector = null;
   var outgoingHostDetector = null;
-  var incomingHostDetector = null;
   var incomingEx = null; // if incoming had error, store ex here
   var outgoingEx = null; // if incoming had error, store ex here
   var incomingDone = (which == "outgoing");
   var outgoingDone = (which == "incoming");
 
   var progress = function(thisTry)
   {
     progressCallback(protocolToString(thisTry.protocol), thisTry.hostname,
@@ -127,41 +112,57 @@ function guessConfig(domain, progressCal
                      resultConfig);
   };
 
   var updateConfig = function(config)
   {
     resultConfig = config;
   };
 
+  var errorInCallback = function(e)
+  {
+    // The caller's errorCallback threw.
+    // hopefully shouldn't happen for users.
+    alertPrompt("Error in errorCallback for guessConfig()", e);
+  };
+
   var checkDone = function()
   {
-    if (outgoingEx)
-      outgoingErrorCallback(outgoingEx, resultConfig);
-
     if (incomingEx)
-      incomingErrorCallback(incomingEx, resultConfig);
-
-    if (incomingEx && outgoingEx)
     {
-      errorCallback(incomingEx, resultConfig);
+      try {
+        errorCallback(incomingEx, resultConfig);
+      } catch (e) { errorInCallback(e); }
       return;
     }
-    if ((incomingDone || incomingEx) && (outgoingDone || outgoingEx))
+    if (outgoingEx)
     {
-      successCallback(resultConfig);
+      try {
+        errorCallback(outgoingEx, resultConfig);
+      } catch (e) { errorInCallback(e); }
+      return;
+    }
+    if (incomingDone && outgoingDone)
+    {
+      try {
+        successCallback(resultConfig);
+      } catch (e) {
+        try {
+          errorCallback(e);
+        } catch (e) { errorInCallback(e); }
+      }
       return;
     }
   };
 
   var outgoingSuccess = function(thisTry)
   {
     assert(thisTry.protocol == SMTP, "I only know SMTP for outgoing");
-    // Ensure there are no previously saved outgoing errors if we've got success
-    // here.
+    // Ensure there are no previously saved outgoing errors, if we've got
+    // success here.
     outgoingEx = null;
     resultConfig.outgoing.type = "smtp";
     resultConfig.outgoing.hostname = thisTry.hostname;
     resultConfig.outgoing.port = thisTry.port;
     resultConfig.outgoing.socketType = sslConvertToSocketType(thisTry.ssl);
     resultConfig.outgoing.auth = chooseBestAuthMethod(thisTry.authMethods);
     resultConfig.outgoing.authAlternatives = thisTry.authMethods;
     // TODO
@@ -172,26 +173,20 @@ function guessConfig(domain, progressCal
 
     progressCallback(resultConfig.outgoing.type,
         resultConfig.outgoing.hostname, resultConfig.outgoing.port,
         resultConfig.outgoing.socketType, true, resultConfig);
     outgoingDone = true;
     checkDone();
   };
 
-  var outgoingError = function(ex)
-  {
-    outgoingEx = ex;
-    checkDone();
-  };
-
   var incomingSuccess = function(thisTry)
   {
-    // Ensure there are no previously saved incoming errors if we've got success
-    // here.
+    // Ensure there are no previously saved incoming errors, if we've got
+    // success here.
     incomingEx = null;
     resultConfig.incoming.type = protocolToString(thisTry.protocol);
     resultConfig.incoming.hostname = thisTry.hostname;
     resultConfig.incoming.port = thisTry.port;
     resultConfig.incoming.socketType = sslConvertToSocketType(thisTry.ssl);
     resultConfig.incoming.auth = chooseBestAuthMethod(thisTry.authMethods);
     resultConfig.incoming.authAlternatives = thisTry.authMethods;
     resultConfig.incoming.badCert = thisTry.selfSignedCert;
@@ -203,114 +198,66 @@ function guessConfig(domain, progressCal
     incomingDone = true;
     checkDone();
   };
 
   var incomingError = function(ex)
   {
     incomingEx = ex;
     checkDone();
+    incomingHostDetector.cancel(new CancelOthersException());
+    outgoingHostDetector.cancel(new CancelOthersException());
   };
 
-  let incomingHostDetector = null;
-  let outgoingHostDetector = null;
+  var outgoingError = function(ex)
+  {
+    outgoingEx = ex;
+    checkDone();
+    incomingHostDetector.cancel(new CancelOthersException());
+    outgoingHostDetector.cancel(new CancelOthersException());
+  };
+
   incomingHostDetector = new IncomingHostDetector(progress, incomingSuccess,
                                                   incomingError);
   outgoingHostDetector = new OutgoingHostDetector(progress, outgoingSuccess,
                                                   outgoingError);
   if (which == "incoming" || which == "both")
   {
-    incomingHostDetector.start(domain, !!resultConfig.incoming.hostname,
-        resultConfig.incoming.type, resultConfig.incoming.port,
-        resultConfig.incoming.socketType);
+    incomingHostDetector.start(resultConfig.incoming.hostname ?
+            resultConfig.incoming.hostname : domain,
+        !!resultConfig.incoming.hostname, resultConfig.incoming.type,
+        resultConfig.incoming.port, resultConfig.incoming.socketType);
   }
   if (which == "outgoing" || which == "both")
   {
-    outgoingHostDetector.start(domain, !!resultConfig.outgoing.hostname,
-        "smtp", resultConfig.outgoing.port, resultConfig.outgoing.socketType);
+    outgoingHostDetector.start(resultConfig.outgoing.hostname ?
+            resultConfig.outgoing.hostname : domain,
+        !!resultConfig.outgoing.hostname, "smtp",
+        resultConfig.outgoing.port, resultConfig.outgoing.socketType);
   }
 
   return new GuessAbortable(incomingHostDetector, outgoingHostDetector,
                             updateConfig);
 }
 
 function GuessAbortable(incomingHostDetector, outgoingHostDetector,
                         updateConfig)
 {
   Abortable.call(this);
   this._incomingHostDetector = incomingHostDetector;
   this._outgoingHostDetector = outgoingHostDetector;
   this._updateConfig = updateConfig;
 }
 GuessAbortable.prototype =
 {
-  cancel : function(which)
+  cancel : function(ex)
   {
-    switch (which)
-    {
-      case "incoming":
-      default:
-        if (this._incomingHostDetector)
-          this._incomingHostDetector.cancel();
-      case "outgoing":
-        if (which != "incoming")
-        {
-          if (this._outgoingHostDetector)
-            this._outgoingHostDetector.cancel();
-          outgoingDone = true;
-        }
-    }
+    this._incomingHostDetector.cancel(ex);
+    this._outgoingHostDetector.cancel(ex);
   },
-
-  /**
-   * Start a detection that has been cancel()ed before,
-   * possibly with other parameters.
-   *
-   * This is basically an alternative to calling guessConfig() again.
-   * TODO deprecate in favor of that?
-   *
-   * @param domain {String} @see HostDetector.start()
-   * @param config {AccountConfig} @see guessConfig() resultConfig
-   * @param which {String-enum} @see guessConfig() which
-   * @param type {String-enum} @see HostDetector.start()
-   * @param port {Integer} @see HostDetector.start()
-   * @param socketType {Integer-enum} @see HostDetector.start()
-   */
-  restart : function(domain, config, which, type, port, socketType)
-  {
-    // Calling code may have changed config (e.g., user may have changed
-    // username) so put new values in resultConfig.
-    this._updateConfig(config);
-    var incomingHostIsPrecise = !!config.incoming.hostname;
-    var outgoingHostIsPrecise = !!config.outgoing.hostname;
-    switch (which)
-    {
-      case "incoming":
-        assert(this._incomingHostDetector, "need this._incomingHostDetector");
-        this._incomingHostDetector.cancel();
-        this._incomingHostDetector.start(domain, incomingHostIsPrecise,
-                                         type, port, socketType);
-        break;
-      case "outgoing":
-        assert(this._outgoingHostDetector, "need this._outgoingHostDetector");
-        this._outgoingHostDetector.cancel();
-        this._outgoingHostDetector.start(domain, outgoingHostIsPrecise,
-                                         "smtp", port, socketType);
-        break;
-      default: // both
-        assert(this._incomingHostDetector, "need this._incomingHostDetector");
-        assert(this._outgoingHostDetector, "need this._outgoingHostDetector");
-        this._incomingHostDetector.cancel();
-        this._incomingHostDetector.start(domain, incomingHostIsPrecise,
-                                         type, port, socketType);
-        this._outgoingHostDetector.cancel();
-        this._outgoingHostDetector.start(domain, outgoingHostIsPrecise,
-                                         "smtp", port, socketType);
-    }
-  }
 }
 extend(GuessAbortable, Abortable);
 
 
 
 //////////////////////////////////////////////////////////////////////////////
 // Implementation
 //
@@ -356,28 +303,39 @@ HostTry.prototype =
   // {String} Whether the SSL cert is not from a proper CA
   selfSignedCert : false,
   // {String} Which host the SSL cert is made for, if not hostname.
   // If set, this is an SSL error.
   targetSite : null,
 };
 
 /**
+ * When the success or errorCallbacks are called to abort the other requests
+ * which happened in parallel, this ex is used as param for cancel(), so that
+ * the cancel doesn't trigger another callback.
+ */
+function CancelOthersException()
+{
+  CancelledException.call(this, "we're done, cancelling the other probes");
+}
+CancelOthersException.prototype = {}
+extend(CancelOthersException, CancelledException);
+
+/**
  * @param successCallback {function(result {HostTry})}
  *    Called when the config is OK
  * @param errorCallback {function(ex)} Called when we could not find a config
  * @param progressCallback { function(server {HostTry}) } Called when we tried
  *    (will try?) a new hostname and port
  */
 function HostDetector(progressCallback, successCallback, errorCallback)
 {
   this.mSuccessCallback = successCallback;
   this.mProgressCallback = progressCallback;
   this.mErrorCallback = errorCallback;
-  this._done = false;
   this._cancel = false;
   // {Array of {HostTry}}, ordered by decreasing preference
   this._hostsToTry = new Array();
 
   // init logging
   this._log = Log4Moz.getConfiguredLogger("mail.wizard");
   this._log.info("created host detector");
 }
@@ -389,24 +347,24 @@ HostDetector.prototype =
     this._cancel = true;
     // We have to actively stop the network calls, as they may result in
     // callbacks e.g. to the cert handler. If the dialog is gone by the time
     // this happens, the javascript stack is horked.
     for (let i = 0; i < this._hostsToTry.length; i++)
     {
       let thisTry = this._hostsToTry[i]; // {HostTry}
       if (thisTry.abortable)
-        thisTry.abortable.cancel();
+        thisTry.abortable.cancel(ex);
       thisTry.status = kFailed; // or don't set? Maybe we want to continue.
     }
+    if (ex instanceof CancelOthersException)
+      return;
     if (!ex)
-      ex = new UserCancelledException(); // TODO use CanceledException, after it was added to util.js (not fetchhttp.js) in bug 534588 or bug 549045.
-    if (!this._done) // success also calls cancel() - skip this in this case
-      this.mErrorCallback(ex);
-    this._done = true;
+      ex = new CancelledException();
+    this.mErrorCallback(ex);
   },
 
   /**
    * Start the detection
    *
    * @param domain {String} to be used as base for guessing.
    *     Should be a domain (e.g. yahoo.co.uk).
    *     If hostIsPrecise == true, it should be a full hostname
@@ -580,27 +538,25 @@ HostDetector.prototype =
     if (successfulTry)
     {
       this._log.info("CHOOSING " + successfulTry.hostname + ":" +
           successfulTry.port + " " +
           protocolToString(successfulTry.protocol) + " auth methods [" +
           successfulTry.authMethods.join(",") + "] ssl " + successfulTry.ssl +
           (successfulTry.selfSignedCert ? " (selfSignedCert)" : ""));
       this.mSuccessCallback(successfulTry);
-      this._done = true;
-      this.cancel();
+      this.cancel(new CancelOthersException());
     }
     else if (!unfinishedBusiness) // all failed
     {
       this._log.info("ran out of options");
-      var errorMsg =
-        getStringBundle("chrome://messenger/locale/accountCreationModel.properties")
-        .GetStringFromName("cannot_find_server.error");
+      var errorMsg = getStringBundle(
+          "chrome://messenger/locale/accountCreationModel.properties")
+          .GetStringFromName("cannot_find_server.error");
       this.mErrorCallback(new Exception(errorMsg));
-      this._done = true;
       // no need to cancel, all failed
     }
     // else let ongoing calls continue
   },
 
 
   /**
    * Which auth mechanism the server claims to support.
@@ -930,17 +886,17 @@ function SSLErrorHandler(thisTry, logger
   this._try = thisTry;
   this._log = logger;
   this._gotCertError = false;
 }
 SSLErrorHandler.prototype =
 {
   processCertError : function(socketInfo, status, targetSite)
   {
-    this._log.warn("Got Cert error for "+ targetSite);
+    this._log.error("Got Cert error for "+ targetSite);
 
     if (!status)
       return true;
 
     let cert = status.QueryInterface(Ci.nsISSLStatus).serverCert;
     let flags = 0;
 
     let parts = targetSite.split(":");
@@ -1137,17 +1093,17 @@ function SocketUtil(hostname, port, ssl,
 function SocketAbortable(transport)
 {
   Abortable.call(this);
   assert(transport instanceof Ci.nsITransport, "need transport");
   this._transport = transport;
 }
 SocketAbortable.prototype =
 {
-  cancel : function()
+  cancel : function(ex)
   {
     try {
       this._transport.close(Components.results.NS_ERROR_ABORT);
     } catch (e) {
       ddump("canceling socket failed: " + e);
     }
   }
 }
--- a/mailnews/base/prefs/content/accountcreation/readFromXML.js
+++ b/mailnews/base/prefs/content/accountcreation/readFromXML.js
@@ -103,19 +103,21 @@ function readFromXML(clientConfigXML)
         throw exception ? exception : "need proper <socketType> in XML";
       exception = null;
 
       for each (let iXauth in iX.authentication)
       {
         try {
           iO.auth = sanitize.translate(iXauth,
               { "password-cleartext" : Ci.nsMsgAuthMethod.passwordCleartext,
-                "plain" : Ci.nsMsgAuthMethod.passwordCleartext, // @deprecated, remove
+                // @deprecated TODO remove
+                "plain" : Ci.nsMsgAuthMethod.passwordCleartext,
                 "password-encrypted" : Ci.nsMsgAuthMethod.passwordEncrypted,
-                "secure" : Ci.nsMsgAuthMethod.passwordEncrypted, // @deprecated, remove
+                // @deprecated TODO remove
+                "secure" : Ci.nsMsgAuthMethod.passwordEncrypted,
                 "GSSAPI" : Ci.nsMsgAuthMethod.GSSAPI,
                 "NTLM" : Ci.nsMsgAuthMethod.NTLM });
           break; // take first that we support
         } catch (e) { exception = e; }
       }
       if (!iO.auth)
         throw exception ? exception : "need proper <authentication> in XML";
       exception = null;
@@ -174,25 +176,31 @@ function readFromXML(clientConfigXML)
       if (!oO.socketType)
         throw exception ? exception : "need proper <socketType> in XML";
       exception = null;
 
       for each (let oXauth in oX.authentication)
       {
         try {
           oO.auth = sanitize.translate(oXauth,
-              { "none" : Ci.nsMsgAuthMethod.none, // open relay
-                "client-IP-address" : Ci.nsMsgAuthMethod.none, // inside ISP or corp network
-                "smtp-after-pop" : Ci.nsMsgAuthMethod.none, // hope for the best
+              { // open relay
+                "none" : Ci.nsMsgAuthMethod.none,
+                // inside ISP or corp network
+                "client-IP-address" : Ci.nsMsgAuthMethod.none,
+                // hope for the best
+                "smtp-after-pop" : Ci.nsMsgAuthMethod.none,
                 "password-cleartext" : Ci.nsMsgAuthMethod.passwordCleartext,
-                "plain" : Ci.nsMsgAuthMethod.passwordCleartext, // @deprecated, remove
+                // @deprecated TODO remove
+                "plain" : Ci.nsMsgAuthMethod.passwordCleartext,
                 "password-encrypted" : Ci.nsMsgAuthMethod.passwordEncrypted,
-                "secure" : Ci.nsMsgAuthMethod.passwordEncrypted, // @deprecated, remove
+                // @deprecated TODO remove
+                "secure" : Ci.nsMsgAuthMethod.passwordEncrypted,
                 "GSSAPI" : Ci.nsMsgAuthMethod.GSSAPI,
-                "NTLM" : Ci.nsMsgAuthMethod.NTLM });
+                "NTLM" : Ci.nsMsgAuthMethod.NTLM,
+              });
           break; // take first that we support
         } catch (e) { exception = e; }
       }
       if (!oO.auth)
         throw exception ? exception : "need proper <authentication> in XML";
       exception = null;
 
       if ("username" in oX ||
--- a/mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js
+++ b/mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js
@@ -47,21 +47,21 @@
  * the expected datatype in JS types. If the value is not as expected,
  * they throw exceptions.
  */
 
 var sanitize =
 {
   integer : function(unchecked)
   {
-    if (typeof(unchecked) == "number")
+    if (typeof(unchecked) == "number" && !isNaN(unchecked))
       return unchecked;
 
     var r = parseInt(unchecked);
-    if (r == NaN)
+    if (isNaN(r))
       throw new MalformedException("no_number.error", unchecked);
 
     return r;
   },
 
   integerRange : function(unchecked, min, max)
   {
     var int = this.integer(unchecked);
@@ -185,17 +185,16 @@ var sanitize =
     {
       if (allowedValue == unchecked)
         return allowedValue;
     }
     // value is bad
     var e = new MalformedException("allowed_value.error", unchecked);
     if (typeof(defaultValue) == "undefined")
       throw e;
-    logException(e);
     return defaultValue;
   },
 
   /**
    * Like enum, allows only certain (string) values as input, but allows the
    * caller to specify another value to return instead of the input value. E.g.,
    * if unchecked == "foo", return 1, if unchecked == "bar", return 2,
    * otherwise throw. This allows to translate string enums into integer enums.
@@ -217,17 +216,16 @@ var sanitize =
     {
       if (inputValue == unchecked)
         return mapping[inputValue];
     }
     // value is bad
     var e = new MalformedException("allowed_value.error", unchecked);
     if (typeof(defaultValue) == "undefined")
       throw e;
-    logException(e);
     return defaultValue;
   }
 };
 
 function MalformedException(msgID, uncheckedBadValue)
 {
   var stringBundle = getStringBundle(
       "chrome://messenger/locale/accountCreationUtil.properties");
--- a/mailnews/base/prefs/content/accountcreation/util.js
+++ b/mailnews/base/prefs/content/accountcreation/util.js
@@ -55,17 +55,26 @@ Cu.import("resource:///modules/errUtils.
 function extend(child, supertype)
 {
   child.prototype.__proto__ = supertype.prototype;
 }
 
 function assert(test, errorMsg)
 {
   if (!test)
-    throw new NotReached(errorMsg);
+    throw new NotReached(errorMsg ? errorMsg :
+          "Programming bug. Assertion failed, see log.");
+}
+
+function makeCallback(obj, func)
+{
+  return function()
+  {
+    return func.apply(obj, arguments);
+  }
 }
 
 
 /**
  * Runs the given function sometime later
  *
  * Currently implemented using setTimeout(), but
  * can later be replaced with an nsITimer impl,
@@ -160,17 +169,18 @@ var _gIOServiceCached;
  */
 function getStringBundle(bundleURI)
 {
   try {
     return Cc["@mozilla.org/intl/stringbundle;1"]
            .getService(Ci.nsIStringBundleService)
            .createBundle(bundleURI);
   } catch (e) {
-    throw new Exception("Failed to get stringbundle URI <" + bundleURI + ">. Error: " + e);
+    throw new Exception("Failed to get stringbundle URI <" + bundleURI +
+                        ">. Error: " + e);
   }
 }
 
 
 function Exception(msg)
 {
   this._message = msg;
 
@@ -191,16 +201,17 @@ Exception.prototype =
   {
     return this._message;
   }
 }
 
 function NotReached(msg)
 {
   Exception.call(this, msg);
+  logException(this);
 }
 extend(NotReached, Exception);
 
 
 /**
  * A handle for an async function which you can cancel.
  * The async function will return an object of this type (a subtype)
  * and you can call cancel() when you feel like killing the function.
@@ -247,17 +258,18 @@ IntervalAbortable.prototype =
   cancel : function()
   {
     clearInterval(this._id);
   }
 }
 extend(IntervalAbortable, Abortable);
 
 
-// Allows you to make several network calls, but return only one Abortable object.
+// Allows you to make several network calls, but return
+// only one Abortable object.
 function SuccessiveAbortable()
 {
   this._current = null;
 }
 SuccessiveAbortable.prototype =
 {
   set current(abortable)
   {
@@ -306,47 +318,51 @@ function deepCopy(org)
 }
 
 let kDebug = false;
 function ddump(text)
 {
   if (kDebug)
     dump(text + "\n");
 }
-function ddumpObject(obj, name, maxDepth, curDepth)
+
+function debugObject(obj, name, maxDepth, curDepth)
 {
   if (curDepth == undefined)
     curDepth = 0;
   if (maxDepth != undefined && curDepth > maxDepth)
-    return;
+    return "";
 
+  var result = "";
   var i = 0;
   for (let prop in obj)
   {
     i++;
     try {
       if (typeof(obj[prop]) == "object")
       {
         if (obj[prop] && obj[prop].length != undefined)
-          ddump(name + "." + prop + "=[probably array, length " +
-                obj[prop].length + "]");
+          result += name + "." + prop + "=[probably array, length " +
+                obj[prop].length + "]\n";
         else
-          ddump(name + "." + prop + "=[" + typeof(obj[prop]) + "]");
-        ddumpObject(obj[prop], name + "." + prop, maxDepth, curDepth+1);
+          result += name + "." + prop + "=[" + typeof(obj[prop]) + "]\n";
+        result += debugObject(obj[prop], name + "." + prop,
+                              maxDepth, curDepth + 1);
       }
       else if (typeof(obj[prop]) == "function")
-        ddump(name + "." + prop + "=[function]");
+        result += name + "." + prop + "=[function]\n";
       else
-        ddump(name + "." + prop + "=" + obj[prop]);
+        result += name + "." + prop + "=" + obj[prop] + "\n";
     } catch (e) {
-      ddump(name + "." + prop + "-> Exception(" + e + ")");
+      result += name + "." + prop + "-> Exception(" + e + ")\n";
     }
   }
   if (!i)
-    ddump(name + " is empty");
+    result += name + " is empty\n";
+  return result;
 }
 
 function alertPrompt(alertTitle, alertMsg)
 {
-  Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
-                    .getService(Components.interfaces.nsIPromptService)
-                    .alert(window, alertTitle, alertMsg);
+  Cc["@mozilla.org/embedcomp/prompt-service;1"]
+      .getService(Ci.nsIPromptService)
+      .alert(window, alertTitle, alertMsg);
 }
--- a/mailnews/base/prefs/content/accountcreation/verifyConfig.js
+++ b/mailnews/base/prefs/content/accountcreation/verifyConfig.js
@@ -36,16 +36,20 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * This checks a given config, by trying a real connection and login,
  * with username and password.
  *
+ * TODO
+ * - give specific errors, bug 555448
+ * - return a working |Abortable| to allow cancel
+ *
  * @param accountConfig {AccountConfig} The guessed account config.
  *    username, password, realname, emailaddress etc. are not filled out,
  *    but placeholders to be filled out via replaceVariables().
  * @param alter {boolean}
  *    Try other usernames and login schemes, until login works.
  *    Warning: Modifies |accountConfig|.
  *
  * This function is async.
@@ -55,38 +59,39 @@
  * @param errorCallback function(ex)
  *   Called when we could guess not the config, either
  *   because we have not found anything or
  *   because there was an error (e.g. no network connection).
  *   The ex.message will contain a user-presentable message.
  */
 function verifyConfig(config, alter, msgWindow, successCallback, errorCallback)
 {
-  ddumpObject(config, "config", 3);
-  assert(config instanceof AccountConfig, "BUG: Arg 'config' needs to be an AccountConfig object");
-  assert(typeof(alter) == "boolean", "BUG: Bad arg 'alter'");
-  assert(typeof(successCallback) == "function", "BUG: 'successCallback' is not a function");
-  assert(typeof(errorCallback) == "function", "BUG: 'errorCallback' is not a function");
+  ddump(debugObject(config, "config", 3));
+  assert(config instanceof AccountConfig,
+         "BUG: Arg 'config' needs to be an AccountConfig object");
+  assert(typeof(alter) == "boolean");
+  assert(typeof(successCallback) == "function");
+  assert(typeof(errorCallback) == "function");
 
   var accountManager = Cc["@mozilla.org/messenger/account-manager;1"]
                        .getService(Ci.nsIMsgAccountManager);
 
   if (accountManager.findRealServer(config.incoming.username,
                                     config.incoming.hostname,
                                     sanitize.enum(config.incoming.type,
                                                   ["pop3", "imap", "nntp"]),
                                     config.incoming.port))
     return errorCallback("Incoming server exists");
 
   // incoming server
   var inServer =
     accountManager.createIncomingServer(config.incoming.username,
                                         config.incoming.hostname,
                                         sanitize.enum(config.incoming.type,
-                                                      ["pop3", "imap", "nntp"]));
+                                                    ["pop3", "imap", "nntp"]));
   inServer.port = config.incoming.port;
   inServer.password = config.incoming.password;
   if (config.incoming.socketType == 1) // plain
     inServer.socketType = Ci.nsMsgSocketType.plain;
   else if (config.incoming.socketType == 2) // SSL
     inServer.socketType = Ci.nsMsgSocketType.SSL;
   else if (config.incoming.socketType == 3) // STARTTLS
     inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
@@ -160,28 +165,29 @@ urlListener.prototype =
                           this.mConfig.identity.emailAddress) +
                           ", have savedUsername=" +
                           (this.mConfig.usernameSaved ? "true" : "false"));
     this._log.info("  authMethod=" + this.mServer.authMethod);
   },
 
   OnStopRunningUrl: function(aUrl, aExitCode)
   {
-    this._log.info("Finished testing " + aUrl + " resulted in " + aExitCode);
+    this._log.info("Finished verifyConfig resulted in " + aExitCode);
     if (Components.isSuccessCode(aExitCode))
     {
       this._cleanup();
       this.mSuccessCallback(this.mConfig);
     }
     // Logon failed, and we aren't supposed to try other variations.
     else if (!this.mAlter)
     {
       this._cleanup();
-      var stringBundle = getStringBundle("chrome://messenger/locale/accountCreationModel.properties");
-      var errorMsg = stringBundle.GetStringFromName("cannot_login.error");
+      var errorMsg = getStringBundle(
+          "chrome://messenger/locale/accountCreationModel.properties")
+          .GetStringFromName("cannot_login.error");
       this.mErrorCallback(new Exception(errorMsg));
     }
     // Try other variations, unless there's a cert error, in which
     // case we'll see what the user chooses.
     else if (!this.mCertError)
     {
       this.tryNextLogon()
     }
@@ -224,24 +230,24 @@ urlListener.prototype =
     // sec auth seems to have failed, and we've tried both
     // varieties of user name, sadly.
     // So fall back to non-secure auth, and
     // again try the user name and email address as username
     assert(this.mConfig.incoming.auth == this.mServer.authMethod);
     this._log.info("  Using SSL: " +
         (this.mServer.socketType == Ci.nsMsgSocketType.SSL ||
          this.mServer.socketType == Ci.nsMsgSocketType.alwaysSTARTTLS));
-    this._log.info("  auth alternatives = " +
-        this.mConfig.incoming.authAlternatives.join(","));
     if (this.mConfig.incoming.authAlternatives &&
         this.mConfig.incoming.authAlternatives.length)
         // We may be dropping back to insecure auth methods here,
         // which is not good. But then again, we already warned the user,
         // if it is a config without SSL.
     {
+      this._log.info("  auth alternatives = " +
+          this.mConfig.incoming.authAlternatives.join(","));
       this._log.info("  Decreasing auth.");
       this._log.info("  Have password: " +
                      (this.mServer.password ? "true" : "false"));
       let brokenAuth = this.mConfig.incoming.auth;
       // take the next best method (compare chooseBestAuthMethod() in guess)
       this.mConfig.incoming.auth =
           this.mConfig.incoming.authAlternatives.shift();
       this.mServer.authMethod = this.mConfig.incoming.auth;
@@ -256,63 +262,73 @@ urlListener.prototype =
       verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
                   this.mSuccessCallback, this.mErrorCallback);
       return;
     }
 
     // Tried all variations we can. Give up.
     this._log.info("Giving up.");
     this._cleanup();
-    let stringBundle = getStringBundle("chrome://messenger/locale/accountCreationModel.properties");
-    let errorMsg = stringBundle.GetStringFromName("cannot_login.error");
+    let errorMsg = getStringBundle(
+        "chrome://messenger/locale/accountCreationModel.properties")
+        .GetStringFromName("cannot_login.error");
     this.mErrorCallback(new Exception(errorMsg));
     return;
   },
 
   _cleanup : function()
   {
-    // Avoid pref pollution, clear out server prefs.
-    if (this.mServer) {
-      Cc["@mozilla.org/messenger/account-manager;1"]
-      .getService(Ci.nsIMsgAccountManager)
-      .removeIncomingServer(this.mServer, true);
-
-      this.mServer = null;
-    }
+    try {
+      // Avoid pref pollution, clear out server prefs.
+      if (this.mServer) {
+        Cc["@mozilla.org/messenger/account-manager;1"]
+          .getService(Ci.nsIMsgAccountManager)
+          .removeIncomingServer(this.mServer, true);
+        this.mServer = null;
+      }
+    } catch (e) { this._log.error(e); }
   },
 
   // Suppress any certificate errors
   notifyCertProblem: function(socketInfo, status, targetSite) {
     if (!status)
       return true;
 
     this.mCertError = true;
     this._log.error("cert error");
-    setTimeout(this.informUserOfCertError, 0, socketInfo, targetSite, this);
+    var me = this;
+    setTimeout(function () {
+      try {
+        me.informUserOfCertError(socketInfo, targetSite);
+      } catch (e)  { logException(e); }
+    }, 0);
     return true;
   },
 
-  informUserOfCertError : function(socketInfo, targetSite, self) {
-    let params = { exceptionAdded : false };
-    params.prefetchCert = true;
-    params.location = targetSite;
+  informUserOfCertError : function(socketInfo, targetSite) {
+    var params = {
+      exceptionAdded : false,
+      prefetchCert : true,
+      location : targetSite,
+    };
     window.openDialog("chrome://pippki/content/exceptionDialog.xul",
                       "","chrome,centerscreen,modal", params);
-    self._log.info("cert exception dialog closed");
-    self._log.info("cert exceptionAdded = " + params.exceptionAdded);
+    this._log.info("cert exception dialog closed");
+    this._log.info("cert exceptionAdded = " + params.exceptionAdded);
     if (!params.exceptionAdded) {
-      self._cleanup();
-      let stringBundle = getStringBundle("chrome://messenger/locale/accountCreationModel.properties");
-      let errorMsg = stringBundle.GetStringFromName("cannot_login.error");
-      self.mErrorCallback(new Exception(errorMsg));
+      this._cleanup();
+      let errorMsg = getStringBundle(
+          "chrome://messenger/locale/accountCreationModel.properties")
+          .GetStringFromName("cannot_login.error");
+      this.mErrorCallback(new Exception(errorMsg));
     }
     else {
       // Retry the logon now that we've added the cert exception.
-      verifyLogon(self.mConfig, self.mServer, self.mAlter, self.mMsgWindow,
-                  self.mSuccessCallback, self.mErrorCallback);
+      verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+                  this.mSuccessCallback, this.mErrorCallback);
     }
   },
 
   // nsIInterfaceRequestor
   getInterface: function(iid) {
     return this.QueryInterface(iid);
   },