summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/api.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/formautofill/api.js')
-rw-r--r--browser/extensions/formautofill/api.js222
1 files changed, 222 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/api.js b/browser/extensions/formautofill/api.js
new file mode 100644
index 0000000000..000c393e8e
--- /dev/null
+++ b/browser/extensions/formautofill/api.js
@@ -0,0 +1,222 @@
+/* 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";
+
+/* globals ExtensionAPI, Services, XPCOMUtils */
+
+const CACHED_STYLESHEETS = new WeakMap();
+
+ChromeUtils.defineESModuleGetters(this, {
+ FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
+ FormAutofillParent: "resource://autofill/FormAutofillParent.sys.mjs",
+ FormAutofillStatus: "resource://autofill/FormAutofillParent.sys.mjs",
+ AutoCompleteParent: "resource://gre/actors/AutoCompleteParent.sys.mjs",
+});
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "resProto",
+ "@mozilla.org/network/protocol;1?name=resource",
+ "nsISubstitutingProtocolHandler"
+);
+
+const RESOURCE_HOST = "formautofill";
+
+function insertStyleSheet(domWindow, url) {
+ let doc = domWindow.document;
+ let styleSheetAttr = `href="${url}" type="text/css"`;
+ let styleSheet = doc.createProcessingInstruction(
+ "xml-stylesheet",
+ styleSheetAttr
+ );
+
+ doc.insertBefore(styleSheet, doc.documentElement);
+
+ if (CACHED_STYLESHEETS.has(domWindow)) {
+ CACHED_STYLESHEETS.get(domWindow).push(styleSheet);
+ } else {
+ CACHED_STYLESHEETS.set(domWindow, [styleSheet]);
+ }
+}
+
+function ensureCssLoaded(domWindow) {
+ if (CACHED_STYLESHEETS.has(domWindow)) {
+ // This window already has autofill stylesheets.
+ return;
+ }
+
+ insertStyleSheet(domWindow, "chrome://formautofill/content/formautofill.css");
+ insertStyleSheet(
+ domWindow,
+ "chrome://formautofill/content/skin/autocomplete-item-shared.css"
+ );
+ insertStyleSheet(
+ domWindow,
+ "chrome://formautofill/content/skin/autocomplete-item.css"
+ );
+}
+
+this.formautofill = class extends ExtensionAPI {
+ /**
+ * Adjusts and checks form autofill preferences during startup.
+ *
+ * @param {boolean} addressAutofillAvailable
+ * @param {boolean} creditCardAutofillAvailable
+ */
+ adjustAndCheckFormAutofillPrefs(
+ addressAutofillAvailable,
+ creditCardAutofillAvailable
+ ) {
+ // Reset the sync prefs in case the features were previously available
+ // but aren't now.
+ if (!creditCardAutofillAvailable) {
+ Services.prefs.clearUserPref(
+ "services.sync.engine.creditcards.available"
+ );
+ }
+ if (!addressAutofillAvailable) {
+ Services.prefs.clearUserPref("services.sync.engine.addresses.available");
+ }
+
+ if (!addressAutofillAvailable && !creditCardAutofillAvailable) {
+ Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
+ Services.telemetry.scalarSet("formautofill.availability", false);
+ return;
+ }
+
+ // This pref is used for web contents to detect the autocomplete feature.
+ // When it's true, "element.autocomplete" will return tokens we currently
+ // support -- otherwise it'll return an empty string.
+ Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true);
+ Services.telemetry.scalarSet("formautofill.availability", true);
+
+ // These "*.available" prefs determines whether the "addresses"/"creditcards" sync engine is
+ // available (ie, whether it is shown in any UI etc) - it *does not* determine
+ // whether the engine is actually enabled or not.
+ if (FormAutofill.isAutofillAddressesAvailable) {
+ Services.prefs.setBoolPref(
+ "services.sync.engine.addresses.available",
+ true
+ );
+ } else {
+ Services.prefs.clearUserPref("services.sync.engine.addresses.available");
+ }
+ if (FormAutofill.isAutofillCreditCardsAvailable) {
+ Services.prefs.setBoolPref(
+ "services.sync.engine.creditcards.available",
+ true
+ );
+ } else {
+ Services.prefs.clearUserPref(
+ "services.sync.engine.creditcards.available"
+ );
+ }
+ }
+ onStartup() {
+ // We have to do this before actually determining if we're enabled, since
+ // there are scripts inside of the core browser code that depend on the
+ // FormAutofill JSMs being registered.
+ let uri = Services.io.newURI("chrome/res/", null, this.extension.rootURI);
+ resProto.setSubstitution(RESOURCE_HOST, uri);
+
+ let aomStartup = Cc[
+ "@mozilla.org/addons/addon-manager-startup;1"
+ ].getService(Ci.amIAddonManagerStartup);
+ const manifestURI = Services.io.newURI(
+ "manifest.json",
+ null,
+ this.extension.rootURI
+ );
+ this.chromeHandle = aomStartup.registerChrome(manifestURI, [
+ ["content", "formautofill", "chrome/content/"],
+ ]);
+
+ // Until we move to fluent (bug 1446164), we're stuck with
+ // chrome.manifest for handling localization since its what the
+ // build system can handle for localized repacks.
+ if (this.extension.rootURI instanceof Ci.nsIJARURI) {
+ this.autofillManifest = this.extension.rootURI.JARFile.QueryInterface(
+ Ci.nsIFileURL
+ ).file;
+ } else if (this.extension.rootURI instanceof Ci.nsIFileURL) {
+ this.autofillManifest = this.extension.rootURI.file;
+ }
+
+ if (this.autofillManifest) {
+ Components.manager.addBootstrappedManifestLocation(this.autofillManifest);
+ } else {
+ console.error(
+ "Cannot find formautofill chrome.manifest for registring translated strings"
+ );
+ }
+ let addressAutofillAvailable = FormAutofill.isAutofillAddressesAvailable;
+ let creditCardAutofillAvailable =
+ FormAutofill.isAutofillCreditCardsAvailable;
+ this.adjustAndCheckFormAutofillPrefs(
+ addressAutofillAvailable,
+ creditCardAutofillAvailable
+ );
+ if (!creditCardAutofillAvailable && !addressAutofillAvailable) {
+ return;
+ }
+ // Listen for the autocomplete popup message
+ // or the form submitted message (which may trigger a
+ // doorhanger) to lazily append our stylesheets related
+ // to the autocomplete feature.
+ AutoCompleteParent.addPopupStateListener(ensureCssLoaded);
+ FormAutofillParent.addMessageObserver(this);
+ this.onFormSubmitted = (data, window) => ensureCssLoaded(window);
+
+ FormAutofillStatus.init();
+
+ ChromeUtils.registerWindowActor("FormAutofill", {
+ parent: {
+ esModuleURI: "resource://autofill/FormAutofillParent.sys.mjs",
+ },
+ child: {
+ esModuleURI: "resource://autofill/FormAutofillChild.sys.mjs",
+ events: {
+ focusin: {},
+ DOMFormBeforeSubmit: {},
+ },
+ },
+ allFrames: true,
+ });
+ }
+
+ onShutdown(isAppShutdown) {
+ if (isAppShutdown) {
+ return;
+ }
+
+ resProto.setSubstitution(RESOURCE_HOST, null);
+
+ this.chromeHandle.destruct();
+ this.chromeHandle = null;
+
+ if (this.autofillManifest) {
+ Components.manager.removeBootstrappedManifestLocation(
+ this.autofillManifest
+ );
+ }
+
+ ChromeUtils.unregisterWindowActor("FormAutofill");
+
+ AutoCompleteParent.removePopupStateListener(ensureCssLoaded);
+ FormAutofillParent.removeMessageObserver(this);
+
+ for (let win of Services.wm.getEnumerator("navigator:browser")) {
+ let cachedStyleSheets = CACHED_STYLESHEETS.get(win);
+
+ if (!cachedStyleSheets) {
+ continue;
+ }
+
+ while (cachedStyleSheets.length !== 0) {
+ cachedStyleSheets.pop().remove();
+ }
+ }
+ }
+};