Javascript 如何访问iFrame中的DOM元素

Javascript 如何访问iFrame中的DOM元素,javascript,iframe,cross-domain,Javascript,Iframe,Cross Domain,我正在编写一个jQuery插件,它需要能够在iFrame中针对DOM元素运行。我现在正在本地测试这个(即url是file://.../example.html)在Chrome中,我不断点击“SecurityError:未能从“HTMLIFrameElement”中读取“contentDocument”属性:阻止原点为“null”的帧访问跨原点帧”。在Safari中,我只得到一个空文档 考虑到父文件和iFrame的文件都来自我的本地磁盘(在开发中),并且来自同一台服务器(在生产中),我认为我不会受

我正在编写一个jQuery插件,它需要能够在iFrame中针对DOM元素运行。我现在正在本地测试这个(即url是file://.../example.html)在Chrome中,我不断点击“SecurityError:未能从“HTMLIFrameElement”中读取“contentDocument”属性:阻止原点为“null”的帧访问跨原点帧”。在Safari中,我只得到一个空文档

考虑到父文件和iFrame的文件都来自我的本地磁盘(在开发中),并且来自同一台服务器(在生产中),我认为我不会受到跨源问题的影响

有没有办法让浏览器相信我的本地文件实际上属于同一个域

有趣的是,在Safari中,直接使用控制台,我可以键入
$(“iframe”).get(0).contentDocument.find(“ol”)
,它很高兴地找到了我的列表。在Chrome中,这一行抛出安全错误,就像它正在执行一样

更新

根据下面的建议,我启动了一个简单的本地web服务器来测试这一点,现在我没有收到跨源错误-耶-但我也没有收到任何内容

我的Javascript看起来像

$(document).ready(function(){
  var myFrame = $("iframe"),
      myDocument = $(myFrame.get(0).contentDocument),
      myElements;
  myDocument.ready(function(){
    myElements = myDocument.find("ul, ol");
    console.debug("success - iFrame", myFrame, "document", myDocument, "elements", myElements);
  });
});
myDocument.ready
只是为了确保iFrame的文档已经准备好——实际上它没有什么区别

我总是以
myElements
为空而结束。(
[]
在safari中或
jQuery.fn.init[0]
在Chrome中)

但如果我在控制台中手动键入:

$($("iframe").get(0).contentDocument).find("ol, ul")
我得到了预期的清单。现在Safari和Chrome都是这样


因此,我的问题变成了:为什么我的脚本看不到DOM元素,但直接输入浏览器控制台的相同代码可以愉快地看到DOM元素?

Chrome有默认的安全限制,不允许您从硬盘访问其他窗口,即使它们在技术上是同一来源。Chrome中有一个标志可以放松安全限制(我记得Windows上的命令行参数),不过我不建议使用该标志进行快速测试。有关命令行参数的信息,请参见或

如果从web服务器(即使是本地web服务器)而不是硬盘上运行文件,则不会出现此问题

或者,您可以在其他没有限制性的浏览器中进行测试


现在,您已经将问题更改为其他内容,您必须等待iframe窗口加载后才能访问其中的内容,并且不能在其他文档上使用jQuery的
.ready()
(在其他文档上不起作用)

测试页面


编辑:进一步研究后,我发现jQuery可以像这样为您完成一些这方面的工作,而且jQuery解决方案似乎可以在所有主要浏览器中工作:

$(document).ready(function() {
    $("#testFrame").load(function() {
        var doc = this.contentDocument || this.contentWindow.document;
        var target = doc.getElementById("target");
        target.innerHTML = "Found It!";
    });
});
测试页面

在查看jQuery实现时,它真正做的就是在iFrame本身上设置一个
load
事件监听器


如果您想了解调试/解决上述第一种方法的细节:

在我试图解决这个问题的过程中,我发现iFrames(在Chrome中)有一些非常奇怪的东西。当您第一次在iframe的窗口中查看时,有一个文档,它说它的
readyState==“complete”
,因此您认为它已完成加载,但它在撒谎。通过URL加载到iframe的文档中的实际文档和实际的
标记实际上还不存在。我通过在
上放置一个自定义属性并检查该自定义属性来证明这一点。瞧。即使
document.readyState==“complete”
,该自定义属性也不在
标记上。因此,我得出结论(至少在Chrome中),iFrame最初有一个虚拟的、空的文档和主体,而这些文档和主体并不是URL加载到iFrame中后的实际文档和主体。这使得整个检测过程变得相当混乱(我花了好几个小时才弄清楚)。事实上,如果我设置一个间隔计时器并轮询
iWindow.document.body.getAttribute(“数据测试”)
,我会看到它反复显示为
undefined
,最后它会显示正确的值,所有这些都是
document.readyState==“complete”
,这意味着它完全是谎言

我认为现在的情况是,iFrame以一个虚拟的、空的文档和主体开始,然后在内容开始加载后被替换。另一方面,iFrame
窗口是真正的窗口。因此,我发现等待加载内容的唯一方法是监视iFrame
窗口上的
load
事件,因为这似乎并不存在。如果您知道您正在等待某些特定内容,也可以进行投票,直到该内容可用为止。但是,即使这样,您也必须小心,因为您不能过早地获取
iframe.contentWindow.document
,因为如果获取得太快,它将是错误的文档。整件事都很糟糕。我找不到任何方法从iFrame文档本身外部使用
DOMContentLoaded
,因为您无法知道实际的
document
对象是否已就位,您可以将事件处理程序附加到它。所以我决定在iFrame窗口上加载
load
事件,这似乎确实有效


如果您实际控制iFrame中的代码,那么您可以更容易地从iFrame本身触发事件,方法是在iFrame代码中使用jQuery with
$(document).ready()
,并使用它自己的jQuery版本,或者从位于目标元素(t)之后的脚本调用父窗口中的函数
$(document).ready(function() {
    $("#testFrame").load(function() {
        var doc = this.contentDocument || this.contentWindow.document;
        var target = doc.getElementById("target");
        target.innerHTML = "Found It!";
    });
});
// This function ONLY works for iFrames of the same origin as their parent
function iFrameReady(iFrame, fn) {
    var timer;
    var fired = false;

    function ready() {
        if (!fired) {
            fired = true;
            clearTimeout(timer);
            fn.call(this);
        }
    }

    function readyState() {
        if (this.readyState === "complete") {
            ready.call(this);
        }
    }

    // cross platform event handler for compatibility with older IE versions
    function addEvent(elem, event, fn) {
        if (elem.addEventListener) {
            return elem.addEventListener(event, fn);
        } else {
            return elem.attachEvent("on" + event, function () {
                return fn.call(elem, window.event);
            });
        }
    }

    // use iFrame load as a backup - though the other events should occur first
    addEvent(iFrame, "load", function () {
        ready.call(iFrame.contentDocument || iFrame.contentWindow.document);
    });

    function checkLoaded() {
        var doc = iFrame.contentDocument || iFrame.contentWindow.document;
        // We can tell if there is a dummy document installed because the dummy document
        // will have an URL that starts with "about:".  The real document will not have that URL
        if (doc.URL.indexOf("about:") !== 0) {
            if (doc.readyState === "complete") {
                ready.call(doc);
            } else {
                // set event listener for DOMContentLoaded on the new document
                addEvent(doc, "DOMContentLoaded", ready);
                addEvent(doc, "readystatechange", readyState);
            }
        } else {
            // still same old original document, so keep looking for content or new document
            timer = setTimeout(checkLoaded, 1);
        }
    }
    checkLoaded();
}
// call this when you know the iFrame has been loaded
// in jQuery, you would put this in a $(document).ready()
iFrameReady(document.getElementById("testFrame"), function() {
    var target = this.getElementById("target");
    target.innerHTML = "Found It!";
});