Source code

Revision control

Other Tools

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
/*
6
* This module implements a number of utility functions that can be loaded
7
* into content scope.
8
*
9
* All asynchronous helper methods should return promises, rather than being
10
* callback based.
11
*/
12
13
// Disable ownerGlobal use since that's not available on content-privileged elements.
14
15
/* eslint-disable mozilla/use-ownerGlobal */
16
17
"use strict";
18
19
var EXPORTED_SYMBOLS = ["ContentTaskUtils"];
20
21
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
22
const { clearInterval, setInterval, setTimeout } = ChromeUtils.import(
24
);
25
26
var ContentTaskUtils = {
27
/**
28
* Checks if a DOM element is hidden.
29
*
30
* @param {Element} element
31
* The element which is to be checked.
32
*
33
* @return {boolean}
34
*/
35
is_hidden(element) {
36
let style = element.ownerDocument.defaultView.getComputedStyle(element);
37
if (style.display == "none") {
38
return true;
39
}
40
if (style.visibility != "visible") {
41
return true;
42
}
43
44
// Hiding a parent element will hide all its children
45
if (
46
element.parentNode != element.ownerDocument &&
47
element.parentNode.nodeType != Node.DOCUMENT_FRAGMENT_NODE
48
) {
49
return ContentTaskUtils.is_hidden(element.parentNode);
50
}
51
52
// Walk up the shadow DOM if we've reached the top of the shadow root
53
if (element.parentNode.host) {
54
return ContentTaskUtils.is_hidden(element.parentNode.host);
55
}
56
57
return false;
58
},
59
60
/**
61
* Checks if a DOM element is visible.
62
*
63
* @param {Element} element
64
* The element which is to be checked.
65
*
66
* @return {boolean}
67
*/
68
is_visible(element) {
69
return !this.is_hidden(element);
70
},
71
72
/**
73
* Will poll a condition function until it returns true.
74
*
75
* @param condition
76
* A condition function that must return true or false. If the
77
* condition ever throws, this is also treated as a false.
78
* @param msg
79
* The message to use when the returned promise is rejected.
80
* This message will be extended with additional information
81
* about the number of tries or the thrown exception.
82
* @param interval
83
* The time interval to poll the condition function. Defaults
84
* to 100ms.
85
* @param maxTries
86
* The number of times to poll before giving up and rejecting
87
* if the condition has not yet returned true. Defaults to 50
88
* (~5 seconds for 100ms intervals)
89
* @return Promise
90
* Resolves when condition is true.
91
* Rejects if timeout is exceeded or condition ever throws.
92
*/
93
waitForCondition(condition, msg, interval = 100, maxTries = 50) {
94
return new Promise((resolve, reject) => {
95
let tries = 0;
96
let intervalID = setInterval(() => {
97
if (tries >= maxTries) {
98
clearInterval(intervalID);
99
msg += ` - timed out after ${maxTries} tries.`;
100
reject(msg);
101
return;
102
}
103
104
let conditionPassed = false;
105
try {
106
conditionPassed = condition();
107
} catch (e) {
108
msg += ` - threw exception: ${e}`;
109
clearInterval(intervalID);
110
reject(msg);
111
return;
112
}
113
114
if (conditionPassed) {
115
clearInterval(intervalID);
116
resolve(conditionPassed);
117
}
118
tries++;
119
}, interval);
120
});
121
},
122
123
/**
124
* Waits for an event to be fired on a specified element.
125
*
126
* Usage:
127
* let promiseEvent = ContentTasKUtils.waitForEvent(element, "eventName");
128
* // Do some processing here that will cause the event to be fired
129
* // ...
130
* // Now yield until the Promise is fulfilled
131
* let receivedEvent = yield promiseEvent;
132
*
133
* @param {Element} subject
134
* The element that should receive the event.
135
* @param {string} eventName
136
* Name of the event to listen to.
137
* @param {bool} capture [optional]
138
* True to use a capturing listener.
139
* @param {function} checkFn [optional]
140
* Called with the Event object as argument, should return true if the
141
* event is the expected one, or false if it should be ignored and
142
* listening should continue. If not specified, the first event with
143
* the specified name resolves the returned promise.
144
*
145
* @note Because this function is intended for testing, any error in checkFn
146
* will cause the returned promise to be rejected instead of waiting for
147
* the next event, since this is probably a bug in the test.
148
*
149
* @returns {Promise}
150
* @resolves The Event object.
151
*/
152
waitForEvent(subject, eventName, capture, checkFn, wantsUntrusted = false) {
153
return new Promise((resolve, reject) => {
154
subject.addEventListener(
155
eventName,
156
function listener(event) {
157
try {
158
if (checkFn && !checkFn(event)) {
159
return;
160
}
161
subject.removeEventListener(eventName, listener, capture);
162
setTimeout(() => resolve(event), 0);
163
} catch (ex) {
164
try {
165
subject.removeEventListener(eventName, listener, capture);
166
} catch (ex2) {
167
// Maybe the provided object does not support removeEventListener.
168
}
169
setTimeout(() => reject(ex), 0);
170
}
171
},
172
capture,
173
wantsUntrusted
174
);
175
});
176
},
177
178
/**
179
* Gets an instance of the `EventUtils` helper module for usage in
181
*
182
* @param content
183
* The `content` global object from your content task.
184
*
185
* @returns an EventUtils instance.
186
*/
187
getEventUtils(content) {
188
if (content._EventUtils) {
189
return content._EventUtils;
190
}
191
192
let EventUtils = (content._EventUtils = {});
193
194
EventUtils.window = {};
195
EventUtils.parent = EventUtils.window;
196
/* eslint-disable camelcase */
197
EventUtils._EU_Ci = Ci;
198
EventUtils._EU_Cc = Cc;
199
/* eslint-enable camelcase */
200
// EventUtils' `sendChar` function relies on the navigator to synthetize events.
201
EventUtils.navigator = content.navigator;
202
EventUtils.KeyboardEvent = content.KeyboardEvent;
203
204
Services.scriptloader.loadSubScript(
206
EventUtils
207
);
208
209
return EventUtils;
210
},
211
};