Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
161 lines
9.8 KiB
Plaintext
161 lines
9.8 KiB
Plaintext
/* ── 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 },
|