// Copyright 2016 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/** @fileoverview testcases for CSS Sanitizer.*/

goog.provide('goog.html.CssSanitizerTest');
goog.setTestOnly();

goog.require('goog.array');
goog.require('goog.html.SafeStyle');
goog.require('goog.html.SafeStyleSheet');
goog.require('goog.html.SafeUrl');
goog.require('goog.html.sanitizer.CssSanitizer');
goog.require('goog.html.testing');
goog.require('goog.string');
goog.require('goog.testing.dom');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');
goog.require('goog.userAgent.product');
goog.require('goog.userAgent.product.isVersion');


var isIE8 = goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(9);


var isSafari9OrOlder =
    goog.userAgent.product.SAFARI && !goog.userAgent.product.isVersion(10);


var supportsDomParser =
    !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10);


/**
 * @param {string} cssText CSS text usually associated with an inline style.
 * @return {!CSSStyleDeclaration} A styleSheet object.
 */
function getStyleFromCssText(cssText) {
  var styleDecleration = document.createElement('div').style;
  styleDecleration.cssText = cssText || '';
  return styleDecleration;
}


/**
 * Asserts that the expected CSS text is equal to the actual CSS text.
 * @param {string} expectedCssText Expected CSS text.
 * @param {string} actualCssText Actual CSS text.
 */
function assertCSSTextEquals(expectedCssText, actualCssText) {
  if (isIE8) {
    // We get a bunch of default values set in IE8 because of the way we iterate
    // over the CSSStyleDecleration keys.
    // TODO(danesh): Fix IE8 or remove this hack. It will be problematic for
    // tests which have an extra semi-colon in the value (even if quoted).
    var actualCssArray = actualCssText.split(/\s*;\s*/);
    var ie8StyleString = 'WIDTH: 0px; BOTTOM: 0px; HEIGHT: 0px; TOP: 0px; ' +
        'RIGHT: 0px; TEXT-DECORATION: none underline overline line-through; ' +
        'LEFT: 0px; TEXT-DECORATION: underline line-through;';
    goog.array.forEach(ie8StyleString.split(/\s*;\s*/), function(ie8Css) {
      goog.array.remove(actualCssArray, ie8Css);
    });
    actualCssText = actualCssArray.join('; ');
  }
  assertEquals(
      getStyleFromCssText(expectedCssText).cssText,
      getStyleFromCssText(actualCssText).cssText);
}


/**
 * Gets sanitized inline style.
 * @param {string} sourceCss CSS to be sanitized.
 * @param {function (string, string):?goog.html.SafeUrl=} opt_urlRewrite URL
 *     rewriter that only returns a goog.html.SafeUrl.
 * @return {string} Sanitized inline style.
 */
function getSanitizedInlineStyle(sourceCss, opt_urlRewrite) {
  try {
    return goog.html.SafeStyle.unwrap(
               goog.html.sanitizer.CssSanitizer.sanitizeInlineStyle(
                   getStyleFromCssText(sourceCss), opt_urlRewrite)) ||
        '';
  } catch (err) {
    // IE8 doesn't like setting invalid properties. It throws an "Invalid
    // Argument" exception.
    if (!isIE8) {
      throw err;
    }
    return '';
  }
}


/**
 * Asserts that the input CSS text is equal to the expected CSS text after
 * sanitization using {@link CssSanitizer.sanitizeStyleSheetString}.
 * @param {string} expectedCssText
 * @param {string} inputCssText
 * @param {?string=} opt_containerId
 * @param {function(string, string):?SafeUrl=} opt_uriRewriter
 */
function assertSanitizedCssEquals(
    expectedCssText, inputCssText, opt_containerId, opt_uriRewriter) {
  assertBrowserSanitizedCssEquals(
      {chrome: expectedCssText}, inputCssText, opt_containerId,
      opt_uriRewriter);
}


