chromium/third_party/blink/web_tests/svg/dom/content-model.html

<!DOCTYPE html>
<html>
  <head>
    <script src="../../resources/js-test.js"></script>
    <script src="resources/svgtags.js"></script>
  </head>
  <body>
    <div id="console"></div>
    <div id="container" style="position: fixed;"></div>

    <script>
      var svgNS = "http://www.w3.org/2000/svg";

      function createNode(tag, container) {
        var node = document.createElementNS(svgNS, tag);
        container.appendChild(node);
        return node;
      }

      function createValidNode(tag, container, createAncestors) {
        var realContainer = container;

        if (createAncestors && SvgTags[tag].needParent)
          realContainer = createValidNode(SvgTags[tag].needParent, container, true);

        var node = createNode(tag, realContainer);
        for (var attr in SvgTags[tag].needAttr)
          node.setAttribute(attr, SvgTags[tag].needAttr[attr]);

        var requiredChildren = SvgTags[tag].needChild || [];
        for (var childTag of requiredChildren)
          createValidNode(childTag, node, false);

        return node;
      }

      function expandClasses(tags) {
        var expandedTags = [];
        for (var tag of tags)
          Array.prototype.push.apply(expandedTags, SvgTagClasses[tag] || [tag]);
        return expandedTags;
      }

      function createsRenderer(parentTag, childTag) {
        var divContainer = document.getElementById('container');
        var svgContainer = createNode('svg', divContainer);
        var parentNode = createValidNode(parentTag, svgContainer, true);
        createValidNode(childTag, parentNode, false);

        var drt = internals.elementLayoutTreeAsText(divContainer);
        divContainer.removeChild(svgContainer);

        // Skip to the line after the LayoutSVGRoot.
        var skip = drt.indexOf('LayoutSVGRoot');
        skip = drt.indexOf('\n', skip) + 1;
        // Skip the line with the parent tag.
        skip = drt.indexOf('\n', skip) + 1;

        return drt.substr(skip).search(new RegExp('\\{' + childTag + '\\}|\\[' + childTag + '[\\] ]')) > 0;
      }

      function validateTag(tag, validChildren) {
        var expandedValidChildren = expandClasses(validChildren);
        var unexpectedValid = [];
        var unexpectedInvalid = [];

        for (var childTag in SvgTags) {
          // Only testing elements with renderers.
          if (SvgTags[childTag].noRenderer)
            continue;

          if (createsRenderer(tag, childTag)) {
            if (expandedValidChildren.indexOf(childTag) < 0)
              unexpectedValid.push(childTag);
          } else {
            if (expandedValidChildren.indexOf(childTag) >= 0)
              unexpectedInvalid.push(childTag);
          }
        }

        var msg = '<' + tag + '>: ';
        var passed = (unexpectedValid.length == 0 && unexpectedInvalid.length == 0);
        if (passed) {
          msg += '[' + validChildren + ']';
          testPassed(msg);
        } else {
          if (unexpectedValid.length > 0)
            msg += 'unexpected valid [' + unexpectedValid + '] ';
          if (unexpectedInvalid.length > 0)
            msg += 'unexpected invalid [' + unexpectedInvalid + ']';
          testFailed(msg);
        }
      }


      if (!window.testRunner || !window.internals)
        testFailed('this test requires DumpRenderTree.');

      description('This test validates renderer instantiation against the SVG content model.');

      validateTag('filter', [
        // http://dev.w3.org/fxtf/filters/#FilterElement
        'CLASS_DESCRIPTIVE', 'CLASS_FILTER_PRIMITIVE', 'animate', 'set'
      ]);
      validateTag('linearGradient', [
        // https://svgwg.org/svg2-draft/pservers.html#LinearGradientElement
        'CLASS_DESCRIPTIVE', 'CLASS_PAINT_SERVER', 'animate', 'animateTransform', 'script', 'set',
        'stop'
      ]);
      validateTag('g', [
        // https://svgwg.org/svg2-draft/struct.html#GElement
        'CLASS_ANIMATION', 'CLASS_DESCRIPTIVE', 'CLASS_PAINT_SERVER', 'CLASS_SHAPE',
        'CLASS_STRUCTURAL', 'a', 'clipPath', 'cursor', 'filter', 'foreignObject', 'image', 'marker',
        'mask', 'script', 'style', 'switch', 'text', 'view'
      ]);
      validateTag('radialGradient', [
        // https://svgwg.org/svg2-draft/pservers.html#RadialGradientElement
        'CLASS_DESCRIPTIVE', 'CLASS_PAINT_SERVER', 'animate', 'animateTransform', 'script', 'set',
        'stop'
      ]);
      validateTag('svg', [
        // https://svgwg.org/svg2-draft/struct.html#SVGElement
        'CLASS_ANIMATION', 'CLASS_DESCRIPTIVE', 'CLASS_PAINT_SERVER', 'CLASS_SHAPE',
        'CLASS_STRUCTURAL', 'a', 'clipPath', 'cursor', 'filter', 'foreignObject', 'image', 'marker',
        'mask', 'script', 'style', 'switch', 'text', 'view'
      ]);
    </script>
  </body>
</html>