dikdimon commited on
Commit
b36697d
·
verified ·
1 Parent(s): 34317cc

Upload 4 files

Browse files
!!!!0000-a1111-fix/javascript/fix.js ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ 'use strict';
3
+
4
+ const FIX_TAG = '[a1111-fix]';
5
+ const FIX_VERSION = 'v5.2-hardened';
6
+
7
+ if (window.__a1111_fix_v52_applied) {
8
+ console.warn(FIX_TAG, 'already applied, skipping duplicate load');
9
+ return;
10
+ }
11
+ window.__a1111_fix_v52_applied = true;
12
+
13
+ const wrappedCache = new WeakMap();
14
+ const crashLogOnce = new WeakMap();
15
+ const slowLogOnce = new WeakMap();
16
+
17
+ function markWeakOnce(store, callback, kind) {
18
+ if (typeof callback !== 'function') return false;
19
+ let kinds = store.get(callback);
20
+ if (!kinds) {
21
+ kinds = new Set();
22
+ store.set(callback, kinds);
23
+ }
24
+
25
+ if (kinds.has(kind)) return false;
26
+ kinds.add(kind);
27
+ return true;
28
+ }
29
+
30
+ function isWrappedForKind(callback, kind) {
31
+ return !!(
32
+ callback &&
33
+ typeof callback === 'function' &&
34
+ callback.__a1111_fix_wrapped === true &&
35
+ callback.__a1111_fix_kind === kind &&
36
+ typeof callback.__a1111_fix_original === 'function'
37
+ );
38
+ }
39
+
40
+ function getWrappedCallback(callback, kind, slowThresholdMs) {
41
+ if (typeof callback !== 'function') return callback;
42
+ if (isWrappedForKind(callback, kind)) return callback;
43
+
44
+ let sourceCallback = callback;
45
+ if (
46
+ callback.__a1111_fix_wrapped === true &&
47
+ callback.__a1111_fix_kind === kind &&
48
+ typeof callback.__a1111_fix_original === 'function'
49
+ ) {
50
+ sourceCallback = callback.__a1111_fix_original;
51
+ }
52
+
53
+ let perKind = wrappedCache.get(sourceCallback);
54
+ if (!perKind) {
55
+ perKind = Object.create(null);
56
+ wrappedCache.set(sourceCallback, perKind);
57
+ }
58
+
59
+ if (perKind[kind]) return perKind[kind];
60
+
61
+ const wrapped = function (arg) {
62
+ const t0 = performance.now();
63
+ try {
64
+ return sourceCallback(arg);
65
+ } catch (e) {
66
+ if (markWeakOnce(crashLogOnce, sourceCallback, kind)) {
67
+ console.error(FIX_TAG, kind + ' callback crashed:', sourceCallback.name || '(anonymous)', e);
68
+ }
69
+ } finally {
70
+ const elapsed = performance.now() - t0;
71
+ if (elapsed > slowThresholdMs && markWeakOnce(slowLogOnce, sourceCallback, kind)) {
72
+ console.warn(
73
+ FIX_TAG,
74
+ 'SLOW ' + kind + ': "' + (sourceCallback.name || '(anonymous)') + '" took ' + elapsed.toFixed(0) + 'ms'
75
+ );
76
+ }
77
+ }
78
+ };
79
+
80
+ wrapped.__a1111_fix_wrapped = true;
81
+ wrapped.__a1111_fix_original = sourceCallback;
82
+ wrapped.__a1111_fix_kind = kind;
83
+ perKind[kind] = wrapped;
84
+ return wrapped;
85
+ }
86
+
87
+ function wrapQueue(queue, kind, slowThresholdMs) {
88
+ if (!Array.isArray(queue)) return 0;
89
+
90
+ let wrappedCount = 0;
91
+ for (let i = 0; i < queue.length; i++) {
92
+ if (typeof queue[i] === 'function' && !isWrappedForKind(queue[i], kind)) {
93
+ queue[i] = getWrappedCallback(queue[i], kind, slowThresholdMs);
94
+ wrappedCount++;
95
+ }
96
+ }
97
+ return wrappedCount;
98
+ }
99
+
100
+ function patchQueuePush(queue, kind, slowThresholdMs) {
101
+ if (!Array.isArray(queue) || queue.__a1111_fix_push_patched) return false;
102
+
103
+ const originalPush = queue.push;
104
+ if (typeof originalPush !== 'function') return false;
105
+
106
+ queue.push = function (...items) {
107
+ const wrappedItems = items.map((item) =>
108
+ typeof item === 'function' ? getWrappedCallback(item, kind, slowThresholdMs) : item
109
+ );
110
+ return originalPush.apply(this, wrappedItems);
111
+ };
112
+ queue.__a1111_fix_push_patched = true;
113
+ return true;
114
+ }
115
+
116
+ function safeGradioApp() {
117
+ try {
118
+ if (typeof window.gradioApp === 'function') {
119
+ return window.gradioApp();
120
+ }
121
+ } catch (e) {
122
+ console.warn(FIX_TAG, 'gradioApp() failed while resolving accordion:', e);
123
+ }
124
+ return document;
125
+ }
126
+
127
+ function applyFix1() {
128
+ const originalSchedule = window.scheduleAfterUiUpdateCallbacks;
129
+ if (typeof originalSchedule !== 'function' ||
130
+ typeof window.executeCallbacks !== 'function' ||
131
+ !Array.isArray(window.uiAfterUpdateCallbacks)) {
132
+ console.warn(FIX_TAG, 'scheduleAfterUiUpdateCallbacks/executeCallbacks/uiAfterUpdateCallbacks not found, skipping FIX-1');
133
+ return;
134
+ }
135
+
136
+ if (originalSchedule.__a1111_fix_afterupdate_patched) {
137
+ console.warn(FIX_TAG, 'FIX-1 already patched, skipping duplicate apply');
138
+ return;
139
+ }
140
+
141
+ const MAX_WAIT_MS = 1000;
142
+ let maxWaitTimer = null;
143
+ let running = false;
144
+
145
+ function clearMaxWaitTimer() {
146
+ if (maxWaitTimer) {
147
+ clearTimeout(maxWaitTimer);
148
+ maxWaitTimer = null;
149
+ }
150
+ }
151
+
152
+ function runAfterUpdateNow() {
153
+ clearMaxWaitTimer();
154
+
155
+ const timeoutId = window.uiAfterUpdateTimeout;
156
+ if (timeoutId) {
157
+ clearTimeout(timeoutId);
158
+ window.uiAfterUpdateTimeout = null;
159
+ }
160
+
161
+ if (running) return;
162
+ running = true;
163
+ try {
164
+ if (typeof window.executeCallbacks === 'function') {
165
+ window.executeCallbacks(window.uiAfterUpdateCallbacks);
166
+ }
167
+ } finally {
168
+ running = false;
169
+ }
170
+ }
171
+
172
+ if (!window.executeCallbacks.__a1111_fix_afterupdate_guard) {
173
+ const previousExecuteCallbacks = window.executeCallbacks;
174
+ const guardedExecuteCallbacks = function (queue, arg) {
175
+ if (queue === window.uiAfterUpdateCallbacks) {
176
+ clearMaxWaitTimer();
177
+ }
178
+ return previousExecuteCallbacks(queue, arg);
179
+ };
180
+ guardedExecuteCallbacks.__a1111_fix_afterupdate_guard = true;
181
+ guardedExecuteCallbacks.__a1111_fix_afterupdate_guard_original = previousExecuteCallbacks;
182
+ window.executeCallbacks = guardedExecuteCallbacks;
183
+ }
184
+
185
+ const patchedSchedule = function (...args) {
186
+ const result = originalSchedule.apply(this, args);
187
+
188
+ if (!maxWaitTimer) {
189
+ maxWaitTimer = setTimeout(runAfterUpdateNow, MAX_WAIT_MS);
190
+ }
191
+
192
+ return result;
193
+ };
194
+ patchedSchedule.__a1111_fix_afterupdate_patched = true;
195
+ patchedSchedule.__a1111_fix_afterupdate_original = originalSchedule;
196
+ window.scheduleAfterUiUpdateCallbacks = patchedSchedule;
197
+
198
+ console.log(FIX_TAG, 'FIX-1: bounded after-update max-wait=' + MAX_WAIT_MS + 'ms applied (wrapper mode)');
199
+ }
200
+
201
+ function applyFix2() {
202
+ const configs = [
203
+ { queueName: 'uiLoadedCallbacks', registerName: 'onUiLoaded', slowThresholdMs: 100 },
204
+ { queueName: 'uiAfterUpdateCallbacks', registerName: 'onAfterUiUpdate', slowThresholdMs: 100 },
205
+ { queueName: 'uiUpdateCallbacks', registerName: 'onUiUpdate', slowThresholdMs: 50 },
206
+ { queueName: 'optionsChangedCallbacks', registerName: 'onOptionsChanged', slowThresholdMs: 50 },
207
+ { queueName: 'optionsAvailableCallbacks', registerName: 'onOptionsAvailable', slowThresholdMs: 50 },
208
+ { queueName: 'uiTabChangeCallbacks', registerName: 'onUiTabChange', slowThresholdMs: 50 },
209
+ ];
210
+
211
+ const activeConfigs = [];
212
+ const summary = [];
213
+
214
+ for (const cfg of configs) {
215
+ const queue = window[cfg.queueName];
216
+ const register = window[cfg.registerName];
217
+ if (!Array.isArray(queue) || typeof register !== 'function') {
218
+ continue;
219
+ }
220
+
221
+ const existingCount = wrapQueue(queue, cfg.registerName, cfg.slowThresholdMs);
222
+ patchQueuePush(queue, cfg.registerName, cfg.slowThresholdMs);
223
+
224
+ if (!register.__a1111_fix_register_patched) {
225
+ const originalRegister = register;
226
+ const patchedRegister = function (callback) {
227
+ return originalRegister(getWrappedCallback(callback, cfg.registerName, cfg.slowThresholdMs));
228
+ };
229
+ patchedRegister.__a1111_fix_register_patched = true;
230
+ patchedRegister.__a1111_fix_register_original = originalRegister;
231
+ window[cfg.registerName] = patchedRegister;
232
+ }
233
+
234
+ activeConfigs.push(cfg);
235
+ summary.push(cfg.registerName + '=' + existingCount);
236
+ }
237
+
238
+ if (activeConfigs.length === 0) {
239
+ console.warn(FIX_TAG, 'no callback queues found, skipping FIX-2');
240
+ return;
241
+ }
242
+
243
+ if (typeof window.executeCallbacks === 'function' && !window.executeCallbacks.__a1111_fix_patched) {
244
+ const originalExecuteCallbacks = window.executeCallbacks;
245
+ const patchedExecuteCallbacks = function (queue, arg) {
246
+ for (const cfg of activeConfigs) {
247
+ if (queue === window[cfg.queueName]) {
248
+ wrapQueue(queue, cfg.registerName, cfg.slowThresholdMs);
249
+ break;
250
+ }
251
+ }
252
+ return originalExecuteCallbacks(queue, arg);
253
+ };
254
+ patchedExecuteCallbacks.__a1111_fix_patched = true;
255
+ patchedExecuteCallbacks.__a1111_fix_original = originalExecuteCallbacks;
256
+ window.executeCallbacks = patchedExecuteCallbacks;
257
+ }
258
+
259
+ console.log(FIX_TAG, 'FIX-2: callback guards applied to', summary.join(', '));
260
+ }
261
+
262
+ function applyFix3() {
263
+ if (typeof window.inputAccordionChecked !== 'function') {
264
+ console.warn(FIX_TAG, 'inputAccordionChecked not found, skipping FIX-3');
265
+ return;
266
+ }
267
+
268
+ const warnedOnce = new Set();
269
+
270
+ function isRealAccordion(el) {
271
+ return !!(
272
+ el &&
273
+ el.visibleCheckbox instanceof HTMLInputElement &&
274
+ typeof el.onVisibleCheckboxChange === 'function'
275
+ );
276
+ }
277
+
278
+ function resolveRealAccordion(id) {
279
+ const app = safeGradioApp();
280
+ const direct = app.getElementById ? app.getElementById(id) : null;
281
+ if (isRealAccordion(direct)) return direct;
282
+
283
+ const dotted = app.getElementById ? app.getElementById('.' + id) : null;
284
+ if (isRealAccordion(dotted)) return dotted;
285
+
286
+ if (direct && typeof direct.querySelector === 'function') {
287
+ const nestedCheckbox = direct.querySelector('.input-accordion-checkbox');
288
+ if (nestedCheckbox) {
289
+ const parentAccordion = nestedCheckbox.closest('.input-accordion');
290
+ if (isRealAccordion(parentAccordion)) return parentAccordion;
291
+ }
292
+ }
293
+
294
+ return null;
295
+ }
296
+
297
+ window.inputAccordionChecked = function (id, checked) {
298
+ const realEl = resolveRealAccordion(id);
299
+ if (!realEl) {
300
+ if (!warnedOnce.has(id)) {
301
+ warnedOnce.add(id);
302
+ const app = safeGradioApp();
303
+ const direct = app.getElementById ? app.getElementById(id) : null;
304
+ const dotted = app.getElementById ? app.getElementById('.' + id) : null;
305
+ console.error(
306
+ FIX_TAG, 'FIX-3: cannot resolve accordion for id:', id,
307
+ '\n direct el:', direct,
308
+ '\n direct.visibleCheckbox:', direct && direct.visibleCheckbox,
309
+ '\n direct.visibleCheckbox instanceof HTMLInputElement:',
310
+ direct && (direct.visibleCheckbox instanceof HTMLInputElement),
311
+ '\n dotted el:', dotted
312
+ );
313
+ }
314
+ return;
315
+ }
316
+
317
+ realEl.visibleCheckbox.checked = checked;
318
+ realEl.onVisibleCheckboxChange();
319
+ };
320
+
321
+ console.log(FIX_TAG, 'FIX-3: resilient accordion resolver applied');
322
+ }
323
+
324
+ function applyFix4() {
325
+ window.__a1111ScriptRegistry = window.__a1111ScriptRegistry || {
326
+ mode: 'unknown',
327
+ scripts: {},
328
+ loaded: {},
329
+ errors: {},
330
+ injected_order: [],
331
+ };
332
+
333
+ function registerScriptElement(el) {
334
+ if (!el || el.tagName !== 'SCRIPT') return;
335
+ if (typeof window.__a1111RegisterScript === 'function') {
336
+ window.__a1111RegisterScript(el);
337
+ return;
338
+ }
339
+
340
+ const src = el.getAttribute('src') || '';
341
+ window.__a1111ScriptRegistry.scripts[src] = {
342
+ role: el.getAttribute('data-a1111-role') || 'unknown',
343
+ mode: el.getAttribute('data-a1111-mode') || 'unknown',
344
+ path: el.getAttribute('data-a1111-path') || src,
345
+ basedir: el.getAttribute('data-a1111-basedir') || '',
346
+ };
347
+ if (!window.__a1111ScriptRegistry.injected_order.includes(src)) {
348
+ window.__a1111ScriptRegistry.injected_order.push(src);
349
+ }
350
+ }
351
+
352
+ window.addEventListener('error', function (event) {
353
+ const target = event && event.target;
354
+ if (target && target.tagName === 'SCRIPT') {
355
+ registerScriptElement(target);
356
+ const src = target.getAttribute('src') || '';
357
+ window.__a1111ScriptRegistry.errors[src] = true;
358
+ return;
359
+ }
360
+
361
+ const filename = event && event.filename;
362
+ if (filename) {
363
+ console.error(FIX_TAG, 'window error from script:', filename, event.error || event.message || event);
364
+ }
365
+ }, true);
366
+
367
+ window.addEventListener('unhandledrejection', function (event) {
368
+ console.error(FIX_TAG, 'unhandled promise rejection:', event.reason || event);
369
+ });
370
+
371
+ function summarizeRegistry() {
372
+ const registry = window.__a1111ScriptRegistry || {};
373
+ const scripts = registry.scripts || {};
374
+ const loaded = registry.loaded || {};
375
+ const errors = registry.errors || {};
376
+ const order = registry.injected_order || [];
377
+
378
+ const ok = [];
379
+ const failed = [];
380
+ const pending = [];
381
+
382
+ for (const src of order.length ? order : Object.keys(scripts)) {
383
+ const meta = scripts[src] || {};
384
+ const item = {
385
+ src,
386
+ role: meta.role || 'unknown',
387
+ mode: meta.mode || 'unknown',
388
+ path: meta.path || src,
389
+ };
390
+
391
+ if (errors[src]) {
392
+ failed.push(item);
393
+ } else if (loaded[src]) {
394
+ ok.push(item);
395
+ } else {
396
+ pending.push(item);
397
+ }
398
+ }
399
+
400
+ return { ok, failed, pending, mode: registry.mode || 'unknown' };
401
+ }
402
+
403
+ function summarizeCallbacks() {
404
+ const groups = [
405
+ ['uiLoadedCallbacks', 'onUiLoaded'],
406
+ ['uiAfterUpdateCallbacks', 'onAfterUiUpdate'],
407
+ ['uiUpdateCallbacks', 'onUiUpdate'],
408
+ ['optionsChangedCallbacks', 'onOptionsChanged'],
409
+ ['optionsAvailableCallbacks', 'onOptionsAvailable'],
410
+ ['uiTabChangeCallbacks', 'onUiTabChange'],
411
+ ];
412
+ return groups.map(([queueName, registerName]) => {
413
+ const queue = window[queueName];
414
+ return {
415
+ queue: queueName,
416
+ register: registerName,
417
+ present: Array.isArray(queue),
418
+ count: Array.isArray(queue) ? queue.length : 0,
419
+ };
420
+ });
421
+ }
422
+
423
+ window.__a1111FixReport = function () {
424
+ const report = summarizeRegistry();
425
+ const callbacks = summarizeCallbacks();
426
+ console.log(FIX_TAG, 'loader mode =', report.mode);
427
+ console.log(FIX_TAG, 'loaded scripts =', report.ok.length, 'failed =', report.failed.length, 'pending =', report.pending.length);
428
+ console.table(callbacks);
429
+
430
+ if (report.failed.length) {
431
+ console.warn(FIX_TAG, 'failed scripts:');
432
+ console.table(report.failed);
433
+ }
434
+ if (report.pending.length) {
435
+ console.warn(FIX_TAG, 'pending/unconfirmed scripts:');
436
+ console.table(report.pending);
437
+ }
438
+ if (!report.failed.length && !report.pending.length) {
439
+ console.log(FIX_TAG, 'all tracked scripts loaded cleanly');
440
+ }
441
+ return { ...report, callbacks };
442
+ };
443
+
444
+ window.addEventListener('load', function () {
445
+ setTimeout(function () {
446
+ const report = window.__a1111FixReport();
447
+ if (report.failed.length || report.pending.length) {
448
+ console.warn(FIX_TAG, 'FIX-4: loader diagnostics detected script issues; inspect __a1111FixReport() output');
449
+ }
450
+ }, 1500);
451
+ }, { once: true });
452
+
453
+ console.log(FIX_TAG, 'FIX-4: loader diagnostics ready');
454
+ }
455
+
456
+ applyFix1();
457
+ applyFix2();
458
+ applyFix3();
459
+ applyFix4();
460
+
461
+ console.log(FIX_TAG, FIX_VERSION, 'ready');
462
+ })();
!!!!0000-a1111-fix/metadata.ini ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [Extension]
2
+ Name = 0000-a1111-fix
3
+
4
+ ; Notes:
5
+ ; - real canonical name in current A1111 branches is usually derived from folder name.
6
+ ; - the folder prefix !0000- still keeps this extension very early alphabetically.
7
+ ; - Before targets below are harmless if the target extension does not exist.
8
+ ;
9
+ ; We keep tabs/controlnet/canvas targets because FIX-3 specifically hardens accordion
10
+ ; behavior and FIX-2/4 should be active before heavy JS extensions register callbacks.
11
+
12
+ [javascript/fix.js]
13
+ Before = sd-webui-tabs-extension sd-webui-tabs-extension-main sd-webui-controlnet sd-webui-controlnet-main canvas-zoom sd-webui-canvas-zoom
!!!0000-a1111-fix/javascript/fix.js ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ 'use strict';
3
+
4
+ // =========================================================================
5
+ // 0000-a1111-fix/javascript/fix.js — v4.2
6
+ //
7
+ // Порядок загрузки гарантируется двумя механизмами:
8
+ //
9
+ // 1. Имя '!0000-a1111-fix' — A1111 сортирует user extensions alphabetically
10
+ // (sorted(os.listdir(...))), '!' ставит нас первыми среди пользовательских
11
+ // расширений.
12
+ //
13
+ // 2. metadata.ini [javascript/fix.js] Before = sd-webui-tabs-extension ...
14
+ // A1111 строит topological sort через list_scripts() и учитывает
15
+ // Before/After зависимости для JS-файлов.
16
+ //
17
+ // Что НЕ покрывается:
18
+ // Builtin extensions (extensions-builtin/) грузятся ДО пользовательских.
19
+ // Три builtin используют onUiLoaded: canvas-zoom-and-pan, mobile,
20
+ // prompt-bracket-checker. Все три исторически короткие, но реальная длительность зависит от среды.
21
+ // Источник медленных callbacks — пользовательские расширения (ControlNet и др.).
22
+ //
23
+ // Четыре исправления:
24
+ //
25
+ // [FIX-0] Idempotence guard.
26
+ // Гарантирует однократное применение патча при любых условиях.
27
+ //
28
+ // [FIX-1] scheduleAfterUiUpdateCallbacks — bounded debounce, max-wait 1000ms.
29
+ // Оригинал (script.js:111-115): clearTimeout + setTimeout 200ms.
30
+ // При 50+ расширениях DOM мутирует 2-5 сек непрерывно — uiAfterUpdateTimeout
31
+ // сбрасывается каждый раз, uiAfterUpdateCallbacks так и не вызываются.
32
+ // Фикс: добавляем maxWaitTimer, который форсирует вызов через MAX_WAIT_MS
33
+ // независимо от новых мутаций. uiAfterUpdateTimeout продолжает жить как
34
+ // глобал core (совместимость сохранена).
35
+ // Re-entry guard защищает от рекурсии если callback сам вызывает мутацию.
36
+ //
37
+ // [FIX-2] onUiLoaded timing — полный охват existing + future callbacks.
38
+ // Оригинал (script.js:60-62): uiLoadedCallbacks.push(callback).
39
+ // executeCallbacks (script.js:95-102) — синхронный, не cancelable.
40
+ // v3: оборачивал только БУДУЩИЕ вызовы onUiLoaded — builtin callbacks
41
+ // уже в uiLoadedCallbacks ДО нашего патча → не покрыты.
42
+ // v4: оборачиваем уже существующие элементы uiLoadedCallbacks + будущие.
43
+ // Symbol(WRAPPED) предотвращает двойную обёртку при любом сценарии.
44
+ // Логирование только первого краша на callback (Set по имени).
45
+ //
46
+ // [FIX-3] inputAccordionChecked — resilient resolver для tabs extension.
47
+ // tabs_parser.js (строки 92-100): если accordion.id не null и не
48
+ // начинается с "component-":
49
+ // content.id = accordion.id;
50
+ // accordion.id = '.' + accordion.id;
51
+ // content.visibleCheckbox = {}; <- plain Object, НЕ HTMLInputElement
52
+ // content.onVisibleCheckboxChange = () => {};
53
+ // Кроме того, в оригинальный accordion вставляется клонированный checkbox
54
+ // (строки 73-85) — т.е. .input-accordion-checkbox остаётся В accordion'е.
55
+ //
56
+ // Resolver (resolveRealAccordion) проверяет три пути:
57
+ // 1. getElementById(id) → если реальный accordion (не перемещён)
58
+ // 2. getElementById('.' + id) → tabs: оригинальный accordion переименован
59
+ // 3. querySelector .input-accordion-checkbox внутри el → fallback на случай
60
+ // если tabs extension изменит схему именования id в будущих версиях
61
+ // Log-once через Set предотвращает spam в консоли.
62
+ // =========================================================================
63
+
64
+
65
+ // ─── [FIX-0] Idempotence guard ───────────────────────────────────────────
66
+
67
+ if (window.__a1111_fix_v4_applied) {
68
+ console.warn('[a1111-fix] already applied, skipping duplicate load');
69
+ return;
70
+ }
71
+ window.__a1111_fix_v4_applied = true;
72
+
73
+
74
+ // ─── [FIX-1] scheduleAfterUiUpdateCallbacks — bounded debounce ──��────────
75
+
76
+ function applyFix1() {
77
+ if (typeof scheduleAfterUiUpdateCallbacks === 'undefined' ||
78
+ typeof executeCallbacks === 'undefined' ||
79
+ typeof uiAfterUpdateCallbacks === 'undefined') {
80
+ console.warn('[a1111-fix] scheduleAfterUiUpdateCallbacks/executeCallbacks/uiAfterUpdateCallbacks not found, skipping FIX-1');
81
+ return;
82
+ }
83
+
84
+ const DEBOUNCE_MS = 200;
85
+ const MAX_WAIT_MS = 1000;
86
+
87
+ let debounceTimer = null;
88
+ let maxWaitTimer = null;
89
+ let running = false;
90
+
91
+ function runAfterUpdateCallbacks() {
92
+ // Используем собственный debounce-таймер вместо смешивания bare global
93
+ // uiAfterUpdateTimeout и window.uiAfterUpdateTimeout.
94
+ clearTimeout(debounceTimer);
95
+ clearTimeout(maxWaitTimer);
96
+ debounceTimer = null;
97
+ maxWaitTimer = null;
98
+
99
+ if (running) return;
100
+ running = true;
101
+ try {
102
+ executeCallbacks(uiAfterUpdateCallbacks);
103
+ } finally {
104
+ running = false;
105
+ }
106
+ }
107
+
108
+ window.scheduleAfterUiUpdateCallbacks = function () {
109
+ clearTimeout(debounceTimer);
110
+ debounceTimer = setTimeout(runAfterUpdateCallbacks, DEBOUNCE_MS);
111
+
112
+ if (!maxWaitTimer) {
113
+ maxWaitTimer = setTimeout(runAfterUpdateCallbacks, MAX_WAIT_MS);
114
+ }
115
+ };
116
+
117
+ console.log('[a1111-fix] FIX-1: bounded debounce max-wait=' + MAX_WAIT_MS + 'ms applied');
118
+ }
119
+
120
+
121
+ // ─── [FIX-2] onUiLoaded timing — existing + future callbacks ─────────────
122
+
123
+ function applyFix2() {
124
+ if (typeof onUiLoaded === 'undefined' || typeof uiLoadedCallbacks === 'undefined') {
125
+ console.warn('[a1111-fix] onUiLoaded or uiLoadedCallbacks not found, skipping FIX-2');
126
+ return;
127
+ }
128
+
129
+ const SLOW_THRESHOLD_MS = 100;
130
+ const WRAPPED = Symbol('a1111FixWrapped');
131
+ // WeakSet по исходному callback (не по имени): Set<string> глушил бы все
132
+ // '(anonymous)' callbacks или однофамильцев — один краш скрывал бы другие.
133
+ const crashedOnce = new WeakSet();
134
+
135
+ function wrapCallback(callback) {
136
+ if (typeof callback !== 'function') return callback;
137
+ if (callback[WRAPPED]) return callback; // уже обёрнут — не дублируем
138
+
139
+ const wrapped = function (arg) {
140
+ const t0 = performance.now();
141
+ try {
142
+ return callback(arg);
143
+ } catch (e) {
144
+ // Не бросаем исключение дальше: core executeCallbacks (script.js:95-102)
145
+ // сам ловит ошибки и пишет console.error — двойное логирование не нужно.
146
+ // WeakSet по callback — каждый упавший callback логируется ровно один раз.
147
+ if (!crashedOnce.has(callback)) {
148
+ crashedOnce.add(callback);
149
+ console.error('[a1111-fix] onUiLoaded callback crashed:', callback.name || '(anonymous)', e);
150
+ }
151
+ } finally {
152
+ const elapsed = performance.now() - t0;
153
+ if (elapsed > SLOW_THRESHOLD_MS) {
154
+ console.warn(
155
+ '[a1111-fix] SLOW onUiLoaded: "' + (callback.name || '(anonymous)') + '" took ' + elapsed.toFixed(0) + 'ms'
156
+ );
157
+ }
158
+ }
159
+ };
160
+
161
+ wrapped[WRAPPED] = true;
162
+ wrapped.__a1111_fix_original = callback;
163
+ return wrapped;
164
+ }
165
+
166
+ // 1) Уже зарегистрированные callbacks (builtin extensions и ранние user extensions)
167
+ // uiLoadedCallbacks — глобальный массив (script.js:29), изменяем на месте.
168
+ let existingCount = 0;
169
+ for (let i = 0; i < uiLoadedCallbacks.length; i++) {
170
+ if (typeof uiLoadedCallbacks[i] === 'function') {
171
+ uiLoadedCallbacks[i] = wrapCallback(uiLoadedCallbacks[i]);
172
+ existingCount++;
173
+ }
174
+ }
175
+
176
+ // 2) Будущие callbacks через onUiLoaded(fn)
177
+ const _originalOnUiLoaded = window.onUiLoaded;
178
+ window.onUiLoaded = function (callback) {
179
+ _originalOnUiLoaded(wrapCallback(callback));
180
+ };
181
+
182
+ console.log(
183
+ '[a1111-fix] FIX-2: timing applied to ' + existingCount + ' existing + all future onUiLoaded callbacks'
184
+ );
185
+ }
186
+
187
+
188
+ // ─── [FIX-3] inputAccordionChecked — resilient accordion resolver ─────────
189
+
190
+ function applyFix3() {
191
+ if (typeof inputAccordionChecked === 'undefined') {
192
+ console.warn('[a1111-fix] inputAccordionChecked not found, skipping FIX-3');
193
+ return;
194
+ }
195
+
196
+ const warnedOnce = new Set();
197
+
198
+ /**
199
+ * Проверка, является ли el реальным setupAccordion-элементом.
200
+ * Реальный accordion после setupAccordion (inputAccordion.js:41-43):
201
+ * accordion.visibleCheckbox = visibleCheckbox (document.createElement('INPUT'))
202
+ * accordion.onVisibleCheckboxChange = function() {...}
203
+ * Стаб tabs_parser.js (строки 98-100):
204
+ * content.visibleCheckbox = {} <- plain Object
205
+ * content.onVisibleCheckboxChange = () => {} <- no-op
206
+ */
207
+ function isRealAccordion(el) {
208
+ return !!(
209
+ el &&
210
+ el.visibleCheckbox instanceof HTMLInputElement &&
211
+ typeof el.onVisibleCheckboxChange === 'function'
212
+ );
213
+ }
214
+
215
+ /**
216
+ * Три пути поиска реального accordion'а:
217
+ *
218
+ * 1. getElementById(id) — стандартный путь, работает если tabs extension
219
+ * не трогал этот accordion (нет isInput, или id начинается с "component-").
220
+ *
221
+ * 2. getElementById('.' + id) — tabs_parser.js переименовывает оригинальный
222
+ * accordion: accordion.id = '.' + originalId (строка 94).
223
+ *
224
+ * 3. querySelector('.input-accordion-checkbox') внутри el — tabs_parser.js
225
+ * вставляет клонированный checkbox в content (строки 73-85), но оригинальный
226
+ * .input-accordion-checkbox остаётся внутри переименованного accordion.
227
+ * Этот путь — fallback на случай изменения схемы id в будущих версиях.
228
+ */
229
+ function resolveRealAccordion(id) {
230
+ const direct = gradioApp().getElementById(id);
231
+ if (isRealAccordion(direct)) return direct;
232
+
233
+ // tabs extension: accordion переименован в '.' + id
234
+ const dotted = gradioApp().getElementById('.' + id);
235
+ if (isRealAccordion(dotted)) return dotted;
236
+
237
+ // Fallback: поиск .input-accordion-checkbox в ближайшем контейнере
238
+ // На случай изменения схемы tabs extension или нестандартных layouts
239
+ if (direct) {
240
+ const nestedCheckbox = direct.querySelector('.input-accordion-checkbox');
241
+ if (nestedCheckbox) {
242
+ // Поднимаемся к ближайшему .input-accordion
243
+ const parentAccordion = nestedCheckbox.closest('.input-accordion');
244
+ if (isRealAccordion(parentAccordion)) return parentAccordion;
245
+ }
246
+ }
247
+
248
+ return null;
249
+ }
250
+
251
+ window.inputAccordionChecked = function (id, checked) {
252
+ const realEl = resolveRealAccordion(id);
253
+
254
+ if (!realEl) {
255
+ if (!warnedOnce.has(id)) {
256
+ warnedOnce.add(id);
257
+ const direct = gradioApp().getElementById(id);
258
+ console.error(
259
+ '[a1111-fix] FIX-3: cannot resolve accordion for id:', id,
260
+ '\n direct el:', direct,
261
+ '\n direct.visibleCheckbox:', direct && direct.visibleCheckbox,
262
+ '\n direct.visibleCheckbox instanceof HTMLInputElement:',
263
+ direct && (direct.visibleCheckbox instanceof HTMLInputElement),
264
+ '\n dotted el:', gradioApp().getElementById('.' + id)
265
+ );
266
+ }
267
+ return;
268
+ }
269
+
270
+ realEl.visibleCheckbox.checked = checked;
271
+ realEl.onVisibleCheckboxChange();
272
+ };
273
+
274
+ console.log('[a1111-fix] FIX-3: resilient accordion resolver applied');
275
+ }
276
+
277
+
278
+ // ─── Apply ────────────────────────────────────────────────────────────────
279
+
280
+ applyFix1();
281
+ applyFix2();
282
+ applyFix3();
283
+
284
+ console.log('[a1111-fix] v4.3 ready');
285
+
286
+ })();
!!!0000-a1111-fix/metadata.ini ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [Extension]
2
+ Name = 0000-a1111-fix
3
+
4
+ ; Порядок загрузки JS через topological sort A1111.
5
+ ;
6
+ ; Примечание по canonical name:
7
+ ; В официальной wiki A1111 поле Name описано как canonical name.
8
+ ; Но в загруженном dev-tree (modules/extensions.py) self.canonical_name
9
+ ; перезаписывается именем папки (extension_dirname), а не берётся из Name.
10
+ ; Поэтому на практике в этой ветке реальный canonical name = имя папки.
11
+ ; Поле Name здесь оставлено для читаемости, но не влияет на Before/After логику.
12
+ ;
13
+ ; tabs extension устанавливается двумя способами:
14
+ ; git clone -> папка называется "sd-webui-tabs-extension"
15
+ ; скачать zip с GitHub -> папка называется "sd-webui-tabs-extension-main"
16
+ ;
17
+ ; Перечисляем оба варианта. Несуществующие Before-цели A1111 молча игнорирует
18
+ ; (scripts.py: если scripts.get() и loaded_extensions_scripts.get() вернули None —
19
+ ; пропускается без ошибки).
20
+
21
+ [javascript/fix.js]
22
+ Before = sd-webui-tabs-extension sd-webui-tabs-extension-main