Communication cross-origin avec postMessage et WeakMap en JavaScript

Apprenez a utiliser postMessage pour communiquer entre fenetres et WeakMap pour stocker des donnees avec cles faibles en JavaScript.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 10 min read
Communication cross-origin avec postMessage et WeakMap en JavaScript

Communication entre fenêtres : utiliser .postMessage() et WeakMap

Introduction

Dans le monde du developpement web moderne, la communication entre differents contextes d’execution JavaScript est devenue une necessite incontournable. Que ce soit pour integrer des widgets tiers, communiquer avec des iframes, orchestrer des Web Workers, ou gerer des Service Workers, les developpeurs doivent maitriser les mecanismes de communication inter-contextes.

La Same-Origin Policy (SOP) est une mesure de securite fondamentale des navigateurs qui empeche les scripts d’un origin (combinaison protocole + domaine + port) d’acceder aux ressources d’un autre origin. Cette politique protege les utilisateurs contre les attaques de type Cross-Site Scripting (XSS) et Cross-Site Request Forgery (CSRF).

Cependant, cette restriction pose un defi majeur : comment permettre a des applications legitimes de communiquer entre elles de maniere securisee ? C’est la que .postMessage() entre en jeu, offrant un canal de communication securise et standardise.

Parallelement, la gestion de la memoire en JavaScript est un sujet crucial, particulierement dans les applications Single Page (SPA) qui tournent pendant de longues periodes. Les WeakMap et WeakSet offrent des solutions elegantes pour associer des donnees a des objets sans creer de fuites memoire.

Dans cet article approfondi, nous explorerons :

  • La methode postMessage() dans tous ses contextes d’utilisation
  • Les bonnes pratiques de securite pour la communication cross-origin
  • L’utilisation avancee des WeakMap et WeakSet
  • Les patterns de conception pour eviter les fuites memoire

Utiliser .postMessage() - Guide Complet

La methode .postMessage() est le mecanisme standard pour la communication asynchrone entre differents contextes d’execution JavaScript. Elle respecte la Same-Origin Policy tout en permettant une communication controlee entre origins differents.

Syntaxe complete

La signature complete de postMessage() est la suivante :

targetWindow.postMessage(message, targetOrigin, [transfer]);

Parametres :

ParametreTypeDescription
messageanyLe message a envoyer. Sera clone via l’algorithme de clonage structure
targetOriginstringL’origin cible. Utilisez "*" pour n’importe quel origin (deconseille) ou une URL specifique
transferarray(Optionnel) Tableau d’objets Transferable a transferer plutot qu’a cloner

Communication window vers iframe

Le cas d’utilisation le plus courant est la communication entre une page parente et une iframe. Voici un exemple complet :

Page parente (parent.html) :

// Obtenir une reference a l'iframe
const iframe = document.getElementById('myIframe');

// Attendre que l'iframe soit chargee
iframe.addEventListener('load', () => {
  // Creer un message structure
  const message = {
    type: 'INIT',
    payload: {
      userId: 12345,
      theme: 'dark',
      language: 'fr'
    },
    timestamp: Date.now()
  };

  // Envoyer le message a l'iframe
  // IMPORTANT: Specifier l'origin exact pour la securite
  iframe.contentWindow.postMessage(message, 'https://widget.example.com');
});

// Ecouter les reponses de l'iframe
window.addEventListener('message', (event) => {
  // CRUCIAL: Toujours valider l'origine
  if (event.origin !== 'https://widget.example.com') {
    console.warn('Message rejete - origin non autorise:', event.origin);
    return;
  }

  // Traiter le message
  const { type, payload } = event.data;

  switch (type) {
    case 'READY':
      console.log('Widget pret');
      break;
    case 'USER_ACTION':
      handleUserAction(payload);
      break;
    case 'ERROR':
      console.error('Erreur widget:', payload.message);
      break;
    default:
      console.warn('Type de message inconnu:', type);
  }
});

Iframe (widget.html) :