/**
 * Asserts that on each browser the input CSS text is equal to the expected CSS
 * text after sanitization using {@link CssSanitizer.sanitizeStyleSheetString}.
 * Automatically verifies that on older browsers the sanitizer returns an empty
 * string.
 * @param {!{
 *     chrome: string,
 *     firefox: (string|undefined),
 *     safari: (string|undefined),
 *     newIE: (string|undefined),
 *     ie10: (string|undefined)}} expectedCssTextByBrowser An object that maps
 *     each browser to a different expected value. All browsers but chrome are
 *     optional. If a browser is missing, the chrome expected value is used
 *     instead.
 * @param {string} inputCssText
 * @param {?string=} opt_containerId
 * @param {function(string, string):?SafeUrl=} opt_uriRewriter
 */
function assertBrowserSanitizedCssEquals(
    expectedCssTextByBrowser, inputCssText, opt_containerId, opt_uriRewriter) {
  var expectedCssText = undefined;
  if (goog.userAgent.product.CHROME) {
    expectedCssText = expectedCssTextByBrowser.chrome;
  } else if (goog.userAgent.product.FIREFOX) {
    expectedCssText = expectedCssTextByBrowser.firefox;
  } else if (goog.userAgent.product.SAFARI) {
    expectedCssText = expectedCssTextByBrowser.safari;
  } else if (goog.userAgent.product.IE && document.documentMode == 10) {
    expectedCssText = expectedCssTextByBrowser.ie10;
    console.log('ie10');
  } else if (
      goog.userAgent.product.IE && !goog.userAgent.isVersionOrHigher(10)) {
    // Don't even try with chrome as default for IE8 and IE9.
    expectedCssText = '';
  } else if (goog.userAgent.product.IE || goog.userAgent.product.EDGE) {
    expectedCssText = expectedCssTextByBrowser.newIE;
  } else {
    throw new Error('Unrecognized browser, this function needs to be updated.');
  }
  if (expectedCssText == undefined) {
    expectedCssText = expectedCssTextByBrowser.chrome;
  }
  assertEquals(
      expectedCssText,
      goog.html.SafeStyleSheet.unwrap(
          goog.html.sanitizer.CssSanitizer.sanitizeStyleSheetString(
              inputCssText,
              opt_containerId === undefined ? 'foo' : opt_containerId,
              opt_uriRewriter)));
}


/**
 * Converts rules in STYLE tags into style attributes on the tags they apply to,
 * and returns a new string.
 * @param {string} html
 * @return {string}
 */
function inlineStyleRulesString(html) {
  var tree = goog.html.sanitizer.CssSanitizer.safeParseHtmlAndGetInertElement(
      '<span>' + html + '</span>');
  if (tree == null) {
    return '';
  }
  goog.html.sanitizer.CssSanitizer.inlineStyleRules(tree);
  return tree.innerHTML;
}


/**
 * Inlines the rules in STYLE tags in the original HTML and asserts that it is
 * the same as the expected HTML.
 * @param {string} expectedHtml
 * @param {string} originalHtml
 */
function assertInlinedStyles(expectedHtml, originalHtml) {
  var inlinedHtml = inlineStyleRulesString(originalHtml);
  if (!supportsDomParser) {
    assertEquals('', inlinedHtml);
    return;
  }
  goog.testing.dom.assertHtmlMatches(
      expectedHtml, inlinedHtml, true /* opt_strictAttributes */);
}


function testValidCss() {
  var actualCSS = 'font-family: inherit';
  var expectedCSS = 'font-family: inherit';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));

  // .1 -> 0.1; 1.0 -> 1
  actualCSS = 'padding: 1pt .1pt 1pt 1.0em';
  expectedCSS = 'padding: 1pt 0.1pt 1pt 1em';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));

  // Negative margins are allowed.
  actualCSS = 'margin:    -7px -.5px -23px -1.25px';
  expectedCSS = 'margin: -7px -0.5px -23px -1.25px';
  if (isIE8) {
    // IE8 doesn't like sub-pixels
    // https://blogs.msdn.microsoft.com/ie/2010/11/03/sub-pixel-fonts-in-ie9/
    expectedCSS = expectedCSS.replace('-0.5px', '0px');
    expectedCSS = expectedCSS.replace('-1.25px', '-1px');
  }
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));

  actualCSS = 'quotes: "{" "}" "<" ">"';
  expectedCSS = 'quotes: "{" "}" "<" ">";';
  if (isSafari9OrOlder) {
    // We never figured out why Safari didn't work here, but it's obsolete now.
    expectedCSS = 'quotes: \'{\';';
  }
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
}


