summaryrefslogtreecommitdiffstats
path: root/devtools/shared/webconsole/server-logger-monitor.js
blob: 9cc2682eaeb49725111c1b6958115d8e0685ef24 (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
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
/* 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 {Ci} = require("chrome");
const Services = require("Services");

const {makeInfallible} = require("devtools/shared/DevToolsUtils");

loader.lazyGetter(this, "NetworkHelper", () => require("devtools/shared/webconsole/network-helper"));

// Helper tracer. Should be generic sharable by other modules (bug 1171927)
const trace = {
  log: function (...args) {
  }
};

const acceptableHeaders = ["x-chromelogger-data"];

/**
 * This object represents HTTP events observer. It's intended to be
 * used in e10s enabled browser only.
 *
 * Since child processes can't register HTTP event observer they use
 * this module to do the observing in the parent process. This monitor
 * is loaded through DebuggerServerConnection.setupInParent() that is
 * executed from within the child process. The execution is done by
 * {@ServerLoggingListener}.  The monitor listens to HTTP events and
 * forwards it into the right child process.
 *
 * Read more about the architecture:
 * https://github.com/mozilla/gecko-dev/blob/fx-team/devtools/server/docs/actor-e10s-handling.md
 */
var ServerLoggerMonitor = {
  // Initialization

  initialize: function () {
    this.onChildMessage = this.onChildMessage.bind(this);
    this.onExamineResponse = this.onExamineResponse.bind(this);

    // Set of registered child frames (loggers).
    this.targets = new Set();
  },

  // Parent Child Relationship

  attach: makeInfallible(function ({ mm, prefix }) {
    trace.log("ServerLoggerMonitor.attach; ", arguments);

    let setMessageManager = newMM => {
      if (mm) {
        mm.removeMessageListener("debug:server-logger", this.onChildMessage);
      }
      mm = newMM;
      if (mm) {
        mm.addMessageListener("debug:server-logger", this.onChildMessage);
      }
    };

    // Start listening for messages from the {@ServerLogger} actor
    // living in the child process.
    setMessageManager(mm);

    return {
      onBrowserSwap: setMessageManager,
      onDisconnected: () => {
        trace.log("ServerLoggerMonitor.onDisconnectChild; ", arguments);
        setMessageManager(null);
      }
    };
  }),

  // Child Message Handling

  onChildMessage: function (msg) {
    let method = msg.data.method;

    trace.log("ServerLoggerMonitor.onChildMessage; ", method, msg);

    switch (method) {
      case "attachChild":
        return this.onAttachChild(msg);
      case "detachChild":
        return this.onDetachChild(msg);
      default:
        trace.log("Unknown method name: ", method);
        return undefined;
    }
  },

  onAttachChild: function (event) {
    let target = event.target;
    let size = this.targets.size;

    trace.log("ServerLoggerMonitor.onAttachChild; size: ", size, target);

    // If this is the first child attached, register global HTTP observer.
    if (!size) {
      trace.log("ServerLoggerMonitor.onAttatchChild; Add HTTP Observer");
      Services.obs.addObserver(this.onExamineResponse,
        "http-on-examine-response", false);
    }

    // Collect child loggers. The frame element where the
    // window/document lives.
    this.targets.add(target);
  },

  onDetachChild: function (event) {
    let target = event.target;
    this.targets.delete(target);

    let size = this.targets.size;
    trace.log("ServerLoggerMonitor.onDetachChild; size: ", size, target);

    // If this is the last child process attached, unregister
    // the global HTTP observer.
    if (!size) {
      trace.log("ServerLoggerMonitor.onDetachChild; Remove HTTP Observer");
      Services.obs.removeObserver(this.onExamineResponse,
        "http-on-examine-response", false);
    }
  },

  // HTTP Observer

  onExamineResponse: makeInfallible(function (subject, topic) {
    let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);

    trace.log("ServerLoggerMonitor.onExamineResponse; ", httpChannel.name,
      this.targets);

    // Ignore requests from chrome or add-on code when we are monitoring
    // content.
    if (!httpChannel.loadInfo &&
        httpChannel.loadInfo.loadingDocument === null &&
        httpChannel.loadInfo.loadingPrincipal ===
        Services.scriptSecurityManager.getSystemPrincipal()) {
      return;
    }

    let requestFrame = NetworkHelper.getTopFrameForRequest(httpChannel);
    if (!requestFrame) {
      return;
    }

    // Ignore requests from parent frames that aren't registered.
    if (!this.targets.has(requestFrame)) {
      return;
    }

    let headers = [];

    httpChannel.visitResponseHeaders((header, value) => {
      header = header.toLowerCase();
      if (acceptableHeaders.indexOf(header) !== -1) {
        headers.push({header: header, value: value});
      }
    });

    if (!headers.length) {
      return;
    }

    let { messageManager } = requestFrame;
    messageManager.sendAsyncMessage("debug:server-logger", {
      method: "examineHeaders",
      headers: headers,
    });

    trace.log("ServerLoggerMonitor.onExamineResponse; headers ",
      headers.length, ", ", headers);
  }),
};

/**
 * Executed automatically by the framework.
 */
function setupParentProcess(event) {
  return ServerLoggerMonitor.attach(event);
}

// Monitor initialization.
ServerLoggerMonitor.initialize();

// Exports from this module
exports.setupParentProcess = setupParentProcess;