summaryrefslogtreecommitdiffstats
path: root/remote/cdp/test/browser/browser_httpd.js
blob: f3d7b6581fc03f0c228fc5f37274f4f2f9a3c3c3 (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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const { JSONHandler } = ChromeUtils.importESModule(
  "chrome://remote/content/cdp/JSONHandler.sys.mjs"
);

// Get list of supported routes from JSONHandler
const routes = new JSONHandler().routes;

add_task(async function json_version() {
  const { userAgent } = Cc[
    "@mozilla.org/network/protocol;1?name=http"
  ].getService(Ci.nsIHttpProtocolHandler);

  const json = await requestJSON("/json/version");
  is(
    json.Browser,
    `${Services.appinfo.name}/${Services.appinfo.version}`,
    "Browser name and version found"
  );
  is(json["Protocol-Version"], "1.3", "Protocol version found");
  is(json["User-Agent"], userAgent, "User agent found");
  is(json["V8-Version"], "1.0", "V8 version found");
  is(json["WebKit-Version"], "1.0", "Webkit version found");
  is(
    json.webSocketDebuggerUrl,
    RemoteAgent.cdp.targetList.getMainProcessTarget().wsDebuggerURL,
    "Websocket URL for main process target found"
  );
});

add_task(async function check_routes() {
  for (const route in routes) {
    const { parameter, method } = routes[route];
    // Skip routes expecting parameter
    if (parameter) {
      continue;
    }

    // Check request succeeded (200) and responded with valid JSON
    info(`Checking ${route}`);
    await requestJSON(route, { method });

    // Check with trailing slash
    info(`Checking ${route + "/"}`);
    await requestJSON(route + "/", { method });

    // Test routes expecting a certain method
    if (method) {
      const responseText = await requestJSON(route, {
        method: "DELETE",
        status: 405,
        json: false,
      });
      is(
        responseText,
        `Using unsafe HTTP verb DELETE to invoke ${route}. This action supports only ${method} verb.`,
        "/json/new fails with 405 when using GET"
      );
    }
  }
});

add_task(async function json_list({ client }) {
  const { Target } = client;
  const { targetInfos } = await Target.getTargets();

  const json = await requestJSON("/json/list");
  const jsonAlias = await requestJSON("/json");

  Assert.deepEqual(json, jsonAlias, "/json/list and /json return the same");

  ok(Array.isArray(json), "Target list is an array");

  is(
    json.length,
    targetInfos.length,
    "Targets as listed on /json/list are equal to Target.getTargets"
  );

  for (let i = 0; i < json.length; i++) {
    const jsonTarget = json[i];
    const wsTarget = targetInfos[i];

    is(
      jsonTarget.id,
      wsTarget.targetId,
      "Target id matches between HTTP and Target.getTargets"
    );
    is(
      jsonTarget.type,
      wsTarget.type,
      "Target type matches between HTTP and Target.getTargets"
    );
    is(
      jsonTarget.url,
      wsTarget.url,
      "Target url matches between HTTP and Target.getTargets"
    );

    // Ensure expected values specifically for JSON endpoint
    // and that type is always "page" as main process target should not be included
    is(
      jsonTarget.type,
      "page",
      `Target (${jsonTarget.id}) from list has expected type (page)`
    );
    is(
      jsonTarget.webSocketDebuggerUrl,
      `ws://${RemoteAgent.debuggerAddress}/devtools/page/${wsTarget.targetId}`,
      `Target (${jsonTarget.id}) from list has expected webSocketDebuggerUrl value`
    );
  }
});

add_task(async function json_new_target({ client }) {
  const newUrl = "https://example.com";

  let getError = await requestJSON("/json/new?" + newUrl, {
    status: 405,
    json: false,
  });
  is(
    getError,
    "Using unsafe HTTP verb GET to invoke /json/new. This action supports only PUT verb.",
    "/json/new fails with 405 when using GET"
  );

  const newTarget = await requestJSON("/json/new?" + newUrl, { method: "PUT" });

  is(newTarget.type, "page", "Returned target type is 'page'");
  is(newTarget.url, newUrl, "Returned target URL matches");
  ok(!!newTarget.id, "Returned target has id");
  ok(
    !!newTarget.webSocketDebuggerUrl,
    "Returned target has webSocketDebuggerUrl"
  );

  const { Target } = client;
  const targets = await getDiscoveredTargets(Target);
  const foundTarget = targets.find(target => target.targetId === newTarget.id);

  ok(!!foundTarget, "Returned target id was found");
});

add_task(async function json_activate_target({ client, tab }) {
  const { Target, target } = client;

  const currentTargetId = target.id;
  const targets = await getDiscoveredTargets(Target);
  const initialTarget = targets.find(
    target => target.targetId === currentTargetId
  );
  ok(!!initialTarget, "The current target has been found");

  // open some more tabs in the initial window
  await openTab(Target);
  await openTab(Target);

  const lastTabFirstWindow = await openTab(Target);
  is(
    gBrowser.selectedTab,
    lastTabFirstWindow.newTab,
    "Selected tab has changed to a new tab"
  );

  const activateResponse = await requestJSON(
    "/json/activate/" + initialTarget.targetId,
    { json: false }
  );

  is(
    activateResponse,
    "Target activated",
    "Activate endpoint returned expected string"
  );

  is(gBrowser.selectedTab, tab, "Selected tab is the initial tab again");

  const invalidResponse = await requestJSON("/json/activate/does-not-exist", {
    status: 404,
    json: false,
  });

  is(invalidResponse, "No such target id: does-not-exist");
});

add_task(async function json_close_target({ CDP, client }) {
  const { Target } = client;

  const { targetInfo, newTab } = await openTab(Target);

  const targetListBefore = await CDP.List();
  const beforeTarget = targetListBefore.find(
    target => target.id === targetInfo.targetId
  );

  ok(!!beforeTarget, "New target has been found");

  const tabClosed = BrowserTestUtils.waitForEvent(newTab, "TabClose");
  const targetDestroyed = Target.targetDestroyed();

  const activateResponse = await requestJSON(
    "/json/close/" + targetInfo.targetId,
    { json: false }
  );
  is(
    activateResponse,
    "Target is closing",
    "Close endpoint returned expected string"
  );

  await tabClosed;
  info("Tab was closed");

  await targetDestroyed;
  info("Received the Target.targetDestroyed event");

  const targetListAfter = await CDP.List();
  const afterTarget = targetListAfter.find(
    target => target.id === targetInfo.targetId
  );

  Assert.equal(afterTarget, null, "New target is gone");

  const invalidResponse = await requestJSON("/json/close/does-not-exist", {
    status: 404,
    json: false,
  });

  is(invalidResponse, "No such target id: does-not-exist");
});

add_task(async function json_prevent_load_in_iframe({ client }) {
  const { Page } = client;

  const PAGE = `https://example.com/document-builder.sjs?html=${encodeURIComponent(
    '<iframe src="http://localhost:9222/json/version"></iframe>`'
  )}`;

  await Page.enable();

  const NAVIGATED = "Page.frameNavigated";

  const history = new RecordEvents(2);
  history.addRecorder({
    event: Page.frameNavigated,
    eventName: NAVIGATED,
    messageFn: payload => {
      return `Received ${NAVIGATED} for frame id ${payload.frame.id}`;
    },
  });

  await loadURL(PAGE);

  const frameNavigatedEvents = await history.record();

  const frames = frameNavigatedEvents
    .map(({ payload }) => payload.frame)
    .filter(frame => frame.parentId !== undefined);

  const windowGlobal = BrowsingContext.get(frames[0].id).currentWindowGlobal;
  ok(
    windowGlobal.documentURI.spec.startsWith("about:neterror?e=cspBlocked"),
    "Expected page not be loaded within an iframe"
  );
});

async function requestJSON(path, options = {}) {
  const { method = "GET", status = 200, json = true } = options;

  info(`${method} http://${RemoteAgent.debuggerAddress}${path}`);

  const response = await fetch(`http://${RemoteAgent.debuggerAddress}${path}`, {
    method,
  });

  is(response.status, status, `JSON response is ${status}`);

  if (json) {
    return response.json();
  }

  return response.text();
}