function testInvalidCssRemoved() {
  var actualCSS;

  // Tests all have null results.
  var expectedCSS = '';

  actualCSS = 'font: Arial Black,monospace,Helvetica,#88ff88';
  // Hash values are not allowed so are dropped.
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));

  // Negative numbers for border not allowed.
  actualCSS = 'border : -7px -0.5px -23px -1.25px';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));

  // Negative numbers converted to empty.
  actualCSS = 'padding: -0 -.0 -0. -0.0 ';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));

  // Invalid values not allowed.
  actualCSS = 'padding : #123 - 5 "5"';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));

  // Font-family does not allow quantities at all.
  actualCSS = 'font-family: 7 .5 23 1.25 -7 -.5 -23 -1.25 +7 +.5 +23 +1.25 ' +
      '7cm .5em 23.mm 1.25px -7cm -.5em -23.mm -1.25px ' +
      '+7cm +.5em +23.mm +1.25px 0 .0 -0+00.0 /';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));

  actualCSS = 'background: bogus url("foo.png") transparent';
  assertCSSTextEquals(
      expectedCSS,
      getSanitizedInlineStyle(actualCSS, goog.html.SafeUrl.sanitize));

  // expression(...) is not allowed for font so is rejected wholesale -- the
  // internal string "pwned" is not passed through.
  actualCSS = 'font-family: Arial Black,monospace,expression(return "pwned"),' +
      'Helvetica,#88ff88';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
}


function testCssBackground() {
  var actualCSS, expectedCSS;

  function proxyUrl(url) {
    return goog.html.testing.newSafeUrlForTest(
        'https://goo.gl/proxy?url=' + url);
  }

  // Don't require the URL sanitizer to protect string boundaries.
  actualCSS = 'background-image: url("javascript:evil(1337)")';
  expectedCSS = '';
  assertCSSTextEquals(
      expectedCSS,
      getSanitizedInlineStyle(actualCSS, goog.html.SafeUrl.sanitize));

  actualCSS = 'background-image: url("http://goo.gl/foo.png")';
  expectedCSS =
      'background-image: url(https://goo.gl/proxy?url=http://goo.gl/foo.png)';
  assertCSSTextEquals(
      expectedCSS, getSanitizedInlineStyle(actualCSS, proxyUrl));

  // Without any URL sanitizer.
  actualCSS = 'background: transparent url("Bar.png")';
  var sanitizedCss = getSanitizedInlineStyle(actualCSS);
  assertFalse(goog.string.contains(sanitizedCss, 'background-image'));
  assertFalse(goog.string.contains(sanitizedCss, 'Bar.png'));
}

function testVendorPrefixed() {
  var actualCSS = '-webkit-text-stroke: 1px red';
  var expectedCSS = '';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
}

function testDisallowedFunction() {
  var actualCSS = 'border-width: calc(10px + 20px)';
  var expectedCSS = '';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
}

function testColor() {
  var colors = [
    'red', 'Red', 'RED', 'Gray', 'grey', '#abc', '#123', '#ABC123',
    'rgb( 127, 64 , 255 )'
  ];
  var notcolors = [
    // Finding words that are not X11 colors is harder than you think.
    'killitwithfire', 'invisible', 'expression(red=blue)', '#aa-1bb',
    '#expression', '#doevil'
    // 'rgb(0, 0, 100%)' // Invalid in all browsers
    // 'rgba(128,255,128,50%)', // Invalid in all browsers
  ];

  for (var i = 0; i < colors.length; ++i) {
    var validColorValue = 'color: ' + colors[i];
    assertCSSTextEquals(
        validColorValue, getSanitizedInlineStyle(validColorValue));
  }

  for (var i = 0; i < notcolors.length; ++i) {
    var invalidColorValue = 'color: ' + notcolors[i];
    assertCSSTextEquals('', getSanitizedInlineStyle(invalidColorValue));
  }
}


function testCustomVariablesSanitized() {
  var actualCSS = '\\2d-leak: leakTest; background: var(--leak);';
  assertCSSTextEquals('', getSanitizedInlineStyle(actualCSS));
}


