зеркало из https://github.com/mozilla/gecko-dev.git
275 строки
8.5 KiB
JavaScript
275 строки
8.5 KiB
JavaScript
/* 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/. */
|
|
|
|
/*
|
|
* Implements a service used to access storage and communicate with content.
|
|
*
|
|
* A "fields" array is used to communicate with FormAutofillContent. Each item
|
|
* represents a single input field in the content page as well as its
|
|
* @autocomplete properties. The schema is as below. Please refer to
|
|
* FormAutofillContent.js for more details.
|
|
*
|
|
* [
|
|
* {
|
|
* section,
|
|
* addressType,
|
|
* contactType,
|
|
* fieldName,
|
|
* value,
|
|
* index
|
|
* },
|
|
* {
|
|
* // ...
|
|
* }
|
|
* ]
|
|
*/
|
|
|
|
/* exported FormAutofillParent */
|
|
|
|
"use strict";
|
|
|
|
this.EXPORTED_SYMBOLS = ["FormAutofillParent"];
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
|
"resource://gre/modules/osfile.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "ProfileStorage",
|
|
"resource://formautofill/ProfileStorage.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillPreferences",
|
|
"resource://formautofill/FormAutofillPreferences.jsm");
|
|
|
|
this.log = null;
|
|
FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
|
|
|
|
const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
|
|
const ENABLED_PREF = "browser.formautofill.enabled";
|
|
|
|
function FormAutofillParent() {
|
|
}
|
|
|
|
FormAutofillParent.prototype = {
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
|
|
|
|
_profileStore: null,
|
|
|
|
/**
|
|
* Whether Form Autofill is enabled in preferences.
|
|
* Caches the latest value of this._getStatus().
|
|
*/
|
|
_enabled: false,
|
|
|
|
/**
|
|
* Initializes ProfileStorage and registers the message handler.
|
|
*/
|
|
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");
|
|
Services.ppmm.addMessageListener("FormAutofill:GetProfiles", this);
|
|
Services.ppmm.addMessageListener("FormAutofill:SaveProfile", this);
|
|
Services.ppmm.addMessageListener("FormAutofill:RemoveProfiles", this);
|
|
|
|
// Observing the pref and storage changes
|
|
Services.prefs.addObserver(ENABLED_PREF, this);
|
|
Services.obs.addObserver(this, "formautofill-storage-changed");
|
|
|
|
// Force to trigger the onStatusChanged function for setting listeners properly
|
|
// while initizlization
|
|
this._setStatus(this._getStatus());
|
|
this._updateSavedFieldNames();
|
|
},
|
|
|
|
observe(subject, topic, data) {
|
|
log.debug("observe:", topic, "with data:", data);
|
|
switch (topic) {
|
|
case "advanced-pane-loaded": {
|
|
let formAutofillPreferences = new FormAutofillPreferences();
|
|
let document = subject.document;
|
|
let prefGroup = formAutofillPreferences.init(document);
|
|
let parentNode = document.getElementById("mainPrefPane");
|
|
let insertBeforeNode = document.getElementById("locationBarGroup");
|
|
parentNode.insertBefore(prefGroup, insertBeforeNode);
|
|
break;
|
|
}
|
|
|
|
case "nsPref:changed": {
|
|
// Observe pref changes and update _enabled cache if status is changed.
|
|
let currentStatus = this._getStatus();
|
|
if (currentStatus !== this._enabled) {
|
|
this._setStatus(currentStatus);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case "formautofill-storage-changed": {
|
|
// Early exit if the action is not "add" nor "remove"
|
|
if (data != "add" && data != "remove") {
|
|
break;
|
|
}
|
|
|
|
this._updateSavedFieldNames();
|
|
let currentStatus = this._getStatus();
|
|
if (currentStatus !== this._enabled) {
|
|
this._setStatus(currentStatus);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
throw new Error(`FormAutofillParent: Unexpected topic observed: ${topic}`);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Broadcast the status to frames when the form autofill status changes.
|
|
*/
|
|
_onStatusChanged() {
|
|
log.debug("_onStatusChanged: Status changed to", this._enabled);
|
|
Services.ppmm.broadcastAsyncMessage("FormAutofill:enabledStatus", this._enabled);
|
|
// Sync process data autofillEnabled to make sure the value up to date
|
|
// no matter when the new content process is initialized.
|
|
Services.ppmm.initialProcessData.autofillEnabled = this._enabled;
|
|
},
|
|
|
|
/**
|
|
* Query pref and storage status to determine the overall status for
|
|
* form autofill feature.
|
|
*
|
|
* @returns {boolean} status of form autofill feature
|
|
*/
|
|
_getStatus() {
|
|
if (!Services.prefs.getBoolPref(ENABLED_PREF)) {
|
|
return false;
|
|
}
|
|
|
|
return this._profileStore.getAll().length > 0;
|
|
},
|
|
|
|
/**
|
|
* Set status and trigger _onStatusChanged.
|
|
*
|
|
* @param {boolean} newStatus The latest status we want to set for _enabled
|
|
*/
|
|
_setStatus(newStatus) {
|
|
this._enabled = newStatus;
|
|
this._onStatusChanged();
|
|
},
|
|
|
|
/**
|
|
* Handles the message coming from FormAutofillContent.
|
|
*
|
|
* @param {string} message.name The name of the message.
|
|
* @param {object} message.data The data of the message.
|
|
* @param {nsIFrameMessageManager} message.target Caller's message manager.
|
|
*/
|
|
receiveMessage({name, data, target}) {
|
|
switch (name) {
|
|
case "FormAutofill:GetProfiles": {
|
|
this._getProfiles(data, target);
|
|
break;
|
|
}
|
|
case "FormAutofill:SaveProfile": {
|
|
if (data.guid) {
|
|
this.getProfileStore().update(data.guid, data.profile);
|
|
} else {
|
|
this.getProfileStore().add(data.profile);
|
|
}
|
|
break;
|
|
}
|
|
case "FormAutofill:RemoveProfiles": {
|
|
data.guids.forEach(guid => this.getProfileStore().remove(guid));
|
|
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.
|
|
*
|
|
* @returns {ProfileStorage}
|
|
*/
|
|
getProfileStore() {
|
|
return this._profileStore;
|
|
},
|
|
|
|
/**
|
|
* Uninitializes FormAutofillParent. This is for testing only.
|
|
*
|
|
* @private
|
|
*/
|
|
_uninit() {
|
|
if (this._profileStore) {
|
|
this._profileStore._saveImmediately();
|
|
this._profileStore = null;
|
|
}
|
|
|
|
Services.ppmm.removeMessageListener("FormAutofill:GetProfiles", this);
|
|
Services.ppmm.removeMessageListener("FormAutofill:SaveProfile", this);
|
|
Services.ppmm.removeMessageListener("FormAutofill:RemoveProfiles", 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
|
|
* @param {string} data.searchString
|
|
* The typed string for filtering out the matched profile.
|
|
* @param {string} data.info
|
|
* The input autocomplete property's information.
|
|
* @param {nsIFrameMessageManager} target
|
|
* Content's message manager.
|
|
*/
|
|
_getProfiles({searchString, info}, target) {
|
|
let profiles = [];
|
|
|
|
if (info && info.fieldName) {
|
|
profiles = this._profileStore.getByFilter({searchString, info});
|
|
} else {
|
|
profiles = this._profileStore.getAll();
|
|
}
|
|
|
|
target.sendAsyncMessage("FormAutofill:Profiles", profiles);
|
|
},
|
|
|
|
_updateSavedFieldNames() {
|
|
if (!Services.ppmm.initialProcessData.autofillSavedFieldNames) {
|
|
Services.ppmm.initialProcessData.autofillSavedFieldNames = new Set();
|
|
} else {
|
|
Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
|
|
}
|
|
|
|
this._profileStore.getAll().forEach((profile) => {
|
|
Object.keys(profile).forEach((fieldName) => {
|
|
if (!profile[fieldName]) {
|
|
return;
|
|
}
|
|
Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
|
|
});
|
|
});
|
|
|
|
// Remove the internal guid and metadata fields.
|
|
this._profileStore.INTERNAL_FIELDS.forEach((fieldName) => {
|
|
Services.ppmm.initialProcessData.autofillSavedFieldNames.delete(fieldName);
|
|
});
|
|
|
|
Services.ppmm.broadcastAsyncMessage("FormAutofill:savedFieldNames",
|
|
Services.ppmm.initialProcessData.autofillSavedFieldNames);
|
|
},
|
|
};
|