Child.maxReportedPopups
    );
    const result = [];

    for (let i = 0; i < length; ++i) {
      const popup = state.popups[i];
      const { popupWindowURISpec } = popup;
      result.push({
        popupWindowURISpec,
      });
    }

    return result;
  }

  #getBlockedRedirect() {
    const redirect = this.#getOrCreateDocState().redirect;
    if (!redirect) {
      return null;
    }

    return {
      redirectURISpec: redirect.redirectURISpec,
    };
  }

  #unblockPopup(aMessage) {
    const idx = aMessage.data.index;
    const popup = this.#getOrCreateDocState().popups[idx];

    if (popup?.requestingWindow?.document == popup.requestingDocument) {
      popup.requestingWindow.open(
        popup.popupWindowURISpec,
        popup.popupWindowName,
        popup.popupWindowFeatures
      );
    }
  }

  handleEvent(aEvent) {
    // Ignore events from other docs (e.g. subframes or old navigated docs).
    if (aEvent.target != this.document) {
      return;
    }

    switch (aEvent.type) {
      case "DOMPopupBlocked":
        this.#onPopupBlocked(aEvent);
        break;
      case "DOMRedirectBlocked":
        this.#onRedirectBlocked(aEvent);
        break;
    }
  }

  #onPopupBlocked(aEvent) {
    const state = this.#getOrCreateDocState();
    if (
      state.popups.length >= PopupAndRedirectBlockingChild.maxReportedPopups
    ) {
      return;
    }

    const popup = {
      // If an empty string is passed to window.open, it will open
      // "about:blank".
      popupWindowURISpec: aEvent.popupWindowURI?.spec ?? "about:blank",
      popupWindowFeatures: aEvent.popupWindowFeatures,
      popupWindowName: aEvent.popupWindowName,
      requestingWindow: aEvent.requestingWindow,
      requestingDocument: aEvent.requestingWindow.document,
    };
    state.popups.push(popup);

    this.#updateParentAboutBlockedPopups();
  }

  #onRedirectBlocked(aEvent) {
    const state = this.#getOrCreateDocState();
    // In case of multiple blocked redirects, we only store the first
    // since after redirecting the whole state would be destroyed anyway.
    if (state.redirect) {
      return;
    }

    const redirect = {
      redirectURISpec: aEvent.redirectURI.spec,
      requestingWindow: aEvent.requestingWindow,
      requestingDocument: aEvent.requestingWindow.document,
    };
    state.redirect = redirect;

    this.#updateParentAboutBlockedRedirect();
  }

  #updateParentAboutBlockedPopups() {
    this.sendAsyncMessage("UpdateBlockedPopups", {
      count: this.#getOrCreateDocState().popups.length,
    });
  }

  #updateParentAboutBlockedRedirect() {
    this.sendAsyncMessage("UpdateBlockedRedirect");
  }
}

XPCOMUtils.defineLazyPreferenceGetter(
  PopupAndRedirectBlockingChild,
  "maxReportedPopups",
  "privacy.popups.maxReported"
);
PK