Pourquoi diviser la balise <script> lors de son écriture avec document.write ()?

268

Pourquoi certains sites (ou annonceurs qui fournissent du code javascript aux clients) utilisent une technique de fractionnement des balises <script>et / ou des </script>balises dans les document.write()appels?

J'ai remarqué qu'Amazon le fait également, par exemple:

<script type='text/javascript'>
  if (typeof window['jQuery'] == 'undefined') document.write('<scr'+'ipt type="text/javascript" src="http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js"></sc'+'ript>');
</script>
Danois
la source

Réponses:

373

</script>doit être rompu car sinon il mettrait fin au <script></script>bloc de fermeture trop tôt. Vraiment, il devrait être divisé entre le <et le /, car un bloc de script est censé (selon SGML) se terminer par une séquence ETAGO (end-tag open) (c'est-à-dire </) :

Bien que les éléments STYLE et SCRIPT utilisent CDATA pour leur modèle de données, pour ces éléments, CDATA doit être géré différemment par les agents utilisateurs. Le balisage et les entités doivent être traités comme du texte brut et transmis à l'application tels quels. La première occurrence de la séquence de caractères " </" (délimiteur ouvert de balise de fin) est traitée comme terminant la fin du contenu de l'élément. Dans les documents valides, ce serait la balise de fin de l'élément.

Cependant, dans la pratique, les navigateurs ne terminent l'analyse d'un bloc de script CDATA que sur une </script>balise de fermeture réelle .

En XHTML, il n'y a pas une telle manipulation spéciale pour les blocs de script, donc tout caractère <(ou &) à l'intérieur doit être &escaped;comme dans n'importe quel autre élément. Cependant, les navigateurs qui analysent le XHTML en HTML à l'ancienne seront confus. Il existe des solutions de contournement impliquant des blocs CDATA, mais il est plus simple d'éviter simplement d'utiliser ces caractères sans échapper. Une meilleure façon d'écrire un élément de script à partir d'un script qui fonctionne sur l'un ou l'autre type d'analyseur serait:

<script type="text/javascript">
    document.write('\x3Cscript type="text/javascript" src="foo.js">\x3C/script>');
</script>
bobince
la source
30
\/est une séquence d'échappement valide pour /, alors pourquoi ne pas simplement l'utiliser à la place de ces échappements littéraux de chaîne pour <? Par exemple document.write('<script src=foo.js><\/script>');. En outre, ce </script>n'est pas la seule séquence de caractères qui peut fermer un <script>élément. Plus d'infos ici: mathiasbynens.be/notes/etago
Mathias Bynens
11
@Mathias: <\/script>c'est bien dans ce cas, mais cela ne fonctionnerait qu'en HTML; en XHTML sans habillage de section CDATA supplémentaire, c'est toujours une erreur de bonne forme. Vous pouvez également utiliser \x3Cdes attributs de gestionnaire d'événements en ligne où ils <seraient également invalides à la fois en HTML et en XHTML, de sorte qu'il a une applicabilité plus large: si je choisissais un, un moyen facilement automatisé pour échapper des caractères sensibles dans les littéraux de chaîne JS pour tous les contextes, c'est celui que je choisirais.
bobince
3
En HTML, <peut être utilisé dans les attributs du gestionnaire d'événements en ligne. html5.validator.nu/… Et vous avez raison sur la compatibilité XHTML d' \x3Cun sich, mais puisque XHTML ne prend pas en charge document.write(ou innerHTML) de toute façon, je ne vois pas en quoi cela est pertinent.
Mathias Bynens
2
@ MathiasBynens— document.writen'est pas pertinent, il se trouve que c'est juste l'exemple. L'OP aurait pu utiliser innerHTML, il s'agit de cacher la </séquence de caractères à l'analyseur de balisage, où qu'elle se produise. C'est juste que la plupart des analyseurs le tolèrent dans un élément de script alors qu'ils ne le devraient pas strictement (mais les analyseurs HTML sont très tolérants). Vous avez cependant raison, cela <\/suffit dans tous les cas pour HTML.
RobG
3
Je ne pense pas qu'il soit nécessaire d'échapper à l'ouverture <... document.write('<script src="foo.js">\x3C/script>')semble être suffisant dans tous les navigateurs pour revenir à IE6. (J'ai omis l'attribut type car il n'est pas requis en HTML5, ni appliqué comme requis par n'importe quel navigateur.)
Matt Browne
34

Voici une autre variante que j'ai utilisée lorsque je voulais générer une balise de script en ligne (afin qu'elle s'exécute immédiatement) sans avoir besoin d'aucune forme d'échappement:

<script>
    var script = document.createElement('script');
    script.src = '/path/to/script.js';
    document.write(script.outerHTML);
</script>

(Remarque: contrairement à la plupart des exemples sur le net, je ne mets type="text/javascript"ni la balise englobante, ni la balise générée: il n'y a pas de navigateur qui n'en a pas par défaut, et donc c'est redondant, mais ça ne fera pas de mal non plus, si vous n'êtes pas d'accord).

Stoffe
la source
Bon point re: type. La valeur par défaut est définie comme "texte / javascript" à partir de HTML5, c'est donc un attribut inutile. w3.org/html/wg/drafts/html/master/…
Luke
8
Celui-ci est meilleur que la réponse acceptée car cette variation peut en fait être minimisée. Ce 'x3C / script>' deviendra '</script>' après minification.
Zoltan Kochan
20

Je pense que c'est pour empêcher l'analyseur HTML du navigateur d'interpréter le <script>, et principalement le </script> comme la balise de fermeture du script réel, mais je ne pense pas que l'utilisation de document.write est une excellente idée pour évaluer le script blocs, pourquoi ne pas utiliser le DOM ...

var newScript = document.createElement("script");
...
CMS
la source
4
Il est nécessaire d'empêcher l'analyseur de fermer le bloc de script prématurément ...
roenving
9

La solution proposée par Bobince fonctionne parfaitement pour moi. Je voulais aussi proposer une méthode alternative aux futurs visiteurs:

if (typeof(jQuery) == 'undefined') {
    (function() {
        var sct = document.createElement('script');
        sct.src = ('https:' == document.location.protocol ? 'https' : 'http') +
          '://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js';
        sct.type = 'text/javascript';
        sct.async = 'true';
        var domel = document.getElementsByTagName('script')[0];
        domel.parentNode.insertBefore(sct, domel);
    })();
}

Dans cet exemple, j'ai inclus une charge conditionnelle pour jQuery pour illustrer le cas d'utilisation. J'espère que c'est utile pour quelqu'un!

Jongosi
la source
3
pas besoin de détecter le protocole - l'URI sans protocole fonctionne très bien ('//foo.com/bar.js' etc.)
dmp
3
pas besoin non plus de définir async. il est activé pour toutes les balises de script créées dynamiquement.
Noishe
M'a sauvé! Lors de l'utilisation de document.write (<script>), mon site affichait un écran vide. Cela a fonctionné.
Tomas Gonzalez
@dmp sauf lors de l'exécution à partir d'un système de fichiers où le protocole sera fichier: //
mplungjan
9

L' </script>intérieur de la chaîne Javascript littérale est interprété par l'analyseur HTML comme une balise de fermeture, provoquant un comportement inattendu ( voir l'exemple sur JSFiddle ).

Pour éviter cela, vous pouvez placer votre javascript entre les commentaires (ce style de codage était une pratique courante, à l'époque où Javascript était mal pris en charge par les navigateurs). Cela fonctionnerait ( voir l'exemple dans JSFiddle ):

<script type="text/javascript">
    <!--
    if (jQuery === undefined) {
        document.write('<script type="text/javascript" src="http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js"></script>');
    }
    // -->
</script>

... mais pour être honnête, l'utilisation document.writen'est pas quelque chose que je considérerais comme la meilleure pratique. Pourquoi ne pas manipuler directement le DOM?

<script type="text/javascript">
    <!--
    if (jQuery === undefined) {
        var script = document.createElement('script');
        script.setAttribute('type', 'text/javascript');
        script.setAttribute('src', 'http://z-ecx.images-amazon.com/images/G/01/javascripts/lib/jquery/jquery-1.2.6.pack._V265113567_.js');
        document.body.appendChild(script);
    }
    // -->
</script>
Mathieu Rodic
la source
1
Si vous voulez utiliser du JS pur, vous voudrez toujours utiliser document.write;)
Nirav Zaveri
Désolé, je voulais écrire - cela nécessite la bibliothèque jQuery, non? Lorsque j'ai utilisé, document.body.append, cela a provoqué une erreur indiquant que document.body.append n'est pas une fonction.
Nirav Zaveri
1
Désolé, mon erreur: j'ai écrit à la appendplace de appendChild. Correction de la réponse. Merci d'avoir remarqué!
Mathieu Rodic
1
Cela semble tellement plus général que de diviser la chaîne manuellement. Par exemple, le fractionnement n'est pas possible si la chaîne est insérée par un moteur de modèle.
w1th0utnam3