summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/popovers/popover-focus-2.html
blob: 892e5fd68f77c5ea9524a1f72c2c9d6f125910cf (plain)
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
<!DOCTYPE html>
<meta charset="utf-8" />
<title>Popover focus behaviors</title>
<link rel="author" href="mailto:masonf@chromium.org">
<link rel=help href="https://open-ui.org/components/popover.research.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/popover-utils.js"></script>

<div id=fixup>
  <button id=button1 tabindex="0">Button1</button>
  <div popover id=popover0  tabindex="0" style="top:300px">
  </div>
  <div popover id=popover1 style="top:100px">
    <button id=inside_popover1 tabindex="0">Inside1</button>
    <button id=invoker2 popovertarget=popover2 tabindex="0">Nested Invoker 2</button>
    <button id=inside_popover2 tabindex="0">Inside2</button>
  </div>
  <button id=button2 tabindex="0">Button2</button>
  <div popover id=popover_no_invoker tabindex="0" style="top:300px"></div>
  <button popovertarget=popover0 id=invoker0 tabindex="0">Invoker0</button>
  <button popovertarget=popover1 id=invoker1 tabindex="0">Invoker1</button>
  <button id=button3 tabindex="0">Button3</button>
  <div popover id=popover2 style="top:200px">
    <button id=inside_popover3 tabindex="0">Inside3</button>
    <button id=invoker3 popovertarget=popover3 tabindex="0">Nested Invoker 3</button>
  </div>
  <div popover id=popover3 style="top:300px">
    Non-focusable popover
  </div>
  <button id=button4 tabindex="0">Button4</button>
</div>
<style>
  #fixup [popover] {
    bottom:auto;
  }
</style>
<script>
async function verifyFocusOrder(order,description) {
  order[0].focus();
  for(let i=0;i<order.length;++i) {
    const control = order[i];
    assert_equals(document.activeElement,control,`${description}: Step ${i+1}`);
    await sendTab();
  }
  for(let i=order.length-1;i>=0;--i) {
    const control = order[i];
    await sendShiftTab();
    assert_equals(document.activeElement,control,`${description}: Step ${i+1} (backwards)`);
  }
}
promise_test(async t => {
  button1.focus();
  assert_equals(document.activeElement,button1);
  await sendTab();
  assert_equals(document.activeElement,button2,'Hidden popover should be skipped');
  await sendShiftTab();
  assert_equals(document.activeElement,button1,'Hidden popover should be skipped backwards');
  popover_no_invoker.showPopover();
  await sendTab();
  await sendTab();
  assert_equals(document.activeElement,popover_no_invoker,"Focusable popover that is opened without an invoker should get focused");
  await sendTab();
  assert_equals(document.activeElement,invoker0);
  await sendEnter(); // Activate the invoker0
  assert_true(popover0.matches(':popover-open'), 'popover0 should be invoked by invoker0');
  assert_equals(document.activeElement,invoker0,'Focus should not move when popover is shown');
  await sendTab();
  await sendEnter(); // Activate the invoker
  assert_true(popover1.matches(':popover-open'), 'popover1 should be invoked by invoker1');
  assert_equals(document.activeElement,invoker1,'Focus should not move when popover is shown');
  await sendTab();
  // Make invoker1 non-focusable.
  invoker1.disabled = true;
  assert_equals(document.activeElement,inside_popover1,'Focus should move from invoker into the open popover');
  await sendTab();
  assert_equals(document.activeElement,invoker2,'Focus should move within popover');
  await sendShiftTab();
  await sendShiftTab();
  assert_equals(document.activeElement, button1 ,'Focus should not move back to invoker as it is non-focusable');
  // Reset invoker1 to focusable.
  invoker1.disabled = false;
  await verifyFocusOrder([button1, button2, invoker0, invoker1, inside_popover1, invoker2, inside_popover2, button3, button4],'set 1');
  invoker2.focus();
  await sendEnter(); // Activate the nested invoker
  assert_true(popover2.matches(':popover-open'), 'popover2 should be invoked by nested invoker');
  assert_equals(document.activeElement,invoker2,'Focus should stay on the invoker');
  await sendTab();
  assert_equals(document.activeElement,inside_popover3,'Focus should move into nested popover');
  await sendTab();
  assert_equals(document.activeElement,invoker3);
  await sendEnter(); // Activate the (empty) nested invoker
  assert_true(popover3.matches(':popover-open'), 'popover3 should be invoked by nested invoker');
  assert_equals(document.activeElement,invoker3,'Focus should stay on the invoker');
  await sendTab();
  assert_equals(document.activeElement,inside_popover2,'Focus should skip popover without focusable content, going back to higher scope');
  await sendShiftTab();
  assert_equals(document.activeElement,invoker3,'Shift-tab from the higher scope should return to the lower scope');
  await sendTab();
  assert_equals(document.activeElement,inside_popover2);
  await sendTab();
  assert_equals(document.activeElement,button3,'Focus should exit popovers');
  await sendTab();
  assert_equals(document.activeElement,button4,'Focus should skip popovers');
  button1.focus();
  await verifyFocusOrder([button1, button2, invoker0, invoker1, inside_popover1, invoker2, inside_popover3, invoker3, inside_popover2, button3, button4],'set 2');
}, "Popover focus navigation");
</script>