function testExpressionsPreserved() {
  if (isIE8) {
    // Disable this test as IE8 doesn't support expressions.
    // https://msdn.microsoft.com/en-us/library/ms537634(v=VS.85).aspx
    return;
  }

  var actualCSS, expectedCSS;
  actualCSS = 'background-image: linear-gradient(to bottom right, red, blue)';
  expectedCSS = 'background-image: linear-gradient(to right bottom, red, blue)';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
}


function testMultipleInlineStyles() {
  var actualCSS = 'margin: 1px ; padding: 0';
  var expectedCSS = 'margin: 1px; padding: 0px;';
  assertCSSTextEquals(expectedCSS, getSanitizedInlineStyle(actualCSS));
}


function testSanitizeInlineStyleString() {
  var tests = [
    {
      // empty string
      inputCss: '',
      sanitizedCss: ''
    },
    {
      // one rule
      inputCss: 'color: red',
      sanitizedCss: 'color: red;'
    },
    {
      // two rules
      inputCss: 'color: green; padding: 10px',
      sanitizedCss: 'color: green; padding: 10px;'
    },
    {
      // malicious rule
      inputCss: 'color: expression("pwned")',
      sanitizedCss: ''
    },
    {
      // disallowed URL
      inputCss: 'background-image: url("http://example.com")',
      sanitizedCss: ''
    },
    {
      // disallowed URL
      inputCss: 'background-image: url("http://example.com")',
      sanitizedCss: '',
      uriRewriter: function(uri) {
        return null;
      }
    },
    {
      // allowed URL
      inputCss: 'background-image: url("http://example.com")',
      sanitizedCss: 'background-image: url("http://example.com");',
      uriRewriter: goog.html.SafeUrl.sanitize
    },
    {
      // preserves case
      inputCss: 'font-family: Roboto, sans-serif',
      sanitizedCss: 'font-family: Roboto, sans-serif'
    }
  ];

  for (var i = 0; i < tests.length; i++) {
    var test = tests[i];

    var expectedOutput = test.sanitizedCss;
    if (goog.userAgent.IE && document.documentMode < 10) {
      expectedOutput = '';
    }

    var safeStyle = goog.html.sanitizer.CssSanitizer.sanitizeInlineStyleString(
        test.inputCss, test.uriRewriter);
    var output = goog.html.SafeStyle.unwrap(safeStyle);
    assertCSSTextEquals(expectedOutput, output);
  }
}


/**
 * @suppress {accessControls}
 */
function testInertDocument() {
  if (!document.implementation.createHTMLDocument) {
    return;  // skip test
  }

  window.xssFiredInertDocument = false;
  var doc = goog.html.sanitizer.CssSanitizer.createInertDocument_();
  try {
    doc.write('<script> window.xssFiredInertDocument = true; </script>');
  } catch (e) {
    // ignore
  }
  assertFalse(window.xssFiredInertDocument);
}


/**
 * @suppress {accessControls}
 */
function testInertCustomElements() {
  if (typeof HTMLTemplateElement != 'function' || !document.registerElement) {
    return;  // skip test
  }

  var inertDoc = goog.html.sanitizer.CssSanitizer.createInertDocument_();
  var xFooConstructor = document.registerElement('x-foo');
  var xFooElem =
      document.implementation.createHTMLDocument('').createElement('x-foo');
  assertTrue(xFooElem instanceof xFooConstructor);  // sanity check

  var inertXFooElem = inertDoc.createElement('x-foo');
  assertFalse(inertXFooElem instanceof xFooConstructor);
}


function testSanitizeStyleSheetString_basic() {
  var input = '';
  assertSanitizedCssEquals(input, input);

  input = 'a {color: red}';
  var expected = '#foo a{color: red;}';
  assertSanitizedCssEquals(expected, input);

  input = 'a {color: red} b {color:red; not-allowed: 1; ' +
      'background-image: url(\'http://not.allowed\');}';
  expected = '#foo a{color: red;}#foo b{color: red;}';
  assertSanitizedCssEquals(expected, input);
}


function testSanitizeInlineStyleString_noSelector() {
  var input = 'a{color: red;}';
  assertSanitizedCssEquals(input, input, null /* opt_containerId */);
}


