/**
 * Bug 1247843 - A test case for testing whether the channel used to load favicon
 * has correct classFlags.
 * Note that this test is modified based on browser_favicon_userContextId.js.
 */

const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;

const TEST_SITE = "http://example.net";
const TEST_THIRD_PARTY_SITE = "http://mochi.test:8888";

const TEST_PAGE = TEST_SITE + "/browser/browser/components/originattributes/" +
                  "test/browser/file_favicon.html";
const FAVICON_URI = TEST_SITE + "/browser/browser/components/originattributes/" +
                    "test/browser/file_favicon.png";
const TEST_THIRD_PARTY_PAGE = "http://example.com/browser/browser/components/" +
                              "originattributes/test/browser/file_favicon_thirdParty.html";
const THIRD_PARTY_FAVICON_URI = TEST_THIRD_PARTY_SITE + "/browser/browser/components/" +
                                "originattributes/test/browser/file_favicon.png";

XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                  "resource://gre/modules/PromiseUtils.jsm");

let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();

function clearAllImageCaches() {
  var tools = Cc["@mozilla.org/image/tools;1"]
                .getService(SpecialPowers.Ci.imgITools);
  var imageCache = tools.getImgCacheForDocument(window.document);
  imageCache.clearCache(true);  // true=chrome
  imageCache.clearCache(false); // false=content
}

function clearAllPlacesFavicons() {
  return new Promise(resolve => {
    Services.obs.addObserver(function observer() {
      Services.obs.removeObserver(observer, "places-favicons-expired");
      resolve();
    }, "places-favicons-expired");

    PlacesUtils.favicons.expireAllFavicons();
  });
}

function FaviconObserver(aPageURI, aFaviconURL, aTailingEnabled) {
  this.reset(aPageURI, aFaviconURL, aTailingEnabled);
}

FaviconObserver.prototype = {
  observe(aSubject, aTopic, aData) {
    // Make sure that the topic is 'http-on-modify-request'.
    if (aTopic === "http-on-modify-request") {
      let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
      let reqLoadInfo = httpChannel.loadInfo;
      // Make sure this is a favicon request.
      if (httpChannel.URI.spec !== this._faviconURL) {
        return;
      }

      let cos = aSubject.QueryInterface(Ci.nsIClassOfService);
      if (!cos) {
        ok(false, "Http channel should implement nsIClassOfService.");
        return;
      }

      if (!reqLoadInfo) {
        ok(false, "Should have load info.");
        return;
      }

      let loadingPrincipal = reqLoadInfo.loadingPrincipal;

      if (loadingPrincipal.equals(systemPrincipal)) {
        this._faviconReqXUL = true;
      } else {
        this._faviconReqPlaces = true;
      }

      let haveTailFlag = !!(cos.classFlags & Ci.nsIClassOfService.Tail);
      info("classFlags=" + cos.classFlags);
      is(haveTailFlag, this._tailingEnabled, "Should have correct cos flag.");
    } else {
      ok(false, "Received unexpected topic: ", aTopic);
    }

    if (this._faviconReqXUL && this._faviconReqPlaces) {
      this._faviconLoaded.resolve();
    }
  },

  reset(aPageURI, aFaviconURL, aTailingEnabled) {
    this._faviconReqXUL = false;
    this._faviconReqPlaces = false;
    this._faviconURL = aFaviconURL;
    this._faviconLoaded = PromiseUtils.defer();
    this._tailingEnabled = aTailingEnabled;
  },

  get promise() {
    return this._faviconLoaded.promise;
  }
};

function waitOnFaviconLoaded(aFaviconURL) {
  return PlacesTestUtils.waitForNotification(
    "onPageChanged",
    (uri, attr, value, id) => attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
                              value === aFaviconURL,
    "history");
}

async function doTest(aTestPage, aFaviconURL, aTailingEnabled) {
  let pageURI = Services.io.newURI(aTestPage);

  // Create the observer object for observing favion channels.
  let observer = new FaviconObserver(pageURI, aFaviconURL, aTailingEnabled);

  let promiseWaitOnFaviconLoaded = waitOnFaviconLoaded(aFaviconURL);

  // Add the observer earlier in case we miss it.
  Services.obs.addObserver(observer, "http-on-modify-request");

  // Open the tab.
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, aTestPage);
  // Waiting for favicon requests are all made.
  await observer.promise;
  // Waiting for favicon loaded.
  await promiseWaitOnFaviconLoaded;

  // Close the tab.
  await BrowserTestUtils.removeTab(tab);
}

async function setupTailingPreference(aTailingEnabled) {
  await SpecialPowers.pushPrefEnv({"set": [
      ["network.http.tailing.enabled", aTailingEnabled]
  ]});
}

async function cleanup() {
  // Clear all cookies.
  Services.cookies.removeAll();
  // Clear cache.
  Services.cache2.clear();
  // Clear Places favicon caches.
  await clearAllPlacesFavicons();
  // Clear all image caches and network caches.
  clearAllImageCaches();
  // Clear Places history.
  await PlacesUtils.history.clear();
}

// A clean up function to prevent affecting other tests.
registerCleanupFunction(async () => {
  await cleanup();
});

add_task(async function test_favicon_with_tailing_enabled() {
  await cleanup();

  let tailingEnabled = true;

  await setupTailingPreference(tailingEnabled);

  await doTest(TEST_PAGE, FAVICON_URI, tailingEnabled);
});

add_task(async function test_favicon_with_tailing_disabled() {
  await cleanup();

  let tailingEnabled = false;

  await setupTailingPreference(tailingEnabled);

  await doTest(TEST_THIRD_PARTY_PAGE, THIRD_PARTY_FAVICON_URI, tailingEnabled);
});