<button id=circular0 popovertarget=popover4 tabindex="0">Invoker</button>
<div id=popover4 popover>
  <button id=circular1 autofocus popovertarget=popover4 popovertargetaction=hide tabindex="0"></button>
  <button id=circular2 popovertarget=popover4 popovertargetaction=show tabindex="0"></button>
  <button id=circular3 popovertarget=popover4 tabindex="0"></button>
</div>
<button id=circular4 tabindex="0">after</button>
<script>
promise_test(async t => {
  circular0.focus();
  await sendEnter(); // Activate the invoker
  await verifyFocusOrder([circular0, circular1, circular2, circular3, circular4],'circular reference');
  popover4.hidePopover();
}, "Circular reference tab navigation");
</script>

<div id=focus-return1>
  <button popovertarget=focus-return1-p popovertargetaction=show tabindex="0">Show popover</button>
  <div popover id=focus-return1-p>
    <button popovertarget=focus-return1-p popovertargetaction=hide autofocus tabindex="0">Hide popover</button>
  </div>
</div>
<script>
promise_test(async t => {
  const invoker = document.querySelector('#focus-return1>button');
  const popover = document.querySelector('#focus-return1>[popover]');
  const hideButton = popover.querySelector('[popovertargetaction=hide]');
  invoker.focus(); // Make sure button is focused.
  assert_equals(document.activeElement,invoker);
  await sendEnter(); // Activate the invoker
  assert_true(popover.matches(':popover-open'), 'popover should be invoked by invoker');
  assert_equals(document.activeElement,hideButton,'Hide button should be focused due to autofocus attribute');
  await sendEnter(); // Activate the hide invoker
  assert_false(popover.matches(':popover-open'), 'popover should be hidden by invoker');
  assert_equals(document.activeElement,invoker,'Focus should be returned to the invoker');
}, "Popover focus returns when popover is hidden by invoker");
</script>

<div id=focus-return2>
  <button popovertarget=focus-return2-p tabindex="0">Toggle popover</button>
  <div popover id=focus-return2-p>Popover with <button tabindex="0">focusable element</button></div>
  <span tabindex=0>Other focusable element</span>
</div>
<script>
promise_test(async t => {
  const invoker = document.querySelector('#focus-return2>button');
  const popover = document.querySelector('#focus-return2>[popover]');
  const otherElement = document.querySelector('#focus-return2>span');
  invoker.focus(); // Make sure button is focused.
  assert_equals(document.activeElement,invoker);
  invoker.click(); // Activate the invoker
  assert_true(popover.matches(':popover-open'), 'popover should be invoked by invoker');
  assert_equals(document.activeElement,invoker,'invoker should still be focused');
  await sendTab();
  assert_equals(document.activeElement,popover.querySelector('button'),'next up is the popover');
  await sendTab();
  assert_equals(document.activeElement,otherElement,'next focus stop is outside the popover');
  await sendEscape(); // Close the popover via ESC
  assert_false(popover.matches(':popover-open'), 'popover should be hidden');
  assert_equals(document.activeElement,otherElement,'focus does not move because it was not inside the popover');
}, "Popover focus only returns to invoker when focus is within the popover");
</script>