// Signaler que le widget est pret
window.parent.postMessage(
  { type: 'READY', timestamp: Date.now() },
  'https://parent.example.com'
);

// Ecouter les messages du parent
window.addEventListener('message', (event) => {
  // Valider l'origine
  if (event.origin !== 'https://parent.example.com') {
    return;
  }

  const { type, payload } = event.data;

  if (type === 'INIT') {
    // Initialiser le widget avec les donnees recues
    initWidget(payload);
  }
});

function initWidget(config) {
  document.body.classList.add(`theme-${config.theme}`);
  setLanguage(config.language);
  loadUserData(config.userId);
}

Communication avec les Web Workers

Les Web Workers permettent d’executer du code JavaScript dans un thread separe. postMessage() est le seul moyen de communiquer avec eux :

Thread principal :

// Creer un Worker
const worker = new Worker('worker.js');

// Envoyer des donnees au Worker
worker.postMessage({
  action: 'PROCESS_DATA',
  data: largeDataArray
});

// Transferer un ArrayBuffer (zero-copy)
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
worker.postMessage(
  { action: 'PROCESS_BUFFER', buffer },
  [buffer] // Le buffer est transfere, pas clone
);
// Attention: buffer n'est plus utilisable ici apres le transfert

// Recevoir les resultats
worker.addEventListener('message', (event) => {
  const { action, result, error } = event.data;

  if (error) {
    console.error('Erreur Worker:', error);
    return;
  }

  switch (action) {
    case 'PROCESS_COMPLETE':
      updateUI(result);
      break;
    case 'PROGRESS':
      updateProgressBar(result.percentage);
      break;
  }
});

// Gerer les erreurs du Worker
worker.addEventListener('error', (error) => {
  console.error('Erreur fatale Worker:', error.message);
});

worker.js :

// Ecouter les messages du thread principal
self.addEventListener('message', (event) => {
  const { action, data, buffer } = event.data;

  try {
    switch (action) {
      case 'PROCESS_DATA':
        // Traitement intensif sans bloquer l'UI
        const result = heavyComputation(data);
        self.postMessage({ action: 'PROCESS_COMPLETE', result });
        break;

      case 'PROCESS_BUFFER':
        // Traiter le buffer transfere
        const view = new Uint8Array(buffer);
        processBuffer(view);
        // Renvoyer le buffer au thread principal
        self.postMessage(
          { action: 'PROCESS_COMPLETE', buffer },
          [buffer]
        );
        break;
    }
  } catch (error) {
    self.postMessage({
      action: 'ERROR',
      error: error.message
    });
  }
});

function heavyComputation(data) {
  // Simuler un traitement lourd
  let result = 0;
  for (let i = 0; i < data.length; i++) {
    result += Math.sqrt(data[i]);

    // Envoyer la progression tous les 10%
    if (i % (data.length / 10) === 0) {
      self.postMessage({
        action: 'PROGRESS',
        result: { percentage: (i / data.length) * 100 }
      });
    }
  }
  return result;
}

Communication avec les Service Workers

Les Service Workers sont utilises pour le caching, les notifications push et le fonctionnement hors-ligne. La communication avec eux differe legerement :

// Enregistrer le Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then((registration) => {
      console.log('SW enregistre:', registration.scope);
    });
}

// Envoyer un message au Service Worker actif
function sendToServiceWorker(message) {
  return new Promise((resolve, reject) => {
    const messageChannel = new MessageChannel();

    messageChannel.port1.onmessage = (event) => {
      if (event.data.error) {
        reject(event.data.error);
      } else {
        resolve(event.data);
      }
    };

    navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
  });
}

// Utilisation
async function clearCache() {
  const response = await sendToServiceWorker({
    action: 'CLEAR_CACHE',
    cacheName: 'api-cache-v1'
  });
  console.log('Cache efface:', response.success);
}

// Ecouter les messages du Service Worker
navigator.serviceWorker.addEventListener('message', (event) => {
  const { type, payload } = event.data;

  if (type === 'CACHE_UPDATED') {
    // Rafraichir les donnees affichees
    refreshData();
  }
});

