Web API window.postMessage İle Pencereler Arası İletişim
Bir web sayfası veya uygulamasında pencereler (popup, tab, iframe) arasında iletişime ihtiyacımız olabilir.
Google Tag Manager Scroll Derinliği İşlemleri başlıklı yazımda şöyle bir cümle kullanmıştım; "bir sayfa içerisinde iframe etiketi kullanıldığında internet tarayıcısı HTML belgesi için ayrı, iframe için ayrı window nesnesi yaratır.". HTML5 ve API Kullanımları yazısının da devamı olarak bu konuyu farklı bir şekilde ele alacağım.
window Nesnesi
window
nesnesi tarayıcı penceresi olarak da ifade edebiliriz. Web tarayıcısı ile ilgili özellik ve metotları barındıran tarayıcı nesnesidir. Tarayıcı temelinde değerlendirmek gerekirse, nesneler hiyerarşisinde en tepede yer almaktadır. Diğer tüm nesneler bu nesnenin alt nesneleri olarak tanımlanırlar. window
içeriği aynı zamanda global JavaScript nesnesi olarak erişilebilir. Nodejs tarafında da global
olarak ifade edilir. window
nesnesine uygulanan genel bir standart olmasa da tüm modern tarayıcılar tarafından desteklenir.
window
nesnesi window
, history
, location
, navigator
, frames
, screen
ve document
alt nesnelerine sahiptir, bu nesneler açılmış olan pencerenin tümünü hedefleyen yöntemleri içerirler1. Alt nesnelerin herbirine de kendilerine ait, özellik, metot ve alt nesnelere sahiptir.
Tarayıcınızın console alanına window
yazarak nesne ile ilişkili alanları görüntüleyebilirsiniz. Her kod çalıştırma ortamında da (pencere/sekme), sadece bir tek JavaScript window nesnesi bulunabilir.
Gelelim iframe konusuna. Bir sayfa (document
nesnesi) içerisinde iframe
kullanılıyorsa, tarayıcı HTML belgesi için bir window
nesnesi ve her iframe
için de bir ek pencere nesnesi oluşturur2. frames
ile özelliği ile iframe elemenlerine ulaşabilir3, frameElement
ile tanımlı iframe elementine erişebiliriz4.
Özetlemek gerekirse, tarayıcımız içerisindeki pencereler ve iframe elementleri ayrı window
nesnelerine sahipler.
Soru şu, bu nesneler arasında iletişimi nasıl sağlarız? Örneğin, bir sayfa içerisinden popup içerisine ya da bir iframe içerisinden kapsayıcıya (ya da tersi) nasıl bilgi gönderebiliriz?
window.postMessage API
window.postMessage
, iki pencere/çerçeve arasında (cross-window communication) veri mesajları gönderilip alınabilmesine izin veren bir JavaScript metotudur. Bu metot sayesinde cross-domain (alan adları) arasında da güvenli bir şekilde veri mesajları taşıyabilmekteyiz. Diğer durumda, metotun çalışabilmesi için alan adı, protokol ve port tanımlarına uyulması gerekir5. Detaylarına birazdan örnek işlemler üzerinden değineceğim. Öncesinde bu metot için tanımlanan argümanlara bakalım6.
hedefWindow.postMessage(mesaj, alanadi, transfer);
- hedefWindow
- Mesajın gönderileceği
window
nesnesi kaynağını işaret eder.window.open
,window.opener
,HTMLIFrameElement.contentWindow
,window.parent
,window.frames
ile tanımlanabilir. Aşağıda,window.open
veHTMLIFrameElement.contentWindow
ile ilgili örnekleri görebilirsiniz. - mesaj
- Pencereler/çerçeveler arasında gönderilecek mesajı içerir.
- alanadi
targetWindow
kaynağını belirtir. Bu şekilde verinin hangi kaynaktan geldiğini kontrol edebilir ve işlem gerçekleştirilmesini sağlayabiliriz. Geliştirme ortamında"*"
tanımı yapılabilir. Ancak, kullanım aşamasında URL ya da URI olarak bir kaynak belirtilmesine dikkat edilmelidir7.- transfer
- Opsiyonel bir argümandır. Mesajla birlikte aktarılan
Transferable
nesneler dizisidir.
Peki, bu mesajları nasıl alırız?
MessageEvent
MessageEvent
interface, bir hedef nesne (target) tarafından alınan bir mesajı temsil eder8.
window.addEventListener("message", event => {
if (event.origin !== "http://example.org:8080") return;
}, false);
Mesaj kaynağına event.origin
, mesaj içeriğine event.data
ve mesajı gönderen pencere nesnesine event.source
ile erişebiliriz. event.source
, farklı origin
'lere sahip iki pencere arasında iki yönlü iletişim kurmak amacıyla kullanılabilir9.
Artık örnek işlemlere geçebiliriz. İlk örneği aynı alan adı altındaki sayfalar için popup aracılığı ile gerçekleştirelim.
İlk olarak ana sayfamızı, index.htm
, oluşturalım.
<script>
let childwin = null;
let openChild = () => {
childwin = window.open('./popup.htm', "popup", 'height=300px, width=500px');
}
let sendMessage = () => childwin
.postMessage(document
.getElementById('txt')
.value, '*');
</script>
<button onclick="openChild()">Open Popup</button>
<input type="text" id="txt" value="take a deep breath!" />
<button onclick="sendMessage()">send it</button>
Yukarıdaki kod ile temelde yapılan işlem bir popup pencere açmak, bir text input içerisindeki değeri (value) bu popup'e postMessage
ile mesaj (message) olarak göndermek. Şimdi de bu mesajı alacak olan popup sayfasının popup.htm
koduna bakalım.
<div id="data">
<strong>my todo list</strong>
</div>
<script>
const list = document.createElement('ul');
document.getElementById('data').appendChild(list).setAttribute('id', 'myList');
window.addEventListener("message", event => {
const item = document.createElement('li');
const itemVal = document.createTextNode(event.data);
document.getElementById('myList').appendChild(item);
item.appendChild(itemVal);
});
</script>
Bir div
ve JavaScript kodumuz var. JavaScript tarafında div
içerisinde her yeni gelen mesaj için bir li
etiketi oluşturuyor ve değer olarak index.htm
içerisindeki input değerini kullanıyor. Sayfalar arasındaki etkileşim aşağıdakine benzer bir görüntüye sahip olacaktır.
Bu örnekte aynı alan adı altındaki sayfaları kullanmıştık. İkinci örnekte süreci farklı alan adları altında bulunan sayfalar üzerinden çift yönlü olarak ele alalım. Bu defa popup yerine iframe
elementini kullanacağım. İlk dosyamızın adı yine index.htm
olsun ve example.com
alan adı altında yayınlansın. Son olarak, mesajı gönderen kaynağı da kontrol edelim.
<iframe src="http://subdomain.webstore.com/frame.htm" id="ifrm" width="400" height="250"></iframe>
<input type="text" id="txt" value="take a deep breath!" />
<button onclick="sendMessage()">send it</button>
<script>
let elIfrm = document.getElementById('ifrm').contentWindow;
let sendMessage = () => elIfrm
.postMessage(document
.getElementById('txt')
.value, 'http://subdomain.webstore.com');
</script>
iframe
içeriği sayfa oluşturulduktan sonra yüklenmiş olacaktır. Yukarıdaki örnekte JavaScript ile text input içerisindeki değeri iframe
içerisine mesaj olarak göndermekteyiz. frame.htm
içeriğinde çok fazla bir şey değişmedi.
<div id="data">
<strong>my todo list</strong>
</div>
<script>
const list = document.createElement('ul');
document.getElementById('data').appendChild(list).setAttribute('id', 'myList');
window.addEventListener("message", event => {
if (event.origin !== "http://example.com") return;
const item = document.createElement('li');
const itemVal = document.createTextNode(event.data);
document.getElementById('myList').appendChild(item);
item.appendChild(itemVal);
});
</script>
Şimdi, ekleme işleminin ardından origin
'e bir onay mesajı döndürelim. Bu onay mesajı da iframe
içerisinde oluşturulan listedeki child element sayısı olsun. Karşılaştırma yapabilmeniz için ilgili sayfalara ait kodları yeniden paylaşıyorum. İlk kodumuz index.htm
'e ait.
<iframe src="http://subdomain1.dnomia.com/frame.htm" id="ifrm" width="400" height="250"></iframe>
<input type="text" id="txt" value="take a deep breath!" />
<button onclick="sendMessage()">send it</button>
<div id="status"></div>
<script>
let elIfrm = document.getElementById('ifrm').contentWindow;
let sendMessage = () => elIfrm
.postMessage(document
.getElementById('txt')
.value, 'http://subdomain1.dnomia.com');
window.addEventListener("message", event => {
if (event.origin !== "http://subdomain1.dnomia.com") return;
document.getElementById('status').textContent = event.data
});
</script>
Şimdi de frame.htm
içeriğimize bakalım.
<div id="data">
<strong>my todo list</strong>
</div>
<script>
const list = document.createElement('ul');
document.getElementById('data').appendChild(list).setAttribute('id', 'myList');
window.addEventListener("message", event => {
if (event.origin !== "http://differentsubdomain.ezza.work") return;
const item = document.createElement('li');
const itemVal = document.createTextNode(event.data);
document.getElementById('myList').appendChild(item);
item.appendChild(itemVal);
event.source.postMessage(document.getElementById('myList').childElementCount + ' item(s)', 'http://differentsubdomain.ezza.work');
});
</script>
Evet, origin
kontrollerimizi yaptık, dönüş mesajını önceki mesaj kaynağı üzerinden gerçekleştirdik ve birbirleri arasında mesaj taşıyabilen sayfa ve çerçeveler elde ettik.
Artık pencereler ve çerçeveler arasında veri taşıyabileceğimizi biliyoruz. Bu işlemi farklı amaçlar doğrultusunda kullanmak da mümkün. İlerleyen zaman içerisinde bu konuya dair farklı kullanım örneklerinden ayrıca bahsedeceğim.
- Bedri Doğan Emir. (2017). Belge Çözümleyicisi Nesne Modeli (Browser Object Model / BOM) ↩
- The Window Object. w3schools.com ↩
- Window frames Property. w3schools.com ↩
- Window frameElement Property. w3schools.com ↩
- Window postMessage(). MDN web docs ↩
- How do you use window.postMessage across domains?. StackOverlow ↩
- JavaScript.info. (2020). Cross-window communication ↩
- MessageEvent MDN web docs ↩
- David Walsh. (2010) HTML5’s window.postMessage API ↩