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.js235
1 files changed, 235 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/api.js b/browser/extensions/formautofill/api.js
new file mode 100644
index 0000000000..a14a570e5e
--- /dev/null
+++ b/browser/extensions/formautofill/api.js
@@ -0,0 +1,235 @@
+/* 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 */
+
+const CACHED_STYLESHEETS = new WeakMap();
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "FormAutofill",
+ "resource://formautofill/FormAutofill.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "FormAutofillStatus",
+ "resource://formautofill/FormAutofillParent.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "FormAutofillParent",
+ "resource://formautofill/FormAutofillParent.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "AutoCompleteParent",
+ "resource://gre/actors/AutoCompleteParent.jsm"
+);
+
+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"
+ );
+}
+
+function isAvailable() {
+ let availablePref = Services.prefs.getCharPref(
+ "extensions.formautofill.available"
+ );
+ if (availablePref == "on") {
+ return true;
+ } else if (availablePref == "detect") {
+ let region = Services.prefs.getCharPref("browser.search.region", "");
+ let supportedCountries = Services.prefs
+ .getCharPref("extensions.formautofill.supportedCountries")
+ .split(",");
+ if (
+ !Services.prefs.getBoolPref("extensions.formautofill.supportRTL") &&
+ Services.locale.isAppLocaleRTL
+ ) {
+ return false;
+ }
+ return supportedCountries.includes(region);
+ }
+ return false;
+}
+
+this.formautofill = class extends ExtensionAPI {
+ 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 {
+ Cu.reportError(
+ "Cannot find formautofill chrome.manifest for registring translated strings"
+ );
+ }
+
+ if (!isAvailable()) {
+ Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
+ // reset the sync related prefs incase the feature was previously available
+ // but isn't now.
+ Services.prefs.clearUserPref("services.sync.engine.addresses.available");
+ Services.prefs.clearUserPref(
+ "services.sync.engine.creditcards.available"
+ );
+ 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);
+
+ // This pref 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.
+ Services.prefs.setBoolPref(
+ "services.sync.engine.addresses.available",
+ true
+ );
+ if (FormAutofill.isAutofillCreditCardsAvailable) {
+ Services.prefs.setBoolPref(
+ "services.sync.engine.creditcards.available",
+ true
+ );
+ } else {
+ Services.prefs.clearUserPref(
+ "services.sync.engine.creditcards.available"
+ );
+ }
+
+ // 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: {
+ moduleURI: "resource://formautofill/FormAutofillParent.jsm",
+ },
+ child: {
+ moduleURI: "resource://formautofill/FormAutofillChild.jsm",
+ 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();
+ }
+ }
+ }
+};