sw.js (Service Worker) :

self.addEventListener('message', (event) => {
  const { action, cacheName } = event.data;

  if (action === 'CLEAR_CACHE') {
    caches.delete(cacheName)
      .then(() => {
        // Repondre via le port
        event.ports[0].postMessage({ success: true });
      })
      .catch((error) => {
        event.ports[0].postMessage({ error: error.message });
      });
  }
});

// Notifier les clients d'une mise a jour
async function notifyClients(message) {
  const clients = await self.clients.matchAll();
  clients.forEach(client => {
    client.postMessage(message);
  });
}

L’algorithme de clonage structure (Structured Clone)

Contrairement a JSON.stringify(), l’algorithme de clonage structure peut serialiser :

// Types supportes par le clonage structure (mais pas JSON)
const complexData = {
  date: new Date(),
  regex: /pattern/gi,
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  arrayBuffer: new ArrayBuffer(8),
  blob: new Blob(['contenu']),
  file: fileInput.files[0],
  imageData: ctx.getImageData(0, 0, 100, 100),
  error: new Error('Test error')
};

// Ces objets seront correctement clones
worker.postMessage(complexData);

// ATTENTION: Ces types ne sont PAS supportes
const unsupported = {
  func: () => {}, // Fonctions
  symbol: Symbol('test'), // Symbols
  dom: document.body, // Noeuds DOM
  weakMap: new WeakMap(), // WeakMap
  weakSet: new WeakSet() // WeakSet
};
// worker.postMessage(unsupported); // ERREUR!

Validation de l’origine - Securite critique

La validation de l’origine est ESSENTIELLE pour la securite. Voici les bonnes pratiques :

// Liste blanche des origines autorisees
const ALLOWED_ORIGINS = [
  'https://trusted-partner.com',
  'https://widget.myapp.com',
  'https://api.myapp.com'
];

// En developpement, ajouter localhost
if (process.env.NODE_ENV === 'development') {
  ALLOWED_ORIGINS.push('http://localhost:3000');
}

window.addEventListener('message', (event) => {
  // Methode 1: Verification stricte
  if (!ALLOWED_ORIGINS.includes(event.origin)) {
    console.warn(`Origin bloque: ${event.origin}`);
    return;
  }

  // Methode 2: Verification par pattern (pour sous-domaines)
  const trustedPattern = /^https:\/\/[\w-]+\.myapp\.com$/;
  if (!trustedPattern.test(event.origin)) {
    return;
  }

  // Validation du format du message
  if (!isValidMessage(event.data)) {
    console.warn('Format de message invalide');
    return;
  }

  // Traitement securise
  processMessage(event.data, event.source);
});

function isValidMessage(data) {
  return (
    typeof data === 'object' &&
    data !== null &&
    typeof data.type === 'string' &&
    data.type.length <= 50
  );
}

Utiliser WeakMap - Guide Complet

Une WeakMap est une collection de paires cle-valeur ou les cles doivent etre des objets et sont referencees “faiblement”. Cela signifie que si aucune autre reference a l’objet cle n’existe, celui-ci peut etre garbage-collecte.

Differences fondamentales avec Map

// Comparaison Map vs WeakMap
const map = new Map();
const weakMap = new WeakMap();

let obj = { id: 1 };

map.set(obj, 'valeur map');
weakMap.set(obj, 'valeur weakmap');

// La Map maintient une reference forte
// La WeakMap maintient une reference faible

obj = null; // On supprime la reference

// Apres garbage collection:
// - map contient toujours l'entree (fuite memoire potentielle)
// - weakMap a perdu l'entree automatiquement

// Differences de fonctionnalites
console.log(map.size); // 1 (meme apres obj = null)
// console.log(weakMap.size); // ERREUR: size n'existe pas

// Iteration possible sur Map
for (const [key, value] of map) {
  console.log(key, value);
}

