File: interestfor-keyboard-behavior.tentative.html

package info (click to toggle)
firefox 144.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,637,504 kB
  • sloc: cpp: 7,576,692; javascript: 6,430,831; ansic: 3,748,119; python: 1,398,978; xml: 628,810; asm: 438,679; java: 186,194; sh: 63,212; makefile: 19,159; objc: 13,086; perl: 12,986; yacc: 4,583; cs: 3,846; pascal: 3,448; lex: 1,720; ruby: 1,003; exp: 762; php: 436; lisp: 258; awk: 247; sql: 66; sed: 53; csh: 10
file content (229 lines) | stat: -rw-r--r-- 11,018 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
<!DOCTYPE html>
<meta charset="utf-8" />
<meta name="timeout" content="long">
<link rel="author" href="mailto:masonf@chromium.org">
<link rel="help" href="https://open-ui.org/components/interest-invokers.explainer/" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/invoker-utils.js"></script>
<script src="/html/semantics/popovers/resources/popover-utils.js"></script>

<button data-testcase="<button>" interestfor=target>Button</button>

<a data-testcase="<a>" href=foo interestfor=target>Link</a>

<img src="/images/blue.png" usemap="#map">
<map id=map>
  <area data-testcase="<area>" interestfor=target href="/" shape=default>
</map>

<svg viewBox="0 0 100 100" style="width: 100px" xmlns="http://www.w3.org/2000/svg">
  <a data-testcase="SVG <a>" href=foo interestfor=target>
    <text x=50 y=90>SVG A</text>
  </a>
</svg>

<div id=target popover>Popover</div>
<button id="otherbutton">Other button</button>
<button id="another" interestfor=anothertarget>Another Button</button>
<div id=anothertarget popover>Another Popover</div>

<style>
  [interestfor] {
    interest-delay: 0s;
  }
  [interestfor].longhide {
    interest-hide-delay: 10000s;
  }
</style>

<script>
const allInterestForElements = document.querySelectorAll('[data-testcase]');
assert_true(allInterestForElements.length > 0);

function verifyInterest(onlyElements,description) {
  if (!(onlyElements instanceof Array)) {
    onlyElements = [onlyElements];
  }
  [...allInterestForElements, another].forEach(el => {
    const expectInterest = onlyElements.includes(el);
    assert_equals(el.matches(':interest-source'),expectInterest,`${description}, element ${el.dataset.testcase} should ${expectInterest ? "" : "NOT "}have interest`);
  })
}
allInterestForElements.forEach(el => {
  const description = el.dataset.testcase;
  promise_test(async function (t) {
    t.add_cleanup(() => otherbutton.focus());
    target.hidePopover(); // Just in case
    await focusOn(el);
    assert_equals(document.activeElement,el,'Elements should all be focusable');
    assert_true(target.matches(':popover-open'),'Focusing should trigger interest');
    verifyInterest(el,`After show interest in ${description}`);
    await focusOn(otherbutton);
    assert_not_equals(document.activeElement,el);
    assert_false(target.matches(':popover-open'),'Blurring should trigger lose interest');
    verifyInterest(undefined,`After lose interest in ${description}`);
  },`Basic keyboard focus behavior, ${description}`);

  promise_test(async function (t) {
    t.add_cleanup(() => otherbutton.focus());
    target.hidePopover(); // Just in case
    await focusOn(el);
    assert_true(target.matches(':popover-open'),'Focusing should trigger interest');
    verifyInterest(el,`After show interest in ${description}`);
    await sendLoseInterestHotkey();
    assert_false(target.matches(':popover-open'),'Pressing lose interest hot key should trigger lose interest');
    verifyInterest(undefined,`After lose interest in ${description}`);
    await focusOn(otherbutton);
    assert_not_equals(document.activeElement,el);
    assert_false(target.matches(':popover-open'),'Blurring should do nothing at this point');
    verifyInterest(undefined,`After blurring ${description}`);
  },`Lose interest hot key behavior, ${description}`);

  promise_test(async function (t) {
    t.add_cleanup(() => otherbutton.focus());
    // Ensure blurring doesn't immediately lose interest:
    el.classList.add('longhide');
    t.add_cleanup(() => (el.classList.remove('longhide')));
    target.hidePopover(); // Just in case
    await focusOn(el);
    assert_true(target.matches(':popover-open'),'Focusing should trigger interest');
    verifyInterest(el,`After show interest in ${description}`);
    await focusOn(otherbutton);
    assert_not_equals(document.activeElement,el);
    assert_true(target.matches(':popover-open'),'Blurring should not immediately lose interest');
    verifyInterest(el,`After blurring ${description}`);
    // Send lose interest hot key to the other button (not the invoker):
    await sendLoseInterestHotkey();
    assert_false(target.matches(':popover-open'),'Pressing lose interest hot key should trigger lose interest');
    verifyInterest(undefined,`After lose interest in ${description}`);
  },`Lose interest hot key behavior with element not focused, ${description}`);

  promise_test(async function (t) {
    t.add_cleanup(() => otherbutton.focus());
    target.hidePopover(); // Just in case
    target.addEventListener('interest', (e) => e.preventDefault(), {once: true});
    await focusOn(el);
    assert_false(target.matches(':popover-open'));
    verifyInterest(undefined,`Nothing has interest, ${description}`);
  }, `canceling the interest event stops behavior, ${description}`);

  let events = [];
  function addListeners(t,element) {
    const signal = t.get_signal();
    element.addEventListener('interest',(e) => events.push(`${e.target.id} interest`),{signal});
    element.addEventListener('loseinterest',(e) => events.push(`${e.target.id} loseinterest (${e.cancelable ? 'cancelable' : 'not cancelable'})`),{signal});
  }
  promise_test(async function (t) {
    t.add_cleanup(() => otherbutton.focus());
    target.hidePopover(); // Just in case
    anothertarget.hidePopover(); // Just in case
    events = [];
    addListeners(t,target);
    addListeners(t,anothertarget);
    await focusOn(el);
    assert_array_equals(events,['target interest'],'first hotkey');
    verifyInterest(el,`After show interest in ${description}`);
    await focusOn(another);
    assert_array_equals(events,['target interest','target loseinterest (cancelable)','anothertarget interest'],
        'showing interest in another trigger should lose interest in the first, then gain interest in second');
    verifyInterest(another,`After show interest in ${another.id}`);
    await sendLoseInterestHotkey();
    assert_array_equals(events,['target interest','target loseinterest (cancelable)','anothertarget interest','anothertarget loseinterest (not cancelable)']);
    verifyInterest(undefined,`After lose interest in ${another.id}`);
    assert_false(target.matches(':popover-open'));
    assert_false(anothertarget.matches(':popover-open'));
  }, `Showing interest in a second element loses interest in the first, ${description}`);

  promise_test(async function (t) {
    t.add_cleanup(() => otherbutton.focus());
    target.hidePopover(); // Just in case
    anothertarget.hidePopover(); // Just in case
    events = [];
    addListeners(t,target);
    addListeners(t,anothertarget);
    await focusOn(el);
    assert_array_equals(events,['target interest'],'setup');
    verifyInterest(el,`After show interest in ${description}`);
    const signal = t.get_signal();
    let shouldCancelLoseInterest = true;
    target.addEventListener('loseinterest',(e) => {
      if (shouldCancelLoseInterest) {
        e.preventDefault();
      }
    },{signal});
    await focusOn(another);
    assert_array_equals(events,['target interest','target loseinterest (cancelable)','anothertarget interest','target loseinterest (cancelable)'],
        'the loseinterest listener should fire but get cancelled, anothertarget should still get interest, and that should close the first target popover firing another loseinterest');
    events = [];
    verifyInterest([el,another],`${description} should still have interest because loseinterest was cancelled`);
    assert_false(target.matches(':popover-open'),'anothertarget popover opens, closing target');
    assert_true(anothertarget.matches(':popover-open'));
    await sendLoseInterestHotkey();
    assert_array_equals(events,['anothertarget loseinterest (not cancelable)', 'target loseinterest (not cancelable)'],'Lose interest hot key loses interest in all elements');
    assert_false(target.matches(':popover-open'));
    assert_false(anothertarget.matches(':popover-open'));
    verifyInterest(undefined,`Nothing has interest now`);
  }, `Canceling loseinterest caused by keyboard-gained interest cancels interest, ${description}`);
});
</script>

