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
7const EXPORTED_SYMBOLS = [
8  "openAccountSetup",
9  "openAccountSettings",
10  "open_advanced_settings",
11  "click_account_tree_row",
12  "get_account_tree_row",
13  "remove_account",
14  "wait_for_account_tree_load",
15];
16
17var utils = ChromeUtils.import("resource://testing-common/mozmill/utils.jsm");
18
19var fdh = ChromeUtils.import(
20  "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
21);
22var wh = ChromeUtils.import(
23  "resource://testing-common/mozmill/WindowHelpers.jsm"
24);
25
26var {
27  content_tab_e,
28  open_content_tab_with_url,
29  wait_for_content_tab_load,
30} = ChromeUtils.import(
31  "resource://testing-common/mozmill/ContentTabHelpers.jsm"
32);
33
34var mc = fdh.mc;
35
36/**
37 * Waits until the Account Manager tree fully loads after first open.
38 */
39function wait_for_account_tree_load(tab) {
40  mc.waitFor(
41    () => tab.browser.contentWindow.currentAccount != null,
42    "Timeout waiting for currentAccount to become non-null"
43  );
44}
45
46async function openAccountSettings() {
47  return new Promise(resolve => {
48    let tab = open_content_tab_with_url("about:accountsettings");
49    wait_for_account_tree_load(tab);
50    resolve(tab);
51  });
52}
53
54/**
55 * Opens the Account Manager.
56 * @callback tabCallback
57 *
58 * @param {tabCallback} callback - The callback for the account manager tab that is opened.
59 */
60async function open_advanced_settings(callback) {
61  let tab = open_content_tab_with_url("about:accountsettings");
62  wait_for_account_tree_load(tab);
63  await callback(tab);
64  mc.tabmail.closeTab(tab);
65}
66
67async function openAccountSetup() {
68  return new Promise(resolve => {
69    let tab = open_content_tab_with_url("about:accountsetup");
70    wait_for_content_tab_load(tab, "about:accountsetup", 10000);
71    resolve(tab);
72  });
73}
74
75/**
76 * Click a row in the account settings tree.
77 *
78 * @param {Object} tab - The account manager tab controller that opened.
79 * @param {Number} rowIndex - The row to click.
80 */
81function click_account_tree_row(tab, rowIndex) {
82  utils.waitFor(
83    () => tab.browser.contentWindow.currentAccount != null,
84    "Timeout waiting for currentAccount to become non-null"
85  );
86
87  let tree = content_tab_e(tab, "accounttree");
88
89  fdh.click_tree_row(tree, rowIndex, mc);
90
91  utils.waitFor(
92    () => tab.browser.contentWindow.pendingAccount == null,
93    "Timeout waiting for pendingAccount to become null"
94  );
95
96  // Ensure the page is fully loaded (e.g. onInit functions).
97  wh.wait_for_frame_load(
98    content_tab_e(tab, "contentFrame"),
99    tab.browser.contentWindow.pageURL(
100      tree.view.getItemAtIndex(rowIndex).getAttribute("PageTag")
101    )
102  );
103}
104
105/**
106 * Returns the index of the row in account tree corresponding to the wanted
107 * account and its settings pane.
108 *
109 * @param {Number} accountKey - The key of the account to return.
110 *                              If 'null', the SMTP pane is returned.
111 * @param {Number} paneId - The ID of the account settings pane to select.
112 *
113 *
114 * @returns {Number} The row index of the account and pane. If it was not found return -1.
115 *                   Do not throw as callers may intentionally just check if a row exists.
116 *                   Just dump into the log so that a subsequent throw in
117 *                   click_account_tree_row has a useful context.
118 */
119function get_account_tree_row(accountKey, paneId, tab) {
120  let rowIndex = 0;
121  let accountTreeNode = content_tab_e(tab, "account-tree-children");
122
123  for (let i = 0; i < accountTreeNode.children.length; i++) {
124    if ("_account" in accountTreeNode.children[i]) {
125      let accountHead = accountTreeNode.children[i];
126      if (accountKey == accountHead._account.key) {
127        // If this is the wanted account, find the wanted settings pane.
128        let accountBlock = accountHead.querySelectorAll("[PageTag]");
129        // A null paneId means the main pane.
130        if (!paneId) {
131          return rowIndex;
132        }
133
134        // Otherwise find the pane in the children.
135        for (let j = 0; j < accountBlock.length; j++) {
136          if (accountBlock[j].getAttribute("PageTag") == paneId) {
137            return rowIndex + j + 1;
138          }
139        }
140
141        // The pane was not found.
142        dump(
143          "The treerow for pane " +
144            paneId +
145            " of account " +
146            accountKey +
147            " was not found!\n"
148        );
149        return -1;
150      }
151      // If this is not the wanted account, skip all of its settings panes.
152      rowIndex += accountHead.querySelectorAll("[PageTag]").length;
153    } else if (accountKey == null) {
154      // A row without _account should be the SMTP server.
155      return rowIndex;
156    }
157    rowIndex++;
158  }
159
160  // The account was not found.
161  dump("The treerow for account " + accountKey + " was not found!\n");
162  return -1;
163}
164
165/**
166 * Remove an account via the account manager UI.
167 *
168 * @param {Object} account - The account to remove.
169 * @param {Object} tab - The account manager tab that opened.
170 * @param {boolean} removeAccount - Remove the account itself.
171 * @param {boolean} removeData - Remove the message data of the account.
172 */
173function remove_account(
174  account,
175  tab,
176  removeAccount = true,
177  removeData = false
178) {
179  let accountRow = get_account_tree_row(account.key, "am-server.xhtml", tab);
180  click_account_tree_row(tab, accountRow);
181
182  account = null;
183  // Use the Remove item in the Account actions menu.
184  mc.click(content_tab_e(tab, "accountActionsButton"));
185  mc.click(content_tab_e(tab, "accountActionsDropdownRemove"));
186
187  let cdc = wh.wait_for_frame_load(
188    tab.browser.contentWindow.gSubDialog._topDialog._frame,
189    "chrome://messenger/content/removeAccount.xhtml"
190  );
191
192  // Account removal confirmation dialog. Select what to remove.
193  if (removeAccount) {
194    cdc.click(cdc.window.document.getElementById("removeAccount"));
195  }
196  if (removeData) {
197    cdc.click(cdc.window.document.getElementById("removeData"));
198  }
199
200  cdc.window.document.documentElement.querySelector("dialog").acceptDialog();
201  cdc.waitFor(
202    () =>
203      !cdc.window.document.querySelector("dialog").getButton("accept").disabled,
204    "Timeout waiting for finish of account removal",
205    5000,
206    100
207  );
208  cdc.window.document.documentElement.querySelector("dialog").acceptDialog();
209}
210