web-dev-qa-db-fra.com

Comment obtenir une référence à un objet window d'iframe dans le gestionnaire onload d'iframe créé à partir de la fenêtre parente

Avant de coller du code, voici le scénario:

  1. J'ai un document HTML qui crée un iframe vide en utilisant JavaScript
  2. JavaScript crée une fonction et associe une référence à cette fonction à l'objet document de l'iframe (à l'aide de doc.open() pour obtenir une référence au document).
  3. La fonction est ensuite attachée en tant que gestionnaire onload pour le document de l’iframe (en écrivant <body onload="..."> dans l'iframe.

Maintenant, ce qui m'a stoppé, c'est que les objets globaux (fenêtre) et document dans le gestionnaire de chargement onload (pendant son exécution) sont différents des mêmes objets exécutés via JavaScript ajouté via des nœuds de script.

Voici le code HTML:

<!doctype html>
<html>
<head>
<script>
(function(){
  var dom,doc,where,iframe;

  iframe = document.createElement('iframe');
  iframe.src="javascript:false";

  where = document.getElementsByTagName('script')[0];
  where.parentNode.insertBefore(iframe, where);

  doc = iframe.contentWindow.document;

  var _doc = document;

  doc.open()._l=function() {
    // the window object should be the one that doc is inside
    window.vanishing_global=new Date().getTime();

    var js = this.createElement("script");
    js.src = 'test-vanishing-global.js?' + window.vanishing_global;

    window.name="foobar";
    this.foobar="foobar:" + Math.random();
    document.foobar="barfoo:" + Math.random();

    // `this` should be the document object, but it's not
    console.log("this == document: %s", this == document);
    console.log("this == doc:      %s", this == doc);

    // the next two lines added based on @Ian's comment below
    console.log("_doc == document: %s", _doc == document);
    console.log("_doc == doc:      %s", _doc == doc);

    console.log("name: " + window.name + "\n" + "window.vanishing_global: " + window.vanishing_global + "\ntypeof window.vanishing_global: " + typeof window.vanishing_global + "\ndocument.foobar: " + document.foobar);
    this.body.appendChild(js);
  };
  doc.write('<body onload="document._l();"></body>');
  doc.close();
})();
</script>
</head>
<body>
</body>
</html>

Et voici test-vanishing-global.js:

console.log("name: " + window.name + "\n" + "window.vanishing_global: " + window.vanishing_global + "\ntypeof window.vanishing_global: " + typeof window.vanishing_global + "\ndocument.foobar: " + document.foobar);

Instructions:

Mettez ces deux fichiers dans un répertoire et ouvrez le code HTML dans un navigateur (testé dans les derniers Chrome et Firefox, résultat identique dans les deux cas).

Voici le résultat obtenu:

this == document: false
this == doc:      true
_doc == document: true
_doc == doc:      false

name: foobar
window.vanishing_global: 1366037771608
typeof window.vanishing_global: number
document.foobar: barfoo:0.9013048021588475

name: 
window.vanishing_global: undefined
typeof window.vanishing_global: undefined
document.foobar: foobar:0.5015988759696484

L'objet this à l'intérieur du gestionnaire doit être soit l'objet document. C'est est un objet de document, mais pas le même objet de document que le document dans lequel il est exécuté (ce n'est pas non plus la même chose que le document parent). L'objet window à l'intérieur du gestionnaire n'est également pas le même que l'objet window exécuté dans JavaScript chargé dans la page.

Donc finalement ma question:

Quelqu'un sait-il ce qui se passe et comment puis-je obtenir une référence à l'objet window réel ou au moins déclarer et référencer une variable globale à partir du même contexte global?

Notes de bas de page:

Il n'y a pas de problèmes inter-domaines avec cette iframe puisqu'ils sont sur le même domaine. Il y a un problème si quelqu'un définit document.domain, mais cela ne se fait pas dans cet exemple de code.

50
bluesmoon

Vous déclarez tout dans la page parent. Ainsi, les références à window et document concernent la page parente. Si vous voulez faire des choses sur les iframe, utilisez iframe || iframe.contentWindow Pour accéder à ses window, et iframe.contentDocument || iframe.contentWindow.document Pour accéder à ses document.

Il y a un mot pour ce qui se passe, éventuellement "portée lexicale": Qu'est-ce que la portée lexicale?

Le seul contexte d’une portée est celui-ci. Et dans votre exemple, le propriétaire de la méthode est doc, qui est le iframe 'document. En dehors de cela, tout ce qui est accédé dans cette fonction et qui utilise des objets connus est celui du parent (s'il n'est pas déclaré dans la fonction). Ce serait une histoire différente si la fonction était déclarée à un endroit différent, mais dans la page parent.

Voici comment je l'écrirais:

(function () {
  var dom, win, doc, where, iframe;

  iframe = document.createElement('iframe');
  iframe.src = "javascript:false";

  where = document.getElementsByTagName('script')[0];
  where.parentNode.insertBefore(iframe, where);

  win = iframe.contentWindow || iframe;
  doc = iframe.contentDocument || iframe.contentWindow.document;

  doc.open();
  doc._l = (function (w, d) {
    return function () {
      w.vanishing_global = new Date().getTime();

      var js = d.createElement("script");
      js.src = 'test-vanishing-global.js?' + w.vanishing_global;

      w.name = "foobar";
      d.foobar = "foobar:" + Math.random();
      d.foobar = "barfoo:" + Math.random();
      d.body.appendChild(js);
    };
  })(win, doc);
  doc.write('<body onload="document._l();"></body>');
  doc.close();
})();

Les alias de win et doc comme w et d ne sont pas nécessaires, cela pourrait le rendre moins déroutant en raison de la mauvaise compréhension des portées. De cette façon, ce sont des paramètres et vous devez les référencer pour accéder au contenu de iframe. Si vous voulez accéder au parent, vous utilisez toujours window et document.

Je ne suis pas sûr des implications de l'ajout de méthodes à un document (doc dans ce cas), mais il serait peut-être plus logique de définir la méthode _l Sur win. De cette façon, les choses peuvent être exécutées sans préfixe ... tel que <body onload="_l();"></body>

57
Ian