Bug 1919788 - LSNG: Always acquire a directory lock for PreparedDatastoreOp; r=dom...
[gecko.git] / testing / mochitest / BrowserTestUtils / BrowserTestUtilsChild.sys.mjs
blob9d6338847107c86878afa11a5795568315fbfe3a
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/. */
6 const lazy = {};
7 ChromeUtils.defineESModuleGetters(lazy, {
8   E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
9 });
11 class BrowserTestUtilsChildObserver {
12   constructor() {
13     this.currentObserverStatus = "";
14     this.observerItems = [];
15   }
17   startObservingTopics(aTopics) {
18     for (let topic of aTopics) {
19       Services.obs.addObserver(this, topic);
20       this.observerItems.push({ topic });
21     }
22   }
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     }
40     if (this.currentObserverStatus) {
41       let error = new Error(this.currentObserverStatus);
42       this.currentObserverStatus = "";
43       throw error;
44     }
45   }
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     }
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   }
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       }
83       if (--item.count >= 0) {
84         if (item.count == 0 && item.promiseResolver) {
85           item.promiseResolver();
86         }
87         return;
88       }
89     }
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   }
100 BrowserTestUtilsChildObserver.prototype.QueryInterface = ChromeUtils.generateQI(
101   ["nsIObserver", "nsISupportsWeakReference"]
104 export class BrowserTestUtilsChild extends JSWindowActorChild {
105   actorCreated() {
106     this._EventUtils = null;
107   }
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       };
125       EventUtils.window = {};
126       EventUtils.parent = EventUtils.window;
127       EventUtils._EU_Ci = Ci;
128       EventUtils._EU_Cc = Cc;
130       Services.scriptloader.loadSubScript(
131         "chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
132         EventUtils
133       );
135       this._EventUtils = EventUtils;
136     }
138     return this._EventUtils;
139   }
141   receiveMessage(aMessage) {
142     switch (aMessage.name) {
143       case "Test:SynthesizeMouse": {
144         return this.synthesizeMouse(aMessage.data, this.contentWindow);
145       }
147       case "Test:SynthesizeTouch": {
148         return this.synthesizeTouch(aMessage.data, this.contentWindow);
149       }
151       case "Test:SendChar": {
152         return this.EventUtils.sendChar(aMessage.data.char, this.contentWindow);
153       }
155       case "Test:SynthesizeKey":
156         this.EventUtils.synthesizeKey(
157           aMessage.data.key,
158           aMessage.data.event || {},
159           this.contentWindow
160         );
161         break;
163       case "Test:SynthesizeComposition": {
164         return this.EventUtils.synthesizeComposition(
165           aMessage.data.event,
166           this.contentWindow
167         );
168       }
170       case "Test:SynthesizeCompositionChange":
171         this.EventUtils.synthesizeCompositionChange(
172           aMessage.data.event,
173           this.contentWindow
174         );
175         break;
177       case "BrowserTestUtils:StartObservingTopics": {
178         this.observer = new BrowserTestUtilsChildObserver();
179         this.observer.startObservingTopics(aMessage.data.topics);
180         break;
181       }
183       case "BrowserTestUtils:StopObservingTopics": {
184         if (this.observer) {
185           this.observer.stopObservingTopics(aMessage.data.topics);
186           this.observer = null;
187         }
188         break;
189       }
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           }
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       }
211       case "BrowserTestUtils:CrashFrame": {
212         // This is to intentionally crash the frame.
213         // We crash by using js-ctypes. The crash
214         // should happen immediately
215         // upon loading this frame script.
217         const { ctypes } = ChromeUtils.importESModule(
218           "resource://gre/modules/ctypes.sys.mjs"
219         );
221         let dies = function () {
222           dump("\nEt tu, Brute?\n");
223           ChromeUtils.privateNoteIntentionalCrash();
225           try {
226             // Annotate test failure to allow callers to separate intentional
227             // crashes from unintentional crashes.
228             Services.appinfo.annotateCrashReport("TestKey", "CrashFrame");
229           } catch (e) {
230             dump(`Failed to annotate crash in CrashFrame: ${e}\n`);
231           }
233           switch (aMessage.data.crashType) {
234             case "CRASH_OOM": {
235               let debug = Cc["@mozilla.org/xpcom/debug;1"].getService(
236                 Ci.nsIDebug2
237               );
238               debug.crashWithOOM();
239               break;
240             }
241             case "CRASH_SYSCALL": {
242               if (Services.appinfo.OS == "Linux") {
243                 let libc = ctypes.open("libc.so.6");
244                 let chroot = libc.declare(
245                   "chroot",
246                   ctypes.default_abi,
247                   ctypes.int,
248                   ctypes.char.ptr
249                 );
250                 chroot("/");
251               }
252               break;
253             }
254             case "CRASH_INVALID_POINTER_DEREF": // Fallthrough
255             default: {
256               // Dereference a bad pointer.
257               let zero = new ctypes.intptr_t(8);
258               let badptr = ctypes.cast(
259                 zero,
260                 ctypes.PointerType(ctypes.int32_t)
261               );
262               badptr.contents;
263             }
264           }
265         };
267         if (aMessage.data.asyncCrash) {
268           let { setTimeout } = ChromeUtils.importESModule(
269             "resource://gre/modules/Timer.sys.mjs"
270           );
271           // Get out of the stack.
272           setTimeout(dies, 0);
273         } else {
274           dies();
275         }
276       }
277     }
279     return undefined;
280   }
282   handleEvent(aEvent) {
283     switch (aEvent.type) {
284       case "DOMContentLoaded":
285       case "load": {
286         this.sendAsyncMessage(aEvent.type, {
287           internalURL: aEvent.target.documentURI,
288           visibleURL: aEvent.target.location
289             ? aEvent.target.location.href
290             : null,
291         });
292         break;
293       }
294     }
295   }
297   synthesizeMouse(data, window) {
298     let target = data.target;
299     if (typeof target == "string") {
300       target = this.document.querySelector(target);
301     } else if (typeof data.targetFn == "string") {
302       let runnablestr = `
303         (() => {
304           return (${data.targetFn});
305         })();`;
306       /* eslint-disable no-eval */
307       target = eval(runnablestr)();
308       /* eslint-enable no-eval */
309     }
311     let left = data.x;
312     let top = data.y;
313     if (target) {
314       if (target.ownerDocument !== this.document) {
315         // Account for nodes found in iframes.
316         let cur = target;
317         do {
318           // eslint-disable-next-line mozilla/use-ownerGlobal
319           let frame = cur.ownerDocument.defaultView.frameElement;
320           let rect = frame.getBoundingClientRect();
322           left += rect.left;
323           top += rect.top;
325           cur = frame;
326         } while (cur && cur.ownerDocument !== this.document);
328         // node must be in this document tree.
329         if (!cur) {
330           throw new Error("target must be in the main document tree");
331         }
332       }
334       let rect = target.getBoundingClientRect();
335       left += rect.left;
336       top += rect.top;
338       if (data.event.centered) {
339         left += rect.width / 2;
340         top += rect.height / 2;
341       }
342     }
344     let result;
346     lazy.E10SUtils.wrapHandlingUserInput(window, data.handlingUserInput, () => {
347       if (data.event && data.event.wheel) {
348         this.EventUtils.synthesizeWheelAtPoint(left, top, data.event, window);
349       } else {
350         result = this.EventUtils.synthesizeMouseAtPoint(
351           left,
352           top,
353           data.event,
354           window
355         );
356       }
357     });
359     return result;
360   }
362   synthesizeTouch(data, window) {
363     let target = data.target;
364     if (typeof target == "string") {
365       target = this.document.querySelector(target);
366     } else if (typeof data.targetFn == "string") {
367       let runnablestr = `
368         (() => {
369           return (${data.targetFn});
370         })();`;
371       /* eslint-disable no-eval */
372       target = eval(runnablestr)();
373       /* eslint-enable no-eval */
374     }
376     if (target) {
377       if (target.ownerDocument !== this.document) {
378         // Account for nodes found in iframes.
379         let cur = target;
380         do {
381           cur = cur.ownerGlobal.frameElement;
382         } while (cur && cur.ownerDocument !== this.document);
384         // node must be in this document tree.
385         if (!cur) {
386           throw new Error("target must be in the main document tree");
387         }
388       }
389     }
391     return this.EventUtils.synthesizeTouch(
392       target,
393       data.x,
394       data.y,
395       data.event,
396       window
397     );
398   }