<button id="esc_invoker1" class="longhide" interestfor="esc_target1">ESC Invoker 1</button>
<div id="esc_target1">Non-popover target for ESC test</div>
<button id="esc_invoker2" class="longhide" interestfor="esc_target2">ESC Invoker 2</button>
<div id="esc_target2">Non-popover target for ESC test</div>
<button id="esc_invoker3" class="longhide" interestfor="esc_target3">ESC Invoker 3</button>
<div id="esc_target3">Non-popover target for ESC test</div>

<script>
promise_test(async function (t) {
  const invoker1 = document.getElementById('esc_invoker1');
  const target1 = document.getElementById('esc_target1');
  const invoker2 = document.getElementById('esc_invoker2');
  const target2 = document.getElementById('esc_target2');
  const invoker3 = document.getElementById('esc_invoker3');
  const target3 = document.getElementById('esc_target3');
  const otherbutton = document.getElementById('otherbutton');
  t.add_cleanup(() => otherbutton.focus());

  let events = [];
  const signal = t.get_signal();
  [target1, target2, target3].forEach(target => {
    target.addEventListener('interest',(e) => events.push(`${e.source.id} interest`),{signal});
    target.addEventListener('loseinterest',(e) => events.push(`${e.source.id} loseinterest`),{signal});
    // These loseinterest events should not be cancelable:
    target.addEventListener('loseinterest',(e) => e.preventDefault(),{signal});
  });

  // Invoke them in non-tree order:
  await focusOn(invoker1);
  await focusOn(invoker3);
  await focusOn(invoker2);
  assert_array_equals(events,
    ['esc_invoker1 interest','esc_invoker3 interest','esc_invoker2 interest'],
    'Events after gaining interest');
  events = [];

  // Hit ESC once, while focused on the body
  document.body.focus();
  await waitForRender();
  assert_true(invoker1.matches(':interest-source'), 'invoker1 should still have interest');
  assert_true(invoker3.matches(':interest-source'), 'invoker3 should still have interest');
  assert_true(invoker2.matches(':interest-source'), 'invoker2 should still have interest');
  const kEscape = '\uE00C';
  await new test_driver.Actions()
    .keyDown(kEscape)
    .keyUp(kEscape)
    .send();
  await waitForRender();
  assert_false(invoker2.matches(':interest-source'), 'invoker2 should lose interest');
  assert_false(invoker1.matches(':interest-source'), 'invoker1 should lose interest');
  assert_false(invoker3.matches(':interest-source'), 'invoker3 should lose interest');
  assert_array_equals(events, [
    'esc_invoker2 loseinterest', 'esc_invoker3 loseinterest', 'esc_invoker1 loseinterest'],
    'ESC should lose interest in all invokers, in reverse order');
}, 'ESC key dismisses all interest invokers');
</script>