Bug 1019471 - Create a dialog to add/edit/view an autofill profile
MozReview-Commit-ID: avVGZAYq0y
--- 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