api.js (5943B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /* globals ExtensionAPI, Services, XPCOMUtils */ 8 9 const CACHED_STYLESHEETS = new WeakMap(); 10 11 ChromeUtils.defineESModuleGetters(this, { 12 FormAutofill: "resource://autofill/FormAutofill.sys.mjs", 13 FormAutofillParent: "resource://autofill/FormAutofillParent.sys.mjs", 14 FormAutofillStatus: "resource://autofill/FormAutofillParent.sys.mjs", 15 AutoCompleteParent: "resource://gre/actors/AutoCompleteParent.sys.mjs", 16 }); 17 18 XPCOMUtils.defineLazyServiceGetter( 19 this, 20 "resProto", 21 "@mozilla.org/network/protocol;1?name=resource", 22 Ci.nsISubstitutingProtocolHandler 23 ); 24 25 const RESOURCE_HOST = "formautofill"; 26 27 function insertStyleSheet(domWindow, url) { 28 let doc = domWindow.document; 29 let styleSheetAttr = `href="${url}" type="text/css"`; 30 let styleSheet = doc.createProcessingInstruction( 31 "xml-stylesheet", 32 styleSheetAttr 33 ); 34 35 doc.insertBefore(styleSheet, doc.documentElement); 36 37 if (CACHED_STYLESHEETS.has(domWindow)) { 38 CACHED_STYLESHEETS.get(domWindow).push(styleSheet); 39 } else { 40 CACHED_STYLESHEETS.set(domWindow, [styleSheet]); 41 } 42 } 43 44 function ensureCssLoaded(domWindow) { 45 if (CACHED_STYLESHEETS.has(domWindow)) { 46 // This window already has autofill stylesheets. 47 return; 48 } 49 50 insertStyleSheet(domWindow, "chrome://formautofill/content/formautofill.css"); 51 } 52 53 this.formautofill = class extends ExtensionAPI { 54 /** 55 * Adjusts and checks form autofill preferences during startup. 56 * 57 * @param {boolean} addressAutofillAvailable 58 * @param {boolean} creditCardAutofillAvailable 59 */ 60 adjustAndCheckFormAutofillPrefs( 61 addressAutofillAvailable, 62 creditCardAutofillAvailable 63 ) { 64 // Reset the sync prefs in case the features were previously available 65 // but aren't now. 66 if (!creditCardAutofillAvailable) { 67 Services.prefs.clearUserPref( 68 "services.sync.engine.creditcards.available" 69 ); 70 } 71 if (!addressAutofillAvailable) { 72 Services.prefs.clearUserPref("services.sync.engine.addresses.available"); 73 } 74 75 if (!addressAutofillAvailable && !creditCardAutofillAvailable) { 76 Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill"); 77 Glean.formautofill.availability.set(false); 78 return; 79 } 80 81 // This pref is used for web contents to detect the autocomplete feature. 82 // When it's true, "element.autocomplete" will return tokens we currently 83 // support -- otherwise it'll return an empty string. 84 Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true); 85 Glean.formautofill.availability.set(true); 86 87 // These "*.available" prefs determines whether the "addresses"/"creditcards" sync engine is 88 // available (ie, whether it is shown in any UI etc) - it *does not* determine 89 // whether the engine is actually enabled or not. 90 if (FormAutofill.isAutofillAddressesAvailable) { 91 Services.prefs.setBoolPref( 92 "services.sync.engine.addresses.available", 93 true 94 ); 95 } else { 96 Services.prefs.clearUserPref("services.sync.engine.addresses.available"); 97 } 98 if (FormAutofill.isAutofillCreditCardsAvailable) { 99 Services.prefs.setBoolPref( 100 "services.sync.engine.creditcards.available", 101 true 102 ); 103 } else { 104 Services.prefs.clearUserPref( 105 "services.sync.engine.creditcards.available" 106 ); 107 } 108 } 109 onStartup() { 110 // We have to do this before actually determining if we're enabled, since 111 // there are scripts inside of the core browser code that depend on the 112 // FormAutofill JSMs being registered. 113 let uri = Services.io.newURI("chrome/res/", null, this.extension.rootURI); 114 resProto.setSubstitution(RESOURCE_HOST, uri); 115 116 let aomStartup = Cc[ 117 "@mozilla.org/addons/addon-manager-startup;1" 118 ].getService(Ci.amIAddonManagerStartup); 119 const manifestURI = Services.io.newURI( 120 "manifest.json", 121 null, 122 this.extension.rootURI 123 ); 124 this.chromeHandle = aomStartup.registerChrome(manifestURI, [ 125 ["content", "formautofill", "content/"], 126 ]); 127 128 this.adjustAndCheckFormAutofillPrefs( 129 FormAutofill.isAutofillAddressesAvailable, 130 FormAutofill.isAutofillCreditCardsAvailable 131 ); 132 133 // Listen for the autocomplete popup message 134 // or the form submitted message (which may trigger a 135 // doorhanger) to lazily append our stylesheets related 136 // to the autocomplete feature. 137 AutoCompleteParent.addPopupStateListener(ensureCssLoaded); 138 FormAutofillParent.addMessageObserver(this); 139 this.onFormSubmitted = (data, window) => ensureCssLoaded(window); 140 141 FormAutofillStatus.init(); 142 143 ChromeUtils.registerWindowActor("FormAutofill", { 144 parent: { 145 esModuleURI: "resource://autofill/FormAutofillParent.sys.mjs", 146 }, 147 child: { 148 esModuleURI: "resource://autofill/FormAutofillChild.sys.mjs", 149 events: { 150 focusin: { capture: true }, 151 "form-changed": { createActor: false }, 152 "form-submission-detected": { createActor: false }, 153 }, 154 }, 155 allFrames: true, 156 }); 157 } 158 159 onShutdown(isAppShutdown) { 160 if (isAppShutdown) { 161 return; 162 } 163 164 resProto.setSubstitution(RESOURCE_HOST, null); 165 166 this.chromeHandle.destruct(); 167 this.chromeHandle = null; 168 169 ChromeUtils.unregisterWindowActor("FormAutofill"); 170 171 AutoCompleteParent.removePopupStateListener(ensureCssLoaded); 172 FormAutofillParent.removeMessageObserver(this); 173 174 for (let win of Services.wm.getEnumerator("navigator:browser")) { 175 let cachedStyleSheets = CACHED_STYLESHEETS.get(win); 176 177 if (!cachedStyleSheets) { 178 continue; 179 } 180 181 while (cachedStyleSheets.length !== 0) { 182 cachedStyleSheets.pop().remove(); 183 } 184 } 185 } 186 };