function testSanitizeStyleSheetString_comma() {
  var input = 'a, b, c > d {color: red}';
  var expected = '#foo a,#foo b,#foo c > d{color: red;}';
  assertSanitizedCssEquals(expected, input);
}


function testSanitizeStyleSheetString_atRule() {
  var input = '@media screen { a { color: red; } }';
  var expected = '';
  assertSanitizedCssEquals(expected, input);
}


function testSanitizeStyleSheetString_urlRewrite() {
  var urlRewriter = function(url) {
    if (input.indexOf('bar') > -1) {
      return goog.html.SafeUrl.sanitize(url);
    } else {
      return null;
    }
  };

  var input = 'a {background-image: url("http://bar.com")}';
  var quoted = '#foo a{background-image: url("http://bar.com");}';
  // Safari will strip quotes if they are not needed and add a slash.
  var unquoted = '#foo a{background-image: url(http://bar.com/);}';
  assertBrowserSanitizedCssEquals(
      {safari: unquoted, chrome: quoted}, input,
      undefined /* opt_containerId */, urlRewriter);

  input = 'a {background-image: url("http://nope.com")}';
  var expected = '#foo a{}';
  assertSanitizedCssEquals(
      expected, input, undefined /* opt_containerId */, urlRewriter);

  input = 'a{background-image: url("javascript:alert(\"bar\")")}';
  expected = '#foo a{}';
  assertSanitizedCssEquals(
      expected, input, undefined /* opt_containerId */, urlRewriter);
}


function testSanitizeStyleSheetString_unrecognized() {
  var input = 'a {mso-font-size: 2px; color: black}';
  var expected = 'a{color: black;}';
  assertSanitizedCssEquals(expected, input, null /* opt_containerId */);
}


function testSanitizeStyleSheetString_malformed() {
  var input = '<script>alert(1)</script>';
  var expected = '';
  assertSanitizedCssEquals(expected, input);

  input = 'a { } </style><script>alert(1)</script><style>';
  expected = '#foo a{}';
  assertSanitizedCssEquals(expected, input);

  input = 'a < b { } a { font-size: 10px }';
  expected = '#foo a{font-size: 10px;}';
  assertSanitizedCssEquals(expected, input);

  input = 'a {;;;} a { font-size: 10px } ;;; a { background-image: url(() }};;';
  expected = '#foo a{}#foo a{font-size: 10px;}';
  assertSanitizedCssEquals(expected, input);

  input = 'a[a=\"ccc] { color: red;}';
  expected = '';
  assertSanitizedCssEquals(expected, input);
}


function testSanitizeStyleSheetString_stringWithNoEscapedQuotesInSelector() {
  var input = 'a[data-foo="foo,bar"], b { color: red }';
  // IE converts the string to single quotes.
  var doubleQuoted = '#foo a[data-foo="foo,bar"],#foo b{color: red;}';
  var singleQuoted = '#foo a[data-foo=\'foo,bar\'],#foo b{color: red;}';
  assertBrowserSanitizedCssEquals(
      {chrome: doubleQuoted, newIE: singleQuoted, ie10: singleQuoted}, input);

  input = 'a[data-foo=\'foo,bar\'], b { color: red }';
  // Chrome converts the string to double quotes.
  doubleQuoted = '#foo a[data-foo="foo,bar"],#foo b{color: red;}';
  singleQuoted = '#foo a[data-foo=\'foo,bar\'],#foo b{color: red;}';
  assertBrowserSanitizedCssEquals(
      {chrome: doubleQuoted, newIE: singleQuoted, ie10: singleQuoted}, input);

  input = 'a[foo="foo,bar"][bar="baz"], b { color: blue }';
  doubleQuoted = '#foo a[foo="foo,bar"][bar="baz"],#foo b{color: blue;}';
  singleQuoted = '#foo a[foo=\'foo,bar\'][bar=\'baz\'],#foo b{color: blue;}';
  assertBrowserSanitizedCssEquals(
      {chrome: doubleQuoted, newIE: singleQuoted, ie10: singleQuoted}, input);

  input = 'a[foo="foo,bar"], b, c[foo="f,b"][bar="f,b"] { color: red }';
  doubleQuoted = '#foo a[foo="foo,bar"],#foo b,#foo c[foo="f,b"][bar="f,b"]' +
      '{color: red;}';
  singleQuoted =
      '#foo a[foo=\'foo,bar\'],#foo b,#foo c[bar=\'f,b\'][foo=\'f,b\']' +
      '{color: red;}';
  assertBrowserSanitizedCssEquals(
      {chrome: doubleQuoted, newIE: singleQuoted, ie10: singleQuoted}, input);
}


