// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

function NotificationCallbacks(browser) {
  this._browser = browser;
}
NotificationCallbacks.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
  getInterface(iid) {
    if (iid.equals(Ci.nsILoadContext)) {
      return this._browser.browsingContext;
    }
    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
  },
};

// This object implements the JS parts of nsIWebNavigation.
export class RemoteWebNavigation {
  constructor(aBrowser) {
    this._browser = aBrowser;
    this._cancelContentJSEpoch = 1;
    this._currentURI = null;
    this._canGoBack = false;
    this._canGoBackIgnoringUserInteraction = false;
    this._canGoForward = false;
    this.referringURI = null;
  }

  swapBrowser(aBrowser) {
    this._browser = aBrowser;
  }

  maybeCancelContentJSExecution(aNavigationType, aOptions = {}) {
    const epoch = this._cancelContentJSEpoch++;
    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
      aNavigationType,
      { ...aOptions, epoch }
    );
    return epoch;
  }

  get canGoBack() {
    if (Services.appinfo.sessionHistoryInParent) {
      const sessionHistory = this._browser.browsingContext.sessionHistory;
      return sessionHistory?.canGoBackFromEntryAtIndex(sessionHistory?.index);
    }
    return this._canGoBack;
  }

  get canGoBackIgnoringUserInteraction() {
    if (Services.appinfo.sessionHistoryInParent) {
      const sessionHistory = this._browser.browsingContext.sessionHistory;
      return sessionHistory?.index > 0;
    }
    return this._canGoBackIgnoringUserInteraction;
  }

  get canGoForward() {
    if (Services.appinfo.sessionHistoryInParent) {
      let sessionHistory = this._browser.browsingContext.sessionHistory;
      return sessionHistory?.index < sessionHistory?.count - 1;
    }
    return this._canGoForward;
  }

  goBack(requireUserInteraction = false) {
    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_BACK
    );
    this._browser.browsingContext.goBack(
      cancelContentJSEpoch,
      requireUserInteraction,
      true
    );
  }
  goForward(requireUserInteraction = false) {
    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_FORWARD
    );
    this._browser.browsingContext.goForward(
      cancelContentJSEpoch,
      requireUserInteraction,
      true
    );
  }
  gotoIndex(aIndex) {
    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_INDEX,
      { index: aIndex }
    );
    this._browser.browsingContext.goToIndex(aIndex, cancelContentJSEpoch, true);
  }

  _speculativeConnect(uri, loadURIOptions) {
    return;

    /* eslint-disable-next-line no-unreachable */
    try {
      // Let's start a network connection before the content process asks.
      // Note that we might have already set up the speculative connection in
      // some cases, especially when the url is from location bar or its popup
      // menu.
      if (uri.schemeIs("http") || uri.schemeIs("https")) {
        let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
          this._browser
        );
        let principal = loadURIOptions.triggeringPrincipal;
        // We usually have a triggeringPrincipal assigned, but in case we
        // don't have one or if it's a SystemPrincipal, let's create it with OA
        // inferred from the current context.
        if (!principal || principal.isSystemPrincipal) {
          let attrs = {
            userContextId: this._browser.getAttribute("usercontextid") || 0,
            privateBrowsingId: isBrowserPrivate ? 1 : 0,
          };
          principal = Services.scriptSecurityManager.createContentPrincipal(
            uri,
            attrs
          );
        }
        Services.io.speculativeConnect(
          uri,
          principal,
          new NotificationCallbacks(this._browser),
          false
        );
      }
    } catch (ex) {
      // Can't setup speculative connection for this uri for some
      // reason, just ignore it.
    }
  }

  loadURI(uri, loadURIOptions) {
    this._speculativeConnect(uri, loadURIOptions);
    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_URL,
      { uri }
    );
    this._browser.browsingContext.loadURI(uri, {
      ...loadURIOptions,
      cancelContentJSEpoch,
    });
  }

  fixupAndLoadURIString(uriString, loadURIOptions) {
    let uri;
    try {
      let fixupFlags = Services.uriFixup.webNavigationFlagsToFixupFlags(
        uriString,
        loadURIOptions.loadFlags
      );
      let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
        this._browser
      );
      if (isBrowserPrivate) {
        fixupFlags |= Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT;
      }

      uri = Services.uriFixup.getFixupURIInfo(
        uriString,
        fixupFlags
      ).preferredURI;
    } catch (ex) {
      // In rare cases `uriFixup` can throw. We ignore this here, but it's
      // likely that the fixupAndLoadURIString call below will still throw,
      // hopefully with more details.
    }
    if (uri) {
      this._speculativeConnect(uri, loadURIOptions);
    }

    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_URL,
      { uri }
    );
    // For now, continue to use fixup here, but note that ideally we should be
    // doing fixup only once and reusing the URI we created above. Addressing
    // this is bug 1815509.
    this._browser.browsingContext.fixupAndLoadURIString(uriString, {
      ...loadURIOptions,
      cancelContentJSEpoch,
    });
  }

  reload(aReloadFlags) {
    this._browser.browsingContext.reload(aReloadFlags);
  }
  stop(aStopFlags) {
    this._browser.browsingContext.stop(aStopFlags);
  }

  get document() {
    return this._browser.contentDocument;
  }

  get currentURI() {
    if (!this._currentURI) {
      this._currentURI = Services.io.newURI("about:blank");
    }
    return this._currentURI;
  }
  set currentURI(aURI) {
    // Bug 1498600 verify usages of systemPrincipal here
    let loadURIOptions = {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    };
    this.loadURI(aURI.spec, loadURIOptions);
  }

  // Bug 1233803 - accessing the sessionHistory of remote browsers should be
  // done in content scripts.
  get sessionHistory() {
    throw new Components.Exception(
      "Not implemented",
      Cr.NS_ERROR_NOT_IMPLEMENTED
    );
  }
  set sessionHistory(aValue) {
    throw new Components.Exception(
      "Not implemented",
      Cr.NS_ERROR_NOT_IMPLEMENTED
    );
  }

  _sendMessage(aMessage, aData) {
    try {
      this._browser.sendMessageToActor(aMessage, aData, "WebNavigation");
    } catch (e) {
      console.error(e);
    }
  }
}
