/* 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";

this.EXPORTED_SYMBOLS = ["PrivacyFilter"];

const Cu = Components.utils;

Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
  "resource:///modules/sessionstore/PrivacyLevel.jsm");

/**
 * A module that provides methods to filter various kinds of data collected
 * from a tab by the current privacy level as set by the user.
 */
this.PrivacyFilter = Object.freeze({
  /**
   * Filters the given (serialized) session storage |data| according to the
   * current privacy level and returns a new object containing only data that
   * we're allowed to store.
   *
   * @param data The session storage data as collected from a tab.
   * @return object
   */
  filterSessionStorageData: function (data) {
    let retval = {};

    for (let host of Object.keys(data)) {
      if (PrivacyLevel.check(host)) {
        retval[host] = data[host];
      }
    }

    return Object.keys(retval).length ? retval : null;
  },

  /**
   * Filters the given (serialized) form |data| according to the current
   * privacy level and returns a new object containing only data that we're
   * allowed to store.
   *
   * @param data The form data as collected from a tab.
   * @return object
   */
  filterFormData: function (data) {
    // If the given form data object has an associated URL that we are not
    // allowed to store data for, bail out. We explicitly discard data for any
    // children as well even if storing data for those frames would be allowed.
    if (data.url && !PrivacyLevel.check(data.url)) {
      return;
    }

    let retval = {};

    for (let key of Object.keys(data)) {
      if (key === "children") {
        let recurse = child => this.filterFormData(child);
        let children = data.children.map(recurse).filter(child => child);

        if (children.length) {
          retval.children = children;
        }
      // Only copy keys other than "children" if we have a valid URL in
      // data.url and we thus passed the privacy level check.
      } else if (data.url) {
        retval[key] = data[key];
      }
    }

    return Object.keys(retval).length ? retval : null;
  },

  /**
   * Removes any private windows and tabs from a given browser state object.
   *
   * @param browserState (object)
   *        The browser state for which we remove any private windows and tabs.
   *        The given object will be modified.
   */
  filterPrivateWindowsAndTabs: function (browserState) {
    // Remove private opened windows.
    for (let i = browserState.windows.length - 1; i >= 0; i--) {
      let win = browserState.windows[i];

      if (win.isPrivate) {
        browserState.windows.splice(i, 1);

        if (browserState.selectedWindow >= i) {
          browserState.selectedWindow--;
        }
      } else {
        // Remove private tabs from all open non-private windows.
        this.filterPrivateTabs(win);
      }
    }

    // Remove private closed windows.
    browserState._closedWindows =
      browserState._closedWindows.filter(win => !win.isPrivate);

    // Remove private tabs from all remaining closed windows.
    browserState._closedWindows.forEach(win => this.filterPrivateTabs(win));
  },

  /**
   * Removes open private tabs from a given window state object.
   *
   * @param winState (object)
   *        The window state for which we remove any private tabs.
   *        The given object will be modified.
   */
  filterPrivateTabs: function (winState) {
    // Remove open private tabs.
    for (let i = winState.tabs.length - 1; i >= 0 ; i--) {
      let tab = winState.tabs[i];

      if (tab.isPrivate) {
        winState.tabs.splice(i, 1);

        if (winState.selected >= i) {
          winState.selected--;
        }
      }
    }

    // Note that closed private tabs are only stored for private windows.
    // There is no need to call this function for private windows as the
    // whole window state should just be discarded so we explicitly don't
    // try to remove closed private tabs as an optimization.
  }
});
