@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.
@@ -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">const selfRows = document.getElementById("self-rows");
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
- // Others ordered by when they first connected, so rows don't reshuffle.
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((a, b) => a.firstSeen - b.firstSeen);
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
- // A searchable list of pages; picking one navigates the client's active
563
- // tab there (tabId from activeTab so only that tab moves).
564
- const openNavigateDialog = async (client) => {
565
- dlgTitle.textContent = `Navigate ${shortLabel(client)} to…`;
566
- dlgCopy.style.display = "none";
567
- dlgBody.innerHTML = `
568
- <input class="picker-filter" id="page-filter" type="search" placeholder="Filter pages…" />
569
- <ul class="list" id="page-list"></ul>`;
570
- dlg.showModal();
571
- const pages = await loadPages();
572
- const listEl = document.getElementById("page-list");
573
- const filterEl = document.getElementById("page-filter");
574
- const renderPages = (query) => {
575
- const needle = query.trim().toLowerCase();
576
- const matches = needle
577
- ? pages.filter((p) => p.toLowerCase().includes(needle))
578
- : pages;
579
- listEl.innerHTML = matches.length
580
- ? matches
581
- .slice(0, 300)
582
- .map(
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,