summaryrefslogtreecommitdiffstats
path: root/comm/mail/test/browser/shared-modules/DOMHelpers.jsm
blob: 719de9c38197d1fcf5f10a82a779acce3b3489c8 (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
246
247
248
249
250
251
252
253
254
255
256
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const EXPORTED_SYMBOLS = [
  "assert_element_visible",
  "element_visible_recursive",
  "assert_element_not_visible",
  "wait_for_element",
  "assert_next_nodes",
  "assert_previous_nodes",
  "wait_for_element_enabled",
  "check_element_visible",
  "wait_for_element_visible",
  "wait_for_element_invisible",
  "collapse_panes",
];

const lazy = {};

ChromeUtils.defineModuleGetter(
  lazy,
  "mc",
  "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
);

var { Assert } = ChromeUtils.importESModule(
  "resource://testing-common/Assert.sys.mjs"
);

var utils = ChromeUtils.import("resource://testing-common/mozmill/utils.jsm");

/**
 * This function takes either a string or an elementlibs.Elem, and returns
 * whether it is hidden or not (simply by poking at its hidden property). It
 * doesn't try to do anything smart, like is it not into view, or whatever.
 *
 * @param aElt The element to query.
 * @returns Whether the element is visible or not.
 */
function element_visible(aElt) {
  let e;
  if (typeof aElt == "string") {
    e = lazy.mc.window.document.getElementById(aElt);
  } else {
    e = aElt;
  }
  return !e.hidden;
}

/**
 * Assert that en element's visible.
 *
 * @param aElt The element, an ID or an elementlibs.Elem
 * @param aWhy The error message in case of failure
 */
function assert_element_visible(aElt, aWhy) {
  Assert.ok(element_visible(aElt), aWhy);
}

/**
 * Returns if a element is visible by traversing all parent elements and check
 * that all are visible.
 *
 * @param aElem The element to be checked
 */
function element_visible_recursive(aElem) {
  if (aElem.hidden || aElem.collapsed) {
    return false;
  }
  let parent = aElem.parentNode;
  if (parent == null) {
    return true;
  }

  // #tabpanelcontainer and its parent #tabmail-tabbox have the same selectedPanel.
  // Don't ask me why, it's just the way it is.
  if (
    "selectedPanel" in parent &&
    parent.selectedPanel != aElem &&
    aElem.id != "tabpanelcontainer"
  ) {
    return false;
  }
  return element_visible_recursive(parent);
}

/**
 * Assert that en element's not visible.
 *
 * @param aElt The element, an ID or an elementlibs.Elem
 * @param aWhy The error message in case of failure
 */
function assert_element_not_visible(aElt, aWhy) {
  Assert.ok(!element_visible(aElt), aWhy);
}

/**
 * Wait for and return an element matching a particular CSS selector.
 *
 * @param aParent the node to begin searching from
 * @param aSelector the CSS selector to search with
 */
function wait_for_element(aParent, aSelector) {
  let target = null;
  utils.waitFor(function () {
    target = aParent.querySelector(aSelector);
    return target != null;
  }, "Timed out waiting for a target for selector: " + aSelector);

  return target;
}

/**
 * Given some starting node aStart, ensure that aStart and the aNum next
 * siblings of aStart are nodes of type aNodeType.
 *
 * @param aNodeType the type of node to look for, example: "br".
 * @param aStart the first node to check.
 * @param aNum the number of sibling br nodes to check for.
 */
function assert_next_nodes(aNodeType, aStart, aNum) {
  let node = aStart;
  for (let i = 0; i < aNum; ++i) {
    node = node.nextSibling;
    if (node.localName != aNodeType) {
      throw new Error(
        "The node should be followed by " +
          aNum +
          " nodes of " +
          "type " +
          aNodeType
      );
    }
  }
  return node;
}

/**
 * Given some starting node aStart, ensure that aStart and the aNum previous
 * siblings of aStart are nodes of type aNodeType.
 *
 * @param aNodeType the type of node to look for, example: "br".
 * @param aStart the first node to check.
 * @param aNum the number of sibling br nodes to check for.
 */
function assert_previous_nodes(aNodeType, aStart, aNum) {
  let node = aStart;
  for (let i = 0; i < aNum; ++i) {
    node = node.previousSibling;
    if (node.localName != aNodeType) {
      throw new Error(
        "The node should be preceded by " +
          aNum +
          " nodes of " +
          "type " +
          aNodeType
      );
    }
  }
  return node;
}

/**
 * Given some element, wait for that element to be enabled or disabled,
 * depending on the value of aEnabled.
 *
 * @param aController the controller parent of the element
 * @param aNode the element to check.
 * @param aEnabled whether or not the node should be enabled, or disabled.
 */
function wait_for_element_enabled(aController, aElement, aEnabled) {
  if (!("disabled" in aElement)) {
    throw new Error(
      "Element does not appear to have disabled property; id=" + aElement.id
    );
  }

  utils.waitFor(
    () => aElement.disabled != aEnabled,
    "Element should have eventually been " +
      (aEnabled ? "enabled" : "disabled") +
      "; id=" +
      aElement.id
  );
}

function check_element_visible(aController, aId) {
  let element = aController.window.document.getElementById(aId);
  if (!element) {
    return false;
  }

  while (element) {
    if (
      element.hidden ||
      element.collapsed ||
      element.clientWidth == 0 ||
      element.clientHeight == 0 ||
      aController.window.getComputedStyle(element).display == "none"
    ) {
      return false;
    }
    element = element.parentElement;
  }
  return true;
}

/**
 * Wait for a particular element to become fully visible.
 *
 * @param aController  the controller parent of the element
 * @param aId          id of the element to wait for
 */
function wait_for_element_visible(aController, aId) {
  utils.waitFor(function () {
    return check_element_visible(aController, aId);
  }, "Timed out waiting for element with ID=" + aId + " to become visible");
}

/**
 * Wait for a particular element to become fully invisible.
 *
 * @param aController  the controller parent of the element
 * @param aId          id of the element to wait for
 */
function wait_for_element_invisible(aController, aId) {
  utils.waitFor(function () {
    return !check_element_visible(aController, aId);
  }, "Timed out waiting for element with ID=" + aId + " to become invisible");
}

/**
 * Helper to collapse panes separated by splitters. If aElement is a splitter
 * itself, then this splitter is collapsed, otherwise all splitters that are
 * direct children of aElement are collapsed.
 *
 * @param aElement              The splitter or container
 * @param aShouldBeCollapsed    If true, collapse the pane
 */
function collapse_panes(aElement, aShouldBeCollapsed) {
  let state = aShouldBeCollapsed ? "collapsed" : "open";
  if (aElement.localName == "splitter") {
    aElement.setAttribute("state", state);
  } else {
    for (let n of aElement.childNodes) {
      if (n.localName == "splitter") {
        n.setAttribute("state", state);
      }
    }
  }
  // Spin the event loop once to let other window elements redraw.
  utils.sleep(50);
}