chromium/third_party/google-closure-library/closure/goog/cssom/iframe/style_test.js

/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.module('goog.cssom.iframe.styleTest');
goog.setTestOnly();

const DomHelper = goog.require('goog.dom.DomHelper');
const TagName = goog.require('goog.dom.TagName');
const cssom = goog.require('goog.cssom');
const dom = goog.require('goog.dom');
const style = goog.require('goog.cssom.iframe.style');
const testSuite = goog.require('goog.testing.testSuite');
const userAgent = goog.require('goog.userAgent');

// unit tests
const propertiesToTest = [
  'color',
  'font-family',
  'font-style',
  'font-size',
  'font-variant',
  'border-top-style',
  'border-top-width',
  'border-top-color',
  'background-color',
  'margin-bottom',
];

function crawlDom(startNode, func) {
  if (startNode.nodeType != 1) {
    return;
  }
  func(startNode);
  for (let i = 0; i < startNode.childNodes.length; i++) {
    crawlDom(startNode.childNodes[i], func);
  }
}

function getCurrentCssProperties(node, propList) {
  const props = {};
  if (node.nodeType != 1) {
    return;
  }
  for (let i = 0; i < propList.length; i++) {
    const prop = propList[i];
    if (node.currentStyle) {  // IE
      let propCamelCase = '';
      const propParts = prop.split('-');
      for (let j = 0; j < propParts.length; j++) {
        propCamelCase += propParts[j].charAt(0).toUpperCase() +
            propParts[j].substring(1, propParts[j].length);
      }
      props[prop] = node.currentStyle[propCamelCase];
    } else {  // standards-compliant browsers
      props[prop] = node.ownerDocument.defaultView.getComputedStyle(node, '')
                        .getPropertyValue(prop);
    }
  }
  return props;
}

class CssPropertyCollector {
  constructor() {
    const propsList = [];
    this.propsList = propsList;

    this.collectProps = (node) => {
      const nodeProps = getCurrentCssProperties(node, propertiesToTest);
      if (nodeProps) {
        propsList.push([nodeProps, node]);
      }
    };
  }
}

function recursivelyListCssProperties(el) {
  const collector = new CssPropertyCollector();
  crawlDom(el, collector.collectProps);
  return collector.propsList;
}

function makeIframeDocument(iframe) {
  const doc = dom.getFrameContentDocument(iframe);
  doc.open();
  doc.write('<html><head>');
  doc.write('<style>html,body { background-color: transparent; }</style>');
  doc.write('</head><body></body></html>');
  doc.close();
  return doc;
}

function normalizeCssText(cssText) {
  // Normalize cssText for testing purposes.
  return cssText.replace(/\s/g, '').toLowerCase();
}