// Iteration IMPOSSIBLE sur WeakMap
// for (const [key, value] of weakMap) {} // ERREUR!
CaracteristiqueMapWeakMap
Types de clesTout typeObjets uniquement
ReferencesFortesFaibles
IterationOui (forEach, for…of)Non
Propriete sizeOuiNon
Methode clear()OuiNon
Garbage collectionManuelAutomatique

Cas d’usage 1 : Donnees privees

Les WeakMap permettent de simuler des proprietes privees sans fuites memoire :

// Pattern des donnees privees avec WeakMap
const privateData = new WeakMap();

class User {
  constructor(name, email, password) {
    // Donnees publiques
    this.name = name;
    this.email = email;

    // Donnees privees (non accessibles de l'exterieur)
    privateData.set(this, {
      password: hashPassword(password),
      createdAt: new Date(),
      loginAttempts: 0,
      lastLogin: null
    });
  }

  checkPassword(password) {
    const data = privateData.get(this);
    const isValid = verifyHash(password, data.password);

    if (!isValid) {
      data.loginAttempts++;
      if (data.loginAttempts >= 5) {
        throw new Error('Compte bloque');
      }
    } else {
      data.loginAttempts = 0;
      data.lastLogin = new Date();
    }

    return isValid;
  }

  getAccountInfo() {
    const data = privateData.get(this);
    return {
      name: this.name,
      email: this.email,
      memberSince: data.createdAt,
      lastLogin: data.lastLogin
      // Note: password n'est jamais expose
    };
  }
}

const user = new User('Alice', 'alice@example.com', 'secret123');
console.log(user.password); // undefined (non accessible)
console.log(privateData.get(user)); // Accessible seulement en interne

// Quand user n'est plus reference, les donnees privees
// sont automatiquement garbage-collectees

Cas d’usage 2 : Cache avec garbage collection automatique

// Cache qui ne bloque pas le garbage collection
const computationCache = new WeakMap();

function expensiveComputation(obj) {
  // Verifier le cache
  if (computationCache.has(obj)) {
    console.log('Cache hit!');
    return computationCache.get(obj);
  }

  console.log('Computing...');
  // Simulation d'un calcul couteux
  const result = {
    hash: calculateHash(obj),
    analysis: deepAnalyze(obj),
    timestamp: Date.now()
  };

  // Stocker en cache
  computationCache.set(obj, result);

  return result;
}

// Utilisation
let data = { values: [1, 2, 3, 4, 5] };

expensiveComputation(data); // Computing...
expensiveComputation(data); // Cache hit!

// Quand data n'est plus utilise:
data = null;
// Le cache est automatiquement nettoye par le GC
// Pas besoin de gestion manuelle!

Cas d’usage 3 : Metadonnees sur objets DOM

// Associer des metadonnees aux elements DOM sans polluer l'element
const elementMetadata = new WeakMap();

function trackElement(element, metadata) {
  const existing = elementMetadata.get(element) || {};
  elementMetadata.set(element, { ...existing, ...metadata });
}

function getElementMetadata(element) {
  return elementMetadata.get(element);
}

// Exemple: Tracking d'interactions
document.querySelectorAll('.trackable').forEach(element => {
  trackElement(element, {
    createdAt: Date.now(),
    clicks: 0,
    hovers: 0
  });

  element.addEventListener('click', () => {
    const meta = getElementMetadata(element);
    meta.clicks++;
    meta.lastClick = Date.now();
  });

  element.addEventListener('mouseenter', () => {
    const meta = getElementMetadata(element);
    meta.hovers++;
  });
});

// Quand un element est retire du DOM et n'a plus de references,
// ses metadonnees sont automatiquement nettoyees

Cas d’usage 4 : Memoization d’objets

// Memoization sans fuite memoire
function createMemoizedRenderer() {
  const cache = new WeakMap();

  return function render(component) {
    if (cache.has(component)) {
      const cached = cache.get(component);
      if (cached.props === component.props) {
        return cached.result;
      }
    }

    const result = renderComponent(component);
    cache.set(component, {
      props: component.props,
      result
    });

    return result;
  };
}

