javascript - donde - Eliminación dinámica del lado del cliente de etiquetas<script> en<head>
head javascript (5)
¿Es posible eliminar etiquetas de script en el <head>
de un documento HTML del lado del cliente y antes de la ejecución de esas etiquetas?
En el lado del servidor, puedo insertar un <script>
sobre todas las demás <script>
en el <head>
, excepto uno, y me gustaría poder eliminar todos los scripts posteriores. No tengo la capacidad de eliminar etiquetas <script>
del lado del servidor.
Lo que he intentado:
(function (c,h) {
var i, s = h.getElementsByTagName(''script'');
c.log("Num scripts: " + s.length);
i = s.length - 1;
while(i > 1) {
h.removeChild(s[i]);
i -= 1;
}
})(console, document.head);
Sin embargo, el número registrado de scripts sale a solo 1, ya que (como @ryan señaló) el código se está ejecutando antes de que el DOM esté listo. Si bien el ajuste del código anterior en una llamada de evento document.ready
permite el cálculo adecuado del número de etiquetas <script>
en <head>
, esperar hasta que el DOM esté listo no puede evitar que se carguen los scripts.
¿Hay algún medio confiable para manipular el HTML antes de que el DOM esté listo?
Fondo
Si desea más contexto, esto es parte de un intento de consolidar los scripts donde no hay disponible ninguna opción para la agregación del lado del servidor. Muchas de las bibliotecas JS que se están cargando son de un CMS con opciones de configuración limitadas. El contenido es en su mayoría estático, por lo que existe una pequeña preocupación sobre la agregación manual de JavaScript y el servicio desde una ubicación diferente. Cualquier sugerencia de técnicas alternativas de agregación aplicables también sería bienvenida.
Correcto, tuve otra idea un poco menos loca que la primera, pero depende de qué control tengas para poder insertar etiquetas en el encabezado de las páginas:
requisito
En pocas palabras, si puede insertar una etiqueta <noscript>
como la que tengo debajo antes de cualquiera de las declaraciones <script>
en la cabecera, y luego puede agregar una etiqueta </noscript>
al final de la cabecera, junto con la etiqueta final. Fragmento de secuencia de comandos: debe poder hacer lo que quiera con el marcado entre las etiquetas de noscript antes de volver a escribir en la página.
Lo bueno de este enfoque es que los agentes con secuencias de comandos simplemente ignorarán y analizarán el marcado, pero los agentes con secuencias de comandos almacenarán el contenido pero no lo utilizarán ... exactamente lo que se necesita.
implementación
Si bien esto está diseñado para ser usado con la cabeza, se podría usar fácilmente de la misma manera en el cuerpo, aunque tendría que ser una implementación separada. Esto se debe a que tiene que trabajar con un árbol de nodos equilibrado y completo, debido a la naturaleza de las etiquetas (a menos que pueda administrar todo el marcado en Noscript?!?) .
Upsides / Downsides
No es una prueba completa, porque los scripts pueden estar fuera de las etiquetas de la cabeza y el cuerpo, al menos antes de que se analicen, pero parece funcionar con bastante confianza en todo lo que he probado hasta ahora ... y no depende de un puñado de código alimentado con Ajax al azar que se romperá a la primera señal de una actualización del navegador;)
Además, también me gusta la idea de las etiquetas de script dentro de las etiquetas de noscript ...
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<noscript id="__disabled__">
<script src="jquery.js"></script>
<title>Another example</title>
<script>alert(1);</script>
<link rel="stylesheet" type="text/css" href="core.css" />
<style>body { background: #ddd; }</style>
</noscript>
<script>
(function(){
var noscript = document.getElementById(''__disabled__'');
if ( noscript ) {
document.write(
String(noscript.innerHTML)
/// IE entity encodes noscript content, so reverse
.replace(/>/gi,''>'')
.replace(/</gi,''<'')
/// simple disable script regexp
.replace(/<script[^>]*>/gi,''<''+''!--'')
.replace(/<//script>/gi,''//--''+''>'')
);
}
})()
</script>
</head>
No tu no puedes
No puedo encontrar la documentación oficial en este momento, pero cuando estoy leyendo el Javascript de alto rendimiento de Nicholas Zakas , cuando el motor de procesamiento encuentra un script de etiquetas, detiene el procesamiento de HTML (por lo que no se crea ningún otro nodo), descarga el script y lo ejecuta. . Luego continúa renderizando el HTML. Es por eso que cuando ejecutas "document.write ()" en una etiqueta, el resultado se agrega JUSTO después de la etiqueta, luego se representa el resto de la página.
(No sé si puedo insertar un párrafo del libro aquí ...)
Entonces, no es como representar la página, luego se elimina el nodo y el script no se ejecutará, cuando el navegador encuentre una etiqueta, no podrá hacer nada hasta que se ejecute este código.
Tuvimos un problema muy similar en nuestro producto, agregamos una etiqueta de script al DOM y necesitábamos que se ejecutara un código JUSTO antes de que comience la ejecución de la nueva etiqueta, después de una semana de investigación tuvimos que encontrar otra solución.
Lo siento, pero espero que no pierdas tanto tiempo como lo hicimos nosotros. De todos modos seguiré buscando la especificación del navegador.
Ok, todavía tengo que probar todo esto en Internet Explorer (dudo que funcione) , y no me reprendan por lo horrible de los hacks ... Lo sé;) pero parece funcionar en FireFox, Safari, Chrome y Opera en Mac OSX: las recientes versiones públicas de esos usuarios, al menos. Veré si puedo mejorarlo cuando tenga acceso a una máquina con Windows ... aunque no tengo muchas esperanzas para IE.
(function(xhr,d,de){
d = document;
try{
de = ((de = d.getElementsByTagName(''html'')[0])
? de : ( d.documentElement ? d.documentElement : d.body ));
/// this forces firefox to reasses it''s dom
d.write('' '');
/// make an ajax request to get the source of this page as a string
/// this could be improved, I''ve just chucked it in as an example
if (window.XMLHttpRequest) {
xhr = new window.XMLHttpRequest;
}else{
xhr = new ActiveXObject("MSXML2.XMLHTTP");
}
if ( xhr ) {
/// open non-async so the browser has to wait
xhr.open(''GET'', window.location, false);
xhr.onreadystatechange = function (e,o,ns){
/// when we''ve got the source of the page... then
if ((o = e.target) && (o.readyState == 4) && (o.status == 200)) {
/// remove the script tags
window.ns = ns = String(o.responseText)
.replace(/<script[^>]*>/gi,''<''+''!--'')
.replace(/<//script>/gi,''//--''+''>'');
/// fix for firefox - this causes a complete
/// rewrite of the main docelm
if ( ''MozBoxSizing'' in de.style ) {
de.innerHTML = ns;
}
/// fix for webkit, this seems to work, whereas
/// normal document.write() doesn''t. Probably
/// because the window.location resets the document.
else {
window.location = ''javascript:document.write(window.ns);'';
}
}
};
xhr.send({});
}
}
catch(ex){}
})();
Solo para decir que he probado esto con casi todos los tipos de etiquetas de script que puedo recordar, ubicadas donde sea que pueda ubicarlas. Y todavía no he tenido uno capaz de romper. Como dije, pregunta divertida ... aunque no sé qué tan bien funcionaría lo anterior en un entorno de producción: S;)
Básicamente, esto tendrá que colocarse como una etiqueta de script en la parte superior de la etiqueta principal.
Un ejemplo de prueba:
Podrías intentar usar los eventos de la Mutación DOM :
DOMAttrModified
DOMAttributeNameChanged
DOMCharacterDataModified
DOMElementNameChanged
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified
al igual que:
document.head.addEventListener (''DOMNodeInserted'', function(ev) {
if (ev.target.tagName == ''SCRIPT'') {
ev.target.parentNode.removeChild(ev.target);
}
}, false);
También puedes probar la nueva forma de hacerlo a través de MutationObserver
Ya que no puede evitar que las futuras etiquetas <script>
evalúen (siempre que se haya encontrado la etiqueta </script>
, se busca y evalúa el código correspondiente de <script>
. <script src>
bloqueará la carga del documento hasta que la fuente sea recuperado a menos que se establezca el atributo async
), se debe adoptar un enfoque diferente.
Antes de presentar la solución, pregunto: ¿Qué puede evitar que se ejecute un script dentro de una etiqueta <script>
? En efecto,
- Eliminación de
<script>
del código fuente. - Agregar una directiva de directiva de seguridad de contenido para bloquear scripts de ciertas fuentes.
- Desencadenando un error (en tiempo de ejecución).
1 es obvio, y 2 pueden derivarse de la documentación, así que me centraré en 3. Los ejemplos a continuación son obvios y deben ajustarse para casos de uso del mundo real.
Proxying
Aquí hay un patrón general para la asignación de métodos existentes:
(function(Math) {
var original_method = Math.random;
Math.random = function() {
// use arguments.callee to read source code of caller function
if (/somepattern/.test(arguments.callee.caller)) {
Math.random = original_method; // Restore (run once)
throw ''Prevented execution!'';
}
return random.apply(this, arguments); // Generic method proxy
};
})(Math);
// Demo:
function ok() { return Math.random(); }
function notok() { var somepattern; return Math.random(); }
En este ejemplo, el bloqueador de código se ejecuta solo una vez. Puede eliminar la línea de restauración o agregar var counter=0;
y if(++counter > 1337)
para restaurar el método después de 1337 llamadas.
arguments.callee.caller
es null
si la persona que llama no es una función (por ejemplo, código de nivel superior). No es un desastre, puede leer los argumentos o this
palabra clave, o cualquier otra variable de entorno para determinar si se debe detener la ejecución.
Demostración: http://jsfiddle.net/qFnMX/
Denegar setters / getters
Aquí hay un patrón general para romper setters:
Object.defineProperty(window, ''undefinable'', {set:function(){}});
/*fail*/ function undefinable() {} // or window.undefinable = function(){};
Demostración: http://jsfiddle.net/qFnMX/2/
Y los captadores, por supuesto:
(function() {
var actualValue;
Object.defineProperty(window, ''unreadable'', {
set: function(value) {
// Allow all setters for example
actualValue = value;
},
get: function() {
if (/somepattern/.test(arguments.callee.caller)) {
// Restore, by deleting the property, then assigning value:
delete window.unreadable;
window.unreadable = actualValue;
throw ''Prevented execution!'';
}
return actualValue;
},
configurable: true // Allow re-definition of property descriptor
});
})();
function notok() {var somepattern = window.unreadable; }
// Now OK, because
function nowok() {var somepattern = window.unreadable; }
function ok() {return unreadable;}
Demostración: http://jsfiddle.net/qFnMX/4/
Y así. Busque el código fuente de los scripts que desea bloquear, y debería poder crear un patrón de ruptura de scripts específico (o incluso genérico).
El único inconveniente del método de activación de errores es que el error se registra en la consola. Para los usuarios normales, esto no debería ser un problema en absoluto.