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