/* ── the long session ───────────────────────────────────────────── Every walk before this one was minutes long. A phone runs the app for hours, and nothing in the port had ever been looked at for a leak. This block is both the instrument and the assertion. The registry is installed by a STEP rather than through Page.addScriptToEvaluateOnNewDocument, and that is load-bearing: the first-run block above reloads three times and reattach() opens a NEW DevTools session, so a script registered on the old one is gone for the rest of the run. Installed from here it covers exactly the laps the assertion is about, and the last step takes it out again. It wraps addEventListener/removeEventListener (holding a WeakRef to anything that is not window or document) and the four timer functions, and it counts only what was registered AFTER it went in and is still ATTACHED. Attached, because the naive counter — add minus remove — climbs about twelve a lap in this app and none of it is a leak: {#key nav.seq} replaces the .screen element on every navigation and the old element's scroll handler goes with it. After the install, because nothing can be registered before it: the absolute count is therefore small (two after the whole walk, eight from a settled boot) and only the DELTA across the laps is the measurement. A listener or an interval stranded once a lap shows as +9 in one and +9 in the other; it was driven to check. Then two laps to warm, a reading latched into __ssBase, nine more laps, and a second reading. A lap is the five tabs, the Library, a record, Play, the mini bar, all three faces of the sheet, the artwork viewer, a row's half sheet, the artist page (a catalog lookup) and back — about five seconds each, and fifty in all. The lap count is what makes the assertion sharp: a one-per-lap leak shows as +9 against a headroom of four, and trimming it to save time blunts the only thing this block is for. Nothing here reaches the network or the disk beyond what any other mock step does, and the last step puts the Library back on whichever section the walk left it on. */ { do: ` window.__ssLibTab = (function () { try { return __ss.nav.stacks.library[0].c.tab || 'albums'; } catch (e) { return 'albums'; } })(); window.__ssLapN = 0; window.__ssLap = function (n) { var nap = function (ms) { return new Promise(function (r) { setTimeout(r, ms); }); }; var tap = function (sel, text) { var all = document.querySelectorAll(sel); for (var k = 0; k < all.length; k++) if (!text || (all[k].textContent || '').indexOf(text) >= 0) { all[k].click(); return true; } return false; }; /* the app's own back, never history.back(): walking off the history mirror leaves file:// and the page is simply gone */ var peel = async function () { for (var b = 0; b < 8; b++) { var nv = __ss.nav; if (!nv.timing && !nv.viewer && !nv.half && !nv.sheet && nv.stacks[nv.tab].length === 1) return; __ss.back(); await nap(190); } }; return (async function () { for (var i = 0; i < n; i++) { /* the counter is global, not the loop's own: the laps run in threes, and a lap that always opened the same record would stop asking for anything new after the first call and quietly measure nothing */ var which = window.__ssLapN++; __ss.go('search'); await nap(90); __ss.go('community'); await nap(90); __ss.go('you'); await nap(90); __ss.go('library'); await nap(200); tap('.segtabs button', 'Albums'); await nap(260); var rel = document.querySelectorAll('.grid2 .release'); if (rel.length) { rel[which % rel.length].click(); await nap(440); } tap('.rec-acts .disc'); await nap(440); tap('.mini'); await nap(380); var fb = document.querySelectorAll('.fbtn'); for (var f = 0; f < fb.length; f++) { fb[f].click(); await nap(110); } tap('.np-art'); await nap(240); /* peeled one layer at a time and only if it opened: a record with no cover never opens the viewer, and a blind pair of backs would pop the album page instead and take the rows below with it */ if (__ss.nav.viewer) { __ss.back(); await nap(220); } if (__ss.nav.sheet) { __ss.back(); await nap(260); } var trk = document.querySelector('.trk .r'); if (trk) { trk.click(); await nap(260); tap('.scrim'); await nap(220); } tap('.rec-artist'); await nap(620); await peel(); __ss.go('home'); await nap(160); } return n; })(); }; (function () { if (window.__ssCount) return 'already'; var recs = [], liveT = new Set(), liveI = new Set(); var ael = EventTarget.prototype.addEventListener, rel = EventTarget.prototype.removeEventListener; EventTarget.prototype.addEventListener = function (t, f) { try { recs.push({ t: t, f: f, w: (this === window || this === document) ? null : new WeakRef(this), dead: false }); } catch (e) {} return ael.apply(this, arguments); }; EventTarget.prototype.removeEventListener = function (t, f) { try { for (var i = recs.length - 1; i >= 0; i--) if (!recs[i].dead && recs[i].t === t && recs[i].f === f) { recs[i].dead = true; break; } } catch (e) {} return rel.apply(this, arguments); }; var st = window.setTimeout, ct = window.clearTimeout, si = window.setInterval, ci = window.clearInterval; window.setTimeout = function (fn, ms) { var id, rest = [].slice.call(arguments, 2); var w = typeof fn === 'function' ? function () { liveT.delete(id); return fn.apply(window, arguments); } : fn; id = st.apply(window, [w, ms].concat(rest)); liveT.add(id); return id; }; window.clearTimeout = function (id) { liveT.delete(id); return ct.call(window, id); }; window.setInterval = function () { var id = si.apply(window, arguments); liveI.add(id); return id; }; window.clearInterval = function (id) { liveI.delete(id); return ci.call(window, id); }; /* a listener is LIVE when its target is window, document, or an element still in the tree; a timer is live until it fires or is cleared */ window.__ssCount = function () { var n = 0; for (var i = 0; i < recs.length; i++) { var r = recs[i]; if (r.dead) continue; if (r.w === null) { n++; continue; } var el = r.w.deref(); if (el && el.isConnected) n++; } return { listeners: n, timeouts: liveT.size, intervals: liveI.size }; }; /* and hands the page back exactly as it was found. A sibling round appending after this block must not inherit a patched EventTarget or a patched clock; __ssCount and __ssBase go with them, so every later probe reads the -1 that means 'nothing has measured this' rather than a frozen number. */ window.__ssUninstall = function () { EventTarget.prototype.addEventListener = ael; EventTarget.prototype.removeEventListener = rel; window.setTimeout = st; window.clearTimeout = ct; window.setInterval = si; window.clearInterval = ci; delete window.__ssCount; delete window.__ssBase; delete window.__ssUninstall; return 'removed'; }; return 'installed'; })() `, wait: 500 }, { do: `__ssLap(2)`, name: "session-warm", wait: 1200 }, /* the baseline, taken after the warm laps have settled and their own probe has run — __ssCensus is defined by the probe itself */ { do: `window.__ssBase = window.__ssCensus(); true`, wait: 300 }, /* nine laps, in threes: cdp.send() gives a step forty-five seconds to answer and a lap is about five, so one call of nine times out, the walk records a failure it did not have, and the probe lands in the middle of a lap instead of at Home */ { do: `__ssLap(3)`, wait: 400 }, { do: `__ssLap(3)`, wait: 400 }, { do: `__ssLap(3)`, name: "session-late", wait: 1400 }, /* back to Home with nothing open, the Library on the section the walk was using before this block, and the registry out of the page, so whatever is appended after this finds the app — and the platform — where the block above left them. This step's own probe therefore reports -1 for every grew* field, which is the sentinel for 'nothing has latched a baseline': session-late is where the numbers are. */ { do: `__settle(); try { __ss.go('library'); __ss.patch({ tab: __ssLibTab }); __ss.go('home'); } catch (e) {} try { __ssUninstall(); } catch (e) {} true`, name: "session-clear", wait: 1000 },