Source code

Revision control

Other Tools

1
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
* License, v. 2.0. If a copy of the MPL was not distributed with this
4
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
"use strict";
6
7
var EXPORTED_SYMBOLS = ["BrowserTestUtilsChild"];
8
9
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
10
11
class BrowserTestUtilsChildObserver {
12
constructor() {
13
this.currentObserverStatus = "";
14
this.observerItems = [];
15
}
16
17
startObservingTopics(aTopics) {
18
for (let topic of aTopics) {
19
Services.obs.addObserver(this, topic);
20
this.observerItems.push({ topic });
21
}
22
}
23
24
stopObservingTopics(aTopics) {
25
if (aTopics) {
26
for (let topic of aTopics) {
27
let index = this.observerItems.findIndex(item => item.topic == topic);
28
if (index >= 0) {
29
Services.obs.removeObserver(this, topic);
30
this.observerItems.splice(index, 1);
31
}
32
}
33
} else {
34
for (let topic of this.observerItems) {
35
Services.obs.removeObserver(this, topic);
36
}
37
this.observerItems = [];
38
}
39
40
if (this.currentObserverStatus) {
41
let error = new Error(this.currentObserverStatus);
42
this.currentObserverStatus = "";
43
throw error;
44
}
45
}
46
47
observeTopic(topic, count, filterFn, callbackResolver) {
48
// If the topic is in the list already, assume that it came from a
49
// startObservingTopics call. If it isn't in the list already, assume
50
// that it isn't within a start/stop set and the observer has to be
51
// removed afterwards.
52
let removeObserver = false;
53
let index = this.observerItems.findIndex(item => item.topic == topic);
54
if (index == -1) {
55
removeObserver = true;
56
this.startObservingTopics([topic]);
57
}
58
59
for (let item of this.observerItems) {
60
if (item.topic == topic) {
61
item.count = count || 1;
62
item.filterFn = filterFn;
63
item.promiseResolver = () => {
64
if (removeObserver) {
65
this.stopObservingTopics([topic]);
66
}
67
callbackResolver();
68
};
69
break;
70
}
71
}
72
}
73
74
observe(aSubject, aTopic, aData) {
75
for (let item of this.observerItems) {
76
if (item.topic != aTopic) {
77
continue;
78
}
79
if (item.filterFn && !item.filterFn(aSubject, aTopic, aData)) {
80
break;
81
}
82
83
if (--item.count >= 0) {
84
if (item.count == 0 && item.promiseResolver) {
85
item.promiseResolver();
86
}
87
return;
88
}
89
}
90
91
// Otherwise, if the observer doesn't match, fail.
92
console.log(
93
"Failed: Observer topic " + aTopic + " not expected in content process"
94
);
95
this.currentObserverStatus +=
96
"Topic " + aTopic + " not expected in content process\n";
97
}
98
}
99
100
BrowserTestUtilsChildObserver.prototype.QueryInterface = ChromeUtils.generateQI(
101
[Ci.nsIObserver, Ci.nsISupportsWeakReference]
102
);
103
104
class BrowserTestUtilsChild extends JSWindowActorChild {
105
actorCreated() {
106
this._EventUtils = null;
107
}
108
109
get EventUtils() {
110
if (!this._EventUtils) {
111
// Set up a dummy environment so that EventUtils works. We need to be careful to
112
// pass a window object into each EventUtils method we call rather than having
113
// it rely on the |window| global.
114
let win = this.contentWindow;
115
let EventUtils = {
116
get KeyboardEvent() {
117
return win.KeyboardEvent;
118
},
119
// EventUtils' `sendChar` function relies on the navigator to synthetize events.
120
get navigator() {
121
return win.navigator;
122
},
123
};
124
125
EventUtils.window = {};
126
EventUtils.parent = EventUtils.window;
127
EventUtils._EU_Ci = Ci;
128
EventUtils._EU_Cc = Cc;
129
130
Services.scriptloader.loadSubScript(
132
EventUtils
133
);
134
135
this._EventUtils = EventUtils;
136
}
137
138
return this._EventUtils;
139
}
140
141
receiveMessage(aMessage) {
142
switch (aMessage.name) {
143
case "Test:SynthesizeMouse": {
144
return this.synthesizeMouse(aMessage.data, this.contentWindow);
145
}
146
147
case "Test:SynthesizeTouch": {
148
return this.synthesizeTouch(aMessage.data, this.contentWindow);
149
}
150
151
case "Test:SendChar": {
152
return this.EventUtils.sendChar(aMessage.data.char, this.contentWindow);
153
}
154
155
case "Test:SynthesizeKey":
156
this.EventUtils.synthesizeKey(
157
aMessage.data.key,
158
aMessage.data.event || {},
159
this.contentWindow
160
);
161
break;
162
163
case "Test:SynthesizeComposition": {
164
return this.EventUtils.synthesizeComposition(
165
aMessage.data.event,
166
this.contentWindow
167
);
168
}
169
170
case "Test:SynthesizeCompositionChange":
171
this.EventUtils.synthesizeCompositionChange(
172
aMessage.data.event,
173
this.contentWindow
174
);
175
break;
176
177
case "BrowserTestUtils:StartObservingTopics": {
178
this.observer = new BrowserTestUtilsChildObserver();
179
this.observer.startObservingTopics(aMessage.data.topics);
180
break;
181
}
182
183
case "BrowserTestUtils:StopObservingTopics": {
184
if (this.observer) {
185
this.observer.stopObservingTopics(aMessage.data.topics);
186
this.observer = null;
187
}
188
break;
189
}
190
191
case "BrowserTestUtils:ObserveTopic": {
192
return new Promise(resolve => {
193
let filterFn;
194
if (aMessage.data.filterFunctionSource) {
195
/* eslint-disable-next-line no-eval */
196
filterFn = eval(
197
`(() => (${aMessage.data.filterFunctionSource}))()`
198
);
199
}
200
201
let observer = this.observer || new BrowserTestUtilsChildObserver();
202
observer.observeTopic(
203
aMessage.data.topic,
204
aMessage.data.count,
205
filterFn,
206
resolve
207
);
208
});
209
}
210
211
case "BrowserTestUtils:CrashFrame": {
212
// This is to intentionally crash the frame.
213
// We crash by using js-ctypes and dereferencing
214
// a bad pointer. The crash should happen immediately
215
// upon loading this frame script.
216
217
const { ctypes } = ChromeUtils.import(
219
);
220
221
let dies = function() {
222
ChromeUtils.privateNoteIntentionalCrash();
223
let zero = new ctypes.intptr_t(8);
224
let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
225
badptr.contents;
226
};
227
228
dump("\nEt tu, Brute?\n");
229
dies();
230
}
231
}
232
233
return undefined;
234
}
235
236
handleEvent(aEvent) {
237
switch (aEvent.type) {
238
case "DOMContentLoaded":
239
case "load": {
240
this.sendAsyncMessage(aEvent.type, {
241
internalURL: aEvent.target.documentURI,
242
visibleURL: aEvent.target.location.href,
243
});
244
break;
245
}
246
}
247
}
248
249
synthesizeMouse(data, window) {
250
let target = data.target;
251
if (typeof target == "string") {
252
target = this.document.querySelector(target);
253
} else if (typeof data.targetFn == "string") {
254
let runnablestr = `
255
(() => {
256
return (${data.targetFn});
257
})();`;
258
/* eslint-disable no-eval */
259
target = eval(runnablestr)();
260
/* eslint-enable no-eval */
261
}
262
263
let left = data.x;
264
let top = data.y;
265
if (target) {
266
if (target.ownerDocument !== this.document) {
267
// Account for nodes found in iframes.
268
let cur = target;
269
do {
270
let frame = cur.ownerGlobal.frameElement;
271
let rect = frame.getBoundingClientRect();
272
273
left += rect.left;
274
top += rect.top;
275
276
cur = frame;
277
} while (cur && cur.ownerDocument !== this.document);
278
279
// node must be in this document tree.
280
if (!cur) {
281
throw new Error("target must be in the main document tree");
282
}
283
}
284
285
let rect = target.getBoundingClientRect();
286
left += rect.left;
287
top += rect.top;
288
289
if (data.event.centered) {
290
left += rect.width / 2;
291
top += rect.height / 2;
292
}
293
}
294
295
let result;
296
if (data.event && data.event.wheel) {
297
this.EventUtils.synthesizeWheelAtPoint(left, top, data.event, window);
298
} else {
299
result = this.EventUtils.synthesizeMouseAtPoint(
300
left,
301
top,
302
data.event,
303
window
304
);
305
}
306
307
return result;
308
}
309
310
synthesizeTouch(data, window) {
311
let target = data.target;
312
if (typeof target == "string") {
313
target = this.document.querySelector(target);
314
} else if (typeof data.targetFn == "string") {
315
let runnablestr = `
316
(() => {
317
return (${data.targetFn});
318
})();`;
319
/* eslint-disable no-eval */
320
target = eval(runnablestr)();
321
/* eslint-enable no-eval */
322
}
323
324
if (target) {
325
if (target.ownerDocument !== this.document) {
326
// Account for nodes found in iframes.
327
let cur = target;
328
do {
329
cur = cur.ownerGlobal.frameElement;
330
} while (cur && cur.ownerDocument !== this.document);
331
332
// node must be in this document tree.
333
if (!cur) {
334
throw new Error("target must be in the main document tree");
335
}
336
}
337
}
338
339
return this.EventUtils.synthesizeTouch(
340
target,
341
data.x,
342
data.y,
343
data.event,
344
window
345
);
346
}
347
}