Keep normal playback and progress saving active across tab switches

This commit is contained in:
2026-10-06 02:10:13 +08:00
parent b8d830d231
commit 08c9e0aa4d
6 changed files with 161 additions and 28 deletions
+11 -4
View File
@@ -17,7 +17,13 @@ function harness() {
let now = 100000;
class TestDate extends Date { static now() { return now; } }
const storage = new Map(), session = new Map(), selectors = new Map();
const document = {body: null, getElementById: () => null, addEventListener() {},
function eventSurface(){
const listeners=new Map();
return {addEventListener(type,fn,options){const list=listeners.get(type)||[];list.push({fn,capture:options===true||!!options?.capture});listeners.set(type,list);},
dispatchEvent(event){for(const item of [...(listeners.get(event.type)||[])].sort((a,b)=>Number(b.capture)-Number(a.capture))){item.fn(event);if(event.stopped)break;}return true;}};
}
class TestEvent {constructor(type){this.type=type;}stopImmediatePropagation(){this.stopped=true;}}
const document = {...eventSurface(),body: null, visibilityState:'visible',getElementById: () => null,
querySelector: s => selectors.get(s) || null, querySelectorAll: s => selectors.get(s) || []};
class XHR {
open(...args) { this.openArgs = args; }
@@ -25,17 +31,18 @@ function harness() {
addEventListener(name, fn) { this[name] = fn; }
}
const pending = new Promise(() => {});
const window = {fetch() { return pending; }, addEventListener() {}};
const window = {...eventSurface(),fetch() { return pending; }};
window.self = window.top = window;
const context = {window, document, XMLHttpRequest: XHR, location: {
href: `https://icampus.yunyinsh.com/course/#/my-course/learn?id=${ID}`, hash: `#/my-course/learn?id=${ID}`, reload(){this.reloadCount=(this.reloadCount||0)+1;}},
URL, URLSearchParams, Date: TestDate, crypto: {randomUUID: () => 'test-owner'}, TextDecoder,
URL, URLSearchParams, Date: TestDate, crypto: {randomUUID: () => 'test-owner'}, TextDecoder,Event:TestEvent,
localStorage: {getItem: k => storage.get(k) ?? null, setItem: (k,v) => storage.set(k,v), removeItem: k => storage.delete(k)},
sessionStorage: {getItem:k=>session.get(k)??null,setItem:(k,v)=>session.set(k,v),removeItem:k=>session.delete(k)},
setInterval() {}};
const source = fs.readFileSync('./icampus-study-helper.user.js', 'utf8').replace(
'setInterval(() => { void tick(); }, 1000);',
`window.testing = {receive, stop, tick, tickCourse, tickQuiz, monitor, details, holdLease, waitMedia, recoverMedia, mount,
`window.testing = {receive, stop, tick, tickCourse, tickQuiz, monitor, details, holdLease, waitMedia, recoverMedia, mount, setBackgroundEnabled,
pendingCourse(id){expectedCourse=id;courseNavigationPending=true;navigatingUntil=Date.now()+20000;},
recovered(id){recoveryUsed.add('${ID}/'+id);},
begin(){running=true;runNumber++;mode={value:'all'};startAt=Date.now()-1000;},
track(v){active={video:v,id:'${RID}',courseId:'${ID}',src:v.currentSrc,lastPosition:v.currentTime,progressedAt:Date.now(),started:Date.now()};lastNetworkAt=Date.now();},