1 /* ***** BEGIN LICENSE BLOCK *****
  2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3  *
  4  * The contents of this file are subject to the Mozilla Public License Version
  5  * 1.1 (the "License"); you may not use this file except in compliance with
  6  * the License. You may obtain a copy of the License at
  7  * http://www.mozilla.org/MPL/
  8  *
  9  * Software distributed under the License is distributed on an "AS IS" basis,
 10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 11  * for the specific language governing rights and limitations under the
 12  * License.
 13  *
 14  * The Original Code is gContactSync.
 15  *
 16  * The Initial Developer of the Original Code is
 17  * Josh Geenen <gcontactsync@pirules.org>.
 18  * Portions created by the Initial Developer are Copyright (C) 2009-2010
 19  * the Initial Developer. All Rights Reserved.
 20  *
 21  * Contributor(s):
 22  *
 23  * Alternatively, the contents of this file may be used under the terms of
 24  * either the GNU General Public License Version 2 or later (the "GPL"), or
 25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 26  * in which case the provisions of the GPL or the LGPL are applicable instead
 27  * of those above. If you wish to allow use of your version of this file only
 28  * under the terms of either the GPL or the LGPL, and not to allow others to
 29  * use your version of this file under the terms of the MPL, indicate your
 30  * decision by deleting the provisions above and replace them with the notice
 31  * and other provisions required by the GPL or the LGPL. If you do not delete
 32  * the provisions above, a recipient may use your version of this file under
 33  * the terms of any one of the MPL, the GPL or the LGPL.
 34  *
 35  * ***** END LICENSE BLOCK ***** */
 36 
 37 if (!com) var com = {}; // A generic wrapper variable
 38 // A wrapper for all GCS functions and variables
 39 if (!com.gContactSync) com.gContactSync = {};
 40 
 41 window.addEventListener("load",
 42   /** Initializes the Accounts class when the window has finished loading */
 43   function gCS_AccountsLoadListener(e) {
 44     com.gContactSync.Accounts.initDialog();
 45   },
 46 false);
 47 
 48 /**
 49  * The JavaScript variables and functions that handle different gContactSync
 50  * accounts allowing each synchronized address book to have its own preferences.
 51  * @class
 52  */
 53 com.gContactSync.Accounts = {
 54   mUnsavedChange: false,
 55   /** The column index of the address book name
 56    * change this if adding a column before the AB name
 57    */
 58   mAbNameIndex:  0,
 59   /** Element IDs used when enabling/disabling the preferences */
 60   mPrefElemIDs: [
 61     "Username",
 62     "Groups",
 63     "showAdvanced",
 64     "Plugin",
 65     "SyncDirection",
 66     "disabled"
 67   ],
 68   /**
 69    * Initializes the Accounts dialog by filling the tree of address books,
 70    * filling in the usernames, hiding the advanced settings, etc.
 71    */
 72   initDialog:  function Accounts_initDialog() {
 73     try {
 74       this.fillAbTree();
 75       this.fillUsernames();
 76       this.showAdvancedSettings(document.getElementById("showAdvanced").checked);
 77       this.selectedAbChange();
 78     }
 79     catch (e) {
 80       com.gContactSync.LOGGER.LOG_WARNING("Error in Accounts.initDialog", e);
 81       // TODO remove the alert
 82       com.gContactSync.alertError(e);
 83     }
 84   },
 85   /**
 86    * Create a new username/account for the selected plugin.
 87    * @returns {boolean} True if an authentication HTTP request was sent.
 88    */
 89   newUsername: function Accounts_newUsername() {
 90     var prompt   = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
 91                              .getService(Components.interfaces.nsIPromptService)
 92                              .promptUsernameAndPassword,
 93         username = {},
 94         password = {},
 95         // opens a username/password prompt
 96         ok = prompt(window, com.gContactSync.StringBundle.getStr("loginTitle"),
 97                     com.gContactSync.StringBundle.getStr("loginText"), username, password, null,
 98                     {value: false});
 99     if (!ok) {
100       return false;
101     }
102     if (com.gContactSync.LoginManager.getAuthToken(username.value)) { // the username already exists
103       com.gContactSync.alertWarning(com.gContactSync.StringBundle.getStr("usernameExists"));
104       return false;
105     }
106     // This is a primitive way of validating an e-mail address, but Google takes
107     // care of the rest.  It seems to allow getting an auth token w/ only the
108     // username, but returns an error when trying to do anything w/ that token
109     // so this makes sure it is a full e-mail address.
110     if (username.value.indexOf("@") < 1) {
111       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("invalidEmail"));
112       return this.newUsername();
113     }
114     // fix the username before authenticating
115     username.value = com.gContactSync.fixUsername(username.value);
116     var body    = com.gContactSync.gdata.makeAuthBody(username.value, password.value),
117         httpReq = new com.gContactSync.GHttpRequest("authenticate", null, null, body);
118     // if it succeeds and Google returns the auth token, store it and then start
119     // a new sync
120     httpReq.mOnSuccess = function newUsernameSuccess(httpReq) {
121       com.gContactSync.LoginManager.addAuthToken(username.value,
122                                                  'GoogleLogin' + httpReq.responseText.split("\n")[2]);
123       com.gContactSync.Accounts.selectedAbChange();
124       com.gContactSync.Accounts.fillUsernames();
125     };
126     // if it fails, alert the user and prompt them to try again
127     httpReq.mOnError   = function newUsernameError(httpReq) {
128       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr('authErr'));
129       com.gContactSync.LOGGER.LOG_ERROR('Authentication Error - ' +
130                                         httpReq.status,
131                                         httpReq.responseText);
132       com.gContactSync.Accounts.newUsername();
133     };
134     // if the user is offline, alert them and quit
135     httpReq.mOnOffline = function newUsernameOffline(httpReq) {
136       com.gContactSync.alertWarning(com.gContactSync.StringBundle.getStr('offlineErr'));
137       com.gContactSync.LOGGER.LOG_ERROR(com.gContactSync.StringBundle.getStr('offlineErr'));
138     };
139     httpReq.send();
140     return true;
141   },
142   /**
143    * Returns a new GAddressBook corresponding to the currently-selected address
144    * book in the accounts tree.
145    * @returns {com.gContactSync.GAddressBook} A GAddressBook if one is selected, else false.
146    */
147   getSelectedAb: function Accounts_getSelectedAb() {
148     var tree = document.getElementById("loginTree");
149     if (tree.currentIndex < 0) {
150       this.enablePreferences(false);
151       return false;
152     }
153     this.enablePreferences(true);
154     var abName = tree.view.getCellText(tree.currentIndex,
155                                        tree.columns.getColumnAt(this.mAbNameIndex)),
156         ab     = com.gContactSync.GAbManager.getAbByName(abName);
157     if (!ab) {
158       return false;
159     }
160     return new com.gContactSync.GAddressBook(ab);    
161   },
162   /**
163    * Creates and returns a new address book after requesting a name for it.
164    * If an AB of any type already exists this function will do nothing.
165    * @returns {nsIAbDirectory} The new address book.
166    */
167   newAddressBook: function Accounts_newAddressBook() {
168     var name = com.gContactSync.prompt(com.gContactSync.StringBundle.getStr("newABPrompt"), null, window);
169     if (!name)
170       return false;
171     var ab = com.gContactSync.AbManager.getAbByName(name);
172     this.fillAbTree();
173     return ab;
174   },
175   /**
176    * Saves the preferences for the selected address book.
177    * @returns {boolean} True if the preferences were saved
178    */
179   saveSelectedAccount: function Accounts_saveSelectedAccount() {
180     var usernameElem  = document.getElementById("Username"),
181         groupElem     = document.getElementById("Groups"),
182         directionElem = document.getElementById("SyncDirection"),
183         pluginElem    = document.getElementById("Plugin"),
184         disableElem   = document.getElementById("disabled"),
185         updateGElem   = document.getElementById("updateGoogleInConflicts"),
186         ab            = this.getSelectedAb(),
187         needsReset    = false;
188     if (!ab) {
189       return null;
190     }
191 
192     if (!usernameElem || !groupElem || !directionElem || !pluginElem || !disableElem) {
193       return false;
194     }
195     var syncGroups = String(groupElem.value === "All"),
196         myContacts = String(groupElem.value !== "All" && groupElem.value !== "false");
197     // the simple preferences
198     ab.savePref("Username",                usernameElem.value);
199     ab.savePref("Plugin",                  pluginElem.value);
200     ab.savePref("Disabled",                disableElem.checked);
201     ab.savePref("updateGoogleInConflicts", updateGElem.checked);
202     // this is for backward compatibility
203     ab.savePref("Primary",  "true");
204     // Group to sync
205     ab.savePref("syncGroups",     syncGroups);
206     ab.savePref("myContacts",     myContacts);
207     ab.savePref("myContactsName", groupElem.value);
208     // Sync Direction
209     ab.savePref("writeOnly", directionElem.value === "WriteOnly");
210     ab.savePref("readOnly",  directionElem.value === "ReadOnly");
211     // this is done before the needsReset call in case something happens
212     // reset the unsaved change
213     this.mUnsavedChange = false;
214     this.fillUsernames();
215     this.selectedAbChange();
216     this.fillAbTree();
217     // check if the AB should be reset based on the new values
218     needsReset = this.needsReset(ab, usernameElem.value, syncGroups, myContacts, groupElem.value);
219     if (needsReset) {
220       ab.reset();
221       com.gContactSync.alert(com.gContactSync.StringBundle.getStr("finishedAcctSave"));
222     }
223     else {
224       com.gContactSync.alert(com.gContactSync.StringBundle.getStr("finishedAcctSaveNoRestart"));
225     }
226     return true;
227   },
228   /**
229    * Enables or disables the preference elements.
230    * @param aEnable {boolean} Set to true to enable elements or false to disable
231    *                          them.
232    */
233   enablePreferences: function Accounts_enablePreferences(aEnable) {
234     var elem, i;
235     for (i = 0; i < this.mPrefElemIDs.length; i++) {
236       elem = document.getElementById(this.mPrefElemIDs[i]);
237       if (!elem) {
238         com.gContactSync.LOGGER.LOG_WARNING(this.mPrefElemIDs[i] + " not found");
239         continue;
240       }
241       elem.disabled = aEnable ? false : true;
242     }
243   },
244   /**
245    * Show or hide the advanced settings and then call window.sizeToContent().
246    * @param aShow {boolean} Set to true to show the advanced settings or false
247    *                        to hide them.
248    * @returns {boolean} True if the advanced settings were shown or hidden.
249    */
250   showAdvancedSettings: function Accounts_showAdvanceDsettings(aShow) {
251     var elem = document.getElementById("advancedGroupBox");
252     if (!elem) return false;
253     elem.setAttribute("collapsed", aShow ? "false" : "true");
254     window.sizeToContent();
255     return true;
256   },
257   /**
258    * Called when the selected address book changes in the accounts tree.
259    * @returns {boolean} true if there is currently an address book selected.
260    */
261   selectedAbChange: function Accounts_selectedAbChange() {
262     var usernameElem  = document.getElementById("Username"),
263         groupElem     = document.getElementById("Groups"),
264         directionElem = document.getElementById("SyncDirection"),
265         pluginElem    = document.getElementById("Plugin"),
266         disableElem   = document.getElementById("disabled"),
267         updateGElem   = document.getElementById("updateGoogleInConflicts"),
268         ab            = this.getSelectedAb();
269     this.restoreGroups();
270     if (!usernameElem || !groupElem || !directionElem || !pluginElem || !disableElem) {
271       return false;
272     }
273     if (!ab) {
274       return ab;
275     }
276     // Username/Account
277     this.fillUsernames(ab.mPrefs.Username);
278     // Group
279     // The myContacts pref (enable sync w/ one group) has priority
280     // If that is checked an the myContactsName is pref sync just that group
281     // Otherwise sync all or no groups based on the syncGroups pref
282     var group = ab.mPrefs.myContacts ?
283                (ab.mPrefs.myContactsName ? ab.mPrefs.myContactsName : "false") :
284                (ab.mPrefs.syncGroups !== "false" ? "All" : "false");
285     com.gContactSync.selectMenuItem(groupElem, group, true);
286     // Sync Direction
287     var direction = ab.mPrefs.readOnly === "true" ? "ReadOnly" :
288                       ab.mPrefs.writeOnly === "true" ? "WriteOnly" : "Complete";
289     com.gContactSync.selectMenuItem(directionElem, direction, true);
290     // Temporarily disable synchronization with the address book
291     disableElem.checked = ab.mPrefs.Disabled === "true";
292     // Overwrite remote changes with local changes in a conflict
293     updateGElem.checked = ab.mPrefs.updateGoogleInConflicts === "true";
294     // Select the correct plugin
295     com.gContactSync.selectMenuItem(pluginElem, ab.mPrefs.Plugin, true);
296     
297     return true;
298   },
299   /**
300    * Fills the 'Username' menulist with all the usernames of the current plugin.
301    * @param aDefault {string} The default account to select.  If not present or
302    *                          evaluating to 'false' then 'None' will be
303    *                          selected.
304    */
305   fillUsernames: function Accounts_fillUsernames(aDefault) {
306     var usernameElem = document.getElementById("Username"),
307         tokens       = com.gContactSync.LoginManager.getAuthTokens(),
308         item,
309         username,
310         index = -1;
311     if (!usernameElem) {
312       return false;
313     }
314     // Remove all existing logins from the menulist
315     usernameElem.removeAllItems();
316 
317     usernameElem.appendItem(com.gContactSync.StringBundle.getStr("noAccount"), "none");
318     // Add a menuitem for each account with an auth token
319     for (username in tokens) {
320       item = usernameElem.appendItem(username, username);
321       if (aDefault === username && aDefault !== undefined) {
322         index = usernameElem.menupopup.childNodes.length - 1;
323       }
324     }
325 
326     if (index > -1) {
327       usernameElem.selectedIndex = index;
328     }
329     // if the default value isn't in the menu list, add & select it
330     // this can happen when an account is added through one version of the
331     // login manager and the Accounts dialog was opened in another
332     // This isn't retained (for now?) to prevent anyone from setting up a new
333     // synchronized account with it and expecting it to work.
334     else if (aDefault) {
335       com.gContactSync.selectMenuItem(usernameElem, aDefault, true);
336     }
337     // Otherwise select None
338     else {
339       usernameElem.selectedIndex = 0;
340     }
341 
342     return true;
343   },
344   /**
345    * Populates the address book tree with all Personal/Mork Address Books
346    */
347   fillAbTree: function Accounts_fillAbTree() {
348     var tree          = document.getElementById("loginTree"),
349         treechildren  = document.getElementById("loginTreeChildren"),
350         newTreeChildren,
351         abs,
352         i;
353   
354     if (treechildren) {
355       try { tree.removeChild(treechildren); } catch (e) {}
356     }
357     newTreeChildren = document.createElement("treechildren");
358     newTreeChildren.setAttribute("id", "loginTreeChildren");
359     tree.appendChild(newTreeChildren);
360 
361     // Get all Personal/Mork DB Address Books (type == 2,
362     // see mailnews/addrbook/src/nsDirPrefs.h)
363     // TODO - there should be a way to change the allowed dir types...
364     abs = com.gContactSync.GAbManager.getAllAddressBooks(2);
365     for (i in abs) {
366       if (abs[i] instanceof com.gContactSync.GAddressBook) {
367         this.addToTree(newTreeChildren, abs[i]);
368       }
369     }
370     return true;
371   },
372   /**
373    * Adds login information (username and directory name) to the tree.
374    * @param aTreeChildren {object} The <treechildren> XUL element.
375    * @param aAB           {GAddressBook} The GAddressBook to add.
376    */
377   addToTree: function Accounts_addToTree(aTreeChildren, aAB) {
378     if (!aAB || !aAB instanceof com.gContactSync.GAddressBook) {
379       throw "Error - Invalid AB passed to addToTree";
380     }
381     var treeitem    = document.createElement("treeitem"),
382         treerow     = document.createElement("treerow"),
383         addressbook = document.createElement("treecell"),
384         synced      = document.createElement("treecell");
385 
386     addressbook.setAttribute("label", aAB.getName());
387     synced.setAttribute("label", aAB.mPrefs.Username ? aAB.mPrefs.Username : com.gContactSync.StringBundle.getStr("noAccount"));
388 
389     treerow.appendChild(addressbook);
390     treerow.appendChild(synced);
391     treeitem.appendChild(treerow);
392     aTreeChildren.appendChild(treeitem);
393 
394     return true;
395   },
396   /**
397    * Deletes the selected address book
398    * This function is commented out as the associated button was removed
399    */
400   /*
401   deleteSelectedAB: function Accounts_deleteSelectedAB() {
402     var ab = this.getSelectedAb();
403     if (!ab) {
404       com.gContactSync.alertWarning(com.gContactSync.StringBundle.getStr("noABSelected"));
405       return ab;
406     }
407     // Make sure sure the user doesn't try to delete the CAB or PAB
408     var uri = ab.mURI;
409     if (!uri || uri.indexOf("abook.mab") !== -1 || uri.indexOf("history.mab") !== -1) {
410       com.gContactSync.alertError(com.gContactSync.StringBundle.getStr("deletePAB"));
411       return false;
412     }
413     if (!com.gContactSync.confirm(com.gContactSync.StringBundle.getStr("deleteAB")))
414       return false;
415     // This function also checks that the AB isn't the PAB or CAB
416     ab.deleteAB();
417     this.fillAbTree();
418     return true;
419   },
420   */
421   /**
422    * Removes the synchronization settings from the selected address book.
423    * @return {boolean} True if the synchronization settings were removed.
424    */
425   /*
426   removeSyncSettings: function Accounts_removeSelectedLogin() {  
427     var ab = this.getSelectedAb();
428     if (!ab) {
429       com.gContactSync.alertWarning(com.gContactSync.StringBundle.getStr("noABSelected"));
430       return false;
431     }
432     if (!com.gContactSync.confirm(com.gContactSync.StringBundle.getStr("removeSyncSettings"))) {
433       return false;
434     }
435     // remove the saved prefs from the address book
436     ab.savePref("Username", "");
437     ab.setLastSyncDate(0);
438     ab.savePref("myContactsName", "");
439     ab.savePref("myContacts",     "");
440     this.fillUsernames();
441     this.selectedAbChange();
442     this.fillAbTree();
443     return true;
444   },
445   */
446   /**
447    * Shows an alert dialog that briefly explains the synchronization direction
448    * preference.
449    */
450   directionPopup: function Accounts_directionPopup() {
451     com.gContactSync.alert(com.gContactSync.StringBundle.getStr("directionPopup")); 
452   },
453   /**
454    * Restores the Groups menulist to contain only the default groups.
455    */
456   restoreGroups: function Accounts_restoreGroups() {
457     var groupElem = document.getElementById("GroupsPopup");
458     for (var i = groupElem.childNodes.length - 1; i > -1; i--) {
459       if (groupElem.childNodes[i].getAttribute("class") !== "default")
460         groupElem.removeChild(groupElem.childNodes[i]);
461     }
462   },
463   /**
464    * Fetch all groups for the selected account and add custom groups to the
465    * menulist.
466    */
467   getAllGroups: function Accounts_getAllGroups() {
468     var usernameElem  = document.getElementById("Username");
469     this.restoreGroups();
470     if (usernameElem.value === "none" || !usernameElem.value)
471       return false;
472     var token = com.gContactSync.LoginManager.getAuthTokens()[usernameElem.value];
473     if (!token) {
474       com.gContactSync.LOGGER.LOG_WARNING("Unable to find the token for username " + usernameElem.value);
475       return false;
476     }
477     com.gContactSync.LOGGER.VERBOSE_LOG("Fetching groups for username: " + usernameElem.value);
478     var httpReq = new com.gContactSync.GHttpRequest("getGroups", token, null,
479                                    null, usernameElem.value);
480     httpReq.mOnSuccess = function getAllGroupsSuccess(httpReq) {
481       com.gContactSync.LOGGER.VERBOSE_LOG(com.gContactSync.serializeFromText(httpReq.responseText));
482       com.gContactSync.Accounts.addGroups(httpReq.responseXML,
483                                           usernameElem.value);
484     };
485     httpReq.mOnError   = function getAllGroupsError(httpReq) {
486       com.gContactSync.LOGGER.LOG_ERROR(httpReq.responseText);
487     };
488     httpReq.mOnOffline = null;
489     httpReq.send();
490     return true;
491   },
492   /**
493    * Adds groups in the given atom feed to the Groups menulist provided the
494    * username hasn't changed since the groups request was sent and the username
495    * isn't blank.
496    */
497   addGroups: function Accounts_addGroups(aAtom, aUsername) {
498     var usernameElem  = document.getElementById("Username"),
499         menulistElem  = document.getElementById("Groups"),
500         group,
501         title,
502         i,
503         arr;
504     if (!aAtom) {
505       return false;
506     }
507     if (usernameElem.value === "none" || usernameElem.value !== aUsername) {
508       return false;
509     }
510     arr = aAtom.getElementsByTagNameNS(com.gContactSync.gdata.namespaces.ATOM.url, "entry");
511     com.gContactSync.LOGGER.VERBOSE_LOG("Adding groups from username: " + aUsername);
512     for (i = 0; i < arr.length; i++) {
513       group = new com.gContactSync.Group(arr[i]);
514       title = group.getTitle();
515       com.gContactSync.LOGGER.VERBOSE_LOG(" * " + title);
516       // don't add system groups again
517       if (!title || group.isSystemGroup()) {
518         com.gContactSync.LOGGER.VERBOSE_LOG("    - Skipping system group");
519         continue;
520       }
521       menulistElem.appendItem(title, title);
522     }
523     return true;
524   },
525   /**
526    * Returns whether the given address book should be reset and prompts the user
527    * before returning true.
528    * Resetting an address book is necessary when ALL of the following
529    * conditions marked with * are met:
530    *  * The username was NOT originally blank
531    *  * The new username is NOT blank
532    *  * The last sync date of the AB is > 0
533    *  * The user agrees that the AB should be reset (using a confirm dialog)
534    *  * AND at least one of the following is true:
535    *    o The username has changed (and wasn't originally blank)
536    *    o OR The group to sync has been changed
537    *
538    * @param aAB {string}              The GAddressBook being modified.  If this
539    *                                  function returns true this AB should be
540    *                                  reset.
541    * @param aUsername {string}        The new username for the account with
542    *                                  which aAB will be synchronized.
543    * @param aSyncGroups {string}      The new value for the syncGroups pref.
544    * @param aMyContacts {string}      The new value for the myContacts pref.
545    * @param aMyContactsName {string}  The new value for the myContactsName pref.
546    *
547    * @return {boolean} true if the AB should be reset.  See the detailed
548    *                        description for more details.
549    */
550   needsReset: function Accounts_needsReset(aAB, aUsername, aSyncGroups, aMyContacts, aMyContactsName) {
551     com.gContactSync.LOGGER.VERBOSE_LOG
552       (
553        "**Determining if the address book '" + aAB.getName() +
554        "' should be reset:\n" +
555       "  * " + aUsername       + " <- " + aAB.mPrefs.Username + "\n" +
556       "  * " + aSyncGroups     + " <- " + aAB.mPrefs.syncGroups + "\n" +
557       "  * " + aMyContacts     + " <- " + aAB.mPrefs.myContacts + "\n" +
558       "  * " + aMyContactsName + " <- " + aAB.mPrefs.myContactsName + "\n" +
559       "  * Last sync date: " + aAB.mPrefs.lastSync
560      );
561     // TODO - remove once this is no longer necessary
562     aAB.getPrefs();
563     // NOTE: mUnsavedChange is reset to false before this method is called
564     if ((aAB.mPrefs.Username && aAB.mPrefs.Username !== "none") &&
565          aUsername !== "none" &&
566          parseInt(aAB.mPrefs.lastSync, 10) > 0 &&
567          (
568           aAB.mPrefs.Username !== aUsername ||
569           aAB.mPrefs.syncGroups !== aSyncGroups ||
570           aAB.mPrefs.myContacts !== aMyContacts ||
571           aAB.mPrefs.myContactsName !== aMyContactsName
572          )) {
573       var reset = com.gContactSync.confirm(com.gContactSync.StringBundle.getStr("confirmABReset"));
574       com.gContactSync.LOGGER.VERBOSE_LOG("  * Confirmation result: " + reset + "\n");
575       return reset;
576     }
577     com.gContactSync.LOGGER.VERBOSE_LOG("  * The AB will NOT be reset\n");
578     return false;
579   },
580   /**
581    * This method is called when the user clicks the Accept button
582    * (labeled Close) or when acceptDialog() is called.
583    * If there are unsaved changes it will let the user save changes if
584    * desired.
585    * @returns {boolean} Always returns true (close the dialog).
586    */
587   close: function Accounts_close() {
588     if (this.mUnsavedChange &&
589         com.gContactSync.confirm(com.gContactSync.StringBundle.getStr("unsavedAcctChanges"))) {
590       this.saveSelectedAccount();
591     }
592     return true;
593   }
594 };
595