summaryrefslogtreecommitdiffstats
path: root/devtools/shared/commands/resource/tests/browser_resources_network_events_parent_process.js
blob: c5b3e436db0f46039bee8b21fa14c00152151a50 (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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/**
 * !! AFTER MOVING OR RENAMING THIS METHOD, UPDATE `EXPECTED` CONSTANTS BELOW !!
 */
const createParentProcessRequests = async () => {
  info("Do some requests from the parent process");
  // The line:column for `fetch` should be EXPECTED_REQUEST_LINE_1/COL_1
  await fetch(FETCH_URI);

  const img = new Image();
  const onLoad = new Promise(r => img.addEventListener("load", r));
  // The line:column for `img` below should be EXPECTED_REQUEST_LINE_2/COL_2
  img.src = IMAGE_URI;
  await onLoad;
};

const EXPECTED_METHOD_NAME = "createParentProcessRequests";
const EXPECTED_REQUEST_LINE_1 = 12;
const EXPECTED_REQUEST_COL_1 = 9;
const EXPECTED_REQUEST_LINE_2 = 17;
const EXPECTED_REQUEST_COL_2 = 3;

// Test the ResourceCommand API around NETWORK_EVENT for the parent process

const FETCH_URI = "https://example.com/document-builder.sjs?html=foo";
// The img.src request gets cached regardless of `devtools.cache.disabled`.
// Add a random parameter to the request to bypass the cache.
const uuid = `${Date.now()}-${Math.random()}`;
const IMAGE_URI = URL_ROOT_SSL + "test_image.png?" + uuid;

add_task(async function testParentProcessRequests() {
  // The test expects the main process commands instance to receive resources
  // for content process requests.
  await pushPref("devtools.browsertoolbox.scope", "everything");

  const commands = await CommandsFactory.forMainProcess();
  await commands.targetCommand.startListening();
  const { resourceCommand } = commands;

  const receivedNetworkEvents = [];
  const receivedStacktraces = [];
  const onAvailable = resources => {
    for (const resource of resources) {
      if (resource.resourceType == resourceCommand.TYPES.NETWORK_EVENT) {
        receivedNetworkEvents.push(resource);
      } else if (
        resource.resourceType == resourceCommand.TYPES.NETWORK_EVENT_STACKTRACE
      ) {
        receivedStacktraces.push(resource);
      }
    }
  };
  const onUpdated = updates => {
    for (const { resource } of updates) {
      is(
        resource.resourceType,
        resourceCommand.TYPES.NETWORK_EVENT,
        "Received a network update event resource"
      );
    }
  };

  await resourceCommand.watchResources(
    [
      resourceCommand.TYPES.NETWORK_EVENT,
      resourceCommand.TYPES.NETWORK_EVENT_STACKTRACE,
    ],
    {
      ignoreExistingResources: true,
      onAvailable,
      onUpdated,
    }
  );

  await createParentProcessRequests();

  const img2 = new Image();
  img2.src = IMAGE_URI;

  info("Wait for the network events");
  await waitFor(() => receivedNetworkEvents.length == 3);
  info("Wait for the network events stack traces");
  // Note that we aren't getting any stacktrace for the second cached request
  await waitFor(() => receivedStacktraces.length == 2);

  info("Assert the fetch request");
  const fetchRequest = receivedNetworkEvents[0];
  is(
    fetchRequest.url,
    FETCH_URI,
    "The first resource is for the fetch request"
  );
  ok(fetchRequest.chromeContext, "The fetch request is privileged");

  const fetchStacktrace = receivedStacktraces[0].lastFrame;
  is(receivedStacktraces[0].resourceId, fetchRequest.stacktraceResourceId);
  is(fetchStacktrace.filename, gTestPath);
  is(fetchStacktrace.lineNumber, EXPECTED_REQUEST_LINE_1);
  is(fetchStacktrace.columnNumber, EXPECTED_REQUEST_COL_1);
  is(fetchStacktrace.functionName, EXPECTED_METHOD_NAME);
  is(fetchStacktrace.asyncCause, null);

  async function getResponseContent(networkEvent) {
    const packet = {
      to: networkEvent.actor,
      type: "getResponseContent",
    };
    const response = await commands.client.request(packet);
    return response.content.text;
  }

  const fetchContent = await getResponseContent(fetchRequest);
  is(fetchContent, "foo");

  info("Assert the first image request");
  const firstImageRequest = receivedNetworkEvents[1];
  is(
    firstImageRequest.url,
    IMAGE_URI,
    "The second resource is for the first image request"
  );
  ok(!firstImageRequest.fromCache, "The first image request isn't cached");
  ok(firstImageRequest.chromeContext, "The first image request is privileged");

  const firstImageStacktrace = receivedStacktraces[1].lastFrame;
  is(receivedStacktraces[1].resourceId, firstImageRequest.stacktraceResourceId);
  is(firstImageStacktrace.filename, gTestPath);
  is(firstImageStacktrace.lineNumber, EXPECTED_REQUEST_LINE_2);
  is(firstImageStacktrace.columnNumber, EXPECTED_REQUEST_COL_2);
  is(firstImageStacktrace.functionName, EXPECTED_METHOD_NAME);
  is(firstImageStacktrace.asyncCause, null);

  info("Assert the second image request");
  const secondImageRequest = receivedNetworkEvents[2];
  is(
    secondImageRequest.url,
    IMAGE_URI,
    "The third resource is for the second image request"
  );
  ok(secondImageRequest.fromCache, "The second image request is cached");
  ok(
    secondImageRequest.chromeContext,
    "The second image request is privileged"
  );

  info(
    "Open a content page to ensure we also receive request from content processes"
  );
  const pageUrl = "https://example.org/document-builder.sjs?html=foo";
  const requestUrl = "https://example.org/document-builder.sjs?html=bar";
  const tab = await addTab(pageUrl);

  await waitFor(() => receivedNetworkEvents.length == 4);
  const tabRequest = receivedNetworkEvents[3];
  is(tabRequest.url, pageUrl, "The 4th resource is for the tab request");
  ok(!tabRequest.chromeContext, "The 4th request is content");

  info(
    "Also spawn a privileged request from the content process, not bound to any WindowGlobal"
  );
  await SpecialPowers.spawn(
    tab.linkedBrowser,
    [requestUrl],
    async function (uri) {
      const { NetUtil } = ChromeUtils.importESModule(
        "resource://gre/modules/NetUtil.sys.mjs"
      );
      const channel = NetUtil.newChannel({
        uri,
        loadUsingSystemPrincipal: true,
      });
      channel.open();
    }
  );
  await removeTab(tab);

  await waitFor(() => receivedNetworkEvents.length == 5);
  const privilegedContentRequest = receivedNetworkEvents[4];
  is(
    privilegedContentRequest.url,
    requestUrl,
    "The 5th resource is for the privileged content process request"
  );
  ok(privilegedContentRequest.chromeContext, "The 5th request is privileged");

  info("Now focus only on parent process resources");
  await pushPref("devtools.browsertoolbox.scope", "parent-process");

  info(
    "Retrigger the two last requests. The tab document request and a privileged request. Both happening in the tab's content process."
  );
  const secondTab = await addTab(pageUrl);
  await SpecialPowers.spawn(
    secondTab.linkedBrowser,
    [requestUrl],
    async function (uri) {
      const { NetUtil } = ChromeUtils.importESModule(
        "resource://gre/modules/NetUtil.sys.mjs"
      );
      const channel = NetUtil.newChannel({
        uri,
        loadUsingSystemPrincipal: true,
      });
      channel.open();
    }
  );

  await waitFor(() => receivedNetworkEvents.length == 6);

  // nsIHttpChannel doesn't expose any attribute allowing to identify
  // privileged requests done in content processes.
  // Thus, preventing us from filtering them out correctly.
  // Ideally, we would need some new attribute to know from which (content) process
  // any channel originates from.
  info(
    "For now, we are still notified about the privileged content process request"
  );
  const secondPrivilegedContentRequest = receivedNetworkEvents[5];
  is(
    secondPrivilegedContentRequest.url,
    requestUrl,
    "The 6th resource is for the second privileged content process request"
  );
  ok(privilegedContentRequest.chromeContext, "The 6th request is privileged");

  // Let some time to receive the tab request if that's not correctly filtered out
  await wait(1000);
  is(
    receivedNetworkEvents.length,
    6,
    "But we don't receive the request for the tab request"
  );

  await removeTab(secondTab);

  await resourceCommand.unwatchResources(
    [resourceCommand.TYPES.NETWORK_EVENT],
    {
      onAvailable,
      onUpdated,
    }
  );

  await commands.destroy();
});