/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

/* This list allows pre-existing or 'unfixable' CSS issues to remain, while we
 * detect newly occurring issues in shipping CSS. It is a list of objects
 * specifying conditions under which an error should be ignored.
 *
 * Every property of the objects in it needs to consist of a regular expression
 * matching the offending error. If an object has multiple regex criteria, they
 * ALL need to match an error in order for that error not to cause a test
 * failure. */
const kWhitelist = [
  // Cleopatra is imported as-is, see bug 1004421.
  {sourceName: /cleopatra.*(tree|ui)\.css/i},
  // CodeMirror is imported as-is, see bug 1004423.
  {sourceName: /codemirror\.css/i},
  // PDFjs is futureproofing its pseudoselectors, and those rules are dropped.
  {sourceName: /web\/viewer\.css/i,
   errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i},
  // Tracked in bug 1004428.
  {sourceName: /aboutaccounts\/(main|normalize)\.css/i},
  // TokBox SDK assets, see bug 1032469.
  {sourceName: /loop\/.*sdk-content\/.*\.css$/i},
  // Loop standalone client CSS uses placeholder cross browser pseudo-element
  {sourceName: /loop\/.*\.css/i,
   errorMessage: /Unknown pseudo-class.*placeholder/i},
  // Highlighter CSS uses chrome-only pseudo-class, see bug 985597.
  {sourceName: /highlighter\.css/i,
   errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i},
];

let moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
let {generateURIsFromDirTree} = Cu.import(moduleLocation, {});

/**
 * Check if an error should be ignored due to matching one of the whitelist
 * objects defined in kWhitelist
 *
 * @param aErrorObject the error to check
 * @return true if the error should be ignored, false otherwise.
 */
function ignoredError(aErrorObject) {
  for (let whitelistItem of kWhitelist) {
    let matches = true;
    for (let prop in whitelistItem) {
      if (!whitelistItem[prop].test(aErrorObject[prop] || "")) {
        matches = false;
        break;
      }
    }
    if (matches) {
      return true;
    }
  }
  return false;
}

add_task(function checkAllTheCSS() {
  let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
  // This asynchronously produces a list of URLs (sadly, mostly sync on our
  // test infrastructure because it runs against jarfiles there, and
  // our zipreader APIs are all sync)
  let uris = yield generateURIsFromDirTree(appDir, ".css");

  // Create a clean iframe to load all the files into:
  let hiddenWin = Services.appShell.hiddenDOMWindow;
  let iframe = hiddenWin.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
  hiddenWin.document.documentElement.appendChild(iframe);
  let doc = iframe.contentWindow.document;


  // Listen for errors caused by the CSS:
  let errorListener = {
    observe: function(aMessage) {
      if (!aMessage || !(aMessage instanceof Ci.nsIScriptError)) {
        return;
      }
      // Only care about CSS errors generated by our iframe:
      if (aMessage.category.includes("CSS") && aMessage.innerWindowID === 0 && aMessage.outerWindowID === 0) {
        // Check if this error is whitelisted in kWhitelist
        if (!ignoredError(aMessage)) {
          ok(false, "Got error message for " + aMessage.sourceName + ": " + aMessage.errorMessage);
          errors++;
        } else {
          info("Ignored error for " + aMessage.sourceName + " because of filter.");
        }
      }
    }
  };

  // We build a list of promises that get resolved when their respective
  // files have loaded and produced no errors.
  let allPromises = [];
  let errors = 0;
  // Register the error listener to keep track of errors.
  Services.console.registerListener(errorListener);
  for (let uri of uris) {
    let linkEl = doc.createElement("link");
    linkEl.setAttribute("rel", "stylesheet");
    let promiseForThisSpec = Promise.defer();
    let onLoad = (e) => {
      promiseForThisSpec.resolve();
      linkEl.removeEventListener("load", onLoad);
      linkEl.removeEventListener("error", onError);
    };
    let onError = (e) => {
      promiseForThisSpec.reject({error: e, href: linkEl.getAttribute("href")});
      linkEl.removeEventListener("load", onLoad);
      linkEl.removeEventListener("error", onError);
    };
    linkEl.addEventListener("load", onLoad);
    linkEl.addEventListener("error", onError);
    linkEl.setAttribute("type", "text/css");
    linkEl.setAttribute("href", uri.spec);
    allPromises.push(promiseForThisSpec.promise);
    doc.head.appendChild(linkEl);
  }

  // Wait for all the files to have actually loaded:
  yield Promise.all(allPromises);
  // Count errors (the test output will list actual issues for us, as well
  // as the ok(false) in the error listener)
  is(errors, 0, "All the styles (" + allPromises.length + ") loaded without errors.");

  // Clean up to avoid leaks:
  Services.console.unregisterListener(errorListener);
  iframe.remove();
  doc.head.innerHTML = '';
  doc = null;
  iframe = null;
});
