客户端动态删除<head> </script>中的<script>标记

时间:2012-10-05 14:07:40

标签: javascript html browser aggregation

是否可以删除HTML文档客户端的<head>中的脚本标记,然后才能执行这些标记?

在服务器端,我可以在<script>中的所有其他<script>代码上方插入<head>,除了一个,我希望能够删除所有后续内容脚本。我无法从服务器端删除 <script>标记。

我尝试了什么:

(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);

但是,记录的脚本数量仅为1,因为(正如@ryan指出的那样)代码在DOM准备好之前执行。虽然将上面的代码包装在document.ready事件回调中,但确实能够正确计算<script><head>个标记的数量,等待DOM准备就绪,无法阻止脚本加载。

在DOM准备好之前是否有可靠的方法来操作HTML?

背景

如果您需要更多上下文,这是尝试整合没有服务器端聚合选项的脚本的一部分。许多正在加载的JS库来自具有有限配置选项的CMS。内容大多是静态的,因此很少关注手动聚合JavaScript并从不同位置提供JavaScript。对于替代适用的聚合技术的任何建议也是受欢迎的。

5 个答案:

答案 0 :(得分:9)

由于您无法阻止将来的<script>代码进行评估(每当找到</script>代码时,都会获取并评估<script>的相应代码。<script src>将阻止除非设置了async属性,否则从进一步加载到获取源的文档,需要采取不同的方法。
在我提出解决方案之前,我问:什么可以阻止<script>标记内的脚本执行?事实上,

  1. 从源代码中删除<script>
  2. 添加Content Security policy指令以阻止某些来源的脚本。
  3. 触发(运行时)错误。
  4. 1是显而易见的,2可以从文档中获得,因此我将重点关注3.以下示例是显而易见的,需要针对实际用例进行调整。

    代理功能

    以下是代理现有方法的一般模式:

    (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(); }
    

    在此示例中,代码阻止程序仅运行一次。您可以删除恢复行,或添加var counter=0;if(++counter > 1337)以在1337呼叫后恢复该方法。

    如果调用者不是函数(例如,顶级代码),则

    arguments.callee.callernull。不是灾难,您可以从参数或this关键字或任何其他环境变量中读取,以确定是否必须停止执行。
    演示:http://jsfiddle.net/qFnMX/

    拒绝二传手/吸气员

    这是打破setter的一般模式:

    Object.defineProperty(window, 'undefinable', {set:function(){}});
    /*fail*/ function undefinable() {} // or window.undefinable = function(){};
    

    演示:http://jsfiddle.net/qFnMX/2/

    当然是吸气鬼:

    (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;}
    

    演示:http://jsfiddle.net/qFnMX/4/

    等等。查看要阻止的脚本的源代码,您应该能够创建特定于脚本(甚至通用)的脚本中断模式。

    错误触发方法的唯一缺点是错误记录在控制台中。对于普通用户来说,这应该不是问题。

答案 1 :(得分:8)

是的,还有另一个稍微不那么疯狂的想法,但它确实取决于你能够在页面的头部插入标签的控件:

要求

简单地说,如果您可以在头部中的任何<noscript>声明之前插入我之前的<script>标记,那么您可以在末尾添加</noscript>标记头部以及最终的脚本片段 - 在将noscript标记写回页面之前,你应该可以使用noscript标记之间的标记做任何你想做的事。

这种方法的优点在于脚本禁用代理只会忽略和解析标记,但启用脚本的代理会将内容存储到但不会使用 ...究竟是什么需要的。

实施

虽然这是设计用于头部,但它可以很容易地在身体中使用,虽然它必须是一个单独的实现。这是因为它必须使用平衡且完整的节点树,因为标签的性质(除非你能设法将整个标记包装在noscript中?!?)

上升空间/缺点

它不是完全证明的,因为脚本可以位于头部和身体标签之外 - 至少在它们被解析之前 - 但它似乎对我迄今为止测试的所有内容非常自信...... 它不依赖于随机的ajax驱动的代码,这些代码会在浏览器更新的第一个迹象时中断;)

另外,我也喜欢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(/&gt;/gi,'>')
        .replace(/&lt;/gi,'<')
        /// simple disable script regexp
        .replace(/<script[^>]*>/gi,'<'+'!--')
        .replace(/<\/script>/gi,'//--'+'>')
    );
  }
})()
</script>
</head>

答案 2 :(得分:7)

您可以尝试使用DOM Mutation事件:

DOMAttrModified
DOMAttributeNameChanged
DOMCharacterDataModified
DOMElementNameChanged
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified
像这样:

document.head.addEventListener ('DOMNodeInserted', function(ev) {
   if (ev.target.tagName == 'SCRIPT') {
       ev.target.parentNode.removeChild(ev.target);
   }
}, false);

您也可以尝试通过MutationObserver

执行此操作的新方法

答案 3 :(得分:6)

好的,所以我还没有在Internet Explorer中测试任何这些(我怀疑它会起作用),并且不要因为黑客的可怕而责备我......我知道;但它似乎在Mac OSX上的FireFox,Safari,Chrome和Opera中起作用 - 至少是最近这些使用者的公开发布。当我访问Windows机器时,我会看看是否可以改进它...虽然我对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('&nbsp;');
    /// 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){}
})();

只是说我已经用几乎所有我能想到的脚本标签来测试它,放在我可以放置的地方。我还没有一个人能够突破。正如我所说,有趣的问题......虽然我不知道上述在生产环境中的运作情况如何:S;)

基本上,这必须作为脚本标签放在head标签的顶部。

测试示例:

http://pebbl.co.uk/stackoverflow/12748067.html

答案 4 :(得分:1)

不,你不能

我现在找不到官方文档,但是当我在High Performance Javascript上阅读Nicholas Zakas时,当渲染引擎找到Tag脚本时,它会停止HTML渲染(因此没有创建其他节点) ),下载脚本并执行它。然后它继续呈现HTML。这就是为什么当你在标签上执行“document.write()”时,结果会在标签之后添加,然后呈现页面的其余部分。

(我不知道我是否可以在这里插入一本书......)

因此,它不像渲染页面,然后删除节点并且脚本不会被执行,当浏览器找到标记时,在执行此代码之前,您无法执行任何操作。

我们的产品遇到了一个非常类似的问题,我们在DOM中添加了一个脚本标签,我们需要在新标签执行开始之前执行一些代码,经过一周的研究,我们必须找到另一个解决方案。< / p>

抱歉,但我希望你不要像我们那样浪费那么多时间。无论如何,我会继续寻找浏览器规范。