function testSanitizeStyleSheetString_stringWithEscapedQuotesInSelector() {
  // Contains an escaped string, but the selector is converted to a[a='a"b']
  // before the regex is executed.
  var input = 'a[a="a\\"b"] { color: black; }';
  var doubleQuoted = '#foo a[a="a\\"b"]{color: black;}';
  var singleQuoted = '#foo a[a=\'a"b\']{color: black;}';
  assertBrowserSanitizedCssEquals(
      {chrome: doubleQuoted, ie10: singleQuoted, newIE: singleQuoted}, input);

  input = 'a[a="a\\\'b"] { color: grey; }';
  doubleQuoted = '#foo a[a="a\'b"]{color: grey;}';
  singleQuoted = '#foo a[a=\'a\\\'b\']{color: grey;}';
  assertBrowserSanitizedCssEquals(
      {
        chrome: doubleQuoted,
        firefox: doubleQuoted,
        ie10: '',
        newIE: singleQuoted
      },
      input);

  input = 'a[a=\'\\\'b\'] {color: red; }';
  doubleQuoted = '#foo a[a="\'b"]{color: red;}';
  singleQuoted = '#foo a[a=\'\\\'b\']{color: red;}';
  assertBrowserSanitizedCssEquals(
      {
        chrome: doubleQuoted,
        firefox: doubleQuoted,
        newIE: singleQuoted,
        ie10: ''
      },
      input);

  input = 'a[foo=\'b\\\'a, ,\'] { color: blue; }';
  doubleQuoted = '#foo a[foo="b\'a, ,"]{color: blue;}';
  singleQuoted = '#foo a[foo=\'b\\\'a, ,\']{color: blue;}';
  assertBrowserSanitizedCssEquals(
      {
        chrome: doubleQuoted,
        firefox: doubleQuoted,
        newIE: singleQuoted,
        ie10: ''
      },
      input);

  input = 'a[a=\'a\\"b\'] { color: black; }';
  doubleQuoted = '#foo a[a="a\\"b"]{color: black;}';
  singleQuoted = '#foo a[a=\'a"b\']{color: black;}';
  assertBrowserSanitizedCssEquals(
      {chrome: doubleQuoted, ie10: singleQuoted, newIE: singleQuoted}, input);
}


function testSanitizeInlineStyleString_invalidSelector() {
  var input = 'a{}';
  if (supportsDomParser) {
    assertThrows(function() {
      goog.html.sanitizer.CssSanitizer.sanitizeStyleSheetString(
          input, '</style>');
    });
  }
}


function testInlineStyleRules_empty() {
  assertInlinedStyles('', '');
}


function testInlineStyleRules_basic() {
  var input = '<style>a{color:red}</style><a>foo</a>';
  var expected = '<a style="color:red;">foo</a>';
  assertInlinedStyles(expected, input);
}


function testInlineStyleRules_onlyStyle() {
  var input = '<style>a{color:red}</style>';
  assertInlinedStyles('', input);
}


function testInlineStyleRules_noStyle() {
  var input = '<a>hi</a>';
  assertInlinedStyles(input, input);
}


function testInlineStyleRules_onlyText() {
  var input = 'hello';
  assertInlinedStyles(input, input);
}


function testInlineStyleRules_specificity() {
  var input = '<style>a{color: red; border: 1px}' +
      '#foo{color: white; border: 2px}</style>' +
      '<a id="foo" style="color: black">foo</a>';
  var expected = '<a id="foo" style="color: black; border: 2px">foo</a>';
  assertInlinedStyles(expected, input);
}


function testInlineStyleRules_media() {
  var input = '<style>a{color: red;} @media screen { border: 1px; }</style>' +
      '<a id="foo">foo</a>';
  var expected = '<a id="foo" style="color: red;">foo</a>';
  assertInlinedStyles(expected, input);
}
