summaryrefslogtreecommitdiffstats
path: root/toolkit/components/prompts/test/PromptTestUtils.sys.mjs
blob: 138c61b1895c42ef6da6c89c87adf007ef5c85ad (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
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

/*
 * Utility module for tests to interact with prompts spawned by nsIPrompt or
 * nsIPromptService.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { BrowserTestUtils } from "resource://testing-common/BrowserTestUtils.sys.mjs";
import { TestUtils } from "resource://testing-common/TestUtils.sys.mjs";

const kPrefs = {};

// Whether prompts with modal type TAB are shown as SubDialog (true) or
// TabModalPrompt (false).
XPCOMUtils.defineLazyPreferenceGetter(
  kPrefs,
  "tabPromptSubDialogEnabled",
  "prompts.tabChromePromptSubDialog",
  false
);

// Whether web content prompts (alert etc.) are shown as SubDialog (true)
// or TabModalPrompt (false)
XPCOMUtils.defineLazyPreferenceGetter(
  kPrefs,
  "contentPromptSubDialogEnabled",
  "prompts.contentPromptSubDialog",
  false
);

function isCommonDialog(modalType) {
  return (
    modalType === Services.prompt.MODAL_TYPE_WINDOW ||
    (kPrefs.tabPromptSubDialogEnabled &&
      modalType === Services.prompt.MODAL_TYPE_TAB) ||
    (kPrefs.contentPromptSubDialogEnabled &&
      modalType === Services.prompt.MODAL_TYPE_CONTENT)
  );
}

export let PromptTestUtils = {
  /**
   * Wait for a prompt from nsIPrompt or nsIPromptsService, interact with it and
   * click the specified button to close it.
   * @param {Browser|Window} [parent] - Parent of the prompt. This can be
   * either the parent window or the browser. For tab prompts, if given a
   * window, the currently selected browser in that window will be used.
   * @param {Object} promptOptions - @see waitForPrompt
   * @param {Object} promptActions - @see handlePrompt
   * @returns {Promise} - A promise which resolves once the prompt has been
   * closed.
   */
  async handleNextPrompt(parent, promptOptions, promptActions) {
    let dialog = await this.waitForPrompt(parent, promptOptions);
    return this.handlePrompt(dialog, promptActions);
  },

  /**
   * Interact with an existing prompt and close it.
   * @param {Dialog} dialog - The dialog instance associated with the prompt.
   * @param {Object} [actions] - Options on how to interact with the
   * prompt and how to close it.
   * @param {Boolean} [actions.checkboxState] - Set the checkbox state.
   * true = checked, false = unchecked.
   * @param {Number} [actions.buttonNumClick] - Which button to click to close
   * the prompt.
   * @param {String} [actions.loginInput] - Input text for the login text field.
   * This field is also used for text input for the "prompt" type.
   * @param {String} [actions.passwordInput] - Input text for the password text
   * field.
   * @returns {Promise} - A promise which resolves once the prompt has been
   * closed.
   */
  handlePrompt(
    dialog,
    {
      checkboxState = null,
      buttonNumClick = 0,
      loginInput = null,
      passwordInput = null,
    } = {}
  ) {
    let promptClosePromise;

    // Get parent window to listen for prompt close event
    let win;
    if (isCommonDialog(dialog.args.modalType)) {
      win = dialog.ui.prompt?.opener;
    } else {
      // Tab prompts should always have a parent window
      win = dialog.ui.prompt.win;
    }

    if (win) {
      promptClosePromise = BrowserTestUtils.waitForEvent(
        win,
        "DOMModalDialogClosed"
      );
    } else {
      // We don't have a parent, wait for window close instead
      promptClosePromise = BrowserTestUtils.windowClosed(dialog.ui.prompt);
    }

    if (typeof checkboxState == "boolean") {
      dialog.ui.checkbox.checked = checkboxState;
    }

    if (loginInput != null) {
      dialog.ui.loginTextbox.value = loginInput;
    }

    if (passwordInput != null) {
      dialog.ui.password1Textbox.value = passwordInput;
    }

    let button = dialog.ui["button" + buttonNumClick];
    if (!button) {
      throw new Error("Could not find button with index " + buttonNumClick);
    }
    button.click();

    return promptClosePromise;
  },

  /**
   * Wait for a prompt from nsIPrompt or nsIPromptsService to open.
   * @param {Browser|Window} [parent] - Parent of the prompt. This can be either
   * the parent window or the browser. For tab prompts, if given a window, the
   * currently selected browser in that window will be used.
   * If not given a parent, the method will return on prompts of any window.
   * @param {Object} attrs - The prompt attributes to filter for.
   * @param {Number} attrs.modalType - Whether the expected prompt is a content, tab or window prompt.
   * nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} [attrs.promptType] - Common dialog type of the prompt to filter for.
   *  @see {@link CommonDialog} for possible prompt types.
   * @returns {Promise<CommonDialog>} - A Promise which resolves with a dialog
   * object once the prompt has loaded.
   */
  async waitForPrompt(parent, { modalType, promptType = null } = {}) {
    if (!modalType) {
      throw new Error("modalType is mandatory");
    }

    // Get window by browser or browser by window, depending on what is passed
    // via the parent arg. If the caller passes parent=null, both will be null.
    let parentWindow;
    let parentBrowser;
    if (parent) {
      if (Element.isInstance(parent)) {
        // Parent is browser
        parentBrowser = parent;
        parentWindow = parentBrowser.ownerGlobal;
      } else if (parent.isChromeWindow) {
        // Parent is window
        parentWindow = parent;
        parentBrowser = parentWindow.gBrowser?.selectedBrowser;
      } else {
        throw new Error("Invalid parent. Expected browser or dom window");
      }
    }

    let topic = isCommonDialog(modalType)
      ? "common-dialog-loaded"
      : "tabmodal-dialog-loaded";

    let dialog;
    await TestUtils.topicObserved(topic, subject => {
      // If we are not given a browser, use the currently selected browser of the window
      let browser =
        parentBrowser || subject.ownerGlobal.gBrowser?.selectedBrowser;
      if (isCommonDialog(modalType)) {
        // Is not associated with given parent window, skip
        if (parentWindow && subject.opener !== parentWindow) {
          return false;
        }

        // For tab prompts, ensure that the associated browser matches.
        if (browser && modalType == Services.prompt.MODAL_TYPE_TAB) {
          let dialogBox = parentWindow.gBrowser.getTabDialogBox(browser);
          let hasMatchingDialog = dialogBox
            .getTabDialogManager()
            ._dialogs.some(
              d => d._frame?.browsingContext == subject.browsingContext
            );
          if (!hasMatchingDialog) {
            return false;
          }
        }

        if (browser && modalType == Services.prompt.MODAL_TYPE_CONTENT) {
          let dialogBox = parentWindow.gBrowser.getTabDialogBox(browser);
          let hasMatchingDialog = dialogBox
            .getContentDialogManager()
            ._dialogs.some(
              d => d._frame?.browsingContext == subject.browsingContext
            );
          if (!hasMatchingDialog) {
            return false;
          }
        }

        // subject is the window object of the prompt which has a Dialog object
        // attached.
        dialog = subject.Dialog;
      } else {
        // subject is the tabprompt dom node
        // Get the full prompt object which has the dialog object
        let prompt = browser.tabModalPromptBox.getPrompt(subject);

        // Is not associated with given parent browser, skip.
        if (!prompt) {
          return false;
        }

        dialog = prompt.Dialog;
      }

      // Not the modalType we're looking for.
      // For window prompts dialog.args.modalType is undefined.
      if (isCommonDialog(modalType) && dialog.args.modalType !== modalType) {
        return false;
      }

      // Not the promptType we're looking for.
      if (promptType && dialog.args.promptType !== promptType) {
        return false;
      }

      // Prompt found
      return true;
    });

    return dialog;
  },
};