summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/netmonitor/test/browser_net_copy_as_curl.js')
-rw-r--r--devtools/client/netmonitor/test/browser_net_copy_as_curl.js242
1 files changed, 242 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/test/browser_net_copy_as_curl.js b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
new file mode 100644
index 0000000000..4cce679fe9
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
@@ -0,0 +1,242 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if Copy as cURL works.
+ */
+
+const POST_PAYLOAD = "Plaintext value as a payload";
+
+add_task(async function () {
+ const { tab, monitor } = await initNetMonitor(HTTPS_CURL_URL, {
+ requestCount: 1,
+ });
+ info("Starting test... ");
+
+ // Different quote chars are used for Windows and POSIX
+ const QUOTE_WIN = '"';
+ const QUOTE_POSIX = "'";
+
+ const isWin = Services.appinfo.OS === "WINNT";
+ const testData = isWin
+ ? [
+ {
+ menuItemId: "request-list-context-copy-as-curl-win",
+ data: buildTestData(QUOTE_WIN),
+ },
+ {
+ menuItemId: "request-list-context-copy-as-curl-posix",
+ data: buildTestData(QUOTE_POSIX),
+ },
+ ]
+ : [
+ {
+ menuItemId: "request-list-context-copy-as-curl",
+ data: buildTestData(QUOTE_POSIX),
+ },
+ ];
+
+ await testForPlatform(tab, monitor, testData);
+
+ await teardown(monitor);
+});
+
+function buildTestData(QUOTE) {
+ // Quote a string, escape the quotes inside the string
+ function quote(str) {
+ return QUOTE + str.replace(new RegExp(QUOTE, "g"), `\\${QUOTE}`) + QUOTE;
+ }
+
+ // Header param is formatted as -H "Header: value" or -H 'Header: value'
+ function header(h) {
+ return "-H " + quote(h);
+ }
+
+ // Construct the expected command
+ const SIMPLE_BASE = ["curl " + quote(HTTPS_SIMPLE_SJS)];
+ const SLOW_BASE = ["curl " + quote(HTTPS_SLOW_SJS)];
+ const BASE_RESULT = [
+ "--compressed",
+ header("User-Agent: " + navigator.userAgent),
+ header("Accept: */*"),
+ header("Accept-Language: " + navigator.language),
+ header("X-Custom-Header-1: Custom value"),
+ header("X-Custom-Header-2: 8.8.8.8"),
+ header("X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT"),
+ header("Referer: " + HTTPS_CURL_URL),
+ header("Connection: keep-alive"),
+ header("Pragma: no-cache"),
+ header("Cache-Control: no-cache"),
+ header("Sec-Fetch-Dest: empty"),
+ header("Sec-Fetch-Mode: cors"),
+ header("Sec-Fetch-Site: same-origin"),
+ ];
+
+ const COOKIE_PARTIAL_RESULT = [header("Cookie: bob=true; tom=cool")];
+
+ const POST_PARTIAL_RESULT = [
+ "-X",
+ "POST",
+ "--data-raw " + quote(POST_PAYLOAD),
+ header("Content-Type: text/plain;charset=UTF-8"),
+ ];
+ const ORIGIN_RESULT = [header("Origin: https://example.com")];
+
+ const HEAD_PARTIAL_RESULT = ["-I"];
+
+ return {
+ SIMPLE_BASE,
+ SLOW_BASE,
+ BASE_RESULT,
+ COOKIE_PARTIAL_RESULT,
+ POST_PAYLOAD,
+ POST_PARTIAL_RESULT,
+ ORIGIN_RESULT,
+ HEAD_PARTIAL_RESULT,
+ };
+}
+
+async function testForPlatform(tab, monitor, testData) {
+ // GET request, no cookies (first request)
+ await performRequest("GET");
+ for (const test of testData) {
+ await testClipboardContent(test.menuItemId, [
+ ...test.data.SIMPLE_BASE,
+ ...test.data.BASE_RESULT,
+ ]);
+ }
+ // Check to make sure it is still OK after we view the response (bug#1452442)
+ await selectIndexAndWaitForSourceEditor(monitor, 0);
+ for (const test of testData) {
+ await testClipboardContent(test.menuItemId, [
+ ...test.data.SIMPLE_BASE,
+ ...test.data.BASE_RESULT,
+ ]);
+ }
+
+ // GET request, cookies set by previous response
+ await performRequest("GET");
+ for (const test of testData) {
+ await testClipboardContent(test.menuItemId, [
+ ...test.data.SIMPLE_BASE,
+ ...test.data.BASE_RESULT,
+ ...test.data.COOKIE_PARTIAL_RESULT,
+ ]);
+ }
+
+ // Unfinished request (bug#1378464, bug#1420513)
+ const waitSlow = waitForNetworkEvents(monitor, 0);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [HTTPS_SLOW_SJS],
+ async function (url) {
+ content.wrappedJSObject.performRequest(url, "GET", null);
+ }
+ );
+ await waitSlow;
+ for (const test of testData) {
+ await testClipboardContent(test.menuItemId, [
+ ...test.data.SLOW_BASE,
+ ...test.data.BASE_RESULT,
+ ...test.data.COOKIE_PARTIAL_RESULT,
+ ]);
+ }
+
+ // POST request
+ await performRequest("POST", POST_PAYLOAD);
+ for (const test of testData) {
+ await testClipboardContent(test.menuItemId, [
+ ...test.data.SIMPLE_BASE,
+ ...test.data.BASE_RESULT,
+ ...test.data.COOKIE_PARTIAL_RESULT,
+ ...test.data.POST_PARTIAL_RESULT,
+ ...test.data.ORIGIN_RESULT,
+ ]);
+ }
+
+ // HEAD request
+ await performRequest("HEAD");
+ for (const test of testData) {
+ await testClipboardContent(test.menuItemId, [
+ ...test.data.SIMPLE_BASE,
+ ...test.data.BASE_RESULT,
+ ...test.data.COOKIE_PARTIAL_RESULT,
+ ...test.data.HEAD_PARTIAL_RESULT,
+ ]);
+ }
+
+ async function performRequest(method, payload) {
+ const waitRequest = waitForNetworkEvents(monitor, 1);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [
+ {
+ url: HTTPS_SIMPLE_SJS,
+ method_: method,
+ payload_: payload,
+ },
+ ],
+ async function ({ url, method_, payload_ }) {
+ content.wrappedJSObject.performRequest(url, method_, payload_);
+ }
+ );
+ await waitRequest;
+ }
+
+ async function testClipboardContent(menuItemId, expectedResult) {
+ const { document } = monitor.panelWin;
+
+ const items = document.querySelectorAll(".request-list-item");
+ const itemIndex = items.length - 1;
+ EventUtils.sendMouseEvent({ type: "mousedown" }, items[itemIndex]);
+ EventUtils.sendMouseEvent(
+ { type: "contextmenu" },
+ document.querySelectorAll(".request-list-item")[0]
+ );
+
+ /* Ensure that the copy as cURL option is always visible */
+ is(
+ !!getContextMenuItem(monitor, menuItemId),
+ true,
+ `The "Copy as cURL" context menu item "${menuItemId}" should not be hidden.`
+ );
+
+ await waitForClipboardPromise(
+ async function setup() {
+ await selectContextMenuItem(monitor, menuItemId);
+ },
+ function validate(result) {
+ if (typeof result !== "string") {
+ return false;
+ }
+
+ // Different setups may produce the same command, but with the
+ // parameters in a different order in the commandline (which is fine).
+ // Here we confirm that the commands are the same even in that case.
+
+ // This monster regexp parses the command line into an array of arguments,
+ // recognizing quoted args with matching quotes and escaped quotes inside:
+ // [ "curl 'url'", "--standalone-arg", "-arg-with-quoted-string 'value\'s'" ]
+ const matchRe = /[-A-Za-z1-9]+(?: ([\"'])(?:\\\1|.)*?\1)?/g;
+
+ const actual = result.match(matchRe);
+ // Must begin with the same "curl 'URL'" segment
+ if (!actual || expectedResult[0] != actual[0]) {
+ return false;
+ }
+
+ // Must match each of the params in the middle (headers)
+ return (
+ expectedResult.length === actual.length &&
+ expectedResult.some(param => actual.includes(param))
+ );
+ }
+ );
+
+ info(
+ `Clipboard contains a cURL command for item ${itemIndex} by "${menuItemId}"`
+ );
+ }
+}