const gCompleteState =
  Ci.nsIWebProgressListener.STATE_STOP +
  Ci.nsIWebProgressListener.STATE_IS_NETWORK;

function getOriginalURL(request) {
  return request && request.QueryInterface(Ci.nsIChannel).originalURI.spec;
}

var gFrontProgressListener = {
  onProgressChange(
    aWebProgress,
    aRequest,
    aCurSelfProgress,
    aMaxSelfProgress,
    aCurTotalProgress,
    aMaxTotalProgress
  ) {},

  onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
    var url = getOriginalURL(aRequest);
    if (url == "about:blank") {
      return;
    }
    var state = "onStateChange";
    info(
      "FrontProgress (" + url + "): " + state + " 0x" + aStateFlags.toString(16)
    );
    assertCorrectBrowserAndEventOrderForFront(state);
  },

  onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
    var url = getOriginalURL(aRequest);
    if (url == "about:blank") {
      return;
    }
    var state = "onLocationChange";
    info("FrontProgress: " + state + " " + aLocationURI.spec);
    assertCorrectBrowserAndEventOrderForFront(state);
  },

  onSecurityChange(aWebProgress, aRequest, aState) {
    var url = getOriginalURL(aRequest);
    if (url == "about:blank") {
      return;
    }
    var state = "onSecurityChange";
    info("FrontProgress (" + url + "): " + state + " 0x" + aState.toString(16));
    assertCorrectBrowserAndEventOrderForFront(state);
  },
};

function assertCorrectBrowserAndEventOrderForFront(aEventName) {
  Assert.less(
    gFrontNotificationsPos,
    gFrontNotifications.length,
    "Got an expected notification for the front notifications listener"
  );
  is(
    aEventName,
    gFrontNotifications[gFrontNotificationsPos],
    "Got a notification for the front notifications listener"
  );
  gFrontNotificationsPos++;
}

var gAllProgressListener = {
  onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
    var url = getOriginalURL(aRequest);
    if (url == "about:blank") {
      // ignore initial about blank
      return;
    }
    var state = "onStateChange";
    info(
      "AllProgress (" + url + "): " + state + " 0x" + aStateFlags.toString(16)
    );
    assertCorrectBrowserAndEventOrderForAll(state, aBrowser);
    assertReceivedFlags(
      state,
      gAllNotifications[gAllNotificationsPos],
      aStateFlags
    );
    gAllNotificationsPos++;

    if ((aStateFlags & gCompleteState) == gCompleteState) {
      is(
        gAllNotificationsPos,
        gAllNotifications.length,
        "Saw the expected number of notifications"
      );
      is(
        gFrontNotificationsPos,
        gFrontNotifications.length,
        "Saw the expected number of frontnotifications"
      );
      executeSoon(gNextTest);
    }
  },

  onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
    var url = getOriginalURL(aRequest);
    if (url == "about:blank") {
      // ignore initial about blank
      return;
    }
    var state = "onLocationChange";
    info("AllProgress: " + state + " " + aLocationURI.spec);
    assertCorrectBrowserAndEventOrderForAll(state, aBrowser);
    assertReceivedFlags(
      "onLocationChange",
      gAllNotifications[gAllNotificationsPos],
      aFlags
    );
    gAllNotificationsPos++;
  },

  onSecurityChange(aBrowser, aWebProgress, aRequest, aState) {
    var url = getOriginalURL(aRequest);
    if (url == "about:blank") {
      // ignore initial about blank
      return;
    }
    var state = "onSecurityChange";
    info("AllProgress (" + url + "): " + state + " 0x" + aState.toString(16));
    assertCorrectBrowserAndEventOrderForAll(state, aBrowser);
    is(
      state,
      gAllNotifications[gAllNotificationsPos],
      "Got a notification for the all notifications listener"
    );
    gAllNotificationsPos++;
  },
};

function assertCorrectBrowserAndEventOrderForAll(aState, aBrowser) {
  ok(
    aBrowser == gTestBrowser,
    aState + " notification came from the correct browser"
  );
  Assert.less(
    gAllNotificationsPos,
    gAllNotifications.length,
    "Got an expected notification for the all notifications listener"
  );
}

function assertReceivedFlags(aState, aObjOrEvent, aFlags) {
  if (aObjOrEvent !== null && typeof aObjOrEvent === "object") {
    is(
      aState,
      aObjOrEvent.state,
      "Got a notification for the all notifications listener"
    );
    is(aFlags, aFlags & aObjOrEvent.flags, `Got correct flags for ${aState}`);
  } else {
    is(
      aState,
      aObjOrEvent,
      "Got a notification for the all notifications listener"
    );
  }
}

var gFrontNotifications,
  gAllNotifications,
  gFrontNotificationsPos,
  gAllNotificationsPos;
var gBackgroundTab,
  gForegroundTab,
  gBackgroundBrowser,
  gForegroundBrowser,
  gTestBrowser;
