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 7var EXPORTED_SYMBOLS = ["FormAutofillChild"]; 8 9var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 10ChromeUtils.defineModuleGetter( 11 this, 12 "setTimeout", 13 "resource://gre/modules/Timer.jsm" 14); 15ChromeUtils.defineModuleGetter( 16 this, 17 "FormAutofill", 18 "resource://autofill/FormAutofill.jsm" 19); 20ChromeUtils.defineModuleGetter( 21 this, 22 "FormAutofillContent", 23 "resource://autofill/FormAutofillContent.jsm" 24); 25ChromeUtils.defineModuleGetter( 26 this, 27 "FormAutofillUtils", 28 "resource://autofill/FormAutofillUtils.jsm" 29); 30ChromeUtils.defineModuleGetter( 31 this, 32 "AutoCompleteChild", 33 "resource://gre/actors/AutoCompleteChild.jsm" 34); 35 36/** 37 * Handles content's interactions for the frame. 38 */ 39class FormAutofillChild extends JSWindowActorChild { 40 constructor() { 41 super(); 42 43 this._nextHandleElement = null; 44 this._alreadyDOMContentLoaded = false; 45 this._hasDOMContentLoadedHandler = false; 46 this._hasPendingTask = false; 47 this.testListener = null; 48 49 AutoCompleteChild.addPopupStateListener(this); 50 } 51 52 didDestroy() { 53 AutoCompleteChild.removePopupStateListener(this); 54 } 55 56 popupStateChanged(messageName, data, target) { 57 let docShell; 58 try { 59 docShell = this.docShell; 60 } catch (ex) { 61 AutoCompleteChild.removePopupStateListener(this); 62 return; 63 } 64 65 if (!FormAutofill.isAutofillEnabled) { 66 return; 67 } 68 69 const { chromeEventHandler } = docShell; 70 71 switch (messageName) { 72 case "FormAutoComplete:PopupClosed": { 73 FormAutofillContent.onPopupClosed(data.selectedRowStyle); 74 Services.tm.dispatchToMainThread(() => { 75 chromeEventHandler.removeEventListener( 76 "keydown", 77 FormAutofillContent._onKeyDown, 78 true 79 ); 80 }); 81 82 break; 83 } 84 case "FormAutoComplete:PopupOpened": { 85 FormAutofillContent.onPopupOpened(); 86 chromeEventHandler.addEventListener( 87 "keydown", 88 FormAutofillContent._onKeyDown, 89 true 90 ); 91 break; 92 } 93 } 94 } 95 96 _doIdentifyAutofillFields() { 97 if (this._hasPendingTask) { 98 return; 99 } 100 this._hasPendingTask = true; 101 102 setTimeout(() => { 103 FormAutofillContent.identifyAutofillFields(this._nextHandleElement); 104 this._hasPendingTask = false; 105 this._nextHandleElement = null; 106 // This is for testing purpose only which sends a notification to indicate that the 107 // form has been identified, and ready to open popup. 108 this.sendAsyncMessage("FormAutofill:FieldsIdentified"); 109 FormAutofillContent.updateActiveInput(); 110 }); 111 } 112 113 handleEvent(evt) { 114 if (!evt.isTrusted) { 115 return; 116 } 117 118 switch (evt.type) { 119 case "focusin": { 120 if (FormAutofill.isAutofillEnabled) { 121 this.onFocusIn(evt); 122 } 123 break; 124 } 125 case "DOMFormBeforeSubmit": { 126 if (FormAutofill.isAutofillEnabled) { 127 this.onDOMFormBeforeSubmit(evt); 128 } 129 break; 130 } 131 132 default: { 133 throw new Error("Unexpected event type"); 134 } 135 } 136 } 137 138 onFocusIn(evt) { 139 FormAutofillContent.updateActiveInput(); 140 141 let element = evt.target; 142 if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) { 143 return; 144 } 145 this._nextHandleElement = element; 146 147 if (!this._alreadyDOMContentLoaded) { 148 let doc = element.ownerDocument; 149 if (doc.readyState === "loading") { 150 if (!this._hasDOMContentLoadedHandler) { 151 this._hasDOMContentLoadedHandler = true; 152 doc.addEventListener( 153 "DOMContentLoaded", 154 () => this._doIdentifyAutofillFields(), 155 { once: true } 156 ); 157 } 158 return; 159 } 160 this._alreadyDOMContentLoaded = true; 161 } 162 163 this._doIdentifyAutofillFields(); 164 } 165 166 /** 167 * Handle the DOMFormBeforeSubmit event. 168 * @param {Event} evt 169 */ 170 onDOMFormBeforeSubmit(evt) { 171 let formElement = evt.target; 172 173 if (!FormAutofill.isAutofillEnabled) { 174 return; 175 } 176 177 FormAutofillContent.formSubmitted(formElement); 178 } 179 180 receiveMessage(message) { 181 if (!FormAutofill.isAutofillEnabled) { 182 return; 183 } 184 185 const doc = this.document; 186 187 switch (message.name) { 188 case "FormAutofill:PreviewProfile": { 189 FormAutofillContent.previewProfile(doc); 190 break; 191 } 192 case "FormAutofill:ClearForm": { 193 FormAutofillContent.clearForm(); 194 break; 195 } 196 case "FormAutofill:FillForm": { 197 FormAutofillContent.activeHandler.autofillFormFields(message.data); 198 break; 199 } 200 } 201 } 202} 203