热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

jQuery2.2iframe脚本注入的上下文Bug

jQuery2.2iframe脚本注入的上下文Bug
最近在iframe中注入脚本,发现jQuery .append()方法和DOM appendChild()方法的区别:

  • DOM API appendChild()方法插入的 会在iframe所在上下文中运行
  • jQuery(2.2) .append()方法注入的 脚本,其执行上下文总是在当前 window

事实上,jQuery .append()方法对 做了特殊处理: 获取脚本内容并通过 eval()在当前作用域下执行,同时禁用了浏览器默认的脚本调度执行。

先看例子

设置父容器的 window.id="parent",在注入到iframe的脚本中把它打印出来。 如果是与父容器共享上下文则会打印出 "parent",否则应是 undefined。

window.id = 'parent';var idocument = $('iframe').prop('contentDocument');var injected_script = 'console.log("window.id ==", window.id)';

创建一个 标签,使用 document.body.appendChild()方法插入:

var el = idocument.createElement('script');el.text = injected_script;idocument.body.appendChild(el);

输出为: window.id == undefined,说明作用域是隔离的。改用jQuery .append()方法插入:

var el = idocument.createElement('script');el.text = injected_script;$(idocument.body).append(el);

输出为: window.id == parent,显然jQuery不是单纯调用 appendChild(),还做了别的处理。 下面来看jQuery源码。

禁用脚本标签

在Github可访问jQuery源码,看这个文件: manipulation.js。 在真正插入 标签之前,先进入 domManip方法。 获取所有 脚本,并通过 disableScript函数禁用它们。

function domManip( collection, args, callback, ignored )    // ...    if ( first || ignored ) {        scripts = jQuery.map( getAll( fragment, "script" ), disableScript );        hasScripts = scripts.length;        // ...    callback.call( collection[ i ], node, i );

disableScript如果做到禁用脚本的呢?请看:

function disableScript( elem ) {    elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;    return elem;}

对于设置了 type="text/Javascript的脚本,其 type被重写为 type = "true/text/Javascript";对于未设置 type的脚本,其 type被重写为 false/。 总之,浏览器不再把该标签识别为页面脚本,从而禁止了浏览器对 的调度执行。

DOM 节点插入

接下来从 domManip的 callback才真正进入 .append()方法。 可以看到 .append()是通过DOM API appendChild来实现的。 这时 的 type已经被重写了,浏览器在插入 elem的时候不会自动执行该脚本。

jQuery.fn.extend( {    // ...    append: function() {        return domManip( this, arguments, function( elem ) {            if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {                var target = manipulationTarget( this, elem );                target.appendChild( elem );            }        } );    },

eval 脚本执行

callback返回后再次回到 doManip函数中,调用 DOMEval来执行脚本内容。

function domManip( collection, args, callback, ignored ) {    // ...    callback.call( collection[ i ], node, i );    // ...    if ( node.src ) {        if ( jQuery._evalUrl ) {            jQuery._evalUrl( node.src );        }    } else {        jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );    }

jQuery.globalEval()最终调用了Javascript eval来执行脚本, 所以脚本上下文在jQuery所在的 window里。源码: core.js

jQuery.extend( {    // ...    globalEval: function( code ) {        var script, indirect = eval;        code = jQuery.trim( code );        if ( code ) {            if ( code.indexOf( "use strict" ) === 1 ) {                script = document.createElement( "script" );                script.text = code;                document.head.appendChild( script ).parentNode.removeChild( script );            } else {                indirect( code );            }        }    },

在jQuery最新的 master分支中,上下文的问题已经被 修正了。 domManip()方法中最后会调用 DOMEval而不是 globalEval, 同时 globalEval也被实现为 domEval了(见 master/core.js), 不再使用Javascript eval()。

// manipulation.jsDOMEval( node.textContent.replace( rcleanScript, "" ), doc );// core.jsglobalEval: function( code ) {    DOMEval( code );}

推荐阅读
author-avatar
漂浮胖_大卍宝
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有