const memoizedRender = createMemoizedRenderer();

Utiliser WeakSet

Un WeakSet est similaire a un Set, mais ne contient que des objets et utilise des references faibles.

Syntaxe de base

const weakSet = new WeakSet();

let obj1 = { id: 1 };
let obj2 = { id: 2 };

weakSet.add(obj1);
weakSet.add(obj2);

console.log(weakSet.has(obj1)); // true
console.log(weakSet.has({ id: 1 })); // false (objet different)

weakSet.delete(obj1);
console.log(weakSet.has(obj1)); // false

// Pas d'iteration possible
// weakSet.forEach(...); // ERREUR
// for (const item of weakSet) {} // ERREUR
// console.log(weakSet.size); // ERREUR

Cas d’usage : Tracking d’objets visites

// Detecter les references circulaires
function detectCircular(obj, visited = new WeakSet()) {
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  if (visited.has(obj)) {
    return true; // Reference circulaire detectee!
  }

  visited.add(obj);

  for (const key in obj) {
    if (detectCircular(obj[key], visited)) {
      return true;
    }
  }

  return false;
}

// Test
const normal = { a: { b: { c: 1 } } };
const circular = { a: { b: null } };
circular.a.b = circular; // Reference circulaire

console.log(detectCircular(normal)); // false
console.log(detectCircular(circular)); // true

Cas d’usage : Marquer des objets comme traites

// Systeme de traitement qui evite les doublons
class EventProcessor {
  constructor() {
    this.processed = new WeakSet();
  }

  process(event) {
    if (this.processed.has(event)) {
      console.log('Event deja traite, ignore');
      return;
    }

    // Traiter l'event
    this.handleEvent(event);

    // Marquer comme traite
    this.processed.add(event);
  }

  handleEvent(event) {
    console.log('Traitement:', event.type);
  }
}

const processor = new EventProcessor();
const event = { type: 'click', target: 'button' };

processor.process(event); // Traitement: click
processor.process(event); // Event deja traite, ignore

Cas d’usage : Validation d’objets

// Valider que des objets proviennent d'une source fiable
const trustedObjects = new WeakSet();

function createTrustedObject(data) {
  const obj = { ...data, _verified: true };
  trustedObjects.add(obj);
  return obj;
}

function isTrusted(obj) {
  return trustedObjects.has(obj);
}

function processSecurely(obj) {
  if (!isTrusted(obj)) {
    throw new Error('Objet non verifie!');
  }
  // Traitement securise...
}

const trusted = createTrustedObject({ userId: 1 });
const untrusted = { userId: 1, _verified: true };

processSecurely(trusted); // OK
// processSecurely(untrusted); // ERREUR: Objet non verifie!

Bonnes Pratiques

Pour postMessage()

  1. Toujours valider l’origine : Ne jamais utiliser "*" comme targetOrigin en production
// MAL
otherWindow.postMessage(data, '*');

// BIEN
otherWindow.postMessage(data, 'https://trusted-domain.com');
  1. Valider le format des messages : Ne jamais faire confiance aux donnees recues
window.addEventListener('message', (event) => {
  // Valider l'origine
  if (event.origin !== EXPECTED_ORIGIN) return;

  // Valider le type
  if (typeof event.data !== 'object') return;
  if (!event.data.type) return;

  // Valider le contenu selon le type
  switch (event.data.type) {
    case 'UPDATE_USER':
      if (!isValidUserId(event.data.userId)) return;
      break;
  }

  // Traiter le message
});
  1. Utiliser des Transferables pour les gros volumes : Eviter le clonage couteux
// Transferer au lieu de cloner
const buffer = new ArrayBuffer(10 * 1024 * 1024); // 10MB
worker.postMessage({ buffer }, [buffer]); // Zero-copy transfer
  1. Implementer un protocole de communication : Messages types et versiones
