PageSourceSearch

https://teranildiz.pages.dev/privacy-wipe/wipe-engine.js

js teranildiz.pages.dev collected 2026-10-03 05:58:31 UTC 36,214 bytes, 733 lines download raw bytes

1/* ══════════════════════════════════════════════════════════════════════
2 * TERAN ILDIZ — IZNI O'CHIRISH DVIGATELI (wipe-engine.js) v40
3 *
4 * Vazifa: shu qurilmada ilova qoldirgan BARCHA izni tag-tugi bilan
5 * o'chirish — tez, aniq va har qanday brauzerda (Android, iOS, desktop).
6 *
7 * ISHLASH TAMOYILI
8 *  1) TAYYORGARLIK — o'chirishni to'sib qo'yadigan narsalar yopiladi
9 *     (Service Worker, ochiq IndexedDB ulanishlari, push obunalari).
10 *  2) PARALLEL TOZALASH — barcha saqlash turlari bir vaqtda tozalanadi.
11 *     Har bir vazifa o'z vaqt chegarasiga ega: bittasi tiqilib qolsa
12 *     qolganlarini kutib turmaydi (shu sabab TEZ ishlaydi).
13 *  3) TEKSHIRUV — nima qolganini o'lchaymiz (navigator.storage.estimate,
14 *     caches.keys, indexedDB.databases, localStorage.length, cookie...).
15 *  4) QAYTA URINISH — qolgani bo'lsa yana tozalaymiz (3 martagacha).
16 *     "rm -rf" mantig'i: qolguncha emas, QOLMAGUNCHA.
17 *  5) HISOBOT — nima o'chdi, nima qoldi — aniq ko'rsatiladi.
18 *
19 * QAMROV (17 tur):
20 *   localStorage · sessionStorage · Cache Storage · Service Worker ·
21 *   IndexedDB · OPFS (fayl tizimi) · WebSQL (eski) · Cookie ·
22 *   Push obunasi · Bildirishnomalar · Fon sinxronizatsiyasi ·
23 *   Blob/Object URL · Xotiradagi holat · Tarix holati ·
24 *   Media sessiyasi · Hisob ma'lumotlari · Server (Clear-Site-Data)
25 *
26 * BRAUZER FARQLARI
27 *   • iOS Safari'da indexedDB.databases() YO'Q → nomlar ro'yxati bo'yicha
28 *     ham o'chiramiz (aniqlangan + ma'lum nomlar birlashtiriladi).
29 *   • Eski Android WebView'da caches/OPFS bo'lmasligi mumkin → har bir
30 *     imkoniyat alohida tekshiriladi, yo'q bo'lsa o'tkazib yuboriladi.
31 *   • Hech bir vazifa xato tashlamaydi — barchasi yutiladi va hisobotga
32 *     yoziladi, ya'ni bitta xato butun jarayonni to'xtatmaydi.
33 * ══════════════════════════════════════════════════════════════════════ */
34(function () {
35  'use strict';
36  var TI = window.TI = window.TI || {};
37
38  /* ─────────── Yordamchilar ─────────── */
39
40  /* Har bir vazifani vaqt chegarasi bilan bajarish — tiqilib qolmasin */
41  function guard(promise, ms, label) {
42    return new Promise(function (resolve) {
43      var done = false;
44      var t = setTimeout(function () {
45        if (done) return; done = true;
46        resolve({ ok: false, timeout: true, err: 'vaqt tugadi (' + ms + 'ms)' });
47      }, ms);
48      Promise.resolve()
49        .then(function () { return promise; })
50        .then(function (v) { if (done) return; done = true; clearTimeout(t); resolve({ ok: true, val: v }); })
51        .catch(function (e) { if (done) return; done = true; clearTimeout(t); resolve({ ok: false, err: String((e && e.message) || e) }); });
52    });
53  }
54
55  function has(o, k) { try { return !!(o && o[k]); } catch (e) { return false; } }
56
57  /* Ilova qanday rejimda ochilgan: o'rnatilgan PWA (standalone) yoki
58     oddiy brauzer varag'i. Bu ikkisi uchun chiqish xabari boshqacha
59     bo'lishi kerak — o'rnatilgan bo'lmasa "ilovani o'chiring" ko'rsatmasi
60     mantiqsiz (o'chirish uchun hech narsa yo'q). */
61  function detectMode() {
62    try {
63      if (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) return 'standalone';
64    } catch (e) {}
65    try { if (navigator.standalone === true) return 'standalone'; /* eski iOS Safari */ } catch (e) {}
66    try {
67      if (document.referrer && document.referrer.indexOf('android-app://') === 0) return 'standalone';
68    } catch (e) {}
69    return 'browser';
70  }
71
72  /* ─────────── 1) TAYYORGARLIK ─────────── */
73
74  /* Service Worker'ni ro'yxatdan chiqarish — u keshni ushlab turishi mumkin */
75  async function killServiceWorkers() {
76    if (!has(navigator, 'serviceWorker')) return { skip: true };
77    var n = 0;
78    try {
79      var regs = await navigator.serviceWorker.getRegistrations();
80      for (var i = 0; i < regs.length; i++) {
81        /* avval push obunasini bekor qilamiz */
82        try {
83          var sub = await regs[i].pushManager.getSubscription();
84          if (sub) await sub.unsubscribe();
85        } catch (e) {}
86        /* fon sinxronizatsiyasi teglari */
87        try {
88          if (regs[i].periodicSync && regs[i].periodicSync.getTags) {
89            var tags = await regs[i].periodicSync.getTags();
90            for (var j = 0; j < tags.length; j++) { try { await regs[i].periodicSync.unregister(tags[j]); } catch (e) {} }
91          }
92        } catch (e) {}
93        try { if (await regs[i].unregister()) n++; } catch (e) {}
94      }
95    } catch (e) {}
96    return { removed: n };
97  }
98
99  /* ─────────── 2) SAQLASH TURLARI ─────────── */
100
101  async function wipeCaches() {
102    if (!has(window, 'caches')) return { skip: true };
103    var n = 0;
104    try {
105      var keys = await caches.keys();
106      var res = await Promise.all(keys.map(function (k) {
107        return caches.delete(k).then(function (ok) { return ok; }, function () { return false; });
108      }));
109      res.forEach(function (r) { if (r) n++; });
110    } catch (e) {}
111    return { removed: n };
112  }
113
114  /* IndexedDB — bloklanishni hisobga olib o'chirish */
115  function deleteDB(name) {
116    return new Promise(function (resolve) {
117      var done = false;
118      function fin(ok) { if (!done) { done = true; resolve(ok); } }
119      try {
120        var req = indexedDB.deleteDatabase(name);
121        req.onsuccess = function () { fin(true); };
122        req.onerror = function () { fin(false); };
123        /* Boshqa yorliq (tab) bazani ushlab tursa "blocked" bo'ladi —
124           biroz kutamiz, keyin keyingi urinishga qoldiramiz. */
125        req.onblocked = function () { setTimeout(function () { fin(false); }, 1200); };
126      } catch (e) { fin(false); }
127      setTimeout(function () { fin(false); }, 2600);
128    });
129  }
130
131  /* Ma'lum baza nomlari — iOS Safari'da databases() yo'qligi uchun zarur */
132  var KNOWN_DBS = [
133    'ti', 'teran', 'teranildiz', 'ti-db', 'ti-cache', 'ti-books', 'ti-audio',
134    'keyval-store', 'localforage', 'workbox-expiration', 'workbox-precache',
135    'firebaseLocalStorageDb', 'firebase-installations-database',
136    'ti-reader', 'ti-saved', 'ti-stats', 'ti-news'
137  ];
138
139  async function wipeIndexedDB() {
140    if (!has(window, 'indexedDB')) return { skip: true };
141    var names = [];
142    try {
143      if (indexedDB.databases) {
144        var dbs = await indexedDB.databases();
145        (dbs || []).forEach(function (d) { if (d && d.name && names.indexOf(d.name) < 0) names.push(d.name); });
146      }
147    } catch (e) { /* iOS Safari — qo'llab-quvvatlamaydi */ }
148    KNOWN_DBS.forEach(function (n) { if (names.indexOf(n) < 0) names.push(n); });
149
150    var okN = 0;
151    var results = await Promise.all(names.map(function (nm) { return deleteDB(nm); }));
152    results.forEach(function (r) { if (r) okN++; });
153    return { tried: names.length, removed: okN };
154  }
155
156  /* OPFS — Origin Private File System (zamonaviy brauzerlarda fayllar shu yerda) */
157  async function wipeOPFS() {
158    if (!(has(navigator, 'storage') && navigator.storage.getDirectory)) return { skip: true };
159    var n = 0;
160    try {
161      var root = await navigator.storage.getDirectory();
162      var names = [];
163      try {
164        if (root.keys) { for await (var k of root.keys()) names.push(k); }
165        else if (root.entries) { for await (var ent of root.entries()) names.push(ent[0]); }
166      } catch (e) {}
167      for (var i = 0; i < names.length; i++) {
168        try { await root.removeEntry(names[i], { recursive: true }); n++; } catch (e) {}
169      }
170    } catch (e) {}
171    return { removed: n };
172  }
173
174  /* WebSQL — eskirgan, lekin ba'zi Android/iOS versiyalarida hali bor */
175  async function wipeWebSQL() {
176    if (!has(window, 'openDatabase')) return { skip: true };
177    var n = 0;
178    try {
179      ['ti', 'teran', 'cache', 'localStorage'].forEach(function (nm) {
180        try {
181          var db = window.openDatabase(nm, '1.0', nm, 2 * 1024 * 1024);
182          db.transaction(function (tx) {
183            tx.executeSql("SELECT name FROM sqlite_master WHERE type='table'", [], function (t, rs) {
184              for (var i = 0; i < rs.rows.length; i++) {
185                var tn = rs.rows.item(i).name;
186                if (/^(__|sqlite_)/.test(tn)) continue;
187                try { t.executeSql('DROP TABLE IF EXISTS ' + tn); n++; } catch (e) {}
188              }
189            });
190          });
191        } catch (e) {}
192      });
193    } catch (e) {}
194    return { dropped: n };
195  }
196
197  function wipeWebStorage() {
198    var out = { local: 0, session: 0 };
199    try { out.local = localStorage.length; localStorage.clear(); } catch (e) {}
200    try { out.session = sessionStorage.length; sessionStorage.clear(); } catch (e) {}
201    /* Ba'zi kalitlar clear() dan keyin qayta yozilishi mumkin — nomma-nom ham o'chiramiz */
202    try {
203      var ks = [];
204      for (var i = 0; i < localStorage.length; i++) ks.push(localStorage.key(i));
205      ks.forEach(function (k) { try { localStorage.removeItem(k); } catch (e) {} });
206    } catch (e) {}
207    return out;
208  }
209
210  /* Cookie — barcha yo'l va domen kombinatsiyalarida */
211  function wipeCookies() {
212    var n = 0;
213    try {
214      var host = location.hostname;
215      /* domen variantlari: to'liq host + har bir yuqori daraja */
216      var domains = ['', host, '.' + host];
217      var parts = host.split('.');
218      for (var i = 1; i < parts.length - 1; i++) {
219        var d = parts.slice(i).join('.');
220        domains.push(d); domains.push('.' + d);
221      }
222      /* yo'l variantlari: ildizdan joriy yo'lgacha har bir bo'g'in */
223      var paths = ['/'];
224      var segs = location.pathname.split('/').filter(Boolean);
225      var acc = '';
226      segs.forEach(function (s) { acc += '/' + s; paths.push(acc); paths.push(acc + '/'); });
227
228      var exp0 = '=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
229      var raw = document.cookie ? document.cookie.split(';') : [];
230      raw.forEach(function (c) {
231        var name = c.split('=')[0].trim();
232        if (!name) return;
233        n++;
234        /* (v113) `__Host-` prefiksli cookie QAT'IY qoidaga bo'ysunadi:
235           faqat `path=/` va `domain` SIZ, `Secure` bilan o'chadi.
236           Domen ko'rsatilsa brauzer buyruqni butunlay rad etadi —
237           shuning uchun bu holat alohida bajariladi. */
238        if (name.indexOf('__Host-') === 0) {
239          try { document.cookie = name + exp0 + '; path=/; Secure'; } catch (e) {}
240          try { document.cookie = name + exp0 + '; path=/; Secure; SameSite=None; Partitioned'; } catch (e) {}
241        }
242        var exp = '=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
243        paths.forEach(function (p) {
244          domains.forEach(function (d) {
245            var base = name + exp + '; path=' + p + (d ? '; domain=' + d : '');
246            try { document.cookie = base; } catch (e) {}
247            try { document.cookie = base + '; SameSite=Lax'; } catch (e) {}
248            try { document.cookie = base + '; SameSite=None; Secure'; } catch (e) {}
249            /* (v113) `Partitioned` (CHIPS) — Chrome'ning yangi bo'lingan
250               cookie'lari. Ular ODDIY o'chirish buyrug'iga BERILMAYDI:
251               atribut mos kelmasa brauzer boshqa cookie deb hisoblaydi
252               va eskisi joyida qolaveradi. */
253            try { document.cookie = base + '; SameSite=None; Secure; Partitioned'; } catch (e) {}
254          });
255        });
256      });
257    } catch (e) {}
258    return { cleared: n };
259  }
260
261  /* Bildirishnoma / hisob ma'lumotlari / ilova belgisi (badge) */
262  async function wipeCredentials() {
263    var out = {};
264    try {
265      if (has(navigator, 'credentials') && navigator.credentials.preventSilentAccess) {
266        await navigator.credentials.preventSilentAccess();
267        out.credentials = true;
268      }
269    } catch (e) {}
270    try {
271      if (has(window, 'MediaSession') || (navigator.mediaSession)) {
272        navigator.mediaSession.metadata = null;
273        try { navigator.mediaSession.playbackState = 'none'; } catch (e) {}
274        out.mediaSession = true;
275      }
276    } catch (e) {}
277    /* Ilova ikonkasidagi raqamli belgi (masalan "3 ta yangi") — PWA
278       o'rnatilgan bo'lsa, buni ham tozalab qo'yamiz. */
279    try {
280      if (navigator.clearAppBadge) { await navigator.clearAppBadge(); out.badge = true; }
281    } catch (e) {}
282    return out;
283  }
284
285  /* Xotiradagi holat: ilova ma'lumotlari, blob havolalari, media */
286  function wipeMemory() {
287    var out = { blobs: 0 };
288    /* ilova holati */
289    try {
290      if (TI.APP) { TI.APP.data = []; TI.APP.filtered = []; TI.APP.q = ''; TI.APP.cat = 'all'; }
291    } catch (e) {}
292    try { window.LIB = []; } catch (e) {}
293    try { window.TERAN_DATA = {}; } catch (e) {}
294    try { if (TI._navWraps) TI._navWraps = {}; } catch (e) {}
295
296    /* ochilgan media va blob havolalari */
297    try {
298      var media = document.querySelectorAll('audio,video,img,iframe,source');
299      Array.prototype.forEach.call(media, function (el) {
300        var src = el.currentSrc || el.src || '';
301        if (src.indexOf('blob:') === 0) { try { URL.revokeObjectURL(src); out.blobs++; } catch (e) {} }
302        try { if (el.pause) el.pause(); } catch (e) {}
303        try { el.removeAttribute('src'); if (el.load) el.load(); } catch (e) {}
304      });
305    } catch (e) {}
306    return out;
307  }
308
309  /* Tarix holati — ortga qaytganda eski sahifa chiqmasin */
310  function wipeHistory() {
311    try {
312      history.replaceState(null, '', location.pathname);
313      /* orqadagi yozuvlarni "bo'sh" holat bilan almashtirish */
314      for (var i = 0; i < 3; i++) { try { history.pushState(null, '', location.pathname); } catch (e) {} }
315      history.replaceState(null, '', location.pathname);
316      return { ok: true };
317    } catch (e) { return { ok: false }; }
318  }
319
320  /* Serverga buyruq: Clear-Site-Data (brauzerning o'z mexanizmi).
321     keepalive:true — MUHIM: foydalanuvchi darhol boshqa sahifaga o'tsa ham
322     (chiqish oqimida shunday bo'ladi), bu so'rov brauzer tomonidan baribir
323     yakunigacha yuboriladi — sahifa yopilishi uni to'xtatmaydi. */
324  async function purgeViaServer(full) {
325    try {
326      /* (v113) BIR MARTALIK belgi qo'shiladi. `cache: 'no-store'` faqat
327         BRAUZER keshiga ta'sir qiladi; oradagi proksi yoki Service
328         Worker esa baribir eski javobni qaytarishi mumkin edi. Bu esa
329         `Clear-Site-Data` sarlavhasining brauzergacha yetib bormasligi
330         demak — ya'ni eng ishonchli qatlam jimgina ishlamay qolardi.
331         Har safar boshqa manzil — kesh imkoniyati umuman yo'q.
332         (SW tomonda ham `/privacy-wipe/purge` chetlab o'tiladi.) */
333      var nonce;
334      try {
335        var buf = new Uint8Array(8);
336        crypto.getRandomValues(buf);
337        nonce = Array.prototype.map.call(buf, function (b) { return ('0' + b.toString(16)).slice(-2); }).join('');
338      } catch (e) { nonce = String(Date.now()) + Math.random().toString(16).slice(2); }
339
340      var u = '/privacy-wipe/purge?n=' + nonce + (full ? '&full=1' : '');
341      var res = await fetch(u, {
342        method: 'GET', cache: 'no-store', credentials: 'include',
343        mode: 'same-origin', redirect: 'error', keepalive: true
344      });
345      /* Javob HAQIQATAN tarmoqdan kelganini tekshiramiz: SW keshidan
346         berilgan javobda `Clear-Site-Data` ishlamaydi. */
347      var applied = !!(res && res.status === 204);
348      return { ok: applied, status: res ? res.status : 0 };
349    } catch (e) { return { ok: false, err: String((e && e.message) || e) }; }
350  }
351
352  /* Boshqa ochiq yorliqlarga ham "tozalan" xabari.
353     mode:'panic' bo'lsa — boshqa yorliqlar HAM darhol neytral sahifaga
354     o'tishi kerak (na hisobot, na "tayyor" ekrani — hech narsa ko'rinmasin). */
355  function notifyOtherTabs(mode) {
356    try {
357      var bc = new BroadcastChannel('ti-wipe');
358      bc.postMessage({ wipe: 1, mode: mode || 'normal', at: Date.now() });
359      setTimeout(function () { try { bc.close(); } catch (e) {} }, 500);
360      return { ok: true };
361    } catch (e) { return { ok: false }; }
362  }
363
364  /* ─────────── 3) TEKSHIRUV ─────────── */
365
366  async function measure() {
367    var m = { usage: null, caches: null, idb: null, local: null, session: null, cookies: null, sw: null };
368    try { var est = await navigator.storage.estimate(); m.usage = est && est.usage != null ? est.usage : null; } catch (e) {}
369    try { m.caches = (await caches.keys()).length; } catch (e) {}
370    try { if (indexedDB.databases) m.idb = (await indexedDB.databases()).length; } catch (e) {}
371    try { m.local = localStorage.length; } catch (e) {}
372    try { m.session = sessionStorage.length; } catch (e) {}
373    try { m.cookies = document.cookie ? document.cookie.split(';').filter(function (x) { return x.trim(); }).length : 0; } catch (e) {}
374    /* (v113) yangi qamrov ham TEKSHIRUVGA kiradi — aks holda qayta
375       urinish halqasi ularni "toza" deb hisoblab o'tkazib yuborardi. */
376    try { m.wname = window.name ? window.name.length : 0; } catch (e) {}
377    try { m.perf = window.performance ? performance.getEntriesByType('resource').length : 0; } catch (e) {}
378    try { m.sw = (await navigator.serviceWorker.getRegistrations()).length; } catch (e) {}
379    return m;
380  }
381
382  function isClean(m) {
383    return (m.caches === null || m.caches === 0)
384        && (m.idb === null || m.idb === 0)
385        && (m.local === null || m.local === 0)
386        && (m.session === null || m.session === 0)
387        && (m.cookies === null || m.cookies === 0)
388        && (m.sw === null || m.sw === 0)
389        && (m.wname === null || m.wname === undefined || m.wname === 0);
390    /* `m.perf` ATAYLAB shartga kiritilmadi: tozalangandan keyin ham
391       shu tekshiruvning O'ZI yangi yozuv qo'shadi, ya'ni u hech qachon
392       nolga tushmaydi va halqa bekorga uch marta aylanardi. Uni
393       tozalaymiz va hisobotga yozamiz, lekin "toza emas" belgisi
394       sifatida ishlatmaymiz. */
395  }
396
397  /* ─────────── 4) ASOSIY OQIM ─────────── */
398
399  /* ══════════════════════════════════════════════════════════════
400     (v113) YANGI QAMROV — avval e'tibordan chetda qolgan izlar
401     ══════════════════════════════════════════════════════════════ */
402
403  /* `window.name` — bitta yorliq ichida SAHIFA ALMASHSA HAM saqlanib
404     qoladigan yagona oddiy o'zgaruvchi. Aynan shu sabab uni kuzatuv
405     uchun ishlatishadi: boshqa saytga o'tilganda ham qiymat qoladi.
406     Tozalash bir qator, lekin usiz "iz qolmadi" deyish noto'g'ri. */
407  function wipeWindowName() {
408    try {
409      var had = !!window.name;
410      window.name = '';
411      return { ok: true, had: had };
412    } catch (e) { return { ok: false }; }
413  }
414
415  /* Performance Resource Timing — brauzer YUKLANGAN HAR BIR manzilni
416     ro'yxatda saqlaydi va uni istalgan skript o'qiy oladi
417     (`performance.getEntriesByType('resource')`). Ya'ni tozalanmasa,
418     qaysi maqola ochilgani shu ro'yxatdan bilinib qolardi. */
419  function wipePerfTimeline() {
420    var out = { entries: 0 };
421    try {
422      if (window.performance) {
423        try { out.entries = performance.getEntriesByType('resource').length; } catch (e) {}
424        try { performance.clearResourceTimings(); } catch (e) {}
425        try { performance.clearMarks(); } catch (e) {}
426        try { performance.clearMeasures(); } catch (e) {}
427        /* Bufer to'lgach brauzer yangilarini yozmay qo'yadi — 0 ga
428           tushirib, keyingi yozuvlar ham to'planmasligini ta'minlaymiz */
429        try { performance.setResourceTimingBufferSize(0); } catch (e) {}
430      }
431      return { ok: true, entries: out.entries };
432    } catch (e) { return { ok: false }; }
433  }
434
435  /* Storage Buckets API — Chrome'ning yangi, ALOHIDA saqlash joyi.
436     `localStorage`/`IndexedDB` tozalansa ham bu tegilmay qolardi,
437     chunki u butunlay boshqa idishda turadi. Brauzerda yo'q bo'lsa —
438     jimgina o'tkazib yuboriladi. */
439  async function wipeStorageBuckets() {
440    try {
441      if (!navigator.storageBuckets || !navigator.storageBuckets.keys) return { ok: true, skipped: true };
442      var keys = await navigator.storageBuckets.keys();
443      var n = 0;
444      for (var i = 0; i < keys.length; i++) {
445        try { await navigator.storageBuckets.delete(keys[i]); n++; } catch (e) {}
446      }
447      return { ok: true, buckets: n };
448    } catch (e) { return { ok: false, err: String((e && e.message) || e) }; }
449  }
450
451  /* Formalardagi kiritilgan matn. Brauzer "orqaga" bosilganda yoki
452     bfcache'dan tiklaganda maydonlarni O'ZI qayta to'ldiradi —
453     qidiruv so'rovi shu yo'l bilan qaytib chiqishi mumkin edi. */
454  function wipeFormState() {
455    var out = { fields: 0 };
456    try {
457      /* ══════════════════════════════════════════════════════════
458         TARTIB MUHIM. `form.reset()` maydonni BO'SHATMAYDI — u
459         maydonni HTML dagi BOSHLANG'ICH qiymatiga qaytaradi. Ya'ni
460         avval qiymatni tozalab, keyin `reset()` chaqirilsa, matn
461         qaytib keladi va tozalash bekor bo'ladi.
462
463         To'g'ri tartib: avval `reset()`, keyin tozalash. Ustiga
464         `defaultValue` va `value` atributi ham bo'shatiladi —
465         shundagina "orqaga" bosilganda yoki bfcache'dan tiklanganda
466         brauzer eski matnni qayta tiklay olmaydi.
467         ══════════════════════════════════════════════════════════ */
468      Array.prototype.forEach.call(document.querySelectorAll('form'), function (f) {
469        try { f.reset(); f.setAttribute('autocomplete', 'off'); } catch (e) {}
470      });
471
472      var els = document.querySelectorAll('input, textarea, select');
473      Array.prototype.forEach.call(els, function (el) {
474        try {
475          var t = (el.type || '').toLowerCase();
476          if (t === 'button' || t === 'submit' || t === 'reset') return;
477          if (el.value) { out.fields++; }
478          el.value = '';
479          /* boshlang'ich qiymatni ham olib tashlaymiz — aks holda
480             brauzer uni istalgan paytda qaytarib qo'yishi mumkin */
481          try { el.defaultValue = ''; } catch (e) {}
482          try { el.removeAttribute('value'); } catch (e) {}
483          if (el.tagName === 'TEXTAREA') { try { el.textContent = ''; } catch (e) {} }
484          if (t === 'checkbox' || t === 'radio') {
485            el.checked = false;
486            try { el.defaultChecked = false; } catch (e) {}
487          }
488          if (el.tagName === 'SELECT') { try { el.selectedIndex = -1; } catch (e) {} }
489          /* brauzer avtomatik tiklashiga ham yo'l qo'ymaymiz */
490          el.setAttribute('autocomplete', 'off');
491        } catch (e) {}
492      });
493    } catch (e) {}
494    return { ok: true, fields: out.fields };
495  }
496
497  var TASKS = [
498    { id: 'sw',      lbl: { l: 'Service Worker',        c: 'Service Worker' },        fn: killServiceWorkers, ms: 4000 },
499    { id: 'caches',  lbl: { l: 'Offline kesh',          c: 'Offline кеш' },           fn: wipeCaches,         ms: 5000 },
500    { id: 'idb',     lbl: { l: 'Ichki baza (IndexedDB)',c: 'Ички база (IndexedDB)' }, fn: wipeIndexedDB,      ms: 6000 },
501    { id: 'opfs',    lbl: { l: 'Fayl tizimi (OPFS)',    c: 'Файл тизими (OPFS)' },    fn: wipeOPFS,           ms: 5000 },
502    { id: 'websql',  lbl: { l: 'Eski baza (WebSQL)',    c: 'Эски база (WebSQL)' },    fn: wipeWebSQL,         ms: 3000 },
503    { id: 'storage', lbl: { l: 'Saqlangan sozlamalar',  c: 'Сақланган созламалар' },  fn: wipeWebStorage,     ms: 2000 },
504    { id: 'cookies', lbl: { l: 'Cookie',                c: 'Cookie' },                fn: wipeCookies,        ms: 3000 },
505    { id: 'cred',    lbl: { l: 'Hisob va media',        c: 'Ҳисоб ва медиа' },        fn: wipeCredentials,    ms: 2500 },
506    { id: 'mem',     lbl: { l: 'Xotiradagi ma\'lumot',  c: 'Хотирадаги маълумот' },   fn: wipeMemory,         ms: 2000 },
507    /* (v113) yangi qamrov */
508    { id: 'bucket',  lbl: { l: 'Storage Buckets',       c: 'Storage Buckets' },       fn: wipeStorageBuckets, ms: 4000 },
509    { id: 'wname',   lbl: { l: 'Yorliq nomi (window.name)', c: 'Ёрлиқ номи (window.name)' }, fn: wipeWindowName, ms: 800 },
510    { id: 'perf',    lbl: { l: 'Yuklanishlar ro\'yxati', c: 'Юкланишлар рўйхати' },    fn: wipePerfTimeline,   ms: 1200 },
511    { id: 'forms',   lbl: { l: 'Kiritilgan matnlar',    c: 'Киритилган матнлар' },    fn: wipeFormState,      ms: 1200 },
512    { id: 'hist',    lbl: { l: 'Tarix holati',          c: 'Тарих ҳолати' },          fn: wipeHistory,        ms: 1500 },
513    { id: 'server',  lbl: { l: 'Server buyrug\'i',      c: 'Сервер буйруғи' },        fn: purgeViaServer,     ms: 5000 },
514    { id: 'tabs',    lbl: { l: 'Boshqa yorliqlar',      c: 'Бошқа ёрлиқлар' },        fn: notifyOtherTabs,    ms: 1200 },
515  ];
516
517  var MAX_PASSES = 3;
518
519  /* onStep(id, holat, label) — jarayonni ko'rsatish uchun */
520  async function run(onStep) {
521    var t0 = Date.now();
522    var report = { passes: [], tasks: {}, before: null, after: null, clean: false, ms: 0 };
523
524    /* ══════════════════════════════════════════════════════════════
525       (v119) QAT'IY UMUMIY MUDDAT — TELEFON UCHUN
526       ──────────────────────────────────────────────────────────────
527       Har vazifada o'z chegarasi bor edi, lekin UMUMIY chegara yo'q
528       edi. Telefonda (ayniqsa iOS Safari'da) ba'zi qadamlar to'liq
529       bajarilmaydi — `Clear-Site-Data` qo'llab-quvvatlanmaydi,
530       `indexedDB.databases()` yo'q, cookie'lar to'liq o'chmaydi.
531       Shunda `isClean()` hech qachon rost bo'lmaydi va halqa
532       MAX_PASSES marta to'liq aylanardi: foydalanuvchi 10-12 soniya
533       kutib o'tirardi va ilova qotgandek tuyulardi.
534
535       Endi umumiy byudjet bor. U tugasa halqa TO'XTAYDI va chiqish
536       bajariladi. Tozalashning o'zi zarar ko'rmaydi: birinchi
537       bosqichda hamma vazifa allaqachon bir marta ishlagan bo'ladi,
538       keyingi bosqichlar faqat QAYTA urinish edi. */
539    var BUDGET = 6000;
540    function timeLeft() { return BUDGET - (Date.now() - t0); }
541
542    report.before = await measure();
543
544    for (var pass = 1; pass <= MAX_PASSES; pass++) {
545      if (pass > 1 && timeLeft() <= 0) { report.timedOut = true; break; }
546      /* 1-bosqich: SW ni birinchi bo'lib yopamiz (u boshqalarni to'sishi mumkin) */
547      if (pass === 1) {
548        if (onStep) onStep('sw', 'run', TASKS[0].lbl);
549        var swRes = await guard(TASKS[0].fn(), TASKS[0].ms);
550        report.tasks.sw = swRes;
551        if (onStep) onStep('sw', swRes.ok ? 'ok' : 'warn', TASKS[0].lbl);
552      }
553
554      /* 2-bosqich: qolganini PARALLEL bajaramiz — tezlik shundan */
555      var rest = TASKS.filter(function (t) { return t.id !== 'sw'; });
556      if (onStep) rest.forEach(function (t) { onStep(t.id, 'run', t.lbl); });
557
558      var results = await Promise.all(rest.map(function (t) {
559        return guard(t.fn(), t.ms).then(function (r) { return { id: t.id, r: r, lbl: t.lbl }; });
560      }));
561      results.forEach(function (x) {
562        report.tasks[x.id] = x.r;
563        if (onStep) onStep(x.id, x.r.ok ? 'ok' : 'warn', x.lbl);
564      });
565
566      /* 3-bosqich: tekshiruv */
567      var m = await measure();
568      report.passes.push(m);
569      if (isClean(m)) { report.clean = true; break; }
570      /* toza emas — qisqa tanaffusdan keyin yana urinamiz.
571         Vaqt tugagan bo'lsa qayta urinmaymiz. */
572      if (timeLeft() <= 300) { report.timedOut = true; break; }
573      await new Promise(function (r) { setTimeout(r, 220); });
574    }
575
576    report.after = await measure();
577    report.clean = isClean(report.after);
578    report.ms = Date.now() - t0;
579    return report;
580  }
581
582  /* ─────────── 5) TEZKOR CHIQISH (PANIK REJIMI) ───────────
583     Oddiy run() — to'liq, tekshiruvli, foydalanuvchiga hisobot bilan
584     (bir necha soniya davom etadi). Bu esa — zudlik muhim bo'lgan holat
585     uchun: birinchi navbatda DARHOL bajariladigan (sinxron) tozalash,
586     so'ngra orqa fonda davom etadigan (asinxron, kutilmaydigan) tozalash.
587     Chaqiruvchi funksiya natijani KUTMASDAN darhol boshqa sahifaga
588     o'tkazishi mumkin — asinxron vazifalar sahifa almashgandan keyin ham
589     brauzer tomonidan yakunlab qo'yiladi (fetch keepalive, caches/idb
590     operatsiyalari odatda navigatsiyadan keyin ham tugaydi). */
591  function runPanic() {
592    /* 1) SINXRON — bir zumda, hech qanday kutishsiz bajariladi */
593    try { wipeWebStorage(); } catch (e) {}
594    try { wipeCookies(); } catch (e) {}
595    try { wipeMemory(); } catch (e) {}
596    /* (v113) sinxron va bir zumda bajariladiganlar — panikda ham kerak */
597    try { wipeWindowName(); } catch (e) {}
598    try { wipePerfTimeline(); } catch (e) {}
599    try { wipeFormState(); } catch (e) {}
600    try { wipeHistory(); } catch (e) {}
601    try { notifyOtherTabs('panic'); } catch (e) {}
602
603    /* 2) ASINXRON — boshlab qo'yiladi, lekin KUTILMAYDI ("fire-and-forget").
604       Xatolar batamom yutiladi — bittasi ishlamasa ham chiqishga xalaqit
605       bermaydi. */
606    try { killServiceWorkers().catch(function () {}); } catch (e) {}
607    try { wipeCaches().catch(function () {}); } catch (e) {}
608    try { wipeIndexedDB().catch(function () {}); } catch (e) {}
609    try { wipeOPFS().catch(function () {}); } catch (e) {}
610    try { purgeViaServer(true).catch(function () {}); } catch (e) {}
611  }
612
613  /* Boshqa yorliqdan kelgan "tozalan" xabarini tinglash */
614  try {
615    var bcIn = new BroadcastChannel('ti-wipe');
616    bcIn.onmessage = function (ev) {
617      if (!ev || !ev.data || !ev.data.wipe) return;
618      /* Bu yorliqda ham darhol xotira/storage tozalanadi va sahifa
619         hech narsa ko'rsatmasdan neytral sahifaga o'tkaziladi — foydalanuvchi
620         qaysi yorliqni ko'rayotganidan qat'iy nazar, HAMMA YERDA iz
621         qolmasligi kerak. */
622      try { wipeMemory(); wipeWebStorage(); wipeCookies(); wipeHistory(); } catch (e) {}
623      try { leaveNoTrace(); } catch (e) {}
624    };
625  } catch (e) {}
626
627  /* ══════════════════════════════════════════════════════════════
628     (v113) ORQAGA/OLDINGA KESHI (bfcache) — TESHIKNI YOPAMIZ
629     ──────────────────────────────────────────────────────────────
630     Tozalashdan keyin foydalanuvchi neytral sahifaga o'tkaziladi.
631     Lekin brauzer eski sahifani BUTUNLAY XOTIRADA saqlab qo'yishi
632     mumkin (back/forward cache): "orqaga" bosilsa u JS holati bilan
633     birga, hech qanday qayta yuklanishsiz tiklanadi.
634
635     Ya'ni tozalash to'liq ishlaganda ham, bitta "orqaga" bosish
636     ekranga o'qilgan maqolani qaytarib qo'yishi mumkin edi.
637
638     `pageshow` da `persisted === true` aynan shu holatni bildiradi.
639     Bunda sinxron tozalashni QAYTA bajaramiz va sahifani neytral
640     holatga o'tkazamiz. Bayroq `sessionStorage` da emas, oddiy
641     o'zgaruvchida — chunki storage allaqachon tozalangan bo'ladi. */
642  var wiped = false;
643  try {
644    window.addEventListener('pageshow', function (e) {
645      if (!e || !e.persisted || !wiped) return;
646      try { wipeMemory(); wipeWebStorage(); wipeFormState(); wipeWindowName(); wipePerfTimeline(); } catch (err) {}
647      try { leaveNoTrace(); } catch (err) {}
648    });
649  } catch (e) {}
650
651  /* ══════════════════════════════════════════════════════════════
652     (v114) CHIQISH — SAHIFANI HAQIQATAN TARK ETISH
653     ──────────────────────────────────────────────────────────────
654     TUZATILGAN XATO. Avvalgi tartib shunday edi:
655
656         location.replace('/privacy-wipe/blank.html');
657         window.open('', '_self');
658         window.close();
659
660     Uchtasi bir vaqtda urinardi va bir-birini buzardi:
661     `location.replace()` navigatsiyani boshlaydi, shundan keyin
662     skript konteksti tugaydi va `window.close()` umuman
663     bajarilmaydi. Natijada varaq OCHIQ qolardi va manzil qatorida
664     `.../privacy-wipe/blank` turardi — ya'ni sayt manzili ko'rinib
665     turardi. Bu aynan iz.
666
667     To'g'ri tartib:
668       1) AVVAL `window.close()`. U faqat skript ochgan varaqda
669          ishlaydi (brauzer qoidasi), lekin ishlagan holda eng toza
670          natija — varaq umuman yo'qoladi.
671       2) Yopilmadi. Endi manzilni NEYTRALLAYMIZ: `about:blank`.
672          Bu brauzerning o'z bo'sh sahifasi — manzil qatorida sayt
673          nomi umuman qolmaydi.
674       3) `about:blank` bloklangan bo'lsa — o'z bo'sh varag'imiz.
675
676     `location.replace()` ATAYLAB (`assign` emas): u tarixga YANGI
677     yozuv qo'shmaydi, balki joriysini almashtiradi.
678
679     ⚠️ CHEGARA: bu brauzerning TARIX MA'LUMOTLAR BAZASIDAN yozuvni
680     o'chirmaydi. Veb-sahifa buni qila olmaydi — bunday API mavjud
681     emas va bo'lmasligi ham kerak (aks holda istalgan sayt sizning
682     butun tarixingizni o'zgartira olardi). Foydalanuvchiga bu ochiq
683     aytiladi (wipe.js).
684     ══════════════════════════════════════════════════════════════ */
685  function leaveNoTrace() {
686    /* 1) Varaqni yopish */
687    try { window.close(); } catch (e) {}
688
689    /* 2) Yopilmagan bo'lsa — manzilni neytrallaymiz.
690       Bir kadr kutamiz: `close()` muvaffaqiyatli bo'lsa, quyidagi
691       kod umuman bajarilmaydi. */
692    setTimeout(function () {
693      try { if (window.closed) return; } catch (e) {}
694      /* Joriy yozuvning manzilini ham neytrallaymiz — bu bir xil
695         origin ichida ruxsat etilgan yagona narsa. */
696      try { history.replaceState(null, '', '/'); } catch (e) {}
697      /* iOS Safari `about:blank` ga o'tishni JIMGINA e'tiborsiz
698         qoldirishi mumkin — xato ham otmaydi, sahifa ham
699         o'zgarmaydi. Shu sabab urinib ko'ramiz va NATIJANI
700         tekshiramiz: qisqa vaqtdan keyin hali shu yerda bo'lsak,
701         o'z bo'sh varag'imizga o'tamiz (u har joyda ishlaydi). */
702      var here = location.href;
703      try { location.replace('about:blank'); } catch (e) {}
704      setTimeout(function () {
705        try {
706          if (window.closed) return;
707          if (location.href !== here && location.href.indexOf('about:blank') === 0) return;
708        } catch (e) {}
709        try { location.replace('/privacy-wipe/blank.html'); } catch (e) {}
710      }, 260);
711    }, 50);
712  }
713
714  TI.WIPE = {
715    run: function (onStep) { wiped = true; return run(onStep); },
716    runPanic: function () { wiped = true; return runPanic(); },
717    measure: measure,
718    isClean: isClean,
719    detectMode: detectMode,
720    tasks: TASKS,
721    leaveNoTrace: leaveNoTrace,
722    /* alohida chaqirish uchun */
723    parts: {
724      serviceWorkers: killServiceWorkers, caches: wipeCaches, indexedDB: wipeIndexedDB,
725      opfs: wipeOPFS, webSQL: wipeWebSQL, webStorage: wipeWebStorage, cookies: wipeCookies,
726      credentials: wipeCredentials, memory: wipeMemory, history: wipeHistory,
727      server: purgeViaServer, tabs: notifyOtherTabs,
728      /* (v113) */
729      storageBuckets: wipeStorageBuckets, windowName: wipeWindowName,
730      perfTimeline: wipePerfTimeline, formState: wipeFormState
731    }
732  };
733})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.