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
|
/* 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/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "ConsoleAPIStorage", () => {
return Cc["@mozilla.org/consoleAPI-storage;1"].getService(
Ci.nsIConsoleAPIStorage
);
});
/**
* The ConsoleAPIListener can be used to listen for messages coming from console
* API usage in a given windowGlobal, eg. console.log, console.error, ...
*
* Example:
* ```
* const listener = new ConsoleAPIListener(innerWindowId);
* listener.on("message", onConsoleAPIMessage);
* listener.startListening();
*
* const onConsoleAPIMessage = (eventName, data = {}) => {
* const { arguments: msgArguments, level, stacktrace, timeStamp } = data;
* ...
* };
* ```
*
* @fires message
* The ConsoleAPIListener emits "message" events, with the following object as
* payload:
* - {Array<Object>} arguments - Arguments as passed-in when the method was called.
* - {String} level - Importance, one of `info`, `warn`, `error`, `debug`, `trace`.
* - {Array<Object>} stacktrace - List of stack frames, starting from most recent.
* - {Number} timeStamp - Timestamp when the method was called.
*/
export class ConsoleAPIListener {
#emittedMessages;
#innerWindowId;
#listening;
/**
* Create a new ConsoleAPIListener instance.
*
* @param {number} innerWindowId
* The inner window id to filter the messages for.
*/
constructor(innerWindowId) {
lazy.EventEmitter.decorate(this);
this.#emittedMessages = new Set();
this.#innerWindowId = innerWindowId;
this.#listening = false;
}
destroy() {
this.stopListening();
this.#emittedMessages = null;
}
startListening() {
if (this.#listening) {
return;
}
lazy.ConsoleAPIStorage.addLogEventListener(
this.#onConsoleAPIMessage,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
// Emit cached messages after registering the listener, to make sure we
// don't miss any message.
this.#emitCachedMessages();
this.#listening = true;
}
stopListening() {
if (!this.#listening) {
return;
}
lazy.ConsoleAPIStorage.removeLogEventListener(this.#onConsoleAPIMessage);
this.#listening = false;
}
#emitCachedMessages() {
const cachedMessages = lazy.ConsoleAPIStorage.getEvents(
this.#innerWindowId
);
for (const message of cachedMessages) {
this.#onConsoleAPIMessage(message);
}
}
#onConsoleAPIMessage = message => {
const messageObject = message.wrappedJSObject;
// Bail if this message was already emitted, useful to filter out cached
// messages already received by the consumer.
if (this.#emittedMessages.has(messageObject)) {
return;
}
this.#emittedMessages.add(messageObject);
if (messageObject.innerID !== this.#innerWindowId) {
// If the message doesn't match the innerWindowId of the current context
// ignore it.
return;
}
this.emit("message", {
arguments: messageObject.arguments,
level: messageObject.level,
stacktrace: messageObject.stacktrace,
timeStamp: messageObject.timeStamp,
});
};
}
|