const message = {
  version: '1.0',
  type: 'ACTION_NAME',
  payload: { /* ... */ },
  timestamp: Date.now(),
  correlationId: generateUUID()
};

Pour WeakMap et WeakSet

  1. Utiliser pour les metadonnees : Associer des donnees a des objets sans modifier ces objets

  2. Preferer WeakMap pour le caching : Le cache se nettoie automatiquement

  3. Eviter pour les donnees persistantes : Les entrees peuvent disparaitre a tout moment

  4. Ne pas compter sur l’enumeration : Impossible d’iterer ou de connaitre la taille

Pieges Courants

Piege 1 : Utiliser "*" comme targetOrigin

// DANGER: N'importe qui peut intercepter
window.parent.postMessage(sensitiveData, '*');

// Solution: Specifier l'origine exacte
window.parent.postMessage(sensitiveData, 'https://parent.myapp.com');

Piege 2 : Oublier de valider event.origin

// VULNERABLE
window.addEventListener('message', (event) => {
  document.body.innerHTML = event.data.html; // XSS!
});

// SECURISE
window.addEventListener('message', (event) => {
  if (event.origin !== TRUSTED_ORIGIN) return;
  // Traiter le message de facon securisee
});

Piege 3 : Cles primitives dans WeakMap

const wm = new WeakMap();

// ERREUR: Les primitives ne sont pas autorisees
wm.set('string', 'value'); // TypeError
wm.set(123, 'value'); // TypeError
wm.set(Symbol('sym'), 'value'); // TypeError

// CORRECT: Utiliser des objets
wm.set({ key: 'string' }, 'value');

Piege 4 : Attendre un comportement synchrone du GC

const wm = new WeakMap();
let obj = { data: 'test' };
wm.set(obj, 'metadata');

obj = null;

// FAUX: L'entree n'est pas immediatement supprimee
console.log(wm.has(obj)); // Peut etre true ou false!

// Le GC est non deterministe, ne jamais compter
// sur un timing precis

Piege 5 : Confondre clonage et transfert

const buffer = new ArrayBuffer(1024);
const view = new Uint8Array(buffer);
view[0] = 42;

// Apres transfert, le buffer original est "detache"
worker.postMessage({ buffer }, [buffer]);

console.log(buffer.byteLength); // 0! Le buffer est vide
console.log(view[0]); // TypeError ou 0

Conclusion

La maitrise de postMessage(), WeakMap et WeakSet est essentielle pour tout developpeur JavaScript moderne. Ces outils permettent de :

  • Communiquer de facon securisee entre differents contextes (iframes, Workers, Service Workers)
  • Gerer la memoire efficacement sans fuites
  • Associer des metadonnees a des objets sans les modifier
  • Implementer des patterns avances comme le caching intelligent ou les donnees privees

Tableau Comparatif : Map vs WeakMap

CritereMapWeakMap
Types de clesTout type (primitives et objets)Objets uniquement
ReferencesFortes (empechent le GC)Faibles (permettent le GC)
EnumerableOui (for…of, forEach, keys(), values())Non
TailleAccessible via .sizeNon disponible
Effacement.clear() disponibleNon disponible
Cas d’usageCollections generales, lookupsCache, metadonnees, donnees privees
Risque memoireFuite possible si non gereAucun (auto-nettoye)
PerformanceExcellenteExcellente

Tableau Comparatif : Set vs WeakSet

CritereSetWeakSet
Types de valeursTout typeObjets uniquement
ReferencesFortesFaibles
EnumerableOuiNon
Cas d’usageCollections uniquesTracking d’objets, flags

En maitrisant ces concepts, vous serez capable de construire des applications web robustes, securisees et performantes, tout en evitant les pieges courants lies a la communication inter-contextes et a la gestion memoire.

Prochaines etapes

  • Explorer les Shared Workers pour la communication entre onglets
  • Implementer un systeme de messaging avec Broadcast Channel API
  • Combiner WeakMap avec Proxy pour un systeme de reactivite avance
  • Etudier les patterns de communication avec les iframes sandboxees
Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles