/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */

/* eslint-env node */

"use strict";

class ServerCode {
  static async startServer(port) {
    global.http = require("http");
    global.server = global.http.createServer((req, res) => {
      res.setHeader("Content-Type", "text/plain");
      res.setHeader("Content-Length", "12");
      res.setHeader("Transfer-Encoding", "chunked");
      res.setHeader("Trailer", "Server-Timing");
      res.setHeader(
        "Server-Timing",
        "metric; dur=123.4; desc=description, metric2; dur=456.78; desc=description1"
      );
      res.write("data reached");
      res.addTrailers({
        "Server-Timing":
          "metric3; dur=789.11; desc=description2, metric4; dur=1112.13; desc=description3",
      });
      res.end();
    });

    let serverPort = await new Promise(resolve => {
      global.server.listen(0, "0.0.0.0", 2000, () => {
        resolve(global.server.address().port);
      });
    });

    if (process.env.MOZ_ANDROID_DATA_DIR) {
      // When creating a server on Android we must make sure that the port
      // is forwarded from the host machine to the emulator.
      let adb_path = "adb";
      if (process.env.MOZ_FETCHES_DIR) {
        adb_path = `${process.env.MOZ_FETCHES_DIR}/android-sdk-linux/platform-tools/adb`;
      }

      await new Promise(resolve => {
        const { exec } = require("child_process");
        exec(
          `${adb_path} reverse tcp:${serverPort} tcp:${serverPort}`,
          (error, stdout, stderr) => {
            if (error) {
              console.log(`error: ${error.message}`);
              return;
            }
            if (stderr) {
              console.log(`stderr: ${stderr}`);
            }
            // console.log(`stdout: ${stdout}`);
            resolve();
          }
        );
      });
    }

    return serverPort;
  }
}

const responseServerTiming = [
  { metric: "metric", duration: "123.4", description: "description" },
  { metric: "metric2", duration: "456.78", description: "description1" },
];
const trailerServerTiming = [
  { metric: "metric3", duration: "789.11", description: "description2" },
  { metric: "metric4", duration: "1112.13", description: "description3" },
];

let port;

add_task(async function setup() {
  let processId = await NodeServer.fork();
  registerCleanupFunction(async () => {
    await NodeServer.kill(processId);
  });
  await NodeServer.execute(processId, ServerCode);
  port = await NodeServer.execute(processId, `ServerCode.startServer(0)`);
  ok(port);
});

// Test that secure origins can use server-timing, even with plain http
add_task(async function test_localhost_origin() {
  let chan = NetUtil.newChannel({
    uri: `http://localhost:${port}/`,
    loadUsingSystemPrincipal: true,
  });
  await new Promise(resolve => {
    chan.asyncOpen(
      new ChannelListener((request, buffer) => {
        let channel = request.QueryInterface(Ci.nsITimedChannel);
        let headers = channel.serverTiming.QueryInterface(Ci.nsIArray);
        ok(headers.length);

        let expectedResult = responseServerTiming.concat(trailerServerTiming);
        Assert.equal(headers.length, expectedResult.length);

        for (let i = 0; i < expectedResult.length; i++) {
          let header = headers.queryElementAt(i, Ci.nsIServerTiming);
          Assert.equal(header.name, expectedResult[i].metric);
          Assert.equal(header.description, expectedResult[i].description);
          Assert.equal(header.duration, parseFloat(expectedResult[i].duration));
        }
        resolve();
      }, null)
    );
  });
});

// Test that insecure origins can't use server timing.
add_task(async function test_http_non_localhost() {
  Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
  registerCleanupFunction(async () => {
    Services.prefs.clearUserPref("network.dns.native-is-localhost");
  });

  let chan = NetUtil.newChannel({
    uri: `http://example.org:${port}/`,
    loadUsingSystemPrincipal: true,
  });
  await new Promise(resolve => {
    chan.asyncOpen(
      new ChannelListener((request, buffer) => {
        let channel = request.QueryInterface(Ci.nsITimedChannel);
        let headers = channel.serverTiming.QueryInterface(Ci.nsIArray);
        Assert.equal(headers.length, 0);
        resolve();
      }, null)
    );
  });
});
