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/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
11 class BrowserTestUtilsChildObserver {
13 this.currentObserverStatus = "";
14 this.observerItems = [];
17 startObservingTopics(aTopics) {
18 for (let topic of aTopics) {
19 Services.obs.addObserver(this, topic);
20 this.observerItems.push({ topic });
24 stopObservingTopics(aTopics) {
26 for (let topic of aTopics) {
27 let index = this.observerItems.findIndex(item => item.topic == topic);
29 Services.obs.removeObserver(this, topic);
30 this.observerItems.splice(index, 1);
34 for (let topic of this.observerItems) {
35 Services.obs.removeObserver(this, topic);
37 this.observerItems = [];
40 if (this.currentObserverStatus) {
41 let error = new Error(this.currentObserverStatus);
42 this.currentObserverStatus = "";
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);
55 removeObserver = true;
56 this.startObservingTopics([topic]);
59 for (let item of this.observerItems) {
60 if (item.topic == topic) {
61 item.count = count || 1;
62 item.filterFn = filterFn;
63 item.promiseResolver = () => {
65 this.stopObservingTopics([topic]);
74 observe(aSubject, aTopic, aData) {
75 for (let item of this.observerItems) {
76 if (item.topic != aTopic) {
79 if (item.filterFn && !item.filterFn(aSubject, aTopic, aData)) {
83 if (--item.count >= 0) {
84 if (item.count == 0 && item.promiseResolver) {
85 item.promiseResolver();
91 // Otherwise, if the observer doesn't match, fail.
93 "Failed: Observer topic " + aTopic + " not expected in content process"
95 this.currentObserverStatus +=
96 "Topic " + aTopic + " not expected in content process\n";
100 BrowserTestUtilsChildObserver.prototype.QueryInterface = ChromeUtils.generateQI(
101 ["nsIObserver", "nsISupportsWeakReference"]
104 export class BrowserTestUtilsChild extends JSWindowActorChild {
106 this._EventUtils = null;
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;
116 get KeyboardEvent() {
117 return win.KeyboardEvent;
119 // EventUtils' `sendChar` function relies on the navigator to synthetize events.
121 return win.navigator;
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",
135 this._EventUtils = EventUtils;
138 return this._EventUtils;
141 receiveMessage(aMessage) {
142 switch (aMessage.name) {
143 case "Test:SynthesizeMouse": {
144 return this.synthesizeMouse(aMessage.data, this.contentWindow);
147 case "Test:SynthesizeTouch": {
148 return this.synthesizeTouch(aMessage.data, this.contentWindow);
151 case "Test:SendChar": {
152 return this.EventUtils.sendChar(aMessage.data.char, this.contentWindow);
155 case "Test:SynthesizeKey":
156 this.EventUtils.synthesizeKey(
158 aMessage.data.event || {},
163 case "Test:SynthesizeComposition": {
164 return this.EventUtils.synthesizeComposition(
170 case "Test:SynthesizeCompositionChange":
171 this.EventUtils.synthesizeCompositionChange(
177 case "BrowserTestUtils:StartObservingTopics": {
178 this.observer = new BrowserTestUtilsChildObserver();
179 this.observer.startObservingTopics(aMessage.data.topics);
183 case "BrowserTestUtils:StopObservingTopics": {
185 this.observer.stopObservingTopics(aMessage.data.topics);
186 this.observer = null;
191 case "BrowserTestUtils:ObserveTopic": {
192 return new Promise(resolve => {
194 if (aMessage.data.filterFunctionSource) {
195 /* eslint-disable-next-line no-eval */
197 `(() => (${aMessage.data.filterFunctionSource}))()`
201 let observer = this.observer || new BrowserTestUtilsChildObserver();
202 observer.observeTopic(
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"
221 let dies = function () {
222 dump("\nEt tu, Brute?\n");
223 ChromeUtils.privateNoteIntentionalCrash();
226 // Annotate test failure to allow callers to separate intentional
227 // crashes from unintentional crashes.
228 Services.appinfo.annotateCrashReport("TestKey", "CrashFrame");
230 dump(`Failed to annotate crash in CrashFrame: ${e}\n`);
233 switch (aMessage.data.crashType) {
235 let debug = Cc["@mozilla.org/xpcom/debug;1"].getService(
238 debug.crashWithOOM();
241 case "CRASH_SYSCALL": {
242 if (Services.appinfo.OS == "Linux") {
243 let libc = ctypes.open("libc.so.6");
244 let chroot = libc.declare(
254 case "CRASH_INVALID_POINTER_DEREF": // Fallthrough
256 // Dereference a bad pointer.
257 let zero = new ctypes.intptr_t(8);
258 let badptr = ctypes.cast(
260 ctypes.PointerType(ctypes.int32_t)
267 if (aMessage.data.asyncCrash) {
268 let { setTimeout } = ChromeUtils.importESModule(
269 "resource://gre/modules/Timer.sys.mjs"
271 // Get out of the stack.
282 handleEvent(aEvent) {
283 switch (aEvent.type) {
284 case "DOMContentLoaded":
286 this.sendAsyncMessage(aEvent.type, {
287 internalURL: aEvent.target.documentURI,
288 visibleURL: aEvent.target.location
289 ? aEvent.target.location.href
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") {
304 return (${data.targetFn});
306 /* eslint-disable no-eval */
307 target = eval(runnablestr)();
308 /* eslint-enable no-eval */
314 if (target.ownerDocument !== this.document) {
315 // Account for nodes found in iframes.
318 // eslint-disable-next-line mozilla/use-ownerGlobal
319 let frame = cur.ownerDocument.defaultView.frameElement;
320 let rect = frame.getBoundingClientRect();
326 } while (cur && cur.ownerDocument !== this.document);
328 // node must be in this document tree.
330 throw new Error("target must be in the main document tree");
334 let rect = target.getBoundingClientRect();
338 if (data.event.centered) {
339 left += rect.width / 2;
340 top += rect.height / 2;
346 lazy.E10SUtils.wrapHandlingUserInput(window, data.handlingUserInput, () => {
347 if (data.event && data.event.wheel) {
348 this.EventUtils.synthesizeWheelAtPoint(left, top, data.event, window);
350 result = this.EventUtils.synthesizeMouseAtPoint(
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") {
369 return (${data.targetFn});
371 /* eslint-disable no-eval */
372 target = eval(runnablestr)();
373 /* eslint-enable no-eval */
377 if (target.ownerDocument !== this.document) {
378 // Account for nodes found in iframes.
381 cur = cur.ownerGlobal.frameElement;
382 } while (cur && cur.ownerDocument !== this.document);
384 // node must be in this document tree.
386 throw new Error("target must be in the main document tree");
391 return this.EventUtils.synthesizeTouch(