gecko-dev/browser/extensions/formautofill/FormAutofillParent.jsm

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);
},
};