summaryrefslogtreecommitdiffstats
path: root/toolkit/components/formautofill/test/head_common.js
blob: 82b87e4a6cb5f9ddde92321d9f16a75386db46bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

/*
 * Initialization of Form Autofill tests shared between all frameworks.
 *
 * A copy of this file is installed in each of the framework subfolders, this
 * means it becomes a sibling of the test files in the final layout.  This is
 * determined by how manifest "support-files" installation works.
 */

"use strict";

// The requestAutocomplete framework is available at this point, you can add
// mochitest-chrome specific test initialization here.  If you need shared
// functions or initialization that are not specific to mochitest-chrome,
// consider adding them to "head_common.js" in the parent folder instead.

XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
                                  "resource://gre/modules/DownloadPaths.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                  "resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofill",
                                  "resource://gre/modules/FormAutofill.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                  "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                  "resource://gre/modules/osfile.jsm");

/* --- Global helpers --- */

// Some of these functions are already implemented in other parts of the source
// tree, see bug 946708 about sharing more code.

var TestUtils = {
  /**
   * Waits for at least one tick of the event loop.  This means that all pending
   * events at the time of this call will have been processed.  Other events may
   * be processed before the returned promise is resolved.
   *
   * @return {Promise}
   * @resolves When pending events have been processed.
   * @rejects Never.
   */
  waitForTick: function () {
    return new Promise(resolve => executeSoon(resolve));
  },

  /**
   * Waits for the specified timeout.
   *
   * @param aTimeMs
   *        Minimum time to wait from the moment of this call, in milliseconds.
   *        The actual wait may be longer, due to system timer resolution and
   *        pending events being processed before the promise is resolved.
   *
   * @return {Promise}
   * @resolves When the specified time has passed.
   * @rejects Never.
   */
  waitMs: function (aTimeMs) {
    return new Promise(resolve => setTimeout(resolve, aTimeMs));
  },

  /**
   * Allows waiting for an observer notification once.
   *
   * @param aTopic
   *        Notification topic to observe.
   *
   * @return {Promise}
   * @resolves The array [aSubject, aData] from the observed notification.
   * @rejects Never.
   */
  waitForNotification: function (aTopic) {
    Output.print("Waiting for notification: '" + aTopic + "'.");

    return new Promise(resolve => Services.obs.addObserver(
      function observe(aSubject, aTopic, aData) {
        Services.obs.removeObserver(observe, aTopic);
        resolve([aSubject, aData]);
      }, aTopic, false));
  },

  /**
   * Waits for a DOM event on the specified target.
   *
   * @param aTarget
   *        The DOM EventTarget on which addEventListener should be called.
   * @param aEventName
   *        String with the name of the event.
   * @param aUseCapture
   *        This parameter is passed to the addEventListener call.
   *
   * @return {Promise}
   * @resolves The arguments from the observed event.
   * @rejects Never.
   */
  waitForEvent: function (aTarget, aEventName, aUseCapture = false) {
    Output.print("Waiting for event: '" + aEventName + "' on " + aTarget + ".");

    return new Promise(resolve => aTarget.addEventListener(aEventName,
      function onEvent(...aArgs) {
        aTarget.removeEventListener(aEventName, onEvent, aUseCapture);
        resolve(...aArgs);
      }, aUseCapture));
  },

  // While the previous test file should have deleted all the temporary files it
  // used, on Windows these might still be pending deletion on the physical file
  // system.  Thus, start from a new base number every time, to make a collision
  // with a file that is still pending deletion highly unlikely.
  _fileCounter: Math.floor(Math.random() * 1000000),

  /**
   * Returns a reference to a temporary file, that is guaranteed not to exist,
   * and to have never been created before.
   *
   * @param aLeafName
   *        Suggested leaf name for the file to be created.
   *
   * @return {Promise}
   * @resolves Path of a non-existent file in a temporary directory.
   *
   * @note It is not enough to delete the file if it exists, or to delete the
   *       file after calling nsIFile.createUnique, because on Windows the
   *       delete operation in the file system may still be pending, preventing
   *       a new file with the same name to be created.
   */
  getTempFile: Task.async(function* (aLeafName) {
    // Prepend a serial number to the extension in the suggested leaf name.
    let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
    let leafName = base + "-" + this._fileCounter + ext;
    this._fileCounter++;

    // Get a file reference under the temporary directory for this test file.
    let path = OS.Path.join(OS.Constants.Path.tmpDir, leafName);
    Assert.ok(!(yield OS.File.exists(path)));

    // Ensure the file is deleted whe the test terminates.
    add_termination_task(function* () {
      if (yield OS.File.exists(path)) {
        yield OS.File.remove(path);
      }
    });

    return path;
  }),
};

/* --- Local helpers --- */

var FormAutofillTest = {
  /**
   * Stores the response that the next call to the mock requestAutocomplete UI
   * will return to the requester, or null to enable displaying the default UI.
   */
  requestAutocompleteResponse: null,

  /**
   * Displays the requestAutocomplete user interface using the specified data.
   *
   * @param aFormAutofillData
   *        Serializable object containing the set of requested fields.
   *
   * @return {Promise}
   * @resolves An object with the following properties:
   *           {
   *             uiWindow: Reference to the initialized window.
   *             promiseResult: Promise resolved by the UI when it closes.
   *           }
   */
  showUI: Task.async(function* (aFormAutofillData) {
    Output.print("Opening UI with data: " + JSON.stringify(aFormAutofillData));

    // Wait for the initialization event before opening the window.
    let promiseUIWindow =
        TestUtils.waitForNotification("formautofill-window-initialized");
    let ui = yield FormAutofill.integration.createRequestAutocompleteUI(
                                                         aFormAutofillData);
    let promiseResult = ui.show();

    // The window is the subject of the observer notification.
    return {
      uiWindow: (yield promiseUIWindow)[0],
      promiseResult: promiseResult,
    };
  }),
};

var TestData = {
  /**
   * Autofill UI request for the e-mail field only.
   */
  get requestEmailOnly() {
    return {
      sections: [{
        name: "",
        addressSections: [{
          addressType: "",
          fields: [{
            fieldName: "email",
            contactType: "",
          }],
        }],
      }],
    };
  },
};

/* --- Initialization and termination functions common to all tests --- */

add_task_in_parent_process(function* () {
  // If required, we return a mock response instead of displaying the UI.
  let mockIntegrationFn = base => ({
    createRequestAutocompleteUI: Task.async(function* () {
      // Call the base method to display the UI if override is not requested.
      if (FormAutofillTest.requestAutocompleteResponse === null) {
        return yield base.createRequestAutocompleteUI.apply(this, arguments);
      }

      // Return a mock RequestAutocompleteUI object.
      return {
        show: Task.async(function* () {
          let response = FormAutofillTest.requestAutocompleteResponse;
          Output.print("Mock UI response: " + JSON.stringify(response));
          return response;
        }),
      };
    }),
  });

  FormAutofill.registerIntegration(mockIntegrationFn);
  add_termination_task(function* () {
    FormAutofill.unregisterIntegration(mockIntegrationFn);
  });
});

add_task_in_both_processes(function* () {
  // We must manually enable the feature while testing.
  Services.prefs.setBoolPref("dom.forms.requestAutocomplete", true);
  add_termination_task(function* () {
    Services.prefs.clearUserPref("dom.forms.requestAutocomplete");
  });
});