<?xml version="1.0"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="800">
 <title>Touch input event flow on B2G</title>
 <g id="arrows"></g>
 <style type="text/css"><![CDATA[
    text {
        fill: black;
        text-anchor: middle;
        white-space: pre-line;
        font-size: 14px;
    }
    rect {
        fill: none;
    }
    line {
        stroke: black;
    }
    .parentinput rect {
        stroke: black;
    }
    text.parentinput {
        fill: black;
        text-anchor: start;
    }
    .parentmain rect {
        stroke: orange;
    }
    text.parentmain {
        fill: orange;
        text-anchor: start;
    }
    .parentcompositor rect {
        stroke: green;
    }
    text.parentcompositor {
        fill: green;
        text-anchor: start;
    }
    .childmain rect {
        stroke: red;
    }
    text.childmain {
        fill: red;
        text-anchor: start;
    }
    .bothmain rect {
        stroke: blue;
    }
    text.bothmain {
        fill: blue;
        text-anchor: start;
    }
 ]]></style>
 <script type="text/javascript"><![CDATA[
    var svg = "http://www.w3.org/2000/svg";
    var maxY = 0;

    function breaks(text) {
        var count = 0;
        for (var i = text.length - 1; i >= 0; i--) {
            if (text.charAt(i) == '\n') {
                count++;
            }
        }
        return count;
    }

    function makeAction(text, x, y, thread) {
        maxY = Math.max(maxY, y);
        var g = document.createElementNS(svg, "g");
        g.setAttribute("class", "action " + thread);
        g.setAttribute("transform", "translate(" + x + ", " + (y + 30) + ")");
        var r = document.createElementNS(svg, "rect");
        r.setAttribute("width", "100");
        r.setAttribute("height", "40");
        var t = document.createElementNS(svg, "text");
        t.setAttribute("x", "50");
        t.setAttribute("y", 25 - (7 * breaks(text)));
        t.appendChild(document.createTextNode(text));
        g.appendChild(r);
        g.appendChild(t);
        return g;
    }

    function makeChoice(text, x, y, thread) {
        maxY = Math.max(maxY, y);
        var g = document.createElementNS(svg, "g");
        g.setAttribute("class", "choice " + thread);
        g.setAttribute("transform", "translate(" + (x + 15) + ", " + (y + 15) + ")");
        var g2 = document.createElementNS(svg, "g");
        g2.setAttribute("transform", "rotate(-45, 35, 35)");
        var r = document.createElementNS(svg, "rect");
        r.setAttribute("width", "70");
        r.setAttribute("height", "70");
        g2.appendChild(r);
        var t = document.createElementNS(svg, "text");
        t.setAttribute("x", "35");
        t.setAttribute("y", 40 - (7 * breaks(text)));
        t.appendChild(document.createTextNode(text));
        g.appendChild(g2);
        g.appendChild(t);
        return g;
    }

    function makeLabelChoice(label, point) {
        var t = document.createElementNS(svg, "text");
        t.setAttribute("x", point.x);
        t.setAttribute("y", point.y);
        t.appendChild(document.createTextNode(label));
        return t;
    }

    function makeLine(sx, sy, ex, ey) {
        maxY = Math.max(maxY, sy, ey);
        var l = document.createElementNS(svg, "line");
        l.setAttribute("x1", sx);
        l.setAttribute("y1", sy);
        l.setAttribute("x2", ex);
        l.setAttribute("y2", ey);
        return l;
    }

    function makeArrow(start, end) {
        var g = document.createElementNS(svg, "g");
        g.appendChild(makeLine(start.x, start.y, end.x, end.y));
        if (start.x != end.x) {
            start.x = end.x + (4 * Math.sign(start.x - end.x));
            g.appendChild(makeLine(start.x, start.y - 4, end.x, end.y));
            g.appendChild(makeLine(start.x, start.y + 4, end.x, end.y));
        } else if (start.y != end.y) {
            start.y = end.y + (4 * Math.sign(start.y - end.y));
            g.appendChild(makeLine(start.x - 4, start.y, end.x, end.y));
            g.appendChild(makeLine(start.x + 4, start.y, end.x, end.y));
        }
        return g;
    }

    function makeVHArrow(start, end) {
        var g = document.createElementNS(svg, "g");
        g.appendChild(makeLine(start.x, start.y, start.x, end.y));
        start.y = end.y;
        g.appendChild(makeArrow(start, end));
        return g;
    }

    function makeHVArrow(start, end) {
        var g = document.createElementNS(svg, "g");
        g.appendChild(makeLine(start.x, start.y, end.x, start.y));
        start.x = end.x;
        g.appendChild(makeArrow(start, end));
        return g;
    }

    function makeVHVArrow(start, end, length) {
        var g = document.createElementNS(svg, "g");
        g.appendChild(makeLine(start.x, start.y, start.x, start.y + length));
        start.y += length;
        g.appendChild(makeLine(start.x, start.y, end.x, start.y));
        start.x = end.x;
        g.appendChild(makeArrow(start, end));
        return g;
    }

    function makeHVHArrow(start, end, length) {
        var g = document.createElementNS(svg, "g");
        g.appendChild(makeLine(start.x, start.y, start.x + length, start.y));
        start.x += length;
        g.appendChild(makeLine(start.x, start.y, start.x, end.y));
        start.y = end.y;
        g.appendChild(makeArrow(start, end));
        return g;
    }

    function translation(group) {
        var r = new RegExp("translate\\((\\d+), (\\d+)\\)");
        var result = r.exec(group.getAttribute("transform"));
        return { x: parseInt(result[1]), y: parseInt(result[2]) };
    }

    function isAction(group) {
        return group.classList.contains("action");
    }

    function isChoice(group) {
        return group.classList.contains("choice");
    }

    function offset(point, x, y) {
        point.x += x;
        point.y += y;
        return point;
    }

    function rightOf(group) {
        var t = translation(group);
        if (isAction(group)) {
            return offset(t, 100, 20);
        }
        if (isChoice(group)) {
            return offset(t, 85, 35);
        }
        return t;
    }

    function leftOf(group) {
        var t = translation(group);
        if (isAction(group)) {
            return offset(t, 0, 20);
        }
        if (isChoice(group)) {
            return offset(t, -15, 35);
        }
        return t;
    }

    function topOf(group) {
        var t = translation(group);
        if (isAction(group)) {
            return offset(t, 50, 0);
        }
        if (isChoice(group)) {
            return offset(t, 35, -15);
        }
        return t;
    }

    function bottomOf(group) {
        var t = translation(group);
        if (isAction(group)) {
            return offset(t, 50, 40);
        }
        if (isChoice(group)) {
            return offset(t, 35, 85);
        }
        return t;
    }

    function midpoint(start, end) {
        return { x: (start.x + end.x) / 2,
                 y: (start.y + end.y) / 2 };
    }

    function makeLegend(label, thread) {
        var t = document.createElementNS(svg, "text");
        t.setAttribute("x", "10");
        t.setAttribute("y", maxY);
        t.setAttribute("class", thread);
        maxY += 15;
        t.appendChild(document.createTextNode(label));
        return t;
    }

    var android = makeAction("Android/Gonk", 20, 0, "parentinput");
    var sendNative = makeAction("DOMWindowUtils\nsendNativeTouchPoint", 20, 100, "parentmain");
    var apzHitTest = makeAction("APZ hit test", 150, 0, "parentcompositor");
    var apzUntransform = makeAction("APZ\nuntransform", 300, 0, "parentcompositor");
    var apzGesture = makeAction("APZ gesture\ndetection", 450, 0, "parentcompositor");
    var apzTransform = makeAction("APZ transform\nupdate", 600, 0, "parentcompositor");
    var compositor = makeAction("Compositor", 750, 0, "parentcompositor");
    var nsAppShell = makeAction("nsAppShell", 150, 100, "parentmain");
    var rootHitTest = makeAction("Gecko hit test\n(root process)", 300, 100, "parentmain");
    var rootEsm = makeAction("Gecko ESM\n(root process)", 450, 100, "parentmain");
    var isEdgeGesture = makeChoice("Edge gesture?", 300, 200, "parentmain");
    var edgeConsume = makeAction("Consume\nevent block", 150, 200, "parentmain");
    var bepjsm = makeAction("BEParent.jsm\nsendTouchEvent", 450, 200, "parentmain");
    var iframeSend = makeAction("HTMLIFrameElement\nsendTouchEvent", 20, 275, "parentmain");
    var isApzTarget = makeChoice("Target\nhas APZ?", 600, 200, "parentmain");
    var sendTouchEvent = makeAction("Target\nsendTouchEventToWindow", 750, 100, "parentmain");
    var injectTouch = makeAction("injectTouchEvent", 750, 200, "parentmain");
    var targetESM = makeAction("Target window\nESM", 750, 450, "bothmain");
    var tabParent = makeAction("TabParent", 750, 350, "parentmain");
    var geckoUntransform = makeAction("Gecko\nuntransform", 600, 350, "parentmain");
    var tabChild = makeAction("TabChild", 450, 350, "childmain");
    var isApzcEnabled = makeChoice("APZ\nenabled?", 300, 350, "childmain");
    var tabGesture = makeAction("TabChild gesture\ndetection", 150, 350, "childmain");
    var childHitTest = makeAction("Gecko hit test\n(child process)", 300, 450, "childmain");
    var childEsm = makeAction("Gecko ESM\n(child process)", 450, 450, "childmain");
    var childContent = makeAction("Content\n(child process)", 600, 450, "childmain");

    document.documentElement.appendChild(android);
    document.documentElement.appendChild(sendNative);
    document.documentElement.appendChild(apzHitTest);
    document.documentElement.appendChild(apzUntransform);
    document.documentElement.appendChild(apzGesture);
    document.documentElement.appendChild(apzTransform);
    document.documentElement.appendChild(compositor);
    document.documentElement.appendChild(nsAppShell);
    document.documentElement.appendChild(rootHitTest);
    document.documentElement.appendChild(rootEsm);
    document.documentElement.appendChild(isEdgeGesture);
    document.documentElement.appendChild(edgeConsume);
    document.documentElement.appendChild(bepjsm);
    document.documentElement.appendChild(iframeSend);
    document.documentElement.appendChild(isApzTarget);
    document.documentElement.appendChild(sendTouchEvent);
    document.documentElement.appendChild(injectTouch);
    document.documentElement.appendChild(targetESM);
    document.documentElement.appendChild(tabParent);
    document.documentElement.appendChild(geckoUntransform);
    document.documentElement.appendChild(tabChild);
    document.documentElement.appendChild(isApzcEnabled);
    document.documentElement.appendChild(tabGesture);
    document.documentElement.appendChild(childHitTest);
    document.documentElement.appendChild(childEsm);
    document.documentElement.appendChild(childContent);

    document.documentElement.appendChild(makeLabelChoice("Y", offset(leftOf(isEdgeGesture), -5, -5)));
    document.documentElement.appendChild(makeLabelChoice("N", offset(rightOf(isEdgeGesture), 5, -5)));
    document.documentElement.appendChild(makeLabelChoice("N", offset(topOf(isApzTarget), 8, -10)));
    document.documentElement.appendChild(makeLabelChoice("Y", offset(rightOf(isApzTarget), 10, 14)));
    document.documentElement.appendChild(makeLabelChoice("N", offset(leftOf(isApzcEnabled), -5, -5)));
    document.documentElement.appendChild(makeLabelChoice("Y", offset(bottomOf(isApzcEnabled), 10, 14)));

    var arrows = document.getElementById('arrows');
    arrows.appendChild(makeArrow(rightOf(android), leftOf(apzHitTest)));
    arrows.appendChild(makeVHVArrow(topOf(sendNative), midpoint(rightOf(android), leftOf(apzHitTest)), -20));
    arrows.appendChild(makeArrow(rightOf(apzHitTest), leftOf(apzUntransform)));
    arrows.appendChild(makeArrow(rightOf(apzUntransform), leftOf(apzGesture)));
    arrows.appendChild(makeArrow(rightOf(apzGesture), leftOf(apzTransform)));
    arrows.appendChild(makeArrow(rightOf(apzTransform), leftOf(compositor)));
    arrows.appendChild(makeVHVArrow(midpoint(leftOf(apzUntransform), rightOf(apzGesture)), topOf(nsAppShell), 40));
    arrows.appendChild(makeArrow(rightOf(nsAppShell), leftOf(rootHitTest)));
    arrows.appendChild(makeArrow(rightOf(rootHitTest), leftOf(rootEsm)));
    arrows.appendChild(makeVHVArrow(bottomOf(rootEsm), topOf(isEdgeGesture), 15));
    arrows.appendChild(makeArrow(leftOf(isEdgeGesture), rightOf(edgeConsume)));
    arrows.appendChild(makeArrow(rightOf(isEdgeGesture), leftOf(bepjsm), 20));
    arrows.appendChild(makeHVArrow(rightOf(iframeSend), bottomOf(bepjsm)));
    arrows.appendChild(makeArrow(rightOf(bepjsm), leftOf(isApzTarget)));
    arrows.appendChild(makeArrow(rightOf(isApzTarget), leftOf(injectTouch)));
    arrows.appendChild(makeArrow(bottomOf(injectTouch), topOf(tabParent)));
    arrows.appendChild(makeVHArrow(topOf(isApzTarget), leftOf(sendTouchEvent)));
    arrows.appendChild(makeHVHArrow(rightOf(sendTouchEvent), rightOf(targetESM), 30));
    arrows.appendChild(makeArrow(leftOf(tabParent), rightOf(geckoUntransform)));
    arrows.appendChild(makeArrow(leftOf(geckoUntransform), rightOf(tabChild)));
    arrows.appendChild(makeArrow(leftOf(tabChild), rightOf(isApzcEnabled)));
    arrows.appendChild(makeArrow(leftOf(isApzcEnabled), rightOf(tabGesture)));
    arrows.appendChild(makeArrow(bottomOf(isApzcEnabled), topOf(childHitTest)));
    arrows.appendChild(makeVHArrow(bottomOf(tabGesture), leftOf(childHitTest)));
    arrows.appendChild(makeArrow(rightOf(childHitTest), leftOf(childEsm)));
    arrows.appendChild(makeArrow(rightOf(childEsm), leftOf(childContent)));
    arrows.appendChild(makeVHVArrow(midpoint(leftOf(apzGesture), rightOf(apzTransform)), topOf(tabChild), 300));

    document.documentElement.appendChild(makeLegend("Main process input thread", "parentinput"));
    document.documentElement.appendChild(makeLegend("Main process main thread", "parentmain"));
    document.documentElement.appendChild(makeLegend("Main process compositor thread", "parentcompositor"));
    document.documentElement.appendChild(makeLegend("Child process main thread", "childmain"));
    document.documentElement.appendChild(makeLegend("Undetermined process main thread", "bothmain"));
 ]]></script>
</svg>