Repository navigation
Expand file tree
/
Copy pathtabs.html
More file actions
213 lines (196 loc) · 11.3 KB
/
Copy pathtabs.html
File metadata and controls
213 lines (196 loc) · 11.3 KB
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
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Multi-tab · LCA Playground</title>
<meta name="description" content="Every way a new tab gets opened — anchor, script, twin, framed, blocked — with a live handoff inbox so switching back to the first tab asserts something real.">
<meta name="theme-color" content="#060912">
<link rel="icon" href="assets/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/playground.css">
</head>
<body data-sub="tabs.html">
<main id="main" tabindex="-1" class="wrap narrow page">
<header class="page__head">
<span class="eyebrow">Multi-tab mechanics</span>
<h1>Licence console</h1>
<p class="page__note">Each opener below produces a new tab a different way — a real anchor, a scripted open, an identical twin, a framed target, and one that opens nothing at all. Approving anything in a child tab posts a code back to the <strong>handoff inbox</strong> here, so “switch back to the first tab and validate” asserts a live fact instead of re-reading static copy.</p>
</header>
<section class="surface">
<div class="surface__head"><h2 class="surface__title">Open a tab</h2><span class="surface__meta">5 routes, 5 outcomes</span></div>
<div class="surface__body stack">
<div class="grid2">
<div class="stack">
<a id="openInvoice" class="dl-btn" href="tabs/target.html?tab=B" target="_blank" rel="opener">Open invoice in a new tab</a>
<span class="meta">Real <code>target="_blank"</code> anchor → tab <strong>B</strong> (<code>rel="opener"</code>, since Chrome defaults <code>_blank</code> to noopener). Whether script may close it is the browser's call — <em>Close this tab</em> over there reports what actually happened.</span>
</div>
<div class="stack">
<button id="openScript" class="btn-primary" disabled>Open invoice (scripted)</button>
<span class="meta"><code>window.open</code> → tab <strong>C</strong>. Script-opened, so <em>Close this tab</em> always works there. Arms once the flag resolves.</span>
</div>
<div class="stack">
<button id="openTwin">Open a second copy of tab B</button>
<span class="meta">Byte-identical URL <em>and</em> title. Only the index tells them apart.</span>
</div>
<div class="stack">
<button id="openFramed">Open the framed approval</button>
<span class="meta">Tab <strong>F</strong> — its control is inside an iframe. Tab switch, then frame switch.</span>
</div>
<div class="stack">
<button id="openSelfClosing">Open a tab that closes itself</button>
<span class="meta">Tab <strong>D</strong> closes after 6 s, shifting every later index down.</span>
</div>
<div class="stack">
<button id="openBlocked" class="decoy">Open statement in a new tab</button>
<span class="meta">Labelled like an opener, opens nothing. The honest negative case.</span>
</div>
</div>
<p id="openState" class="mono" role="status" aria-live="polite">No opener used yet.</p>
</div>
</section>
<section class="surface">
<div class="surface__head"><h2 class="surface__title">Handoff inbox</h2><span class="live" data-state="healthy"><i></i>live</span></div>
<div class="surface__body stack">
<p id="handoff" class="mono" role="status" aria-live="polite">No handoff received yet.</p>
<div class="rows" id="handoffLog"></div>
<span class="meta">Written by whichever child tab you approved. This is the readout to assert after switching back.</span>
</div>
</section>
<section class="surface">
<div class="surface__head"><h2 class="surface__title">Work that stays on this tab</h2><span class="surface__meta">no switch required</span></div>
<div class="surface__body stack">
<div class="rows">
<div class="row"><span class="row__k">Licence seats</span><span class="row__v" id="seats">1</span></div>
<div class="row"><span class="row__k">Plan</span><span class="row__v">Enterprise · annual</span></div>
</div>
<div class="inline">
<button id="addSeat">Add licence seat</button>
<button id="showDetails" class="decoy">Show licence details</button>
</div>
<p id="seatState" class="mono" role="status" aria-live="polite">Seats unchanged.</p>
<div id="details" hidden>
<hr class="divider">
<p class="mono" id="detailsText">Licence details expanded · contract LC-88231 · renews 2027-04-01</p>
</div>
<span class="meta"><em>Show licence details</em> reveals content in <strong>this</strong> tab. Nothing opens — use it to prove a run does not invent a tab switch.</span>
</div>
</section>
<section class="surface">
<div class="surface__head"><h2 class="surface__title">Tabs this page opened</h2><span class="surface__meta">open order · live status</span></div>
<div class="surface__body"><div id="registry"></div></div>
<div class="surface__foot">
<p class="meta" id="flagNote">Reading the shared break flag…</p>
</div>
</section>
<footer class="page-foot">
<p>Targets: <code>tabs/target.html</code> · <code>tabs/framed.html</code>. Scoring scenarios live on <a href="smart-tab.html">smart-tab.html</a>.</p>
<a href="index.html">← Playground hub</a>
</footer>
</main>
<script src="flag.js"></script>
<script src="assets/page.js"></script>
<script src="assets/tabkit.js"></script>
<script>
(function(){
var openState = document.getElementById('openState');
function say(msg, bad){
openState.textContent = msg;
openState.className = 'mono ' + (bad ? 'is-bad' : 'is-ok');
}
LCA.registry(document.getElementById('registry'));
/* ---------------- handoff inbox ---------------- */
/* token-gated: only handoffs from tabs THIS page opened land here —
the channel is origin-wide and other playground pages open tabs too */
var log = [];
LCA.on(function(msg){
if(msg.type !== 'handoff' || msg.openerToken !== LCA.token) return;
log.push(msg);
var h = document.getElementById('handoff');
h.textContent = 'Handoff received from tab ' + msg.label + ' · ' + msg.code +
(msg.note ? ' · note "' + msg.note + '"' : '');
h.className = 'mono is-ok';
document.getElementById('handoffLog').innerHTML = log.map(function(m, i){
return '<div class="row"><span class="row__k">#' + (i+1) + ' tab ' + LCA.esc(m.label) +
'</span><span class="row__v">' + LCA.esc(m.code) + '</span></div>';
}).join('');
});
/* ---------------- openers ---------------- */
document.getElementById('openInvoice').addEventListener('click', function(){
say('Anchor opener used — tab B should now be open.');
});
document.getElementById('openTwin').addEventListener('click', function(){
LCA.open('tabs/target.html?tab=B', '_blank');
say('Twin of tab B opened. Two tabs now share a URL and a title.');
});
document.getElementById('openFramed').addEventListener('click', function(){
var rec = LCA.open('tabs/framed.html?tab=F&title=' + encodeURIComponent('Hosted approval · Tab F'), '_blank');
say(rec.blocked ? 'The framed approval was blocked by the browser.' : 'Framed approval opened as tab F.', rec.blocked);
});
document.getElementById('openSelfClosing').addEventListener('click', function(){
LCA.open('tabs/target.html?tab=D&autoclose=6000', '_blank');
say('Tab D opened and will close itself in 6 s.');
});
document.getElementById('openBlocked').addEventListener('click', function(){
say('Blocked by site policy — no tab was opened.', true);
});
/* ---------------- work that stays here ---------------- */
var seats = 1;
document.getElementById('addSeat').addEventListener('click', function(){
seats++;
document.getElementById('seats').textContent = String(seats);
var s = document.getElementById('seatState');
s.textContent = 'Seats updated on this tab · now ' + seats;
s.className = 'mono is-ok';
});
document.getElementById('showDetails').addEventListener('click', function(){
document.getElementById('details').hidden = false;
say('Licence details expanded on this tab. No tab was opened.');
});
/* ---------------- the break: an index-shifting decoy ----------------
healthy : the scripted opener yields exactly one tab (invoice = index 1)
drift : a Notifications tab opens FIRST, so the invoice lands on index 2
gone : only Notifications opens — the invoice tab never appears
Same URL, same button, same click. Only the index moves. */
lcaResolve().then(function(r){
var note = document.getElementById('flagNote');
var copy = {
healthy: 'Flag healthy — “Open invoice (scripted)” opens one tab: the invoice, at index 1.',
drift: 'Flag drifted — “Open invoice (scripted)” opens a Notifications tab first, pushing the invoice to index 2.',
gone: 'Flag gone — “Open invoice (scripted)” opens Notifications only. The invoice tab never appears.'
};
note.textContent = copy[r.state] + ' (source: ' + r.source + ')';
note.className = 'meta ' + (r.state === 'healthy' ? 'is-ok' : 'is-bad');
/* The button stays disabled until the flag resolves: binding the
handler late but leaving the button clickable made a fast click a
silent no-op — the worst kind of record/replay skew. */
var btn = document.getElementById('openScript');
btn.disabled = false;
btn.addEventListener('click', function(){
if(r.state === 'healthy'){
var rec = LCA.open('tabs/target.html?tab=C', '_blank');
say(rec.blocked ? 'The invoice tab was blocked by the browser.' : 'Invoice opened as the only new tab (index 1).', rec.blocked);
return;
}
/* Both opens stay inside this one click handler — a deferred
window.open loses the user gesture entirely. Note: two popups in
one gesture needs popup blocking off (BrowserStack Automate and
the LCNC launch paths run Chrome that way; a default local
Chrome may block the second and the registry will say so). */
var n = LCA.open('tabs/target.html?tab=N&title=Notifications%20%C2%B7%20LCA%20Playground', '_blank');
if(r.state === 'gone'){
say(n.blocked ? 'Notifications was blocked by the browser.' : 'Notifications opened. The invoice tab did not open at all.', true);
}else{
var c = LCA.open('tabs/target.html?tab=C', '_blank');
say(c.blocked
? 'Notifications opened but the invoice was popup-blocked — allow popups for this site to exercise the index shift.'
: 'Notifications opened first — the invoice is now index 2, not index 1.', true);
}
});
});
})();
</script>
</body>
</html>