/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";

// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);

const ADDON_ID = "test-devtools-webextension@mozilla.org";
const ADDON_NAME = "test-devtools-webextension";
const ADDON_MANIFEST_PATH = "addons/test-devtools-webextension/manifest.json";

const {
  BrowserToolboxProcess
} = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});

/**
 * This test file ensures that the webextension addon developer toolbox:
 * - when the debug button is clicked on a webextension, the opened toolbox
 *   has a working webconsole with the background page as default target;
 * - the webextension developer toolbox has a working Inspector panel, with the
 *   background page as default target;
 * - the webextension developer toolbox is connected to a fallback page when the
 *   background page is not available (and in the fallback page document body contains
 *   the expected message, which warns the user that the current page is not a real
 *   webextension context);
 * - the webextension developer toolbox has a frame list menu and the noautohide toolbar
 *   toggle button, and they can be used to switch the current target to the extension
 *   popup page.
 */

/**
 * Returns the widget id for an extension with the passed id.
 */
function makeWidgetId(id) {
  id = id.toLowerCase();
  return id.replace(/[^a-z0-9_-]/g, "_");
}

add_task(function* testWebExtensionsToolboxSwitchToPopup() {
  let {
    tab, document, debugBtn,
  } = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);

  let onReadyForOpenPopup = new Promise(done => {
    Services.obs.addObserver(function listener(message, topic) {
      let apiMessage = message.wrappedJSObject;
      if (!apiMessage.originAttributes ||
          apiMessage.originAttributes.addonId != ADDON_ID) {
        return;
      }

      if (apiMessage.arguments[0] == "readyForOpenPopup") {
        Services.obs.removeObserver(listener, "console-api-log-event");
        done();
      }
    }, "console-api-log-event", false);
  });

  // Be careful, this JS function is going to be executed in the addon toolbox,
  // which lives in another process. So do not try to use any scope variable!
  let env = Cc["@mozilla.org/process/environment;1"]
        .getService(Ci.nsIEnvironment);
  let testScript = function () {
    /* eslint-disable no-undef */

    let jsterm;

    toolbox.selectTool("webconsole")
      .then(console => {
        dump(`Clicking the noautohide button\n`);
        toolbox.doc.getElementById("command-button-noautohide").click();
        dump(`Clicked the noautohide button\n`);

        let waitForFrameListUpdate = new Promise((done) => {
          toolbox.target.once("frame-update", () => {
            done(console);
          });
        });

        jsterm = console.hud.jsterm;
        jsterm.execute("myWebExtensionShowPopup()");

        // Wait the initial frame update (which list the background page).
        return waitForFrameListUpdate;
      })
      .then((console) => {
        // Wait the new frame update (once the extension popup has been opened).
        return new Promise((done) => {
          toolbox.target.once("frame-update", done);
        });
      })
      .then(() => {
        dump(`Clicking the frame list button\n`);
        let btn = toolbox.doc.getElementById("command-button-frames");
        let menu = toolbox.showFramesMenu({target: btn});
        dump(`Clicked the frame list button\n`);
        return menu.once("open").then(() => {
          return menu;
        });
      })
      .then(frameMenu => {
        let frames = frameMenu.items;

        if (frames.length != 2) {
          throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
        }

        let popupFrameBtn = frames.filter((frame) => {
          return frame.label.endsWith("popup.html");
        }).pop();

        if (!popupFrameBtn) {
          throw Error("Extension Popup frame not found in the listed frames");
        }

        let waitForNavigated = toolbox.target.once("navigate");

        popupFrameBtn.click();

        return waitForNavigated;
      })
      .then(() => {
        return jsterm.execute("myWebExtensionPopupAddonFunction()");
      })
      .then(() => toolbox.destroy())
      .catch((error) => {
        dump("Error while running code in the browser toolbox process:\n");
        dump(error + "\n");
        dump("stack:\n" + error.stack + "\n");
      });
    /* eslint-enable no-undef */
  };
  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
  registerCleanupFunction(() => {
    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
  });

  // Wait for a notification sent by a script evaluated the test addon via
  // the web console.
  let onPopupCustomMessage = new Promise(done => {
    Services.obs.addObserver(function listener(message, topic) {
      let apiMessage = message.wrappedJSObject;
      if (!apiMessage.originAttributes ||
          apiMessage.originAttributes.addonId != ADDON_ID) {
        return;
      }

      if (apiMessage.arguments[0] == "Popup page function called") {
        Services.obs.removeObserver(listener, "console-api-log-event");
        done(apiMessage.arguments);
      }
    }, "console-api-log-event", false);
  });

  let onToolboxClose = BrowserToolboxProcess.once("close");

  debugBtn.click();

  yield onReadyForOpenPopup;

  let browserActionId = makeWidgetId(ADDON_ID) + "-browser-action";
  let browserActionEl = window.document.getElementById(browserActionId);

  ok(browserActionEl, "Got the browserAction button from the browser UI");
  browserActionEl.click();
  info("Clicked on the browserAction button");

  let args = yield onPopupCustomMessage;
  ok(true, "Received console message from the popup page function as expected");
  is(args[0], "Popup page function called", "Got the expected console message");
  is(args[1] && args[1].name, ADDON_NAME,
     "Got the expected manifest from WebExtension API");

  yield onToolboxClose;

  ok(true, "Addon toolbox closed");

  yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
  yield closeAboutDebugging(tab);
});