testSuite({
  /**
   * @suppress {visibility,missingProperties} CssSelector_ property is private
   * and selectorPartIndex is unknown.
   */
  testMatchCssSelector() {
    const container = dom.createElement(TagName.DIV);
    container.className = 'container';
    const el = dom.createElement(TagName.DIV);
    el.id = 'mydiv';
    el.className = 'colorful foo';
    // set some arbirtrary content
    el.innerHTML = '<div><ul><li>One</li><li>Two</li></ul></div>';
    container.appendChild(el);
    document.body.appendChild(container);

    const elementAncestry = new style.NodeAncestry_(el);
    assertEquals(5, elementAncestry.nodes.length);

    // list of input/output results. Output is the index of the selector
    // that we expect to match - for example, in 'body div div.colorful',
    // 'div.colorful' has an index of 2.
    const expectedResults = [
      ['body div', [4, 1]],
      ['h1', null],
      ['body div h1', [4, 1]],
      ['body div.colorful h1', [4, 1]],
      ['body div div', [4, 2]],
      ['body div div div', [4, 2]],
      ['body div div.somethingelse div', [4, 1]],
      ['body div.somethingelse div', [2, 0]],
      ['div.container', [3, 0]],
      ['div.container div', [4, 1]],
      ['#mydiv', [4, 0]],
      ['div#mydiv', [4, 0]],
      ['div.colorful', [4, 0]],
      ['div#mydiv .colorful', [4, 0]],
      ['.colorful', [4, 0]],
      ['body * div', [4, 2]],
      ['body * *', [4, 2]],
    ];
    for (let i = 0; i < expectedResults.length; i++) {
      const input = expectedResults[i][0];
      const expectedResult = expectedResults[i][1];
      const selector = new style.CssSelector_(input);
      const result =
          /** @type {?} */ (selector.matchElementAncestry(elementAncestry));
      if (expectedResult == null) {
        assertEquals('Expected null result', expectedResult, result);
      } else {
        assertEquals(
            `Expected element index for ${input}`, expectedResult[0],
            result.elementIndex);
        assertEquals(
            `Expected selector part index for ${input}`, expectedResult[1],
            result.selectorPartIndex);
      }
    }
    document.body.removeChild(container);
  },

  testCopyCss() {
    for (let i = 1; i <= 4; i++) {
      const sourceElement = document.getElementById(`source${i}`);
      const newFrame = dom.createElement(TagName.IFRAME);
      newFrame.allowTransparency = true;
      sourceElement.parentNode.insertBefore(
          newFrame, sourceElement.nextSibling);
      const doc = makeIframeDocument(newFrame);
      cssom.addCssText(
          style.getElementContext(sourceElement), new DomHelper(doc));
      doc.body.innerHTML = sourceElement.innerHTML;

      const oldProps = recursivelyListCssProperties(sourceElement);
      const newProps = recursivelyListCssProperties(doc.body);

      assertEquals(oldProps.length, newProps.length);
      for (let j = 0; j < oldProps.length; j++) {
        for (let k = 0; k < propertiesToTest.length; k++) {
          assertEquals(
              'testing property ' + propertiesToTest[k],
              oldProps[j][0][propertiesToTest[k]],
              newProps[j][0][propertiesToTest[k]]);
        }
      }
    }
  },

  testAImportantInFF2() {
    const testDiv = document.getElementById('source1');
    const cssText = normalizeCssText(style.getElementContext(testDiv));
    const color = standardizeCSSValue('color', 'red');
    const NORMAL_RULE = `a{color:${color}`;
    const FF_2_RULE = `a{color:${color}!important`;
    assertContains(NORMAL_RULE, cssText);
    assertNotContains(FF_2_RULE, cssText);
  },

  testCopyBackgroundContext() {
    const testDiv = document.getElementById('backgroundTest');
    const cssText = style.getElementContext(testDiv, undefined, true);
    const iframe = dom.createElement(TagName.IFRAME);
    const ancestor = document.getElementById('backgroundTest-ancestor-1');
    ancestor.parentNode.insertBefore(iframe, ancestor.nextSibling);
    iframe.style.width = '100%';
    iframe.style.height = '100px';
    iframe.style.borderWidth = '0px';
    const doc = makeIframeDocument(iframe);
    cssom.addCssText(cssText, new DomHelper(doc));
    doc.body.innerHTML = testDiv.innerHTML;
    const normalizedCssText = normalizeCssText(cssText);
    assertTrue(
        'Background color should be copied from parent element',
        /body{[^{]*background-color:(?:rgb\(128,0,128\)|#800080)/.test(
            normalizedCssText));
    assertTrue(
        'Background image should be copied from ancestor element',
        /body{[^{]*background-image:url\(/.test(normalizedCssText));
    // Expected x position is:
    // originalBackgroundPositionX - elementOffsetLeft
    // 40px - (1px + 8px) == 31px
    // Expected y position is:
    // originalBackgroundPositionY - elementOffsetLeft
    // 70px - (1px + 10px + 5px) == 54px;
    assertTrue(
        'Background image position should be adjusted correctly',
        /body{[^{]*background-position:31px54px/.test(normalizedCssText));
  },

  testCopyBackgroundContextFromIframe() {
    const testDiv = document.getElementById('backgroundTest');
    const iframe = dom.createElement(TagName.IFRAME);
    iframe.allowTransparency = true;
    iframe.style.position = 'absolute';
    iframe.style.top = '5px';
    iframe.style.left = '5px';
    iframe.style.borderWidth = '2px';
    iframe.style.borderStyle = 'solid';
    testDiv.appendChild(iframe);
    const doc = makeIframeDocument(iframe);
    doc.body.backgroundColor = 'transparent';
    doc.body.style.margin = '0';
    doc.body.style.padding = '0';
    doc.body.innerHTML = '<p style="margin: 0">I am transparent!</p>';
    const normalizedCssText = normalizeCssText(
        style.getElementContext(doc.body.firstChild, undefined, true));
    // Background properties should get copied through from the parent
    // document since the iframe is transparent
    assertTrue(
        'Background color should be copied from parent element',
        /body{[^{]*background-color:(?:rgb\(128,0,128\)|#800080)/.test(
            normalizedCssText));
    assertTrue(
        'Background image should be copied from ancestor element',
        /body{[^{]*background-image:url\(/.test(normalizedCssText));
    // Image offset should have been calculated to be the same as the
    // above example, but adding iframe offset and borderWidth.
    // Expected x position is:
    // originalBackgroundPositionX - elementOffsetLeft
    // 40px - (1px + 8px + 5px + 2px) == 24px
    // Expected y position is:
    // originalBackgroundPositionY - elementOffsetLeft
    // 70px - (1px + 10px + 5px + 5px + 2px) == 47px;
    assertTrue(
        'Background image position should be adjusted correctly',
        !!/body{[^{]*background-position:24px47px/.exec(normalizedCssText));

    iframe.parentNode.removeChild(iframe);
  },

  testCopyFontFaceRules() {
    const isFontFaceCssomSupported = userAgent.WEBKIT || userAgent.GECKO;
    // We cannot use goog.testing.ExpectedFailures since it dynamically
    // brings in CSS which causes the background context tests to fail
    // in IE6.
    if (isFontFaceCssomSupported) {
      const cssText =
          style.getElementContext(document.getElementById('cavalier'));
      assertTrue(
          'The font face rule should have been copied correctly',
          /@font-face/.test(cssText));
    }
  },
});