summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/webconsole/listeners/console-service.js
blob: 11ced5611f0af54f9712ad84e231120f6d9bb822 (plain)
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/* 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/. */

"use strict";

const {
  isWindowIncluded,
} = require("resource://devtools/shared/layout/utils.js");
const {
  WebConsoleUtils,
} = require("resource://devtools/server/actors/webconsole/utils.js");

// The page errors listener

/**
 * The nsIConsoleService listener. This is used to send all of the console
 * messages (JavaScript, CSS and more) to the remote Web Console instance.
 *
 * @constructor
 * @param nsIDOMWindow [window]
 *        Optional - the window object for which we are created. This is used
 *        for filtering out messages that belong to other windows.
 * @param Function handler
 *        This function is invoked with one argument, the nsIConsoleMessage, whenever a
 *        relevant message is received.
 * @param object filteringOptions
 *        Optional - The filteringOptions that this listener should listen to:
 *        - matchExactWindow: Set to true to match the messages on a specific window (when
 *          `window` is defined) and not on the whole window tree.
 */
class ConsoleServiceListener {
  constructor(window, handler, { matchExactWindow } = {}) {
    this.window = window;
    this.handler = handler;
    this.matchExactWindow = matchExactWindow;
  }

  QueryInterface = ChromeUtils.generateQI([Ci.nsIConsoleListener]);

  /**
   * The content window for which we listen to page errors.
   * @type nsIDOMWindow
   */
  window = null;

  /**
   * The function which is notified of messages from the console service.
   * @type function
   */
  handler = null;

  /**
   * Initialize the nsIConsoleService listener.
   */
  init() {
    Services.console.registerListener(this);
  }

  /**
   * The nsIConsoleService observer. This method takes all the script error
   * messages belonging to the current window and sends them to the remote Web
   * Console instance.
   *
   * @param nsIConsoleMessage message
   *        The message object coming from the nsIConsoleService.
   */
  observe(message) {
    if (!this.handler) {
      return;
    }

    if (this.window) {
      if (
        !(message instanceof Ci.nsIScriptError) ||
        !message.outerWindowID ||
        !this.isCategoryAllowed(message.category)
      ) {
        return;
      }

      const errorWindow = Services.wm.getOuterWindowWithId(
        message.outerWindowID
      );

      if (!errorWindow) {
        return;
      }

      if (this.matchExactWindow && this.window !== errorWindow) {
        return;
      }

      if (!isWindowIncluded(this.window, errorWindow)) {
        return;
      }
    }

    // Don't display messages triggered by eager evaluation.
    if (message.sourceName === "debugger eager eval code") {
      return;
    }
    this.handler(message);
  }

  /**
   * Check if the given message category is allowed to be tracked or not.
   * We ignore chrome-originating errors as we only care about content.
   *
   * @param string category
   *        The message category you want to check.
   * @return boolean
   *         True if the category is allowed to be logged, false otherwise.
   */
  isCategoryAllowed(category) {
    if (!category) {
      return false;
    }

    switch (category) {
      case "XPConnect JavaScript":
      case "component javascript":
      case "chrome javascript":
      case "chrome registration":
        return false;
    }

    return true;
  }

  /**
   * Get the cached page errors for the current inner window and its (i)frames.
   *
   * @param boolean [includePrivate=false]
   *        Tells if you want to also retrieve messages coming from private
   *        windows. Defaults to false.
   * @return array
   *         The array of cached messages. Each element is an nsIScriptError or
   *         an nsIConsoleMessage
   */
  getCachedMessages(includePrivate = false) {
    const errors = Services.console.getMessageArray() || [];

    // if !this.window, we're in a browser console. Still need to filter
    // private messages.
    if (!this.window) {
      return errors.filter(error => {
        if (error instanceof Ci.nsIScriptError) {
          if (!includePrivate && error.isFromPrivateWindow) {
            return false;
          }
        }

        return true;
      });
    }

    const ids = this.matchExactWindow
      ? [WebConsoleUtils.getInnerWindowId(this.window)]
      : WebConsoleUtils.getInnerWindowIDsForFrames(this.window);

    return errors.filter(error => {
      if (error instanceof Ci.nsIScriptError) {
        if (!includePrivate && error.isFromPrivateWindow) {
          return false;
        }
        if (
          ids &&
          (!ids.includes(error.innerWindowID) ||
            !this.isCategoryAllowed(error.category))
        ) {
          return false;
        }
      } else if (ids?.[0]) {
        // If this is not an nsIScriptError and we need to do window-based
        // filtering we skip this message.
        return false;
      }

      return true;
    });
  }

  /**
   * Remove the nsIConsoleService listener.
   */
  destroy() {
    Services.console.unregisterListener(this);
    this.handler = this.window = null;
  }
}

exports.ConsoleServiceListener = ConsoleServiceListener;