如何在iFrame中访问DOM元素

时间:2014-07-07 05:31:39

标签: javascript iframe cross-domain

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

鉴于父文件和iFrame的文件都是从我的本地磁盘(正在开发中)下来的,并且将从同一台服务器上(在生产中),我认为我不会受到十字架的影响-origin问题。

有没有办法可以说服浏览器我的本地文件实际上属于同一个域?

< aside> 有趣的是,在Safari中,直接使用控制台,我可以输入$("iframe").get(0).contentDocument.find("ol"),并愉快地找到我的列表。在Chrome中,同一行会抛出安全错误,就像它正在执行一样。< / aside>

更新

基于下面的建议,我已经启动了一个简单的本地网络服务器来测试这个,现在我没有得到跨源错误 - yay - 但我也没有得到任何内容。

我的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中为[]或Chrome中为jQuery.fn.init[0]

但如果我手动将其输入控制台:

$($("iframe").get(0).contentDocument).find("ol, ul")

我按预期获得了我的列表。现在,Safari和Chrome都是如此。

所以我的问题变成了:为什么我的脚本看不到DOM元素,但是当直接输入浏览器的控制台时,相同的代码可以很高兴看到DOM元素?

2 个答案:

答案 0 :(得分:28)

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

如果您从Web服务器(即使它是本地Web服务器)而不是您的硬盘驱动器运行文件,则您不会遇到此问题。

或者,您可以在其他不受限制的浏览器中进行测试。


现在您已将问题更改为不同的内容,您必须等待iframe窗口加载才能访问其中的内容,并且您无法使用jQuery' s {{ 1}}在另一个文档上(它不会在另一个文档上工作)。

.ready()

测试页here


编辑:经过进一步研究,我发现jQuery会为你做一些这样的工作,jQuery解决方案似乎适用于所有主流浏览器:

$(document).ready(function() {
    // get the iframe in my documnet
    var iframe = document.getElementById("testFrame");
    // get the window associated with that iframe
    var iWindow = iframe.contentWindow;

    // wait for the window to load before accessing the content
    iWindow.addEventListener("load", function() {
        // get the document from the window
        var doc = iframe.contentDocument || iframe.contentWindow.document;

        // find the target in the iframe content
        var target = doc.getElementById("target");
        target.innerHTML = "Found It!";
    });
});

测试页here

在查看jQuery实现时,它真正做的就是在iFrame上设置一个$(document).ready(function() { $("#testFrame").load(function() { var doc = this.contentDocument || this.contentWindow.document; var target = doc.getElementById("target"); target.innerHTML = "Found It!"; }); }); 事件监听器。


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

在我试图解决这个问题时,我发现了一些非常奇怪的东西(在Chrome中)与iFrames。当你第一次在iframe的窗口中查看时,会有一个文档,它会说它load,以至于你认为它已经完成加载,但是它正在撒谎。通过URL加载到iframe的文档中的实际文档和实际readyState === "complete"标记实际上还没有。我通过在<body>上添加自定义属性并检查该自定义属性来证明这一点。瞧,看哪。即使<body data-test="hello">document.readyState === "complete"标记上也没有该自定义属性。因此,我总结(至少在Chrome中)iFrame最初有一个虚拟的空文档和正文,这些文档和正文一旦将URL加载到iFrame中,就不会是实际的文档和正文。这使得整个过程可以检测到它何时准备好让人感到困惑(这需要花费数小时来解决这个问题)。事实上,如果我设置间隔计时器并轮询<body>,我会看到它重复显示为iWindow.document.body.getAttribute("data-test"),然后最终会显示正确的值,所有这些都显示undefined这意味着它完全撒谎。

我认为发生的事情是iFrame以虚拟和空文档和正文开始,然后在内容开始加载后替换。另一方面,iFrame document.readyState === "complete"是真正的窗口。因此,我发现实际等待内容加载的唯一方法是监控iFrame window上的load事件,因为这似乎并不存在。如果您知道有一些您正在等待的特定内容,您也可以进行轮询直到该内容可用。但是,即使这样你也要小心,因为你不能太快地获取window,因为如果你过早地获取它将是错误的文件。整件事情非常糟糕。我找不到任何方法可以在iFrame文档本身之外使用iframe.contentWindow.document,因为您无法知道实际的DOMContentLoaded对象是否到位,您可以将事件处理程序附加到它。所以......我在iFrame窗口找到了document事件似乎有效。


如果您实际控制了iFrame中的代码,那么您可以通过在iFrame代码中使用带有load的jQuery及其自己的jQuery版本,更轻松地从iFrame本身触发事件或者通过在目标元素之后的脚本中调用父窗口中的函数(从而确保目标元素已加载并准备就绪)。


进一步修改

经过一系列研究和测试后,这里有一个功能可以告诉您iFrame何时点击$(document).ready()事件而不是等待DOMContentLoaded事件(图片可能需要更长时间)和样式表)。

load

这简单地称为:

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

答案 1 :(得分:1)

  

鉴于父文件和iFrame的文件都是从我的本地磁盘(正在开发中)下来的,并且将从同一台服务器上(在生产中),我认为我不会受到十字架的影响-origin问题。

“请打开我已附加到此电子邮件的完全无害的 HTML文档。”浏览器有充分的理由将跨域安全性应用于本地文件。

  

有没有办法可以说服浏览器我的本地文件实际上属于同一个域?

安装Web服务器。通过http://localhost进行测试。作为奖励,获得HTTP服务器的所有其他好处(例如能够使用以/开头并使用服务器端代码开发的相对URI)。