Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. BroadcastChannel: hamma tablarga radio
- 2.1 Kanal ochish va xabar yuborish
- 2.2 Ikki tab bilan sinash
- 2.3 Kanalni yopish
- 3. storage hodisasi — eski, lekin ishlaydigan yo'l
- 4. window.postMessage: boshqa oyna yoki iframe bilan
- 4.1 Same-origin policy devori
- 4.2 Iframe bilan xabar almashish
- 4.3 targetOrigin va event.origin
- 5. window.open: yangi oyna
- 5.1 Ochish va havola
- 5.2 Popup bloker
- 5.3 noopener va target="_blank"
- 6. Qaysi usulni tanlash
- Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 7.1 event.origin ni tekshirmaslik
- 7.2 Iframe yuklanmasdan xabar yuborish
- 7.3 O'z xabariga javob — aks-sado sikli
- 7.4 Funksiya yoki DOM elementni yuborish
- 7.5 BroadcastChannel ni server aloqasi deb o'ylash
- 8. Mashqlar
- 1-mashq (oson): Tizimdan chiqish hamma tabda
- 2-mashq (o'rta): Iframe balandligi
- 3-mashq (qiyin): Kim asosiy tab?
- 4-mashq: Vazifalar qadami — tablar sinxroni
- 9. Real ishda
- Xulosa
- Manbalar
Oyna va tablar orasida aloqa: BroadcastChannel, postMessage va window.open
Qisqacha: Bir saytning tablari bir-biriga xabar yuborishi uchun eng sodda yo'l —
BroadcastChannel:new BroadcastChannel("vazifalar")ochiladi,postMessage(xabar)yuboradi, boshqa tablarmessagehodisasida oladi. Boshqa origindagi oyna yoki iframe bilan esawindow.postMessage(xabar, "https://kimga")gaplashiladi — va qabul qiluvchi doimevent.originni tekshiradi.window.openyangi oyna ochadi va unga havola qaytaradi.
Bu darsda
BroadcastChannelbilan bir saytning hamma tablariga xabar tarqatasiz.window.postMessagebilan iframe yoki boshqa oyna bilan xavfsiz gaplashasiz:targetOriginvaevent.origintekshiruvi.window.open,openervanoopenerni bilasiz.storagehodisasi,BroadcastChannelvapostMessagedan qaysi birini qachon tanlashni ayta olasiz.vazifalarni ikki tabda ochiq bo'lsa ham bir xil ko'rinadigan qilasiz — "aks-sado" siklisiz.
Oldin bilishingiz kerak: localStorage va sessionStorage, location va History API, IndexedDB, Brauzer xotirasi va xavfsizlik modeli.
1. Nega bu kerak?
Sardor vazifalar ni ikki tabda ochdi. Birinchisida "Choy olish" ni qo'shdi. Ikkinchi tabga o'tdi — u yerda "Choy olish" yo'q. U yerda bitta vazifani o'chirdi. Natijada ikkinchi tab o'z eski ro'yxatini localStorage ga yozdi va birinchi tabdagi "Choy olish" yo'qolib ketdi.
Bu — klassik "oxirgi yozgan yutadi" xatosi. Tablar umumiy omborga yozadi, lekin bir-birining o'zgarishidan bexabar. Har tab o'z xotirasida eski nusxani ushlab turibdi.
Bunday holatlar ko'p:
- Bir tabda tizimdan chiqdingiz — boshqa tablar ham chiqishi kerak.
- Telegram Web bir nechta tabda ochiq — bildirishnoma faqat bittasida chiqishi kerak.
- To'lov sahifasi to'lov tizimining boshqa saytdagi vidjetini iframe ichida ko'rsatadi va undan "to'landi" degan javobni kutadi.
Bugun brauzerdagi oynalar orasidagi uch "telefon liniyasi" ni o'rganamiz. Oshxonadagi o'xshatish: BroadcastChannel — umumiy radio (oshxonadagi hamma eshitadi), postMessage — aniq bir odamga qo'ng'iroq (kimga qo'ng'iroq qilayotganingizni va kim qo'ng'iroq qilganini tekshirasiz).
2. BroadcastChannel: hamma tablarga radio
2.1 Kanal ochish va xabar yuborish
BroadcastChannel — bir origin ichidagi hamma "kontekst"lar (tablar, oynalar, iframe'lar, Web Worker'lar) uchun umumiy kanal. Web Worker — orqa fonda ishlaydigan skript, uni 13-qismda o'rganamiz. Bir xil nom bilan ochilgan hamma kanal obyektlari bir-birini eshitadi:
<script>
const oshxona = new BroadcastChannel("bahor");
const zal = new BroadcastChannel("bahor");
zal.addEventListener("message", (event) => {
console.log("zal oldi:", event.data);
});
oshxona.addEventListener("message", () => {
console.log("oshxona o'zinikini oldi");
});
oshxona.postMessage({ stol: 5, taom: "Osh", tayyor: true });
console.log("yuborildi");
</script>Konsolda:
yuborildi
zal oldi: {stol: 5, taom: 'Osh', tayyor: true}Qadamlar:
new BroadcastChannel("bahor")—"bahor"nomli kanalga ulanish. Nom — oddiy satr; bir xil nom — bir xil kanal.postMessage(xabar)— kanalga xabar yuborish. Xabar structured clone bilan nusxalanadi (IndexedDB darsidagi kabi): obyekt, massiv,Date,Map,Set— o'tadi, funksiya —DataCloneError.messagehodisasi —event.datada xabarning nusxasi.
Ikki narsaga qarang. Birinchi: yuboruvchi obyektning o'zi (oshxona) xabarni olmaydi — faqat boshqa obyektlar. Ikkinchi: yuborildi birinchi chiqdi — xabar asinxron, alohida task bo'lib keladi (Event loop).
Bu misolda ikkala obyekt bitta sahifada — shuning uchun natija oynasida ham ishladi. Haqiqiy foydasi esa tablar orasida.
2.2 Ikki tab bilan sinash
Natija oynasi bitta oyna — tablar orasidagi xabarni Live Server'da ko'ring. index.html:
<!DOCTYPE html>
<html lang="uz">
<head><meta charset="utf-8"><title>Kanal</title></head>
<body>
<button id="yubor" type="button">Xabar yuborish</button>
<script>
const kanal = new BroadcastChannel("bahor");
kanal.addEventListener("message", (event) => {
console.log("Boshqa tabdan:", event.data);
});
document.getElementById("yubor").addEventListener("click", () => {
kanal.postMessage({ taom: "Manti", vaqt: new Date() });
});
</script>
</body>
</html>Sahifani ikki tabda oching, birida tugmani bosing. Ikkinchi tab konsolida Boshqa tabdan: {taom: 'Manti', vaqt: ...} chiqadi, bosgan tabda esa hech narsa. vaqt ikkinchi tabga Date bo'lib yetib keladi, satr emas (Chrome 154 da avtomatik sinov bilan tekshirildi: instanceof Date — true, Set ham Set bo'lib keldi).
Tarmoq bilan aloqasi yo'q: xabar serverga bormaydi, faqat shu brauzer ichida, shu origin'ning tablari orasida yuradi. Boshqa brauzerdagi (yoki boshqa kompyuterdagi) tablar uni olmaydi — buning uchun server va WebSocket kerak.
2.3 Kanalni yopish
kanal.close() — kanaldan uzilish: boshqa xabar kelmaydi, yuborib ham bo'lmaydi. Sahifa yopilganda brauzer kanalni o'zi yopadi. Lekin komponent yoki oyna ichidagi vaqtinchalik qism kanal ochgan bo'lsa, u yo'qolganda close() ni chaqiring — aks holda tinglovchi va unga bog'liq hamma narsa xotirada qoladi (Closure tuzoqlari dagi xotira sizib chiqishi).
messageerror hodisasi esa xabarni nusxalab bo'lmaganda keladi — amalda juda kam.
Tekshirib ko'ring: Bitta tabda
avabnomli ikkiBroadcastChannel("x")obyekti bor.a.postMessage(1)ni kim oladi:a,b, boshqa tablardagi"x"kanallari?
Javob
b va boshqa tablardagi "x" kanallari. Faqat yuborgan obyektning o'zi (a) olmaydi — boshqa obyektlar, hatto shu tabda bo'lsa ham, oladi. Chrome 154 da tekshirildi.
3. storage hodisasi — eski, lekin ishlaydigan yo'l
localStorage darsida storage hodisasini ko'rgan edik: bir tab localStorage ni o'zgartirsa, boshqa tablar xabar oladi. Bu ham tablar orasidagi aloqa — BroadcastChannel paydo bo'lishidan oldin aynan shu ishlatilgan.
storage hodisasi |
BroadcastChannel |
|
|---|---|---|
| Nima uzatiladi | Faqat localStorage o'zgarishi (satr) |
Istalgan nusxalanadigan ma'lumot |
| Xotiraga yozish shartmi | Ha | Yo'q |
| Qayerda ishlaydi | Faqat sahifalar | Sahifalar, iframe'lar, Worker'lar |
| Brauzerlar | Hamma joyda, juda uzoq vaqtdan beri | Baseline 2022 (Safari 15.4 dan) |
Qoida: ma'lumot baribir localStorage da bo'lsa va faqat "o'zgardi" deyish kerak bo'lsa — storage hodisasi yetarli: u o'zgarish shu tabga yetib kelgandan keyin keladi. Agar BroadcastChannel ishlatsangiz, ma'lumotning o'zini xabarga qo'ying — nega, «Vazifalar qadami» da ko'ramiz. Ma'lumot saqlanmasligi kerak bo'lsa ("tizimdan chiqdim", "video to'xtatilsin") — BroadcastChannel. web-features ma'lumotiga ko'ra BroadcastChannel 2022-yil martdan beri hamma asosiy brauzerlarda (Chrome 54, Firefox 38, Safari 15.4).
4. window.postMessage: boshqa oyna yoki iframe bilan
4.1 Same-origin policy devori
BroadcastChannel faqat bitta origin ichida ishlaydi. Boshqa origin'dagi sahifa bilan gaplashish-chi? Masalan, «Bahor» saytida to'lov tizimining iframe'i bor: <iframe src="https://tolov.example.com/vidjet">.
To'g'ridan-to'g'ri kirish taqiqlangan — bu same-origin policy (Brauzer xotirasi va xavfsizlik modeli dagi devorlar). Boshqa origin'dagi iframe'ning document iga tegsangiz, Chrome 154 shunday deydi:
SecurityError: Failed to read a named property 'document' from 'Window': Blocked a frame with origin "http://127.0.0.1:5500" from accessing a cross-origin frame.Tarjimasi: "Xavfsizlik xatosi: Window ning document xususiyatini o'qib bo'lmadi: http://127.0.0.1:5500 origin'li freym boshqa origin'dagi freymga kirishdan to'sildi." Agar bu devor bo'lmaganida, har qanday sayt iframe ichida bankingizni ochib, hisobingizni o'qiy olardi.
Lekin ikkala tomon rozi bo'lsa, xabar almashish mumkin. Buning yo'li — window.postMessage: "devor orqali qog'oz uzatish". Ikkala tomon ham kimdan kelganini tekshiradi.
4.2 Iframe bilan xabar almashish
Natija oynasida ham sinasa bo'ladi — ota sahifa va uning ichidagi iframe:
<iframe id="oshxona" title="Oshxona ekrani" srcdoc="<script>
addEventListener('message', (event) => {
const javob = { stol: event.data.stol, holat: 'qabul qilindi' };
event.source.postMessage(javob, '*');
});
</script>"></iframe>
<script>
addEventListener("message", (event) => {
console.log("Oshxonadan:", event.data);
console.log("origin:", event.origin);
});
const oshxona = document.getElementById("oshxona");
oshxona.addEventListener("load", () => {
const buyurtma = { stol: 5, taom: "Manti" };
oshxona.contentWindow.postMessage(buyurtma, "*");
console.log("Buyurtma yuborildi");
});
</script>Konsolda:
Buyurtma yuborildi
Oshxonadan: {stol: 5, holat: 'qabul qilindi'}
origin: nullXabar yo'li:
- Ota sahifa iframe yuklanishini kutdi (
load) — undan oldin iframe ichida tinglovchi hali yo'q. oshxona.contentWindow— iframe ichidagiwindow. UningpostMessagemetodi — "shu oynaga xabar".- Iframe
messagehodisasida xabarni oldi.event.source— yuborgan oyna; javob unga qaytarildi. - Ota sahifa javobni o'z
windowidagimessagehodisasida oldi.
origin: null — natija oynasining o'zi sandbox'da, uning origin'i null (CORS mijoz tomondan darsida ko'rgan). Shuning uchun bu misolda ikkinchi argument "*" ("har qanday origin"). Haqiqiy saytda bunday qilmaymiz — keyingi bo'lim.
4.3 targetOrigin va event.origin
postMessage(xabar, targetOrigin) ning ikkinchi argumenti — "xabar faqat shu origin'dagi oynaga yetkazilsin". Oynadagi sahifa o'zgarib qolgan bo'lsa (masalan, iframe boshqa saytga yo'naltirilgan bo'lsa), brauzer xabarni jim tashlab yuboradi. Qabul qiluvchi tomonda esa event.origin — xabar kimdan kelgani; uni brauzer qo'yadi va soxtalashtirib bo'lmaydi.
Ikki origin bilan sinash uchun ikki server shart emas. O'tgan darslardagi tuzoqni eslang: 127.0.0.1:5500 va localhost:5500 — ikki xil origin. Bitta Live Server bilan ikkalasini ham olamiz.
index.html (http://127.0.0.1:5500 da ochiladi):
<!DOCTYPE html>
<html lang="uz">
<head><meta charset="utf-8"><title>«Bahor» — bron</title></head>
<body>
<h1>Stol bron qilish</h1>
<iframe id="vidjet" src="http://localhost:5500/vidjet.html"
title="To'lov vidjeti" width="320" height="120"></iframe>
<script>
const VIDJET = "http://localhost:5500";
const vidjet = document.getElementById("vidjet");
addEventListener("message", (event) => {
if (event.origin !== VIDJET) {
return;
}
console.log("Vidjetdan:", event.data);
});
vidjet.addEventListener("load", () => {
vidjet.contentWindow.postMessage(
{ tur: "tolov", summa: 70000 },
VIDJET,
);
});
</script>
</body>
</html>vidjet.html (o'sha papkada):
<!DOCTYPE html>
<html lang="uz">
<head><meta charset="utf-8"><title>Vidjet</title></head>
<body>
<p id="holat">Kutilmoqda...</p>
<script>
const SAYT = "http://127.0.0.1:5500";
addEventListener("message", (event) => {
if (event.origin !== SAYT) {
return;
}
const { tur, summa } = event.data ?? {};
if (tur !== "tolov" || !Number.isInteger(summa)) {
return;
}
document.getElementById("holat").textContent =
`To'lanadi: ${summa} so'm`;
const javob = { tur: "tayyor", summa };
event.source.postMessage(javob, event.origin);
});
</script>
</body>
</html>Live Server'da http://127.0.0.1:5500/index.html ni oching. Iframe'da To'lanadi: 70000 so'm, konsolda Vidjetdan: {tur: 'tayyor', summa: 70000}. Chrome 154 da tekshirdik. Boshqa sahifa ichiga joylangan xuddi shu vidjetga begona origin'dan { tur: "tolov", summa: 1 } yuborilganda vidjet unga umuman javob bermadi — Kutilmoqda... holicha qoldi.
Uch qavat himoyaga qarang:
- Yuboruvchi —
targetOriginaniq: xabar faqatlocalhost:5500dagi vidjetga yetadi. - Qabul qiluvchi —
event.originni oq ro'yxat bilan tekshiradi. Bu eng muhimi:messagetinglovchisiga hamma xabar yuborishi mumkin, hatto siz bilmagan sayt ham. - Ma'lumot — shakli tekshiriladi (
tur,summabutun son). Ishonchli origin'dan kelgan bo'lsa ham,event.dataniinnerHTMLga qo'ymaymiz.
Diqqat:
postMessage(maxfiy, "*")— xabarni oynada hozir qaysi sayt bo'lsa, o'shanga beradi. Iframe yoki oyna sizdan bexabar boshqa saytga o'tgan bo'lsa, token yoki shaxsiy ma'lumot begona qo'lga tushadi."*"faqat natija oynasidagi kabinullorigin bilan va maxfiy bo'lmagan ma'lumot uchun.
5. window.open: yangi oyna
5.1 Ochish va havola
window.open(url, nom, sozlama) — yangi tab yoki oyna ochadi va unga havola (WindowProxy) qaytaradi:
const sozlama = "popup,width=400,height=500";
const chek = window.open("/chek.html", "chek", sozlama);url— ochiladigan manzil.nom— oyna nomi. Shu nomli oyna allaqachon ochiq bo'lsa, yangisi ochilmaydi — o'sha oynada yuklanadi."popup,width=400,height=500"— alohida kichik oyna. Uchinchi argumentsiz — oddiy yangi tab.
Qaytgan chek bilan: chek.postMessage(...) — xabar yuborish, chek.close() — yopish, chek.closed — yopilganmi. Ochilgan oynada esa window.opener — uni ochgan oyna. Chrome 154 da sinadik: ochilgan sahifa window.opener.postMessage({ tayyor: true }, location.origin) yubordi va ochgan sahifa uni oldi. chek.close() dan keyin chek.closed — true.
5.2 Popup bloker
Brauzerlar foydalanuvchi o'zi bosmagan paytdagi window.open ni to'sadi — reklama oynalariga qarshi. To'silsa, window.open null qaytaradi. Shuning uchun:
window.openni faqatclickkabi hodisa ichida, to'g'ridan-to'g'ri chaqiring.await fetch(...)dan keyin chaqirilsa, brauzer uni "foydalanuvchi bosgani" bilan bog'lay olmasligi mumkin.- Natijani tekshiring:
if (chek === null) { elonQil("Popup to'sildi — ruxsat bering"); }.
5.3 noopener va target="_blank"
window.opener — xavfli eshik ham. Ochilgan begona sayt opener.location = "https://example.com/soxta-kirish" bilan sizning tabingizni soxta sahifaga almashtirishi mumkin. Foydalanuvchi esa eski tabga qaytib, "sessiya tugadi, qayta kiring" degan soxta oynani ko'radi. Bu hujum tabnabbing deyiladi.
Himoya — noopener:
<a href="..." target="_blank">— zamonaviy brauzerlarda standart bo'yichanoopener(Chrome 88 dan beri). Sinovda ochilgan sahifadagiwindow.opener—nullchiqdi.window.open(url, "_blank", "noopener")— aloqani uzadi, lekin u holdawindow.opennullqaytaradi (sinovda shunday chiqdi): oynaga havola ham,openerham yo'q.
Qoida: begona saytni ochsangiz — noopener. O'z sahifangiz bilan ishlashingiz kerak bo'lsa (masalan, OAuth kirish oynasi natijani opener ga yuboradi) — opener qoladi, lekin postMessage va origin tekshiruvi bilan.
Tekshirib ko'ring: Sardor kirish tugmasi bosilganda avval
await fetch("/sozlama")qildi, keyinwindow.open(...). Ba'zi brauzerlarda oyna ochilmayapti. Nega?
Javob
Popup bloker. window.open bosish hodisasidan keyin, fetch kutilgandan so'ng — boshqa task'da chaqirildi. Brauzer uni foydalanuvchi bosishi bilan bog'lamasligi mumkin va to'sadi. Yechim: oynani bosishda darhol ochish (const oyna = window.open("", "kirish")), manzilni esa fetch dan keyin berish (oyna.location = url).
6. Qaysi usulni tanlash
flowchart TD
S{"Kim bilan?"} -->|"Shu sayt tablari"| B["BroadcastChannel"]
S -->|"Boshqa origin"| P["postMessage + origin"]
S -->|"O'zim ochgan oyna"| O["window.open + postMessage"]
S -->|"Boshqa qurilma"| W["Server: WebSocket, SSE"]
B -.->|"muqobil"| L["storage hodisasi"]Nimaga qarang: birinchi savol — "origin bir xilmi?". Bir xil bo'lsa — BroadcastChannel, har xil bo'lsa — faqat postMessage. Brauzerdan tashqariga — faqat server orqali.
Kursda keyinroq yana ikkita uchraydi: Web Worker bilan aloqa ham postMessage orqali (Web Workers), Service Worker esa hamma tablarga clients orqali yozadi (Service Worker hayot sikli). Ikkalasi 13-qismda — hozir bilish shart emas.
Hujumchi nigohi
1-hujum — soxta xabar. Saytingizda message tinglovchisi event.origin ni tekshirmaydi va event.data.html ni sahifaga qo'yadi. Hujumchi o'z saytida sizning sahifangizni iframe'da yoki window.open bilan ochadi va unga postMessage({ html: "<img src=x onerror=...>" }, "*") yuboradi. Natijada XSS. Himoya: event.origin oq ro'yxat, ma'lumot shaklini tekshirish, faqat textContent.
2-hujum — xabarni ushlab olish. Siz tokenni opener.postMessage(token, "*") bilan yuborasiz. Lekin opener allaqachon boshqa saytga o'tgan — token o'sha saytga ketadi. Himoya: targetOrigin doim aniq origin.
3-hujum — tabnabbing. Yuqorida ko'rganimizdek: opener orqali ochgan tabni soxta sahifaga almashtirish. Himoya: begona saytlar uchun noopener (target="_blank" da standart).
4-hujum — kanal ichidagi ishonch. BroadcastChannel faqat shu origin ichida — begona sayt unga yoza olmaydi. Lekin saytingizda XSS bo'lsa, hujumchi skripti ham kanalga yozadi. Shuning uchun kanaldan kelgan xabarni ham "ishonchli, lekin tekshiriladigan" deb qabul qiling. vazifalar da shu sababli xabardagi ro'yxat import kabi paketniOqi tekshiruvidan o'tadi — o'tmasa, qabul qilinmaydi.
7. Ko'p uchraydigan xatolar
7.1 event.origin ni tekshirmaslik
Eng ko'p uchraydigan va eng xavfli xato. Tuzatish: har message tinglovchisining birinchi qatori — origin tekshiruvi.
7.2 Iframe yuklanmasdan xabar yuborish
postMessage iframe ichidagi tinglovchi paydo bo'lishidan oldin ketsa — xabar yo'qoladi. Tuzatish: iframe'ning load hodisasini kuting yoki iframe o'zi "tayyorman" deb xabar yuborsin, javobini shundan keyin bering.
7.3 O'z xabariga javob — aks-sado sikli
Tab xabar oldi → ma'lumotni yangiladi → "o'zgardi" deb yana tarqatdi → boshqa tab ham shunday qildi... va cheksiz. Tuzatish: tashqaridan kelgan o'zgarishni qabul qilish yo'lida qayta tarqatmang ("Vazifalar qadami" da aynan shu).
7.4 Funksiya yoki DOM elementni yuborish
postMessage({ bosilganda: () => ... }) yoki elementni yuborish — DataCloneError. Tuzatish: faqat ma'lumot yuboring; harakatni qabul qiluvchi o'zi bajarsin.
7.5 BroadcastChannel ni server aloqasi deb o'ylash
Telefoningizdagi tab kompyuterdagi tabning xabarini olmaydi. Tuzatish: qurilmalar orasida — server (WebSocket va Server-Sent Events).
8. Mashqlar
1-mashq (oson): Tizimdan chiqish hamma tabda
BroadcastChannel("hisob") bilan: «Chiqish» tugmasi bosilganda shu tab ekranga Siz tizimdan chiqdingiz yozsin va kanalga { tur: "chiqish" } yuborsin. Boshqa tablar shu xabarni olsa — ular ham xuddi shu matnni ko'rsatsin. Ishora: tab o'zi yuborgan xabarni olmaydi — demak ekranni yangilashni ikki joyda chaqiring.
Yechim
<p id="holat">Siz tizimdasiz: Dilshod aka</p>
<button id="chiqish" type="button">Chiqish</button>
<script>
const kanal = new BroadcastChannel("hisob");
const holat = document.getElementById("holat");
function chiqqanKorsat() {
holat.textContent = "Siz tizimdan chiqdingiz";
}
document.getElementById("chiqish").addEventListener("click", () => {
chiqqanKorsat();
kanal.postMessage({ tur: "chiqish" });
});
kanal.addEventListener("message", (event) => {
if (event.data?.tur === "chiqish") {
chiqqanKorsat();
}
});
</script>Live Server'da ikki tabda oching va birida bosing — ikkalasi ham "chiqdingiz" deydi. Haqiqiy ilovada chiqqanKorsat kirish sahifasiga location.replace ham qiladi (location va History API). Sessiya cookie'sini esa server o'chiradi (Cookie'lar JS'dan).
2-mashq (o'rta): Iframe balandligi
Iframe ichidagi kontent uzunligi o'zgarib turadi. Iframe ichidagi sahifa o'z balandligini otaga { tur: "balandlik", px: 320 } ko'rinishida yuborsin, ota esa iframe'ning style.height ini moslasin. Ota faqat o'z origin'idan kelgan va to'g'ri shakldagi (son, 0 dan 5000 gacha) xabarni qabul qilsin.
Yechim
Iframe ichida (ichki.html):
const balandlik = document.documentElement.scrollHeight;
const xabar = { tur: "balandlik", px: balandlik };
parent.postMessage(xabar, location.origin);Ota sahifada:
const ramka = document.getElementById("ramka");
addEventListener("message", (event) => {
if (event.origin !== location.origin
|| event.source !== ramka.contentWindow) {
return;
}
const { tur, px } = event.data ?? {};
const togri = Number.isInteger(px) && px > 0 && px <= 5000;
if (tur === "balandlik" && togri) {
ramka.style.height = `${px}px`;
}
});event.source !== ramka.contentWindow — qo'shimcha tekshiruv: xabar aynan shu iframe'dan kelsin, shu origin'dagi boshqa oynadan emas. IlmHamroh'ning natija oynasi ham balandligini aynan shu usul bilan otaga aytadi — shuning uchun natija kontentiga qarab o'sadi.
3-mashq (qiyin): Kim asosiy tab?
Bir nechta tab ochiq. Faqat bittasi har 5 soniyada "Yangi buyurtmalar bormi?" deb tekshirsin (serverni ortiqcha yuklamaslik uchun). Har tab ochilganda kanalga { tur: "salom", id } yuborsin; har tab o'zi ko'rgan eng kichik id li tab "asosiy" deb hisoblasin. id — crypto.randomUUID() emas, Date.now() + tasodifiy son (solishtirish oson bo'lsin). Ishora: yangi tab "salom" yuborganda, eski tablar ham javoban "salom" yuborsin — lekin faqat bir marta.
Yechim
const kanal = new BroadcastChannel("buyurtmalar");
const menId = Date.now() + Math.random();
const tablar = new Set([menId]);
function asosiymi() {
return Math.min(...tablar) === menId;
}
kanal.addEventListener("message", (event) => {
const { tur, id } = event.data ?? {};
if (tur === "salom" && typeof id === "number" && !tablar.has(id)) {
tablar.add(id);
kanal.postMessage({ tur: "salom", id: menId });
}
});
kanal.postMessage({ tur: "salom", id: menId });
setInterval(() => {
if (asosiymi()) {
console.log("Asosiy tab: buyurtmalar tekshirildi");
}
}, 5000);"Faqat bir marta" — !tablar.has(id) tekshiruvi: tanish tabga qayta javob berilmaydi, aks holda tablar bir-biriga cheksiz "salom" yuborardi (aks-sado sikli). Ikki-uch tab ochib tekshiring: konsol yozuvi faqat birinchi ochilgan tabda chiqadi.
Bu yechimning zaif joyi: asosiy tab yopilsa, qolganlari buni bilmaydi. Haqiqiy loyihada tablar "xayr" xabarini (pagehide hodisasida — Sahifa hayot sikli) yuboradi yoki brauzerning Web Locks API'sidan foydalaniladi: navigator.locks.request("asosiy", ...) — qulfni faqat bitta tab ushlaydi. Bu API'ni hozir bilish shart emas.
Faylni kurs/mashqlar/11/31-tablar/asosiy-tab.html ga saqlang:
git add 11/31-tablar/asosiy-tab.html
git commit -m "11/31: BroadcastChannel bilan asosiy tabni tanlash"4-mashq: Vazifalar qadami — tablar sinxroni
Darsning boshidagi muammoni hal qilamiz: ikki tabdagi vazifalar bir-birining o'zgarishini ko'rsin va eski nusxa bilan ustidan yozmasin.
- Yangi branch:
git switch -c feature/tablar-sinxroni. - Yangi modul
assets/js/sinxron.js:"vazifalar"nomliBroadcastChannel, ikkita eksport —tarqat(xabar)(kanalga yuboradi) vatingla(fn)(har xabardafn(event.data)ni chaqiradi). asosiy.js—saqlaVaChiz: saqlagach,tarqat({ tur: "ozgardi", paket: eksportMatni(holat.vazifalar) })— ro'yxat xabarning o'zida ketadi.asosiy.js— boshqa tabdan"ozgardi"kelsa:paketniOqi(xabar.paket)bilan o'qib, ro'yxatni almashtirish varender(). Bu yo'lda saqlamang va tarqatmang. Filtr o'zgarmasin — u har tabning o'z URL'ida.index.html—sinxron.jsuchunmodulepreload.
Ishora: paket — eksportMatni yasagan o'sha JSON matn, keshdagi bilan bir xil format. Uni qabul qilgan tab import kabi paketniOqi dan o'tkazadi: buzuq xabar ro'yxatni buzmaydi.
Yechim
assets/js/sinxron.js:
// sinxron.js — bir saytning tablari orasida xabar almashish
const kanal = new BroadcastChannel("vazifalar");
export function tarqat(xabar) {
kanal.postMessage(xabar);
}
export function tingla(fn) {
kanal.addEventListener("message", (event) => fn(event.data));
}Modul kanalni bir marta ochadi va uni tashqariga bermaydi: qolgan kod faqat tarqat va tingla ni biladi (Closure amaliyotda darsidagi modul naqshi g'oyasi). Ertaga BroadcastChannel o'rniga storage hodisasi yoki server kerak bo'lsa — faqat shu fayl o'zgaradi.
assets/js/asosiy.js — o'zgargan joylar:
import { eksportMatni, paketniOqi } from "./paket.js";
import { saqla } from "./saqlash.js";
import { tarqat, tingla } from "./sinxron.js";
// ...
// Ro'yxat o'zgarsa: xotiraga, boshqa tablarga, ekranga
function saqlaVaChiz() {
saqla(holat.vazifalar);
tarqat({ tur: "ozgardi", paket: eksportMatni(holat.vazifalar) });
render();
}
// Boshqa tab o'zgartirdi: ro'yxat xabarda keldi (saqlamaymiz!)
function boshqaTabdanYangila(xabar) {
if (xabar?.tur !== "ozgardi") {
return;
}
let malumotlar;
try {
malumotlar = paketniOqi(xabar.paket);
} catch (xato) {
console.warn(`Boshqa tab xabari o'qilmadi: ${xato.message}`);
return;
}
holat.vazifalar = VazifalarRoyxati.dan(malumotlar);
holat.vazifalar.obuna(saqlaVaChiz);
const tahrirdagi = holat.tahrirId;
if (tahrirdagi !== null && !holat.vazifalar.top(tahrirdagi)) {
holat.tahrirId = null;
}
render();
}
// ...
// Boshqa tablardagi o'zgarishlar
tingla(boshqaTabdanYangila);
// Ishga tushirish
holat.filtr = filtrniOl();
holat.vazifalar.obuna(saqlaVaChiz);
render();Nega xabar faqat "o'zgardi" signali emas — "signal kelsa, yukla() bilan localStorage dan o'qiymiz" soddaroq-ku? Chunki localStorage dagi o'zgarish boshqa tabga o'z yo'li bilan, BroadcastChannel xabari esa boshqa yo'l bilan boradi va ularning tartibi kafolatlanmagan: xabar ba'zan o'zgarishdan oldin yetadi va tab eski ro'yxatni o'qiydi. Yakuniy loyihada buni o'lchaymiz (Saqlanadigan va sinxronlanadigan vazifalar ilovasi). Ro'yxat xabarning o'zida bo'lsa, bu muammo yo'q — va localStorage to'lib saqlay olmasa ham tablar sinxron qoladi.
paket.js ni asosiy.js statik import qildi. Yangi yuklash qo'shilmadi: localStorage darsidan beri saqlash.js uni baribir ochilishda yuklaydi. Eksport va import tugmalaridagi paketniYukla() ga hozircha tegmang — uni yakuniy loyihada olib tashlaymiz.
Bitta o'zgarishning yo'li — diagrammada. Qarang: 2-tab xabarni oladi, lekin hech kimga qaytarmaydi.
sequenceDiagram
participant A as 1-tab
participant K as Kanal vazifalar
participant B as 2-tab
A->>A: vazifa qo'shildi → saqlaVaChiz
A->>K: tarqat: ozgardi + paket
K->>B: message
B->>B: paketniOqi → ro'yxat almashdi → render
Note over B: saqlamaydi, tarqatmaydiNega aks-sado sikli yo'q? boshqaTabdanYangila ro'yxatni almashtiradi, lekin saqlaVaChiz ni chaqirmaydi — faqat render(). Yangi ro'yxatga saqlaVaChiz faqat obuna bo'ladi: u keyinroq shu tabning o'z o'zgarishida ishlaydi. Shunda har o'zgarish bitta xabar beradi. Chrome 154 da avtomatik sinov bilan ikki tabda tekshirdik: 1-tabdagi 17 o'zgarish — 2-tabga roppa-rosa 17 xabar, 2-tabdagi ikkitasi — 1-tabga ikkita. 16 urinishning hammasida 2-tab yangi vazifani darhol ko'rsatdi.
Tahrirlanayotgan vazifani boshqa tab o'chirgan bo'lsa, tahrir yopiladi. O'chirilmagan bo'lsa, tahrir maydoni qayta chiziladi va unga yozilayotgan matn yo'qoladi — bu kichik noqulaylik, uni yakuniy loyihada hal qilish mumkin.
Brauzerda sinang (Live Server, ikki tab, ikkinchisi ?filtr=faol bilan):
- 1-tabda "Choy olish" qo'shing → 2-tabda u darhol paydo bo'ladi, hisob ikkalasida
3 ta vazifa qoldi, 1 ta bajarildi. - 2-tabda vazifani o'chiring → 1-tabda ham yo'qoladi.
- 2-tabda vazifani belgilang → 1-tabda u bajarilganlar qatoriga o'tadi; 2-tab esa
?filtr=faolda qoladi — filtrlar mustaqil. - Darsning boshidagi "Choy olish yo'qolib ketdi" holati endi takrorlanmaydi: har tab o'zgartirishdan oldin eng yangi ro'yxatga ega.
git add assets/js/sinxron.js assets/js/asosiy.js index.html
git commit -m "Vazifalar: tablarni BroadcastChannel bilan sinxronla"
git switch main
git merge feature/tablar-sinxroni
git branch -d feature/tablar-sinxroni
git push9. Real ishda
- Hisob holati. Gmail, GitHub, banklar — bir tabda chiqsangiz, hammasi chiqadi. Ichida —
BroadcastChannelyokistoragehodisasi. - To'lov vidjetlari va OAuth. Click, Payme, Stripe vidjetlari va "Google bilan kirish" oynalari natijani
postMessagebilan qaytaradi — origin tekshiruvi bilan. - Vidjetlar. Chat oynalari, YouTube pleyeri (
postMessageorqali boshqariladi), Telegram Mini App'lar (Telegram ilovasi va sahifa orasida xabarlar) — hammasi shu mexanizm. - Kutubxonalar.
broadcast-channel(npm) — eski brauzerlar uchun o'ram; TanStack Query va boshqa kesh kutubxonalari tablar aro sinxron imkoniyatini shu API ustiga quradi. - Intervyu. "Ikki tab qanday gaplashadi?", "
postMessageda nimani tekshirish kerak?", "target=_blankxavfi nima?" — xavfsizlik savollari bilan birga ko'p beriladi.
Xulosa
BroadcastChannel(nom)— bir origin'ning hamma tablari, iframe'lari va worker'lari uchun umumiy kanal; yuborgan obyektning o'zi olmaydi; xabar structured clone bilan.storagehodisasi —localStoragega bog'langan eski, lekin ishonchli muqobil.- Boshqa origin bilan — faqat
window.postMessage(xabar, aniqOrigin); qabul qiluvchievent.originva ma'lumot shaklini tekshiradi. window.open— oynaga havola qaytaradi,openerorqali teskari aloqa; begona sayt uchunnoopener; popup bloker — faqat bosishda.- Tashqi o'zgarishni qabul qilish yo'lida qayta tarqatmang — aks-sado sikli bo'lmasin.
Keyingi dars: Loyiha: saqlanadigan va sinxronlanadigan vazifalar ilovasi — qismning yakuniy loyihasi: localStorage kesh, mashq API bilan sinxron, optimistik yangilash, URL'dagi filtr va tablar sinxroni — hammasi bitta vazifalar v3 da.
Manbalar
- HTML Living Standard: "Broadcasting to other browsing contexts", "Cross-document messaging", "window.open" — html.spec.whatwg.org
- MDN: "Broadcast Channel API", "Window: postMessage() method", "Window: open() method", "Window: opener property" — developer.mozilla.org
- OWASP: "HTML5 Security Cheat Sheet" (Web Messaging, Tabnabbing) — cheatsheetseries.owasp.org
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!