@jsenv/core 41.4.13 → 41.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/html/clients_page.html +49 -77
- package/dist/js/client_reporter.js +4 -0
- package/dist/js/page_switcher.js +187 -818
- package/dist/page_picker.js +785 -0
- package/dist/start_dev_server/start_dev_server.js +21 -0
- package/package.json +1 -1
- package/src/plugins/client_monitoring/client/client_reporter.js +4 -0
- package/src/plugins/client_monitoring/client/clients_page.html +48 -74
- package/src/plugins/client_monitoring/jsenv_plugin_client_monitoring.js +18 -0
- package/src/plugins/page_switcher/client/page_switcher.js +193 -819
- package/src/plugins/page_switcher/jsenv_plugin_page_switcher.js +3 -0
- package/src/plugins/protocol_file/client/page_picker.js +783 -0
|
@@ -138,23 +138,6 @@ tr:last-child td {
|
|
|
138
138
|
background: #1d4ed8;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
.picker-filter {
|
|
142
|
-
box-sizing: border-box;
|
|
143
|
-
width: 100%;
|
|
144
|
-
font: inherit;
|
|
145
|
-
border: 1px solid #d1d5db;
|
|
146
|
-
border-radius: 6px;
|
|
147
|
-
margin-bottom: 10px;
|
|
148
|
-
padding: 8px 10px;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.pagepick {
|
|
152
|
-
text-align: left;
|
|
153
|
-
word-break: break-all;
|
|
154
|
-
width: 100%;
|
|
155
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
141
|
.monitor {
|
|
159
142
|
color: #2563eb;
|
|
160
143
|
white-space: nowrap;
|
|
@@ -254,6 +237,16 @@ ul.list li:last-child {
|
|
|
254
237
|
<tbody id="other-rows"></tbody>
|
|
255
238
|
</table>
|
|
256
239
|
|
|
240
|
+
<h2>Headless browsers</h2>
|
|
241
|
+
<p class="intro">
|
|
242
|
+
Browsers driven by a program — a test run, a screenshot script — rather
|
|
243
|
+
than by someone looking at them.
|
|
244
|
+
</p>
|
|
245
|
+
<table>
|
|
246
|
+
<thead id="head3"></thead>
|
|
247
|
+
<tbody id="headless-rows"></tbody>
|
|
248
|
+
</table>
|
|
249
|
+
|
|
257
250
|
<dialog id="dlg">
|
|
258
251
|
<div class="dlg-head">
|
|
259
252
|
<strong id="dlg-title"></strong>
|
|
@@ -264,8 +257,11 @@ ul.list li:last-child {
|
|
|
264
257
|
<div class="dlg-body" id="dlg-body"></div>
|
|
265
258
|
</dialog>
|
|
266
259
|
|
|
267
|
-
<script type="module">
|
|
260
|
+
<script type="module">import { openPagePicker } from "../page_picker.js";
|
|
261
|
+
|
|
262
|
+
const selfRows = document.getElementById("self-rows");
|
|
268
263
|
const otherRows = document.getElementById("other-rows");
|
|
264
|
+
const headlessRows = document.getElementById("headless-rows");
|
|
269
265
|
// The monitoring script (injected into this page too) stores our own id.
|
|
270
266
|
let selfId = null;
|
|
271
267
|
try {
|
|
@@ -280,6 +276,7 @@ ul.list li:last-child {
|
|
|
280
276
|
</tr>`;
|
|
281
277
|
document.getElementById("head").innerHTML = headHtml;
|
|
282
278
|
document.getElementById("head2").innerHTML = headHtml;
|
|
279
|
+
document.getElementById("head3").innerHTML = headHtml;
|
|
283
280
|
const COLSPAN = 8;
|
|
284
281
|
|
|
285
282
|
const ago = (ts) => {
|
|
@@ -446,16 +443,26 @@ ul.list li:last-child {
|
|
|
446
443
|
const render = (clients) => {
|
|
447
444
|
lastClients = clients;
|
|
448
445
|
const self = selfId ? clients.find((d) => d.id === selfId) : null;
|
|
449
|
-
//
|
|
446
|
+
// Ordered by when they first connected, so rows don't reshuffle.
|
|
447
|
+
const byFirstSeen = (a, b) => a.firstSeen - b.firstSeen;
|
|
448
|
+
// A headless browser is a program's, not a person's: it comes and goes
|
|
449
|
+
// with a test run, so it gets its own table instead of pushing the
|
|
450
|
+
// clients someone is actually browsing with out of sight.
|
|
450
451
|
const others = clients
|
|
451
|
-
.filter((d) => d !== self)
|
|
452
|
-
.sort(
|
|
452
|
+
.filter((d) => d !== self && !d.headless)
|
|
453
|
+
.sort(byFirstSeen);
|
|
454
|
+
const headless = clients
|
|
455
|
+
.filter((d) => d !== self && d.headless)
|
|
456
|
+
.sort(byFirstSeen);
|
|
453
457
|
selfRows.innerHTML = self
|
|
454
458
|
? rowHtml(self)
|
|
455
459
|
: `<tr><td class="empty" colspan="${COLSPAN}">This browser isn't registered yet — it will appear shortly.</td></tr>`;
|
|
456
460
|
otherRows.innerHTML = others.length
|
|
457
461
|
? others.map(rowHtml).join("")
|
|
458
462
|
: `<tr><td class="empty" colspan="${COLSPAN}">No other client has connected.</td></tr>`;
|
|
463
|
+
headlessRows.innerHTML = headless.length
|
|
464
|
+
? headless.map(rowHtml).join("")
|
|
465
|
+
: `<tr><td class="empty" colspan="${COLSPAN}">No headless browser has connected.</td></tr>`;
|
|
459
466
|
};
|
|
460
467
|
|
|
461
468
|
// Dialogs (all tabs / recent activity / user agent).
|
|
@@ -540,68 +547,33 @@ ul.list li:last-child {
|
|
|
540
547
|
// dev server gone — ignore
|
|
541
548
|
}
|
|
542
549
|
};
|
|
543
|
-
// The .html pages available to navigate to (fetched once, lazily).
|
|
544
|
-
let navigablePages = null;
|
|
545
|
-
const loadPages = async () => {
|
|
546
|
-
if (navigablePages) {
|
|
547
|
-
return navigablePages;
|
|
548
|
-
}
|
|
549
|
-
try {
|
|
550
|
-
navigablePages = await (await fetch("/.internal/pages.json")).json();
|
|
551
|
-
} catch {
|
|
552
|
-
navigablePages = [];
|
|
553
|
-
}
|
|
554
|
-
return navigablePages;
|
|
555
|
-
};
|
|
556
550
|
const shortLabel = (d) => {
|
|
557
551
|
const parts = [d.os && d.os.name, d.runtime && d.runtime.name].filter(
|
|
558
552
|
Boolean,
|
|
559
553
|
);
|
|
560
554
|
return parts.length ? parts.join(" ") : d.id.slice(0, 8);
|
|
561
555
|
};
|
|
562
|
-
//
|
|
563
|
-
//
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
(p) =>
|
|
584
|
-
`<li><button class="btn pagepick" data-url="${escapeHtml(p)}">${escapeHtml(p)}</button></li>`,
|
|
585
|
-
)
|
|
586
|
-
.join("")
|
|
587
|
-
: '<li class="muted">No page matches.</li>';
|
|
588
|
-
};
|
|
589
|
-
renderPages("");
|
|
590
|
-
filterEl.oninput = () => renderPages(filterEl.value);
|
|
591
|
-
filterEl.focus();
|
|
592
|
-
listEl.onclick = (event) => {
|
|
593
|
-
const button = event.target.closest("button.pagepick");
|
|
594
|
-
if (!button) {
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
sendCommand({
|
|
598
|
-
clientId: client.id,
|
|
599
|
-
tabId: client.activeTab ? client.activeTab.id : null,
|
|
600
|
-
type: "navigate",
|
|
601
|
-
url: button.getAttribute("data-url"),
|
|
602
|
-
});
|
|
603
|
-
dlg.close();
|
|
604
|
-
};
|
|
556
|
+
// Which page to send a client to is the same question cmd+K asks on any
|
|
557
|
+
// dev-served page, so it is the same tree that answers it (see
|
|
558
|
+
// protocol_file/client/page_picker.js) — filters, folding and all. Only
|
|
559
|
+
// the ending differs: the url goes to the client's active tab (tabId from
|
|
560
|
+
// activeTab, so no other tab moves) instead of to this window. No href on
|
|
561
|
+
// the rows for that very reason: a click here must not take THIS browser
|
|
562
|
+
// to a page meant for another one.
|
|
563
|
+
const openNavigateDialog = (client) => {
|
|
564
|
+
openPagePicker({
|
|
565
|
+
title: `Navigate ${shortLabel(client)} to…`,
|
|
566
|
+
placeholder: "Go to page…",
|
|
567
|
+
hereUrl: client.activeTab ? client.activeTab.url : null,
|
|
568
|
+
onPick: (page) => {
|
|
569
|
+
sendCommand({
|
|
570
|
+
clientId: client.id,
|
|
571
|
+
tabId: client.activeTab ? client.activeTab.id : null,
|
|
572
|
+
type: "navigate",
|
|
573
|
+
url: page.url,
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
});
|
|
605
577
|
};
|
|
606
578
|
|
|
607
579
|
document.addEventListener("click", async (event) => {
|
|
@@ -185,6 +185,10 @@ const setup = () => {
|
|
|
185
185
|
pendingActivities = [];
|
|
186
186
|
const payload = JSON.stringify({
|
|
187
187
|
clientId,
|
|
188
|
+
// The server reads browser/OS from the request headers, but a headless
|
|
189
|
+
// firefox/webkit looks exactly like a windowed one there; this flag is
|
|
190
|
+
// what tells them apart (chromium also says "Headless" in its user-agent).
|
|
191
|
+
webdriver: window.navigator.webdriver === true,
|
|
188
192
|
tab: tabInfo({ closing }),
|
|
189
193
|
activities,
|
|
190
194
|
logs,
|