"use strict";

// Tests that unhandled promise rejections generate the appropriate
// console messages.

const { AddonTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/AddonTestUtils.sys.mjs"
);
const { PromiseTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/PromiseTestUtils.sys.mjs"
);

PromiseTestUtils.expectUncaughtRejection(/could not be cloned/);
PromiseTestUtils.expectUncaughtRejection(/An exception was thrown/);
PromiseTestUtils.expectUncaughtRejection(/Bleah/);

const filename = "resource://foo/Bar.jsm";

async function getSandboxMessages(sandbox, code) {
  let { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
    Cu.evalInSandbox(code, sandbox, null, filename, 1);

    // We need two trips through the event loop for this error to be reported.
    await new Promise(executeSoon);
    await new Promise(executeSoon);
  });

  // xpcshell tests on OS-X sometimes include an extra warning, which we
  // unfortunately need to ignore:
  return messages.filter(
    msg =>
      !msg.message.includes(
        "No chrome package registered for chrome://branding/locale/brand.properties"
      )
  );
}

add_task(async function test_unhandled_dom_exception() {
  let sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
  sandbox.StructuredCloneHolder = StructuredCloneHolder;

  let messages = await getSandboxMessages(
    sandbox,
    `new Promise(() => {
      new StructuredCloneHolder("", "", () => {});
    });`
  );

  equal(messages.length, 1, "Got one console message");

  let [msg] = messages;
  ok(msg instanceof Ci.nsIScriptError, "Message is a script error");
  equal(msg.sourceName, filename, "Got expected filename");
  equal(msg.lineNumber, 2, "Got expected line number");
  equal(
    msg.errorMessage,
    "DataCloneError: Function object could not be cloned.",
    "Got expected error message"
  );
});

add_task(async function test_unhandled_dom_exception_wrapped() {
  let sandbox = Cu.Sandbox(
    Services.scriptSecurityManager.createContentPrincipalFromOrigin(
      "http://example.com/"
    )
  );
  Cu.exportFunction(
    function frick() {
      throw new Components.Exception(
        "Bleah.",
        Cr.NS_ERROR_FAILURE,
        Components.stack.caller
      );
    },
    sandbox,
    { defineAs: "frick" }
  );

  let messages = await getSandboxMessages(
    sandbox,
    `new Promise(() => {
      frick();
    });`
  );

  equal(messages.length, 2, "Got two console messages");

  let [msg1, msg2] = messages;
  ok(msg1 instanceof Ci.nsIScriptError, "Message is a script error");
  equal(msg1.sourceName, filename, "Got expected filename");
  equal(msg1.lineNumber, 2, "Got expected line number");
  equal(
    msg1.errorMessage,
    "NS_ERROR_FAILURE: Bleah.",
    "Got expected error message"
  );

  ok(msg2 instanceof Ci.nsIScriptError, "Message is a script error");
  equal(msg2.sourceName, filename, "Got expected filename");
  equal(msg2.lineNumber, 2, "Got expected line number");
  equal(
    msg2.errorMessage,
    "InvalidStateError: An exception was thrown",
    "Got expected error message"
  );
});

add_task(async function test_unhandled_dom_exception_from_sandbox() {
  let sandbox = Cu.Sandbox(
    Services.scriptSecurityManager.createContentPrincipalFromOrigin(
      "http://example.com/"
    ),
    { wantGlobalProperties: ["DOMException"] }
  );
  let ctor = Cu.evalInSandbox("DOMException", sandbox);
  Cu.exportFunction(
    function frick() {
      throw new ctor("Bleah.");
    },
    sandbox,
    { defineAs: "frick" }
  );

  let messages = await getSandboxMessages(
    sandbox,
    `new Promise(() => {
      frick();
    });`
  );

  equal(messages.length, 1, "Got one console messages");

  let [msg] = messages;
  ok(msg instanceof Ci.nsIScriptError, "Message is a script error");
  equal(msg.sourceName, filename, "Got expected filename");
  equal(msg.lineNumber, 2, "Got expected line number");
  equal(msg.errorMessage, "Error: Bleah.", "Got expected error message");
});
