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

mer*_*erv 20 html javascript browser aggregation

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

在服务器端,我能够在除了一个之外的<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);
Run Code Online (Sandbox Code Playgroud)

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

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

背景

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

Rob*_*b W 9

由于您无法阻止将来的<script>标记进行评估(无论何时</script>找到标记,<script>都会获取和评估相应的代码.<script src>将阻止文档进一步加载,直到获取源,除非async设置了属性),需要采用不同的方法拍摄.
在我提出解决方案之前,我问:什么可以阻止<script>标签中的脚本执行?确实,

  1. <script>从源代码中删除.
  2. 添加内容安全策略指令以阻止来自某些源的脚本.
  3. 触发(运行时)错误.

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(); }
Run Code Online (Sandbox Code Playgroud)

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

arguments.callee.callernull如果呼叫者不是函数(例如,顶级代码).不是灾难,您可以从参数或this关键字或任何其他环境变量中读取,以确定是否必须停止执行.
演示:http://jsfiddle.net/qFnMX/

拒绝二传手/吸气鬼

这是打破setter的一般模式:

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

演示: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;}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 即使我选择[@ pebbl的答案](http://stackoverflow.com/a/12872102/570918)来奖励赏金,我选择这个作为*接受*答案.我的理由是,我认为这个答案提供了对手头问题的更广泛的概述,因此对于具有类似问题的未来StackOverflow用户来说可能是一个更好的起点. (2认同)

Peb*_*bbl 8

对,有一个比我的第一个稍微不那么疯狂的想法,但它确实取决于你能够在页面的头部插入标签的控制权:

需求

简而言之,如果您可以在头部的<noscript>任何<script>声明之前插入我之前的标签,然后您可以将</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>
Run Code Online (Sandbox Code Playgroud)


Nel*_*son 7

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

DOMAttrModified
DOMAttributeNameChanged
DOMCharacterDataModified
DOMElementNameChanged
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified
Run Code Online (Sandbox Code Playgroud)

像这样:

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

您也可以尝试通过MutationObserver执行此操作的新方法


Peb*_*bbl 6

好的,所以我还没有在Internet Explorer中测试任何这个(我怀疑它会工作),并且不要因为黑客的可怕而责备我......我知道;)但它似乎在FireFox中工作, Mac OSX上的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){}
})();
Run Code Online (Sandbox Code Playgroud)

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

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

一个测试例子:

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

  • 是的 :) 正如我所说的......我自己根本不会依赖它......它只会让 firefox 开始忽略第一个 `document.write()` *(就像其他浏览器一样)* 和js会再次突破。如果它仅用于脚本聚合 - 而不是删除 - 它可能是一种可能性......但是聚合通常作为优化完成,并且两次调用页面源*(除非大量缓存)*不会是最佳的。 (2认同)