| 
 | 1 | +<!DOCTYPE html>  | 
 | 2 | +<meta charset=utf-8>  | 
 | 3 | +<title> Only one activation behavior is executed during dispatch</title>  | 
 | 4 | +<link rel=" author"  title=" Vincent Hilla"  href=" mailto:[email protected]" >  | 
 | 5 | +<link rel="help" href="https://dom.spec.whatwg.org/#eventtarget-activation-behavior">  | 
 | 6 | +<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">  | 
 | 7 | +<script src="/resources/testharness.js"></script>  | 
 | 8 | +<script src="/resources/testharnessreport.js"></script>  | 
 | 9 | +<div id=log></div>  | 
 | 10 | + | 
 | 11 | +<div id=test_container></div>  | 
 | 12 | + | 
 | 13 | +<!--  | 
 | 14 | +    Three classes:  | 
 | 15 | +    click  | 
 | 16 | +        Element to be clicked to cause activation behavior  | 
 | 17 | +    activates  | 
 | 18 | +        Element that registers the activation behavior  | 
 | 19 | +    container  | 
 | 20 | +        Element in which other elements with activation behavior are placed.  | 
 | 21 | +        We test that those won't be activated too.  | 
 | 22 | +-->  | 
 | 23 | +<template>  | 
 | 24 | +    <!--input, change event bubble, so have to check if checked is true-->  | 
 | 25 | +    <input class="click activates container" type="checkbox" oninput="this.checked ? activated(this) : null">  | 
 | 26 | +    <input class="click activates container" type="radio" oninput="this.checked ? activated(this) : null">  | 
 | 27 | +    <form onsubmit="activated(this); return false" class="activates">  | 
 | 28 | +        <input class="click container" type="submit">  | 
 | 29 | +    </form>  | 
 | 30 | +    <form onsubmit="activated(this); return false" class="activates">  | 
 | 31 | +        <input class="click container" type="image">  | 
 | 32 | +    </form>  | 
 | 33 | +    <form onreset="activated(this)" class="activates">  | 
 | 34 | +        <input class="click container" type="reset">  | 
 | 35 | +    </form>  | 
 | 36 | +    <form onsubmit="activated(this); return false" class="activates">  | 
 | 37 | +        <button class="click container" type="submit"></button>  | 
 | 38 | +    </form>  | 
 | 39 | +    <form onreset="activated(this)" class="activates">  | 
 | 40 | +        <button class="click container" type="reset"></button>  | 
 | 41 | +    </form>  | 
 | 42 | +    <a href="#link" class="click container activates"></a>  | 
 | 43 | +    <area href="#link" class="click container activates">  | 
 | 44 | +    <details ontoggle="activated(this)" class="activates">  | 
 | 45 | +        <summary class="click container"></summary>  | 
 | 46 | +    </details>  | 
 | 47 | +    <label>  | 
 | 48 | +      <input type=checkbox onclick="this.checked ? activated(this) : null" class="activates">  | 
 | 49 | +      <span class="click container">label</span>  | 
 | 50 | +    </label>  | 
 | 51 | +    <!--activation behavior of label for event targeted at interactive content descendant is to do nothing-->  | 
 | 52 | +    <label class="container">  | 
 | 53 | +        <button class="click" type="button"></button>  | 
 | 54 | +    </label>  | 
 | 55 | +</template>  | 
 | 56 | + | 
 | 57 | +<script>  | 
 | 58 | +let activations = [];  | 
 | 59 | +function activated(e) {  | 
 | 60 | +    activations.push(e);  | 
 | 61 | +}  | 
 | 62 | + | 
 | 63 | +function getActivations(testidx) {  | 
 | 64 | +    return activations.filter(a =>  | 
 | 65 | +        (a.endsWith && a.endsWith("test"+testidx+"_link"))  | 
 | 66 | +        || (a.classList && a.classList.contains("test"+testidx))  | 
 | 67 | +    );  | 
 | 68 | +}  | 
 | 69 | + | 
 | 70 | +// for a and area elements  | 
 | 71 | +window.onhashchange = function(e) {  | 
 | 72 | +    if (e.newURL.endsWith("link")) {  | 
 | 73 | +        activated(e.newURL);  | 
 | 74 | +    }  | 
 | 75 | +    window.location.hash = "";  | 
 | 76 | +};  | 
 | 77 | + | 
 | 78 | +function getElementsByClassNameInclusive(e, clsname) {  | 
 | 79 | +    let ls = Array.from(e.getElementsByClassName(clsname));  | 
 | 80 | +    if (e.classList.contains(clsname)) ls.push(e);  | 
 | 81 | +    return ls;  | 
 | 82 | +}  | 
 | 83 | + | 
 | 84 | +function getClickTarget(e) {  | 
 | 85 | +    return getElementsByClassNameInclusive(e, "click")[0];  | 
 | 86 | +}  | 
 | 87 | + | 
 | 88 | +function getContainer(e) {  | 
 | 89 | +    return getElementsByClassNameInclusive(e, "container")[0];  | 
 | 90 | +}  | 
 | 91 | + | 
 | 92 | +function getExpectedActivations(e) {  | 
 | 93 | +    let ls = getElementsByClassNameInclusive(e, "activates");  | 
 | 94 | + | 
 | 95 | +    // special case, for a and area the window registers the activation  | 
 | 96 | +    // have to use string, as testrunner cannot stringify the window object  | 
 | 97 | +    ls = ls.map(e => e.tagName === "A" || e.tagName === "AREA" ? e.href : e);  | 
 | 98 | + | 
 | 99 | +    return ls;  | 
 | 100 | +}  | 
 | 101 | + | 
 | 102 | +function toString(e) {  | 
 | 103 | +    const children = Array.from(e.children);  | 
 | 104 | +    const childstr = (children.map(toString)).join("");  | 
 | 105 | +    const tag = e.tagName;  | 
 | 106 | +    const typestr = e.type ? " type="+e.type : "";  | 
 | 107 | +    return `<${tag}${typestr}>${childstr}</${tag}>`;  | 
 | 108 | +}  | 
 | 109 | + | 
 | 110 | +// generate O(n^2) test combinations  | 
 | 111 | +const template = document.querySelector("template");  | 
 | 112 | +const elements = Array.from(template.content.children);  | 
 | 113 | +const tests = []  | 
 | 114 | +for (const target of elements) {  | 
 | 115 | +    for (const parent of elements) {  | 
 | 116 | +        if (target === parent) continue;  | 
 | 117 | +        tests.push([target.cloneNode(true), parent.cloneNode(true)])  | 
 | 118 | +    }  | 
 | 119 | +}  | 
 | 120 | + | 
 | 121 | +const test_container = document.getElementById("test_container");  | 
 | 122 | + | 
 | 123 | +/**  | 
 | 124 | + * Test that if two elements in an event target chain have activation behavior,  | 
 | 125 | + * only one of them will be activated.  | 
 | 126 | + *  | 
 | 127 | + * Each child of <template> represents one case of activation behavior.  | 
 | 128 | + * The behavior should be triggered by clicking the element of class click  | 
 | 129 | + * and will manifest as a call to activated().  | 
 | 130 | + *  | 
 | 131 | + * For each [target, parent] in tests, we make target a descendant of parent  | 
 | 132 | + * and test that only target gets activated when dispatching a click.  | 
 | 133 | + */  | 
 | 134 | +for (let i = 0; i < tests.length; i++) {  | 
 | 135 | +    let [target, parent] = tests[i];  | 
 | 136 | +    async_test(function(t) {  | 
 | 137 | +        let test = document.createElement("div");  | 
 | 138 | +        test_container.appendChild(test);  | 
 | 139 | +        test.appendChild(parent);  | 
 | 140 | +        getContainer(parent).appendChild(target);  | 
 | 141 | + | 
 | 142 | +        // for later filtering out the activations belonging to this test  | 
 | 143 | +        for (let e of test.getElementsByClassName("activates")) {  | 
 | 144 | +            e.classList.add("test"+i);  | 
 | 145 | +        }  | 
 | 146 | +        for (let e of test.querySelectorAll("a, area")) {  | 
 | 147 | +            e.href = "#test"+i+"_link";  | 
 | 148 | +        }  | 
 | 149 | + | 
 | 150 | +        getClickTarget(target).click();  | 
 | 151 | + | 
 | 152 | +        // Need to spin event loop twice, as some clicks might dispatch another task  | 
 | 153 | +        t.step_timeout(() => {  | 
 | 154 | +            t.step_timeout(t.step_func_done(() => {  | 
 | 155 | +                    assert_array_equals(getActivations(i), getExpectedActivations(target));  | 
 | 156 | +            }), 0);  | 
 | 157 | +        }, 0);  | 
 | 158 | + | 
 | 159 | +        t.add_cleanup(function() {  | 
 | 160 | +            test_container.removeChild(test);  | 
 | 161 | +        });  | 
 | 162 | +    }, `When clicking child ${toString(target)} of parent ${toString(parent)}, only child should be activated.`);  | 
 | 163 | +}  | 
 | 164 | +</script>  | 
0 commit comments