Add packages to optimize svgs dynamically

These packages will be used to dynamically optimize SVG images
during the build.

R=jacktfranklin@chromium.org

Bug: 1216402
Change-Id: I04e95aa7d79c9d67beaf8a7861182c52b16b7d0f
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2939992
Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org>
diff --git a/node_modules/css-tree/lib/lexer/match-graph.js b/node_modules/css-tree/lib/lexer/match-graph.js
new file mode 100644
index 0000000..3d27704
--- /dev/null
+++ b/node_modules/css-tree/lib/lexer/match-graph.js
@@ -0,0 +1,455 @@
+var parse = require('../definition-syntax/parse');
+
+var MATCH = { type: 'Match' };
+var MISMATCH = { type: 'Mismatch' };
+var DISALLOW_EMPTY = { type: 'DisallowEmpty' };
+var LEFTPARENTHESIS = 40;  // (
+var RIGHTPARENTHESIS = 41; // )
+
+function createCondition(match, thenBranch, elseBranch) {
+    // reduce node count
+    if (thenBranch === MATCH && elseBranch === MISMATCH) {
+        return match;
+    }
+
+    if (match === MATCH && thenBranch === MATCH && elseBranch === MATCH) {
+        return match;
+    }
+
+    if (match.type === 'If' && match.else === MISMATCH && thenBranch === MATCH) {
+        thenBranch = match.then;
+        match = match.match;
+    }
+
+    return {
+        type: 'If',
+        match: match,
+        then: thenBranch,
+        else: elseBranch
+    };
+}
+
+function isFunctionType(name) {
+    return (
+        name.length > 2 &&
+        name.charCodeAt(name.length - 2) === LEFTPARENTHESIS &&
+        name.charCodeAt(name.length - 1) === RIGHTPARENTHESIS
+    );
+}
+
+function isEnumCapatible(term) {
+    return (
+        term.type === 'Keyword' ||
+        term.type === 'AtKeyword' ||
+        term.type === 'Function' ||
+        term.type === 'Type' && isFunctionType(term.name)
+    );
+}
+
+function buildGroupMatchGraph(combinator, terms, atLeastOneTermMatched) {
+    switch (combinator) {
+        case ' ':
+            // Juxtaposing components means that all of them must occur, in the given order.
+            //
+            // a b c
+            // =
+            // match a
+            //   then match b
+            //     then match c
+            //       then MATCH
+            //       else MISMATCH
+            //     else MISMATCH
+            //   else MISMATCH
+            var result = MATCH;
+
+            for (var i = terms.length - 1; i >= 0; i--) {
+                var term = terms[i];
+
+                result = createCondition(
+                    term,
+                    result,
+                    MISMATCH
+                );
+            };
+
+            return result;
+
+        case '|':
+            // A bar (|) separates two or more alternatives: exactly one of them must occur.
+            //
+            // a | b | c
+            // =
+            // match a
+            //   then MATCH
+            //   else match b
+            //     then MATCH
+            //     else match c
+            //       then MATCH
+            //       else MISMATCH
+
+            var result = MISMATCH;
+            var map = null;
+
+            for (var i = terms.length - 1; i >= 0; i--) {
+                var term = terms[i];
+
+                // reduce sequence of keywords into a Enum
+                if (isEnumCapatible(term)) {
+                    if (map === null && i > 0 && isEnumCapatible(terms[i - 1])) {
+                        map = Object.create(null);
+                        result = createCondition(
+                            {
+                                type: 'Enum',
+                                map: map
+                            },
+                            MATCH,
+                            result
+                        );
+                    }
+
+                    if (map !== null) {
+                        var key = (isFunctionType(term.name) ? term.name.slice(0, -1) : term.name).toLowerCase();
+                        if (key in map === false) {
+                            map[key] = term;
+                            continue;
+                        }
+                    }
+                }
+
+                map = null;
+
+                // create a new conditonal node
+                result = createCondition(
+                    term,
+                    MATCH,
+                    result
+                );
+            };
+
+            return result;
+
+        case '&&':
+            // A double ampersand (&&) separates two or more components,
+            // all of which must occur, in any order.
+
+            // Use MatchOnce for groups with a large number of terms,
+            // since &&-groups produces at least N!-node trees
+            if (terms.length > 5) {
+                return {
+                    type: 'MatchOnce',
+                    terms: terms,
+                    all: true
+                };
+            }
+
+            // Use a combination tree for groups with small number of terms
+            //
+            // a && b && c
+            // =
+            // match a
+            //   then [b && c]
+            //   else match b
+            //     then [a && c]
+            //     else match c
+            //       then [a && b]
+            //       else MISMATCH
+            //
+            // a && b
+            // =
+            // match a
+            //   then match b
+            //     then MATCH
+            //     else MISMATCH
+            //   else match b
+            //     then match a
+            //       then MATCH
+            //       else MISMATCH
+            //     else MISMATCH
+            var result = MISMATCH;
+
+            for (var i = terms.length - 1; i >= 0; i--) {
+                var term = terms[i];
+                var thenClause;
+
+                if (terms.length > 1) {
+                    thenClause = buildGroupMatchGraph(
+                        combinator,
+                        terms.filter(function(newGroupTerm) {
+                            return newGroupTerm !== term;
+                        }),
+                        false
+                    );
+                } else {
+                    thenClause = MATCH;
+                }
+
+                result = createCondition(
+                    term,
+                    thenClause,
+                    result
+                );
+            };
+
+            return result;
+
+        case '||':
+            // A double bar (||) separates two or more options:
+            // one or more of them must occur, in any order.
+
+            // Use MatchOnce for groups with a large number of terms,
+            // since ||-groups produces at least N!-node trees
+            if (terms.length > 5) {
+                return {
+                    type: 'MatchOnce',
+                    terms: terms,
+                    all: false
+                };
+            }
+
+            // Use a combination tree for groups with small number of terms
+            //
+            // a || b || c
+            // =
+            // match a
+            //   then [b || c]
+            //   else match b
+            //     then [a || c]
+            //     else match c
+            //       then [a || b]
+            //       else MISMATCH
+            //
+            // a || b
+            // =
+            // match a
+            //   then match b
+            //     then MATCH
+            //     else MATCH
+            //   else match b
+            //     then match a
+            //       then MATCH
+            //       else MATCH
+            //     else MISMATCH
+            var result = atLeastOneTermMatched ? MATCH : MISMATCH;
+
+            for (var i = terms.length - 1; i >= 0; i--) {
+                var term = terms[i];
+                var thenClause;
+
+                if (terms.length > 1) {
+                    thenClause = buildGroupMatchGraph(
+                        combinator,
+                        terms.filter(function(newGroupTerm) {
+                            return newGroupTerm !== term;
+                        }),
+                        true
+                    );
+                } else {
+                    thenClause = MATCH;
+                }
+
+                result = createCondition(
+                    term,
+                    thenClause,
+                    result
+                );
+            };
+
+            return result;
+    }
+}
+
+function buildMultiplierMatchGraph(node) {
+    var result = MATCH;
+    var matchTerm = buildMatchGraph(node.term);
+
+    if (node.max === 0) {
+        // disable repeating of empty match to prevent infinite loop
+        matchTerm = createCondition(
+            matchTerm,
+            DISALLOW_EMPTY,
+            MISMATCH
+        );
+
+        // an occurrence count is not limited, make a cycle;
+        // to collect more terms on each following matching mismatch
+        result = createCondition(
+            matchTerm,
+            null, // will be a loop
+            MISMATCH
+        );
+
+        result.then = createCondition(
+            MATCH,
+            MATCH,
+            result // make a loop
+        );
+
+        if (node.comma) {
+            result.then.else = createCondition(
+                { type: 'Comma', syntax: node },
+                result,
+                MISMATCH
+            );
+        }
+    } else {
+        // create a match node chain for [min .. max] interval with optional matches
+        for (var i = node.min || 1; i <= node.max; i++) {
+            if (node.comma && result !== MATCH) {
+                result = createCondition(
+                    { type: 'Comma', syntax: node },
+                    result,
+                    MISMATCH
+                );
+            }
+
+            result = createCondition(
+                matchTerm,
+                createCondition(
+                    MATCH,
+                    MATCH,
+                    result
+                ),
+                MISMATCH
+            );
+        }
+    }
+
+    if (node.min === 0) {
+        // allow zero match
+        result = createCondition(
+            MATCH,
+            MATCH,
+            result
+        );
+    } else {
+        // create a match node chain to collect [0 ... min - 1] required matches
+        for (var i = 0; i < node.min - 1; i++) {
+            if (node.comma && result !== MATCH) {
+                result = createCondition(
+                    { type: 'Comma', syntax: node },
+                    result,
+                    MISMATCH
+                );
+            }
+
+            result = createCondition(
+                matchTerm,
+                result,
+                MISMATCH
+            );
+        }
+    }
+
+    return result;
+}
+
+function buildMatchGraph(node) {
+    if (typeof node === 'function') {
+        return {
+            type: 'Generic',
+            fn: node
+        };
+    }
+
+    switch (node.type) {
+        case 'Group':
+            var result = buildGroupMatchGraph(
+                node.combinator,
+                node.terms.map(buildMatchGraph),
+                false
+            );
+
+            if (node.disallowEmpty) {
+                result = createCondition(
+                    result,
+                    DISALLOW_EMPTY,
+                    MISMATCH
+                );
+            }
+
+            return result;
+
+        case 'Multiplier':
+            return buildMultiplierMatchGraph(node);
+
+        case 'Type':
+        case 'Property':
+            return {
+                type: node.type,
+                name: node.name,
+                syntax: node
+            };
+
+        case 'Keyword':
+            return {
+                type: node.type,
+                name: node.name.toLowerCase(),
+                syntax: node
+            };
+
+        case 'AtKeyword':
+            return {
+                type: node.type,
+                name: '@' + node.name.toLowerCase(),
+                syntax: node
+            };
+
+        case 'Function':
+            return {
+                type: node.type,
+                name: node.name.toLowerCase() + '(',
+                syntax: node
+            };
+
+        case 'String':
+            // convert a one char length String to a Token
+            if (node.value.length === 3) {
+                return {
+                    type: 'Token',
+                    value: node.value.charAt(1),
+                    syntax: node
+                };
+            }
+
+            // otherwise use it as is
+            return {
+                type: node.type,
+                value: node.value.substr(1, node.value.length - 2).replace(/\\'/g, '\''),
+                syntax: node
+            };
+
+        case 'Token':
+            return {
+                type: node.type,
+                value: node.value,
+                syntax: node
+            };
+
+        case 'Comma':
+            return {
+                type: node.type,
+                syntax: node
+            };
+
+        default:
+            throw new Error('Unknown node type:', node.type);
+    }
+}
+
+module.exports = {
+    MATCH: MATCH,
+    MISMATCH: MISMATCH,
+    DISALLOW_EMPTY: DISALLOW_EMPTY,
+    buildMatchGraph: function(syntaxTree, ref) {
+        if (typeof syntaxTree === 'string') {
+            syntaxTree = parse(syntaxTree);
+        }
+
+        return {
+            type: 'MatchGraph',
+            match: buildMatchGraph(syntaxTree),
+            syntax: ref || null,
+            source: syntaxTree
+        };
+    }
+};