Bug 1019471 - Create a dialog to add/edit/view an autofill profile draft
authorScott Wu <scottcwwu@gmail.com>
Wed, 08 Feb 2017 16:13:47 +0800
changeset 493848 9789dc13e8ce50333931bd3d9231be6fd51e4caa
parent 487797 196f9ddbf7aa01abb1976f3b0adc85c738e80f32
child 547948 25005db01c6d66603495d5ce1b35c99c2a76b500
push id47865
push userbmo:scwwu@mozilla.com
push dateMon, 06 Mar 2017 06:53:16 +0000
bugs1019471
milestone54.0a1
Bug 1019471 - Create a dialog to add/edit/view an autofill profile MozReview-Commit-ID: avVGZAYq0y
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/FormAutofillPreferences.jsm
browser/extensions/formautofill/content/editProfile.css
browser/extensions/formautofill/content/editProfile.js
browser/extensions/formautofill/content/editProfile.xhtml
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -71,16 +71,17 @@ FormAutofillParent.prototype = {
    */
   init() {
     log.debug("init");
     let storePath = OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME);
     this._profileStore = new ProfileStorage(storePath);
     this._profileStore.initialize();
 
     Services.obs.addObserver(this, "advanced-pane-loaded", false);
+    Services.ppmm.addMessageListener("FormAutofill:SaveProfile", this);
 
     // Observing the pref and storage changes
     Services.prefs.addObserver(ENABLED_PREF, this, false);
     Services.obs.addObserver(this, "formautofill-storage-changed", false);
     this._enabled = this._getStatus();
     // Force to trigger the onStatusChanged function for setting listeners properly
     // while initizlization
     this._onStatusChanged();
@@ -170,16 +171,23 @@ FormAutofillParent.prototype = {
     switch (name) {
       case "FormAutofill:GetProfiles":
         this._getProfiles(data, target);
         break;
       case "FormAutofill:getEnabledStatus":
         Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus",
                                             this._enabled);
         break;
+      case "FormAutofill:SaveProfile":
+        if (data.guid) {
+          this.getProfileStore().update(data.guid, data.profile);
+        } else {
+          this.getProfileStore().add(data.profile);
+        }
+        break;
     }
   },
 
   /**
    * Returns the instance of ProfileStorage. To avoid syncing issues, anyone
    * who needs to access the profile should request the instance by this instead
    * of creating a new one.
    *
@@ -196,16 +204,17 @@ FormAutofillParent.prototype = {
    */
   _uninit() {
     if (this._profileStore) {
       this._profileStore._saveImmediately();
       this._profileStore = null;
     }
 
     Services.ppmm.removeMessageListener("FormAutofill:GetProfiles", this);
+    Services.ppmm.removeMessageListener("FormAutofill:SaveProfile", this);
     Services.obs.removeObserver(this, "advanced-pane-loaded");
     Services.prefs.removeObserver(ENABLED_PREF, this);
   },
 
   /**
    * Get the profile data from profile store and return profiles back to content process.
    *
    * @private
--- a/browser/extensions/formautofill/FormAutofillPreferences.jsm
+++ b/browser/extensions/formautofill/FormAutofillPreferences.jsm
@@ -10,16 +10,21 @@
 
 this.EXPORTED_SYMBOLS = ["FormAutofillPreferences"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 const PREF_AUTOFILL_ENABLED = "browser.formautofill.enabled";
 const BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://formautofill/FormAutofillUtils.jsm");
+
+this.log = null;
+FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 function FormAutofillPreferences() {
   this.bundle = Services.strings.createBundle(BUNDLE_URI);
 }
 
 FormAutofillPreferences.prototype = {
   /**
    * Check if Form Autofill feature is enabled.
@@ -41,16 +46,36 @@ FormAutofillPreferences.prototype = {
 
   /**
    * Create the Form Autofill preference group.
    *
    * @param   {XULDocument} document
    * @returns {XULElement}
    */
   init(document) {
+    this.createPreferenceGroup(document);
+    this.attachEventListeners();
+
+    return this.refs.formAutofillGroup;
+  },
+
+  /**
+   * Remove event listeners and the preference group.
+   */
+  uninit() {
+    this.detachEventListeners();
+    this.refs.formAutofillGroup.remove();
+  },
+
+  /**
+   * Create Form Autofill preference group
+   *
+   * @param  {XULDocument} document
+   */
+  createPreferenceGroup(document) {
     const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
     let formAutofillGroup = document.createElementNS(XUL_NS, "groupbox");
     let caption = document.createElementNS(XUL_NS, "caption");
     let captionLabel = document.createElementNS(XUL_NS, "label");
     let hbox = document.createElementNS(XUL_NS, "hbox");
     let enabledCheckbox = document.createElementNS(XUL_NS, "checkbox");
     let spacer = document.createElementNS(XUL_NS, "spacer");
@@ -80,28 +105,16 @@ FormAutofillPreferences.prototype = {
     spacer.flex = 1;
 
     formAutofillGroup.appendChild(caption);
     caption.appendChild(captionLabel);
     formAutofillGroup.appendChild(hbox);
     hbox.appendChild(enabledCheckbox);
     hbox.appendChild(spacer);
     hbox.appendChild(savedProfilesBtn);
-
-    this.attachEventListeners();
-
-    return formAutofillGroup;
-  },
-
-  /**
-   * Remove event listeners and the preference group.
-   */
-  uninit() {
-    this.detachEventListeners();
-    this.refs.formAutofillGroup.remove();
   },
 
   /**
    * Handle events
    *
    * @param  {DOMEvent} event
    */
   handleEvent(event) {
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/editProfile.css
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+body {
+  font-size: 1rem;
+}
+
+form,
+label,
+div {
+  display: flex;
+}
+
+form {
+  flex-wrap: wrap;
+}
+
+label {
+  margin: 0 0 0.5em;
+}
+
+span {
+  flex: 0 0 8em;
+  padding-inline-end: 0.5em;
+  align-self: center;
+  text-align: end;
+}
+
+input,
+select {
+  flex: 1 0 auto;
+  width: 9em;
+}
+
+option {
+  padding: 6px;
+}
+
+textarea {
+  resize: none;
+}
+
+button {
+  padding: 3px 2em;
+}
+
+#country-container {
+  width: 15em;
+}
+
+#first-name-container,
+#middle-name-container,
+#address-level1-container,
+#postal-code-container,
+#country-container {
+  flex: 0 1 50%;
+}
+
+#last-name-container,
+#organization-container,
+#street-address-container,
+#address-level2-container,
+#email-container,
+#tel-container,
+#controls-container {
+  flex: 0 1 100%;
+}
+
+#controls-container {
+  justify-content: end;
+}
+
+#last-name,
+#organization,
+#address-level2,
+#tel{
+  flex: 0 0 auto;
+  width: calc(50% - 10em);
+}
+
+#street-address,
+#email {
+  flex: 1 0 auto;
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/editProfile.js
@@ -0,0 +1,140 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://formautofill/FormAutofillUtils.jsm");
+
+function EditDialog(profile) {
+  this._profile = profile;
+  window.addEventListener("DOMContentLoaded", this, {once: true});
+}
+
+EditDialog.prototype = {
+  init() {
+    this.refs = {
+      controlsContainer: document.getElementById("controls-container"),
+      cancel: document.getElementById("cancel"),
+      save: document.getElementById("save"),
+    };
+    this.attachEventListeners();
+  },
+
+  /**
+   * Asks FormAutofillParent to save or update a profile.
+   * @param  {object} data
+   *         {
+   *           {string} guid [optional]
+   *           {object} profile
+   *         }
+   */
+  saveProfile(data) {
+    Services.cpmm.sendAsyncMessage("FormAutofill:SaveProfile", data);
+  },
+
+  /**
+   * Fill the form with a profile object.
+   * @param  {object} profile
+   */
+  loadInitialValues(profile) {
+    for (let field in profile) {
+      let input = document.getElementById(field);
+      if (input) {
+        input.value = profile[field];
+      }
+    }
+  },
+
+  /**
+   * Get inputs from the form.
+   * @returns {object}
+   */
+  buildProfileObject() {
+    return Array.from(document.forms[0].elements).reduce((obj, input) => {
+      if (input.value) {
+        obj[input.id] = input.value;
+      }
+      return obj;
+    }, {});
+  },
+
+  /**
+   * Handle events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleEvent(event) {
+    switch (event.type) {
+      case "DOMContentLoaded": {
+        this.init();
+        if (this._profile) {
+          this.loadInitialValues(this._profile);
+        }
+        break;
+      }
+      case "click": {
+        this.handleClick(event);
+        break;
+      }
+      case "input": {
+        // Toggle disabled attribute on the save button based on
+        // whether the form is filled or empty.
+        if (Object.keys(this.buildProfileObject()).length == 0) {
+          this.refs.save.setAttribute("disabled", true);
+        } else {
+          this.refs.save.removeAttribute("disabled");
+        }
+        break;
+      }
+    }
+  },
+
+  /**
+   * Handle click events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleClick(event) {
+    if (event.target == this.refs.cancel) {
+      this.detachEventListeners();
+      window.close();
+    }
+    if (event.target == this.refs.save) {
+      if (this._profile) {
+        this.saveProfile({
+          guid: this._profile.guid,
+          profile: this.buildProfileObject(),
+        });
+      } else {
+        this.saveProfile({
+          profile: this.buildProfileObject(),
+        });
+      }
+      this.detachEventListeners();
+      window.close();
+    }
+  },
+
+  /**
+   * Attach event listener
+   */
+  attachEventListeners() {
+    this.refs.controlsContainer.addEventListener("click", this);
+    document.addEventListener("input", this);
+  },
+
+  /**
+   * Remove event listener
+   */
+  detachEventListeners() {
+    this.refs.controlsContainer.removeEventListener("click", this);
+    document.removeEventListener("input", this);
+  },
+};
+
+// Pass in argument from openDialog
+new EditDialog(window.arguments[0]);
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/editProfile.xhtml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>Profile Autofill - Edit Profile</title>
+  <link rel="stylesheet" href="chrome://formautofill/content/editProfile.css" />
+  <script type="application/javascript" src="chrome://formautofill/content/editProfile.js"></script>
+</head>
+<body>
+  <form>
+    <label id="first-name-container">
+      <span>First Name</span>
+      <input id="first-name" type="text"/>
+    </label>
+    <label id="middle-name-container">
+      <span>Middle Name</span>
+      <input id="middle-name" type="text"/>
+    </label>
+    <label id="last-name-container">
+      <span>Last Name</span>
+      <input id="last-name" type="text"/>
+    </label>
+    <label id="organization-container">
+      <span>Company</span>
+      <input id="organization" type="text"/>
+    </label>
+    <label id="street-address-container">
+      <span>Street Address</span>
+      <textarea id="street-address"/>
+    </label>
+    <label id="address-level2-container">
+      <span>City/Town</span>
+      <input id="address-level2" type="text"/>
+    </label>
+    <label id="address-level1-container">
+      <span>State/Province</span>
+      <input id="address-level1" type="text"/>
+    </label>
+    <label id="postal-code-container">
+      <span>Zip/Postal</span>
+      <input id="postal-code" type="text"/>
+    </label>
+    <label id="country-container">
+      <span>Country</span>
+      <select id="country">
+        <option/>
+        <option>United States</option>
+      </select>
+    </label>
+    <label id="email-container">
+      <span>Email</span>
+      <input id="email" type="text"/>
+    </label>
+    <label id="tel-container">
+      <span>Phone</span>
+      <input id="tel" type="text"/>
+    </label>
+  </form>
+  <div id="controls-container">
+    <button id="cancel">Cancel</button>
+    <button id="save" disabled="true">Save</button>
+  </div>
+</body>
+</html>
\ No newline at end of file