criptSecurityManager.getSystemPrincipal());

    return await this.#openWindow(aTaskbarTab, args);
  }

  /**
   * Handles common window opening behavior for Taskbar Tabs.
   *
   * @param {TaskbarTab} aTaskbarTab - The Taskbar Tab associated to the window.
   * @param {nsIMutableArray} aArgs - `args` to pass to the opening window.
   * @returns {Promise<DOMWindow>} Resolves once window has opened and tab count
   * has been incremented.
   */
  async #openWindow(aTaskbarTab, aArgs) {
    let url = Services.io.newURI(aTaskbarTab.startUrl);
    let imgPromise = lazy.TaskbarTabsUtils.getFavicon(url);

    let win = await lazy.BrowserWindowTracker.promiseOpenWindow({
      args: aArgs,
      features: kTaskbarTabsWindowFeatures,
      all: false,
    });

    imgPromise.then(imgContainer =>
      lazy.WindowsUIUtils.setWindowIcon(win, imgContainer, imgContainer)
    );

    this.#trackWindow(aTaskbarTab.id, win);

    lazy.WinTaskbar.setGroupIdForWindow(win, aTaskbarTab.id);

    this.#attachWindowFocusTelemetry(win);
    Glean.webApp.activate.record({});

    win.focus();

    win.gBrowser.tabs.forEach(tab => {
      const browser = win.gBrowser.getBrowserForTab(tab);
      browser.browsingContext.displayMode = "minimal-ui";
    });

    return win;
  }

  /**
   * Adds the window to the set of windows open within the taskbar tab.
   * The window will automatically be removed when the window closes if
   * it hasn't been untracked already.
   *
   * @param {string} aId Taskbar Tab ID that the window should be assigned to.
   * @param {DOMWindow} aWindow Window to track.
   */
  #trackWindow(aId, aWindow) {
    let openWindows = this.#openWindows.get(aId);
    if (typeof openWindows === "undefined") {
      openWindows = new Set();
      this.#openWindows.set(aId, openWindows);
    }

    openWindows.add(getWindowId(aWindow));
    aWindow.addEventListener("unload", _e => this.#untrackWindow(aId, aWindow));
  }

  /**
   * Remove the window from the set of windows open within the taskbar tab.
   * This function is idempotent.
   *
   * @param {string} aId Taskbar Tab ID that the window should be assigned to.
   * @param {DOMWindow} aWindow Window to track.
   */
  #untrackWindow(aId, aWindow) {
    let openWindows = this.#openWindows.get(aId);
    if (typeof openWindows === "undefined") {
      // If it is undefined, the window wasn't being tracked anyways.
      return;
    }

    openWindows.delete(getWindowId(aWindow));
    if (openWindows.size === 0) {
      // Avoid leaking entries in the map.
      this.#openWindows.delete(aId);
    }
  }

  #attachWindowFocusTelemetry(aWindow) {
    let timerId = null;

    function focused() {
      if (timerId == null) {
        timerId = Glean.webApp.usageTime.start();
      }
    }

    function blur() {
      if (timerId != null) {
        Glean.webApp.usageTime.stopAndAccumulate(timerId);
      }

      timerId = null;
    }

    aWindow.addEventListener("focus", focused);
    aWindow.addEventListener("blur", blur);
    aWindow.addEventListener("unload", blur);

    // The window might already have been focused, and at any rate it'll get
    // focused. Forcefully trigger focused now to account for that.
    focused();
  }

  /**
   * Reverts a web app to a tab in a regular Firefox window. We will try to use
   * the window the taskbar tab originated from, if that's not avaliable, we
   * will use the most recently active window. If no window is avalaible, a new
   * one will be opened.
   *
   * @param {DOMWindow} aWindow - A Taskbar Tab window.
   */
  async ejectWindow(aWindow) {
    lazy.logConsole.info("Ejecting window from Taskbar Tabs.");

    let taskbarTabId = lazy.TaskbarTabsUtils.getTaskbarTabIdFromWindow(aWindow);
    if (!taskbarTabId) {
      throw new Error("No Taskbar Tab ID found on window.");
    } else {
      lazy.logConsole.debug(`Taskbar Tab ID is ${taskbarTabId}`);
    }

    let windowList = lazy.BrowserWindowTracker.getOrderedWindows({
      private: false,
    });

    Glean.webApp.eject.record({});

    // A Taskbar Tab should only contain one tab, but iterate over the browser's
    // tabs just in case one snuck in.
    for (const tab of aWindow.gBrowser.tabs) {
      let tabId = getTabId(tab);
      let originWindowId = this.#tabOriginMap.get(tabId);

      let win =
        // Find the originating window for the Taskbar Tab if it still exists.
        windowList.find(window => {
          let windowId = getWindowId(window);
          let matching = windowId === originWindowId;
          if (matching) {
            lazy.logConsole.debug(
              `Ejecting into originating window: ${windowId}`
            );
          }
          return matching;
        });

      if (!win) {
        // Otherwise the most recent non-Taskbar Tabs window interacted with.
        win = lazy.BrowserWindowTracker.getTopWindow({
          private: false,
        });

        if (win) {
          lazy.logConsole.debug(`Ejecting into top window.`);
        }
      }

      let newTab;
      if (win) {
        // Set this tab to the last tab position of the window.
        newTab = win.gBrowser.adoptTab(tab, {
          tabIndex: win.gBrowser.openTabs.length,
          selectTab: true,
        });
      } else {
        lazy.logConsole.debug(
          "No originating or existing browser window found, ejecting into newly created window."
        );
        win = await lazy.BrowserWindowTracker.promiseOpenWindow({ args: tab });
        newTab = win.gBrowser.tabs[0];
      }

      win.focus();

      let browser = win.gBrowser.getBrowserForTab(newTab);
      browser.browsingContext.displayMode = "browser";

      this.#tabOriginMap.delete(tabId);
    }

    this.#untrackWindow(taskbarTabId, aWindow);
  }

  /**
   * Returns a count of the current windows associated to a Taskbar Tab.
   *
   * @param {string} aId - The Taskbar Tab ID.
   * @returns {integer} Count of windows associated to the Taskbar Tab ID.
   */
  getCountForId(aId) {
    return this.#openWindows.get(aId)?.size ?? 0;
  }

  /**
   * Utility function to mock `nsIWindowsUIUtils`.
   *
   * @param {nsIWindowsUIUtils} mock - A mock of nsIWindowsUIUtils.
   */
  testOnlyMockUIUtils(mock) {
    if (!Cu.isInAutomation) {
      throw new Error("Can only mock utils in automation.");
    }
    // eslint-disable-next-line mozilla/valid-lazy
    Object.defineProperty(lazy, "WindowsUIUtils", {
      get() {
        if (mock) {
          return mock;
        }
        return Cc["@mozilla.org/windows-ui-utils;1"].getService(
          Ci.nsIWindowsUIUtils
        );
      },
    });
  }
}

/**
 * Retrieves the browser tab's ID.
 *
 * @param {MozTabbrowserTab} aTab - Tab to retrieve the ID from.
 * @returns {object} The permanent key identifying the tab.
 */
function getTabId(aTab) {
  return aTab.permanentKey;
}

/**
 * Retrieves the window ID.
 *
 * @param {DOMWindow} aWindow
 * @returns {string} A unique string identifying the window.
 */
function getWindowId(aWindow) {
  return aWindow.docShell.outerWindowID;
}
PK