perfectionist-plugins 2.0.0 → 2.4.1
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/README.md +123 -121
- package/better-client.js +370 -86
- package/hide-unowned-skins.js +194 -225
- package/package.json +36 -37
- package/perfectionist-utils.js +11 -131
- package/unlock-profile-skins.js +153 -67
- package/tools/betterclient-monitor.js +0 -269
package/better-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const PLUGIN_ID = "better-client";
|
|
2
|
-
const version = "1.
|
|
2
|
+
const version = "1.7.0";
|
|
3
3
|
|
|
4
4
|
const MAX_CACHE_ENTRIES = 24;
|
|
5
5
|
const MAX_CACHE_ENTRY_BYTES = 2 * 1024 * 1024;
|
|
@@ -7,17 +7,23 @@ const MAX_CACHE_BYTES = 16 * 1024 * 1024;
|
|
|
7
7
|
const UNGATE_MS = 1000;
|
|
8
8
|
const UNGATE_IDLE_MS = 5000;
|
|
9
9
|
const UNGATE_BACKOFF_AFTER = 20;
|
|
10
|
-
const
|
|
11
|
-
const IMAGE_BATCH =
|
|
12
|
-
|
|
10
|
+
const PHASE_POLL_FALLBACK_MS = 15000;
|
|
11
|
+
const IMAGE_BATCH = 160;
|
|
12
|
+
const IMAGE_QUEUE_LIMIT = 4000;
|
|
13
|
+
const DECISION_CACHE_LIMIT = 256;
|
|
13
14
|
const TELEMETRY_HOST = /(^|\.)(telemetry|analytics|metrics|sentry|bugsnag|crash-?report(er)?|pulse|tracking|beacon|datadog|loggly)(\.|:|$)/i;
|
|
14
|
-
const TELEMETRY_PATH = /(^|\/)(lol-telemetry|lol-analytics|lol-metrics|lol-rms|rms|lol-sentry|lol-crash|telemetry|analytics|metrics|sentry|bugsnag|crash-?report\w*|tracking|beacon)(
|
|
15
|
+
const TELEMETRY_PATH = /(^|\/)(lol-telemetry|lol-analytics|lol-metrics|lol-rms|rms|lol-sentry|lol-crash|telemetry|analytics|metrics|sentry|bugsnag|crash-?report\w*|tracking|beacon|tracing)(\/|\?|$)/i;
|
|
15
16
|
const ASSET_PATH = /^\/lol-game-data\/assets\//i;
|
|
16
17
|
const LOCAL_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
17
18
|
|
|
19
|
+
const CHAMP_SELECT_DATA = /^\/lol-champ-select\/v1\/(session|all-grid-champions|pickable-champion-ids|bannable-champion-ids)$/i;
|
|
20
|
+
const CHAMP_SELECT_RETRY_MS = [120, 260, 520];
|
|
21
|
+
|
|
18
22
|
const STATIC_ENDPOINTS = [
|
|
19
23
|
["/lol-game-data/assets/", 60 * 60 * 1000],
|
|
20
24
|
["/lol-perks/v1/perks", 60 * 60 * 1000],
|
|
25
|
+
["/riotclient/region-locale", 60 * 1000],
|
|
26
|
+
["/lol-summoner/v1/current-summoner", 10 * 1000],
|
|
21
27
|
["/performance/v1/quickstats", 5000],
|
|
22
28
|
];
|
|
23
29
|
|
|
@@ -31,8 +37,33 @@ const UNGATE_SELECTORS = [
|
|
|
31
37
|
".lol-navigation-item",
|
|
32
38
|
"[class*='friends-list']",
|
|
33
39
|
];
|
|
40
|
+
const UNGATE_SELECTOR = UNGATE_SELECTORS.join(",");
|
|
34
41
|
const BLOCK_MARKER = "[disabled], [aria-disabled='true'], [style*='pointer-events']";
|
|
35
42
|
|
|
43
|
+
const PERF_STYLE_ID = "perfectionist-better-client-performance";
|
|
44
|
+
const PERFORMANCE_CSS = `
|
|
45
|
+
[class*="blur"], [class*="Blur"], [class*="backdrop"], [class*="Backdrop"] {
|
|
46
|
+
filter: none !important;
|
|
47
|
+
backdrop-filter: none !important;
|
|
48
|
+
-webkit-backdrop-filter: none !important;
|
|
49
|
+
}
|
|
50
|
+
[class*="particle"], [class*="Particle"], [class*="parallax"], [class*="Parallax"],
|
|
51
|
+
[class*="animated-background"], [class*="animatedBackground"], [class*="glow"], [class*="Glow"] {
|
|
52
|
+
animation: none !important;
|
|
53
|
+
transition: none !important;
|
|
54
|
+
}
|
|
55
|
+
[class*="background"] video, [class*="Background"] video { display: none !important; }
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const OPTIONS = [
|
|
59
|
+
{
|
|
60
|
+
key: "performanceMode",
|
|
61
|
+
label: "Performance mode",
|
|
62
|
+
description: "Turns off background blur, decorative animation and looping background video to save GPU and CPU on weaker machines.",
|
|
63
|
+
default: false,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
36
67
|
let api = null;
|
|
37
68
|
let active = false;
|
|
38
69
|
let phase = "None";
|
|
@@ -41,10 +72,14 @@ let restores = [];
|
|
|
41
72
|
let ungateTimer = null;
|
|
42
73
|
let ungateIdle = 0;
|
|
43
74
|
let phaseTimer = null;
|
|
75
|
+
let phaseWatchListener = null;
|
|
76
|
+
let performanceMode = false;
|
|
77
|
+
let removePerformanceStyle = null;
|
|
44
78
|
let cacheBytes = 0;
|
|
45
79
|
|
|
46
80
|
const cache = new Map();
|
|
47
81
|
const inflight = new Map();
|
|
82
|
+
const blockDecisions = new Map();
|
|
48
83
|
|
|
49
84
|
function baseHref() {
|
|
50
85
|
try {
|
|
@@ -67,11 +102,24 @@ function isLocalHost(host) {
|
|
|
67
102
|
}
|
|
68
103
|
|
|
69
104
|
function isTelemetryUrl(raw, base) {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
105
|
+
const text = typeof raw === "string" ? raw : urlOf(raw);
|
|
106
|
+
const memo = blockDecisions.get(text);
|
|
107
|
+
if (memo !== undefined) return memo;
|
|
108
|
+
|
|
109
|
+
let verdict;
|
|
110
|
+
if (text.startsWith("/") && !text.startsWith("//")) {
|
|
111
|
+
verdict = !ASSET_PATH.test(text) && TELEMETRY_PATH.test(text);
|
|
112
|
+
} else {
|
|
113
|
+
const url = resolveUrl(text, base);
|
|
114
|
+
if (!url || url.protocol === "data:" || url.protocol === "blob:") verdict = false;
|
|
115
|
+
else if (ASSET_PATH.test(url.pathname)) verdict = false;
|
|
116
|
+
else if (isLocalHost(url.hostname)) verdict = TELEMETRY_PATH.test(url.pathname);
|
|
117
|
+
else verdict = TELEMETRY_HOST.test(url.hostname) || TELEMETRY_PATH.test(url.pathname);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (blockDecisions.size >= DECISION_CACHE_LIMIT) blockDecisions.clear();
|
|
121
|
+
blockDecisions.set(text, verdict);
|
|
122
|
+
return verdict;
|
|
75
123
|
}
|
|
76
124
|
|
|
77
125
|
function shouldBlockUrl(raw, base) {
|
|
@@ -140,54 +188,111 @@ function clearVolatileCache() {
|
|
|
140
188
|
}
|
|
141
189
|
}
|
|
142
190
|
|
|
191
|
+
function champSelectDataPath(url) {
|
|
192
|
+
if (!url) return null;
|
|
193
|
+
return CHAMP_SELECT_DATA.test(url.pathname) ? url.pathname.toLowerCase() : null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isEmptyChampSelect(path, json) {
|
|
197
|
+
if (path.endsWith("/session")) {
|
|
198
|
+
if (!json || typeof json !== "object") return true;
|
|
199
|
+
return !Array.isArray(json.myTeam) || json.myTeam.length === 0;
|
|
200
|
+
}
|
|
201
|
+
return Array.isArray(json) && json.length === 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function champSelectPathOf(rawUrl) {
|
|
205
|
+
return champSelectDataPath(resolveUrl(rawUrl));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function primeChampSelect(raw, path) {
|
|
209
|
+
for (const wait of [0, 120, 240]) {
|
|
210
|
+
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
211
|
+
try {
|
|
212
|
+
const response = await raw(path);
|
|
213
|
+
if (!response?.ok) continue;
|
|
214
|
+
const json = await response.json();
|
|
215
|
+
if (!isEmptyChampSelect(path, json)) return;
|
|
216
|
+
} catch {}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
143
220
|
function createFetch(env) {
|
|
144
221
|
const native = env.fetch.bind(env);
|
|
145
222
|
|
|
223
|
+
function repairChampSelect(input, init, request, path) {
|
|
224
|
+
let attempt = 0;
|
|
225
|
+
const step = (current) =>
|
|
226
|
+
current.then((response) => {
|
|
227
|
+
if (!active || phase !== "ChampSelect" || attempt >= CHAMP_SELECT_RETRY_MS.length) return response;
|
|
228
|
+
if (!response || !response.ok || !response.clone) return response;
|
|
229
|
+
return response.clone().json().then(
|
|
230
|
+
(json) => {
|
|
231
|
+
if (!isEmptyChampSelect(path, json)) return response;
|
|
232
|
+
const wait = CHAMP_SELECT_RETRY_MS[attempt];
|
|
233
|
+
attempt += 1;
|
|
234
|
+
return new Promise((resolve) => setTimeout(resolve, wait)).then(() => step(native(input, init)));
|
|
235
|
+
},
|
|
236
|
+
() => response,
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
return step(request);
|
|
240
|
+
}
|
|
241
|
+
|
|
146
242
|
function betterFetch(input, init) {
|
|
147
243
|
const raw = urlOf(input);
|
|
148
|
-
const method = methodOf(input, init);
|
|
149
244
|
|
|
150
245
|
if (active && shouldBlockUrl(raw)) {
|
|
151
246
|
return Promise.resolve(emptyJsonResponse(env));
|
|
152
247
|
}
|
|
153
248
|
|
|
249
|
+
const method = methodOf(input, init);
|
|
250
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
251
|
+
return native(input, init);
|
|
252
|
+
}
|
|
253
|
+
|
|
154
254
|
const url = resolveUrl(raw);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
255
|
+
if (!url || !isLocalHost(url.hostname)) {
|
|
256
|
+
return native(input, init);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const key = `${method} ${url.pathname}${url.search}`;
|
|
260
|
+
const pending = inflight.get(key);
|
|
261
|
+
if (pending) {
|
|
262
|
+
return pending.then((response) => (response?.clone ? response.clone() : response));
|
|
164
263
|
}
|
|
165
264
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const hit =
|
|
265
|
+
const ttl = active ? staticTtl(url.pathname) : 0;
|
|
266
|
+
if (ttl) {
|
|
267
|
+
const hit = cache.get(key);
|
|
169
268
|
if (hit && Date.now() - hit.at < hit.ttl) {
|
|
170
269
|
return Promise.resolve(hit.res.clone());
|
|
171
270
|
}
|
|
172
271
|
}
|
|
173
272
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
273
|
+
let request = native(input, init);
|
|
274
|
+
const dataPath = champSelectDataPath(url);
|
|
275
|
+
if (dataPath && active && phase === "ChampSelect") {
|
|
276
|
+
request = repairChampSelect(input, init, request, dataPath);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
inflight.set(key, request);
|
|
280
|
+
const done = () => inflight.delete(key);
|
|
281
|
+
request.then(done, done);
|
|
282
|
+
|
|
283
|
+
if (ttl) {
|
|
284
|
+
request.then((response) => {
|
|
285
|
+
if (!response?.ok || !active) return;
|
|
286
|
+
storeInCache(key, ttl, response);
|
|
287
|
+
}, () => {});
|
|
186
288
|
}
|
|
187
|
-
|
|
289
|
+
// Every caller gets its own clone; the original response stays unread so
|
|
290
|
+
// concurrent callers can clone it after one caller consumes the body.
|
|
291
|
+
return request.then((response) => (response?.clone ? response.clone() : response));
|
|
188
292
|
}
|
|
189
293
|
|
|
190
294
|
betterFetch.__better = true;
|
|
295
|
+
betterFetch.raw = native;
|
|
191
296
|
return betterFetch;
|
|
192
297
|
}
|
|
193
298
|
|
|
@@ -224,6 +329,17 @@ function installXhr(env) {
|
|
|
224
329
|
emitBlockedXhr(this, env);
|
|
225
330
|
return undefined;
|
|
226
331
|
}
|
|
332
|
+
const path = champSelectPathOf(this.__betterUrl);
|
|
333
|
+
const raw = env.fetch?.raw;
|
|
334
|
+
if (path && raw && active && phase === "ChampSelect" && !this.__betterPrimed) {
|
|
335
|
+
this.__betterPrimed = true;
|
|
336
|
+
primeChampSelect(raw, path).finally(() => {
|
|
337
|
+
try {
|
|
338
|
+
nativeSend.call(this, body);
|
|
339
|
+
} catch {}
|
|
340
|
+
});
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
227
343
|
return nativeSend.call(this, body);
|
|
228
344
|
};
|
|
229
345
|
proto.__better = true;
|
|
@@ -249,9 +365,16 @@ function installBeacon(env) {
|
|
|
249
365
|
};
|
|
250
366
|
}
|
|
251
367
|
|
|
368
|
+
let mediaObserver = null;
|
|
369
|
+
let mediaScanObserver = null;
|
|
370
|
+
let mediaVisibilityListener = null;
|
|
371
|
+
let mediaPaused = new WeakSet();
|
|
372
|
+
|
|
252
373
|
let imageObserver = null;
|
|
253
374
|
let imageQueue = [];
|
|
254
375
|
let imageFlushTimer = null;
|
|
376
|
+
let imageFlushIdle = false;
|
|
377
|
+
let imageVisibilityListener = null;
|
|
255
378
|
|
|
256
379
|
function markAsyncDecode(img) {
|
|
257
380
|
if (!img || img.tagName !== "IMG" || img.decoding === "async") return 0;
|
|
@@ -263,38 +386,144 @@ function markAsyncDecode(img) {
|
|
|
263
386
|
}
|
|
264
387
|
}
|
|
265
388
|
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
389
|
+
function documentView() {
|
|
390
|
+
try {
|
|
391
|
+
return document.defaultView ?? null;
|
|
392
|
+
} catch {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function pageHidden() {
|
|
398
|
+
try {
|
|
399
|
+
return document.hidden === true;
|
|
400
|
+
} catch {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
271
403
|
}
|
|
272
404
|
|
|
273
405
|
function scheduleImageFlush() {
|
|
274
406
|
if (imageFlushTimer !== null) return;
|
|
275
|
-
|
|
407
|
+
const view = documentView();
|
|
408
|
+
if (typeof view?.requestIdleCallback === "function") {
|
|
409
|
+
imageFlushIdle = true;
|
|
410
|
+
imageFlushTimer = view.requestIdleCallback(flushImageQueue, { timeout: 600 });
|
|
411
|
+
} else {
|
|
412
|
+
imageFlushIdle = false;
|
|
413
|
+
imageFlushTimer = setTimeout(flushImageQueue, 150);
|
|
414
|
+
}
|
|
276
415
|
}
|
|
277
416
|
|
|
278
|
-
function
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
417
|
+
function cancelImageFlush() {
|
|
418
|
+
if (imageFlushTimer === null) return;
|
|
419
|
+
const view = documentView();
|
|
420
|
+
if (imageFlushIdle && typeof view?.cancelIdleCallback === "function") view.cancelIdleCallback(imageFlushTimer);
|
|
421
|
+
else clearTimeout(imageFlushTimer);
|
|
422
|
+
imageFlushTimer = null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function scanImages(root) {
|
|
426
|
+
let found;
|
|
286
427
|
try {
|
|
287
|
-
|
|
428
|
+
found = root.querySelectorAll("img[src]:not([decoding='async'])");
|
|
288
429
|
} catch {
|
|
289
|
-
|
|
430
|
+
return 0;
|
|
290
431
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
432
|
+
let marked = 0;
|
|
433
|
+
for (const img of found) marked += markAsyncDecode(img);
|
|
434
|
+
return marked;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function flushImageQueue() {
|
|
438
|
+
imageFlushTimer = null;
|
|
439
|
+
if (pageHidden()) {
|
|
440
|
+
scheduleImageFlush();
|
|
441
|
+
return;
|
|
294
442
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
443
|
+
const batch = imageQueue.splice(0, IMAGE_BATCH);
|
|
444
|
+
for (const root of batch) {
|
|
445
|
+
if (!root?.isConnected) continue;
|
|
446
|
+
markAsyncDecode(root);
|
|
447
|
+
scanImages(root);
|
|
448
|
+
}
|
|
449
|
+
if (imageQueue.length > 0) scheduleImageFlush();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function enqueueImages(root) {
|
|
453
|
+
if (!root || root.nodeType !== 1) return 0;
|
|
454
|
+
if (imageQueue.length >= IMAGE_QUEUE_LIMIT) return 0;
|
|
455
|
+
if (root.tagName !== "IMG" && root.childElementCount === 0) return 0;
|
|
456
|
+
imageQueue.push(root);
|
|
457
|
+
scheduleImageFlush();
|
|
458
|
+
return 1;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function pauseDecorativeVideo(video) {
|
|
462
|
+
if (!video || video.tagName !== "VIDEO" || video.paused) return;
|
|
463
|
+
if (!video.autoplay && !video.muted) return;
|
|
464
|
+
try {
|
|
465
|
+
video.pause();
|
|
466
|
+
mediaPaused.add(video);
|
|
467
|
+
} catch {}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function resumeDecorativeVideo(video) {
|
|
471
|
+
if (!video || !mediaPaused.has(video)) return;
|
|
472
|
+
mediaPaused.delete(video);
|
|
473
|
+
try {
|
|
474
|
+
const play = video.play?.();
|
|
475
|
+
if (play?.catch) play.catch(() => {});
|
|
476
|
+
} catch {}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function installMediaIdle() {
|
|
480
|
+
if (typeof document === "undefined" || !document.documentElement) return () => {};
|
|
481
|
+
const Obs = document.defaultView?.IntersectionObserver ?? IntersectionObserver;
|
|
482
|
+
const Mut = document.defaultView?.MutationObserver ?? MutationObserver;
|
|
483
|
+
if (!Obs || !Mut) return () => {};
|
|
484
|
+
mediaObserver = new Obs(
|
|
485
|
+
(entries) => {
|
|
486
|
+
for (const entry of entries) {
|
|
487
|
+
if (entry.isIntersecting) resumeDecorativeVideo(entry.target);
|
|
488
|
+
else pauseDecorativeVideo(entry.target);
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
{ rootMargin: "240px" },
|
|
492
|
+
);
|
|
493
|
+
const scan = () => {
|
|
494
|
+
for (const video of document.querySelectorAll("video")) {
|
|
495
|
+
if (video.__betterMedia) continue;
|
|
496
|
+
video.__betterMedia = true;
|
|
497
|
+
try {
|
|
498
|
+
mediaObserver.observe(video);
|
|
499
|
+
} catch {}
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
scan();
|
|
503
|
+
mediaScanObserver = new Mut(scan);
|
|
504
|
+
try {
|
|
505
|
+
mediaScanObserver.observe(document.documentElement, { childList: true, subtree: true });
|
|
506
|
+
} catch {}
|
|
507
|
+
mediaVisibilityListener = () => {
|
|
508
|
+
if (pageHidden()) {
|
|
509
|
+
for (const video of document.querySelectorAll("video")) pauseDecorativeVideo(video);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
scan();
|
|
513
|
+
for (const video of document.querySelectorAll("video")) {
|
|
514
|
+
if (video.getClientRects().length > 0) resumeDecorativeVideo(video);
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
document.addEventListener("visibilitychange", mediaVisibilityListener);
|
|
518
|
+
return () => {
|
|
519
|
+
document.removeEventListener("visibilitychange", mediaVisibilityListener);
|
|
520
|
+
mediaVisibilityListener = null;
|
|
521
|
+
mediaScanObserver?.disconnect();
|
|
522
|
+
mediaScanObserver = null;
|
|
523
|
+
mediaObserver?.disconnect();
|
|
524
|
+
mediaObserver = null;
|
|
525
|
+
mediaPaused = new WeakSet();
|
|
526
|
+
};
|
|
298
527
|
}
|
|
299
528
|
|
|
300
529
|
function installImageDecode() {
|
|
@@ -302,31 +531,37 @@ function installImageDecode() {
|
|
|
302
531
|
const Obs = document.defaultView?.MutationObserver ?? MutationObserver;
|
|
303
532
|
if (imageObserver || !Obs) return () => {};
|
|
304
533
|
imageObserver = new Obs((records) => {
|
|
305
|
-
let queued =
|
|
534
|
+
let queued = 0;
|
|
306
535
|
for (const record of records) {
|
|
307
536
|
if (record.type === "attributes") {
|
|
308
|
-
if (
|
|
537
|
+
if (record.target?.tagName === "IMG") queued += enqueueImages(record.target);
|
|
309
538
|
continue;
|
|
310
539
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
540
|
+
const nodes = record.addedNodes;
|
|
541
|
+
for (let index = 0; index < nodes.length; index++) {
|
|
542
|
+
const node = nodes[index];
|
|
543
|
+
if (node?.nodeType === 1) queued += enqueueImages(node);
|
|
314
544
|
}
|
|
545
|
+
if (queued > 400) break;
|
|
315
546
|
}
|
|
316
|
-
if (queued) scheduleImageFlush();
|
|
317
547
|
});
|
|
318
548
|
try {
|
|
319
|
-
imageObserver.observe(document.documentElement, { childList: true, subtree: true
|
|
549
|
+
imageObserver.observe(document.documentElement, { childList: true, subtree: true });
|
|
320
550
|
} catch {
|
|
321
551
|
imageObserver = null;
|
|
322
552
|
return () => {};
|
|
323
553
|
}
|
|
324
554
|
enqueueImages(document.documentElement);
|
|
325
|
-
|
|
555
|
+
imageVisibilityListener = () => {
|
|
556
|
+
if (pageHidden()) return;
|
|
557
|
+
enqueueImages(document.documentElement);
|
|
558
|
+
scheduleImageFlush();
|
|
559
|
+
};
|
|
560
|
+
document.addEventListener("visibilitychange", imageVisibilityListener);
|
|
326
561
|
return () => {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
562
|
+
document.removeEventListener("visibilitychange", imageVisibilityListener);
|
|
563
|
+
imageVisibilityListener = null;
|
|
564
|
+
cancelImageFlush();
|
|
330
565
|
imageQueue = [];
|
|
331
566
|
imageObserver?.disconnect();
|
|
332
567
|
imageObserver = null;
|
|
@@ -366,16 +601,14 @@ function ungateNode(node) {
|
|
|
366
601
|
|
|
367
602
|
function ungatePass() {
|
|
368
603
|
if (!active || phase !== "InProgress") return 0;
|
|
369
|
-
let
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
} catch {
|
|
375
|
-
nodes = [];
|
|
376
|
-
}
|
|
377
|
-
for (const node of nodes) changed += ungateNode(node);
|
|
604
|
+
let nodes;
|
|
605
|
+
try {
|
|
606
|
+
nodes = document.querySelectorAll(UNGATE_SELECTOR);
|
|
607
|
+
} catch {
|
|
608
|
+
return 0;
|
|
378
609
|
}
|
|
610
|
+
let changed = 0;
|
|
611
|
+
for (const node of nodes) changed += ungateNode(node);
|
|
379
612
|
return changed;
|
|
380
613
|
}
|
|
381
614
|
|
|
@@ -386,6 +619,10 @@ function ungateDelay() {
|
|
|
386
619
|
function ungateTick() {
|
|
387
620
|
ungateTimer = null;
|
|
388
621
|
if (!active || phase !== "InProgress") return;
|
|
622
|
+
if (pageHidden()) {
|
|
623
|
+
ungateTimer = setTimeout(ungateTick, UNGATE_IDLE_MS);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
389
626
|
const changed = ungatePass();
|
|
390
627
|
ungateIdle = changed > 0 ? 0 : ungateIdle + 1;
|
|
391
628
|
ungateTimer = setTimeout(ungateTick, ungateDelay());
|
|
@@ -416,14 +653,47 @@ function onPhase(next) {
|
|
|
416
653
|
}
|
|
417
654
|
|
|
418
655
|
async function refreshPhase() {
|
|
419
|
-
if (!active) return;
|
|
420
|
-
if (typeof document !== "undefined" && document.hidden) return;
|
|
656
|
+
if (!active || pageHidden()) return;
|
|
421
657
|
try {
|
|
422
658
|
const current = await api?.lcu?.get?.("/lol-gameflow/v1/gameflow-phase");
|
|
423
659
|
if (current) onPhase(current);
|
|
424
660
|
} catch {}
|
|
425
661
|
}
|
|
426
662
|
|
|
663
|
+
function startPhaseWatch() {
|
|
664
|
+
stopPhaseWatch();
|
|
665
|
+
if (unsubscribes.length === 0) {
|
|
666
|
+
phaseTimer = setInterval(() => {
|
|
667
|
+
if (!pageHidden()) refreshPhase();
|
|
668
|
+
}, PHASE_POLL_FALLBACK_MS);
|
|
669
|
+
}
|
|
670
|
+
phaseWatchListener = () => {
|
|
671
|
+
if (!pageHidden()) refreshPhase();
|
|
672
|
+
};
|
|
673
|
+
document.addEventListener("visibilitychange", phaseWatchListener);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function stopPhaseWatch() {
|
|
677
|
+
clearInterval(phaseTimer);
|
|
678
|
+
phaseTimer = null;
|
|
679
|
+
if (phaseWatchListener) {
|
|
680
|
+
document.removeEventListener("visibilitychange", phaseWatchListener);
|
|
681
|
+
phaseWatchListener = null;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function applyPerformanceMode() {
|
|
686
|
+
if (!api) return;
|
|
687
|
+
if (performanceMode) {
|
|
688
|
+
if (!removePerformanceStyle) removePerformanceStyle = api.addStyle(PERF_STYLE_ID, PERFORMANCE_CSS);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (removePerformanceStyle) {
|
|
692
|
+
removePerformanceStyle();
|
|
693
|
+
removePerformanceStyle = null;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
427
697
|
function installNetwork(env) {
|
|
428
698
|
const installed = [];
|
|
429
699
|
try {
|
|
@@ -449,7 +719,7 @@ function installNetwork(env) {
|
|
|
449
719
|
function onEnable(utils) {
|
|
450
720
|
api = utils;
|
|
451
721
|
active = true;
|
|
452
|
-
restores = [installNetwork(window), installImageDecode()];
|
|
722
|
+
restores = [installNetwork(window), installImageDecode(), installMediaIdle()];
|
|
453
723
|
unsubscribes = [];
|
|
454
724
|
try {
|
|
455
725
|
const off = api.subscribe("/lol-gameflow/v1/gameflow-phase", onPhase);
|
|
@@ -461,16 +731,15 @@ function onEnable(utils) {
|
|
|
461
731
|
.then((value) => onPhase(value))
|
|
462
732
|
.catch(() => {});
|
|
463
733
|
} catch {}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
734
|
+
startPhaseWatch();
|
|
735
|
+
performanceMode = api.getOption?.(PLUGIN_ID, "performanceMode") === true;
|
|
736
|
+
applyPerformanceMode();
|
|
467
737
|
if (phase === "InProgress") startUngate();
|
|
468
738
|
}
|
|
469
739
|
|
|
470
740
|
function onDisable() {
|
|
471
741
|
active = false;
|
|
472
|
-
|
|
473
|
-
phaseTimer = null;
|
|
742
|
+
stopPhaseWatch();
|
|
474
743
|
stopUngate();
|
|
475
744
|
for (const off of unsubscribes) {
|
|
476
745
|
try {
|
|
@@ -484,12 +753,25 @@ function onDisable() {
|
|
|
484
753
|
} catch {}
|
|
485
754
|
}
|
|
486
755
|
restores = [];
|
|
756
|
+
if (removePerformanceStyle) {
|
|
757
|
+
removePerformanceStyle();
|
|
758
|
+
removePerformanceStyle = null;
|
|
759
|
+
}
|
|
760
|
+
performanceMode = false;
|
|
487
761
|
inflight.clear();
|
|
762
|
+
blockDecisions.clear();
|
|
488
763
|
for (const key of [...cache.keys()]) evictOne(key);
|
|
489
764
|
cacheBytes = 0;
|
|
490
765
|
api = null;
|
|
491
766
|
}
|
|
492
767
|
|
|
768
|
+
function onOptionChange(key, value) {
|
|
769
|
+
if (key === "performanceMode") {
|
|
770
|
+
performanceMode = value === true;
|
|
771
|
+
applyPerformanceMode();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
493
775
|
export function init() {
|
|
494
776
|
const registerWith = (utils) => {
|
|
495
777
|
utils.registerPlugin({
|
|
@@ -499,8 +781,10 @@ export function init() {
|
|
|
499
781
|
"Debloats the client: blocks telemetry and analytics calls, merges duplicate requests, caches static client data, decodes images asynchronously and keeps settings, chat and friend requests usable during a game.",
|
|
500
782
|
version,
|
|
501
783
|
defaultEnabled: true,
|
|
784
|
+
options: OPTIONS,
|
|
502
785
|
onEnable,
|
|
503
786
|
onDisable,
|
|
787
|
+
onOptionChange,
|
|
504
788
|
});
|
|
505
789
|
};
|
|
506
790
|
|