var gTestPage =
  "/browser/browser/base/content/test/general/alltabslistener.html";
const kBasePage =
  "http://mochi.test:8888/browser/browser/base/content/test/general/dummy_page.html";
var gNextTest;

async function test() {
  waitForExplicitFinish();

  gBackgroundTab = BrowserTestUtils.addTab(gBrowser);
  gForegroundTab = BrowserTestUtils.addTab(gBrowser);
  gBackgroundBrowser = gBrowser.getBrowserForTab(gBackgroundTab);
  gForegroundBrowser = gBrowser.getBrowserForTab(gForegroundTab);
  gBrowser.selectedTab = gForegroundTab;

  gAllNotifications = [
    "onStateChange",
    "onLocationChange",
    "onSecurityChange",
    "onStateChange",
  ];

  // We must wait until a page has completed loading before
  // starting tests or we get notifications from that
  let promises = [
    BrowserTestUtils.browserStopped(gBackgroundBrowser, kBasePage),
    BrowserTestUtils.browserStopped(gForegroundBrowser, kBasePage),
  ];
  BrowserTestUtils.loadURIString(gBackgroundBrowser, kBasePage);
  BrowserTestUtils.loadURIString(gForegroundBrowser, kBasePage);
  await Promise.all(promises);
  // If we process switched, the tabbrowser may still be processing the state_stop
  // notification here because of how microtasks work. Ensure that that has
  // happened before starting to test (which would add listeners to the tabbrowser
  // which would get confused by being called about kBasePage loading).
  await new Promise(executeSoon);
  startTest1();
}

function runTest(browser, url, next) {
  gFrontNotificationsPos = 0;
  gAllNotificationsPos = 0;
  gNextTest = next;
  gTestBrowser = browser;
  BrowserTestUtils.loadURIString(browser, url);
}

function startTest1() {
  info("\nTest 1");
  gBrowser.addProgressListener(gFrontProgressListener);
  gBrowser.addTabsProgressListener(gAllProgressListener);

  gFrontNotifications = gAllNotifications;
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest2);
}

function startTest2() {
  info("\nTest 2");
  gFrontNotifications = gAllNotifications;
  runTest(gForegroundBrowser, "https://example.com" + gTestPage, startTest3);
}

function startTest3() {
  info("\nTest 3");
  gFrontNotifications = [];
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest4);
}

function startTest4() {
  info("\nTest 4");
  gFrontNotifications = [];
  runTest(gBackgroundBrowser, "https://example.com" + gTestPage, startTest5);
}

function startTest5() {
  info("\nTest 5");
  // Switch the foreground browser
  [gForegroundBrowser, gBackgroundBrowser] = [
    gBackgroundBrowser,
    gForegroundBrowser,
  ];
  [gForegroundTab, gBackgroundTab] = [gBackgroundTab, gForegroundTab];
  // Avoid the onLocationChange this will fire
  gBrowser.removeProgressListener(gFrontProgressListener);
  gBrowser.selectedTab = gForegroundTab;
  gBrowser.addProgressListener(gFrontProgressListener);

  gFrontNotifications = gAllNotifications;
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest6);
}

function startTest6() {
  info("\nTest 6");
  gFrontNotifications = [];
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest7);
}

// Navigate from remote to non-remote
function startTest7() {
  info("\nTest 7");
  gFrontNotifications = [];
  gAllNotifications = [
    "onStateChange",
    "onLocationChange",
    "onSecurityChange",
    {
      state: "onLocationChange",
      flags: Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT,
    }, // dummy onLocationChange event
    "onStateChange",
  ];
  runTest(gBackgroundBrowser, "about:preferences", startTest8);
}

// Navigate from non-remote to non-remote
function startTest8() {
  info("\nTest 8");
  gFrontNotifications = [];
  gAllNotifications = [
    "onStateChange",
    {
      state: "onStateChange",
      flags:
        Ci.nsIWebProgressListener.STATE_IS_REDIRECTED_DOCUMENT |
        Ci.nsIWebProgressListener.STATE_IS_REQUEST |
        Ci.nsIWebProgressListener.STATE_START,
    },
    "onLocationChange",
    "onSecurityChange",
    "onStateChange",
  ];
  runTest(gBackgroundBrowser, "about:config", startTest9);
}

// Navigate from non-remote to remote
function startTest9() {
  info("\nTest 9");
  gFrontNotifications = [];
  gAllNotifications = [
    "onStateChange",
    "onLocationChange",
    "onSecurityChange",
    "onStateChange",
  ];
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  runTest(gBackgroundBrowser, "http://example.org" + gTestPage, finishTest);
}

function finishTest() {
  gBrowser.removeProgressListener(gFrontProgressListener);
  gBrowser.removeTabsProgressListener(gAllProgressListener);
  gBrowser.removeTab(gBackgroundTab);
  gBrowser.removeTab(gForegroundTab);
  finish();
}
