/* 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/. */

"use strict";

const { Ci, Cu, Cr } = require("chrome");
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
const Services = require("Services");
const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
const { Utils } = require("resource://gre/modules/sessionstore/Utils.jsm");

function readInputStreamToString(stream) {
  return NetUtil.readInputStreamToString(stream, stream.available());
}

/**
 * This object aims to provide the nsIWebNavigation interface for mozbrowser elements.
 * nsIWebNavigation is one of the interfaces expected on <xul:browser>s, so this wrapper
 * helps mozbrowser elements support this.
 *
 * It attempts to use the mozbrowser API wherever possible, however some methods don't
 * exist yet, so we fallback to the WebNavigation frame script messages in those cases.
 * Ideally the mozbrowser API would eventually be extended to cover all properties and
 * methods used here.
 *
 * This is largely copied from RemoteWebNavigation.js, which uses the message manager to
 * perform all actions.
 */
function BrowserElementWebNavigation(browser) {
  this._browser = browser;
}

BrowserElementWebNavigation.prototype = {

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIWebNavigation,
    Ci.nsISupports
  ]),

  get _mm() {
    return this._browser.frameLoader.messageManager;
  },

  canGoBack: false,
  canGoForward: false,

  goBack() {
    this._browser.goBack();
  },

  goForward() {
    this._browser.goForward();
  },

  gotoIndex(index) {
    // No equivalent in the current BrowserElement API
    this._sendMessage("WebNavigation:GotoIndex", { index });
  },

  loadURI(uri, flags, referrer, postData, headers) {
    // No equivalent in the current BrowserElement API
    this.loadURIWithOptions(uri, flags, referrer,
                            Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
                            postData, headers, null, null);
  },

  loadURIWithOptions(uri, flags, referrer, referrerPolicy, postData, headers,
                     baseURI, triggeringPrincipal) {
    // No equivalent in the current BrowserElement API
    this._sendMessage("WebNavigation:LoadURI", {
      uri,
      flags,
      referrer: referrer ? referrer.spec : null,
      referrerPolicy: referrerPolicy,
      postData: postData ? readInputStreamToString(postData) : null,
      headers: headers ? readInputStreamToString(headers) : null,
      baseURI: baseURI ? baseURI.spec : null,
      triggeringPrincipal: triggeringPrincipal
                           ? Utils.serializePrincipal(triggeringPrincipal)
                           : null,
    });
  },

  setOriginAttributesBeforeLoading(originAttributes) {
    // No equivalent in the current BrowserElement API
    this._sendMessage("WebNavigation:SetOriginAttributes", {
      originAttributes,
    });
  },

  reload(flags) {
    let hardReload = false;
    if (flags & this.LOAD_FLAGS_BYPASS_PROXY ||
        flags & this.LOAD_FLAGS_BYPASS_CACHE) {
      hardReload = true;
    }
    this._browser.reload(hardReload);
  },

  stop(flags) {
    // No equivalent in the current BrowserElement API
    this._sendMessage("WebNavigation:Stop", { flags });
  },

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

  _currentURI: null,
  get currentURI() {
    if (!this._currentURI) {
      this._currentURI = Services.io.newURI("about:blank", null, null);
    }
    return this._currentURI;
  },
  set currentURI(uri) {
    this._browser.src = uri.spec;
  },

  referringURI: null,

  // Bug 1233803 - accessing the sessionHistory of remote browsers should be
  // done in content scripts.
  get sessionHistory() {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },
  set sessionHistory(value) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  _sendMessage(message, data) {
    try {
      this._mm.sendAsyncMessage(message, data);
    } catch (e) {
      Cu.reportError(e);
    }
  },

  swapBrowser(browser) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  copyStateFrom(otherWebNavigation) {
    const state = [
      "canGoBack",
      "canGoForward",
      "_currentURI",
    ];
    for (let property of state) {
      this[property] = otherWebNavigation[property];
    }
  },

};

const FLAGS = [
  "LOAD_FLAGS_MASK",
  "LOAD_FLAGS_NONE",
  "LOAD_FLAGS_IS_REFRESH",
  "LOAD_FLAGS_IS_LINK",
  "LOAD_FLAGS_BYPASS_HISTORY",
  "LOAD_FLAGS_REPLACE_HISTORY",
  "LOAD_FLAGS_BYPASS_CACHE",
  "LOAD_FLAGS_BYPASS_PROXY",
  "LOAD_FLAGS_CHARSET_CHANGE",
  "LOAD_FLAGS_STOP_CONTENT",
  "LOAD_FLAGS_FROM_EXTERNAL",
  "LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP",
  "LOAD_FLAGS_FIRST_LOAD",
  "LOAD_FLAGS_ALLOW_POPUPS",
  "LOAD_FLAGS_BYPASS_CLASSIFIER",
  "LOAD_FLAGS_FORCE_ALLOW_COOKIES",
  "STOP_NETWORK",
  "STOP_CONTENT",
  "STOP_ALL",
];

for (let flag of FLAGS) {
  BrowserElementWebNavigation.prototype[flag] = Ci.nsIWebNavigation[flag];
}

exports.BrowserElementWebNavigation = BrowserElementWebNavigation;
