如何访问 iFrame 中的 DOM 元素 [英] How can I access the DOM elements within an iFrame

查看:43
本文介绍了如何访问 iFrame 中的 DOM 元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个 jQuery 插件,它需要能够针对 iFrame 中的 DOM 元素运行.我现在只是在本地测试这个(即 url 是 file://.../example.html),在 Chrome 中我一直点击SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame访问跨域框架的原点为null"."而在 Safari 中,我只得到一个空文档.

鉴于父文件和 iFrame 的文件都从我的本地磁盘(在开发中)中取出,并将从同一台服务器(生产中)中取出,我本以为我不会受到交叉的影响-起源问题.

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

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

更新

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

我的 Javascript 看起来像

$(document).ready(function(){var myFrame = $("iframe"),myDocument = $(myFrame.get(0).contentDocument),我的元素;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 上的命令行参数是这样),但我不建议使用该标志运行超过快速测试.请参阅这篇文章这篇文章 了解有关命令行参数的信息.>

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

或者,您可以在其他不那么严格的浏览器中进行测试.

<小时>

既然您已将问题更改为不同的内容,您必须等待 iframe 窗口加载才能访问其中的内容,并且不能使用 jQuery 的 .ready() 在不同的文档上(它不适用于另一个文档).

$(document).ready(function() {//在我的文档网络中获取 iframevar iframe = document.getElementById("testFrame");//获取与该 iframe 关联的窗口var iWindow = iframe.contentWindow;//在访问内容之前等待窗口加载iWindow.addEventListener("加载", function() {//从窗口中获取文档var doc = iframe.contentDocument ||iframe.contentWindow.document;//在 iframe 内容中找到目标var target = doc.getElementById("target");target.innerHTML = "找到了!";});});

测试页面此处.

<小时>

经过进一步研究,我发现 jQuery 会像这样为您完成一些工作,并且 jQuery 解决方案似乎适用于所有主要浏览器:

$(document).ready(function() {$("#testFrame").load(function() {var doc = this.contentDocument ||this.contentWindow.document;var target = doc.getElementById("target");target.innerHTML = "找到了!";});});

测试页面这里.

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

<小时>

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

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

我认为正在发生的事情是 iFrame 以一个虚拟的空文档和正文开始,然后在内容开始加载后替换它.另一方面,iFrame window 是真正的窗口.因此,我发现实际等待加载内容的唯一方法是监视 iFrame window 上的 load 事件,因为这似乎不是谎言.如果您知道有一些特定的内容正在等待,您也可以轮询直到该内容可用.但是,即便如此,您也必须小心,因为您不能过早获取 iframe.contentWindow.document,因为如果您过早获取它,它将是错误的文档.整个事情都非常破碎.我找不到从 iFrame 文档本身外部使用 DOMContentLoaded 的任何方法,因为您无法知道实际的 document 对象是否就位,您可以附加它的事件处理程序.所以......我在 iFrame 窗口上解决了 load 事件,它似乎确实有效.

<小时>

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

<小时>

进一步编辑

经过大量的研究和测试,这里有一个函数会告诉你 iFrame 何时命中 DOMContentLoaded 事件,而不是等待 load 事件(这可能需要更长的图像和样式表).

//此函数仅适用于与其父级同源的 iFrame函数 iFrameReady(iFrame, fn) {无功定时器;无功发射 = 假;函数准备好(){如果(!解雇){解雇 = 真;清除超时(定时器);fn.call(this);}}函数就绪状态(){如果(this.readyState ===完成"){准备好了电话(这个);}}//跨平台事件处理程序以兼容旧的 IE 版本函数添加事件(元素,事件,fn){如果(elem.addEventListener){返回 elem.addEventListener(event, fn);} 别的 {return elem.attachEvent("on" + event, function () {返回 fn.call(elem, window.event);});}}//使用 iFrame 加载作为备份 - 尽管其他事件应该首先发生addEvent(iFrame,加载",函数(){ready.call(iFrame.contentDocument || iFrame.contentWindow.document);});函数检查加载(){var doc = iFrame.contentDocument ||iFrame.contentWindow.document;//我们可以判断是否安装了一个虚拟文件,因为虚拟文件//将有一个以about:"开头的 URL.真正的文档不会有那个 URLif (doc.URL.indexOf("about:") !== 0) {如果(doc.readyState ===完成"){准备好电话(文档);} 别的 {//为新文档上的 DOMContentLoaded 设置事件监听器addEvent(doc, "DOMContentLoaded", ready);addEvent(doc, "readystatechange", readyState);}} 别的 {//仍然是旧的原始文档,所以继续寻找内容或新文档timer = setTimeout(checkLoaded, 1);}}检查加载();}

这只是这样调用:

//当您知道 iFrame 已加载时调用它//在 jQuery 中,你可以把它放在 $(document).ready() 中iFrameReady(document.getElementById("testFrame"), function() {var target = this.getElementById("target");target.innerHTML = "找到了!";});

I am writing a jQuery Plugin that needs to be able to run against DOM elements within an iFrame. I'm just testing this locally right now (ie url is file://.../example.html) and in Chrome I keep hitting "SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "null" from accessing a cross-origin frame." and in Safari I just get an empty document.

Given that both the parent file and the iFrame's file are coming off my local disk (in development) and will be coming off the same server (in production) I'd have thought that I'd not be subject to the cross-origin issues.

Is there a way I can convince the browser that my local files are actually of the same domain?

<aside>Interestingly in Safari, using the console directly, I can type $("iframe").get(0).contentDocument.find("ol") and it happily finds my list. In Chrome this same line throws the security error just as if it were being executed.</aside>

Update

Based on the suggestions below I have fired up a simple local web-server to test this and am now not getting the cross-origin error - yay - but neither am I getting any content.

My Javascript looks like

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

The myDocument.ready is there just to ensure that the iFrame's document is ready - in reality it makes no difference.

I always end up with myElements being empty. ([] in safari or jQuery.fn.init[0] in Chrome)

But if I manually type this into the console:

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

I get my lists as expected. This is now the case in both Safari and Chrome.

So my question becomes: why can't my script see the DOM elements but the same code when entered directly into the browser's console can happily see the DOM elements?

解决方案

Chrome has default security restrictions that don't allow you to access other windows from the hard disk even though they are technically the same origin. There is a flag in Chrome that can relax that security restriction (command line argument on Windows is what I remember), though I wouldn't recommend running with that flag for more than a quick test. See this post or this article for info about the command line argument.

If you run the files off a web server (even if it's a local web server) instead of your hard drive, you won't have this issue.

Or, you could test in other browsers that aren't as restrictive.


Now that you've changed the question into something different, you have to wait for an iframe window to load before you can access the content in it and you can't use jQuery's .ready() on a different document (it doesn't work on another document).

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

Test page here.


EDIT: Upon further research, I found that jQuery will do some of this work for you like this and the jQuery solution appears to work in all the major browsers:

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

Test page here.

In looking at the jQuery implementation for this, all it is really doing is setting up a load event listener on the iFrame itself.


If you want to know the nitty gritty details that went into debugging/solving the first method above:

In my trying to solve this issue, I discovered some pretty bizarre things (in Chrome) with iFrames. When you first look in the window of the iframe, there is a document and it says that its readyState === "complete" such that you think it's done loading, but it's lying. The actual document and actual <body> tag from the document that is being loaded via URL into the iframe is NOT actually there yet. I proved this by putting a custom attribute on the <body data-test="hello"> and checking for that custom attribute. Lo and behold. Even though document.readyState === "complete", that custom attribute is not there on the <body> tag. So, I conclude (at least in Chrome) that the iFrame initially has a dummy and empty document and body in it that are not the actual ones that will be in place once the URL is loaded into the iFrame. This makes this whole process of detecting when it's ready to be quite confusing (it cost me hours figuring this out). In fact, if I set an interval timer and poll iWindow.document.body.getAttribute("data-test"), I will see it show as undefined repeatedly and then finally it will show up with the correct value and all of this with document.readyState === "complete" which means it's completely lying.

I think what's going on is that the iFrame starts out with a dummy and empty document and body which is then replaced AFTER the content starts loading. On the other hand, the iFrame window is the real window. So, the only ways I've found to actually wait for the content to be loaded are to monitor the load event on the iFrame window as that doesn't seem to lie. If you knew there was some specific content you were waiting for, you could also poll until that content was available. But, even then you have to be careful because you cannot fetch the iframe.contentWindow.document too soon because it will be the wrong document if you fetch it too soon. The whole thing is pretty broken. I can't find any way to use DOMContentLoaded from outside the iFrame document itself because you have no way of knowing then the actual document object is in place to you can attach the event handler to it. So ... I settled for the load event on the iFrame window which does seem to work.


If you actually control the code in the iFrame, then you can trigger the event more easily from the iFrame itself, either by using jQuery with $(document).ready() in the iFrame code with it's own version of jQuery or by calling a function in the parent window from a script located after your target element (thus ensuring the target element is loaded and ready).


Further Edit

After a bunch more research and testing, here's a function that will tell you when an iFrame hits the DOMContentLoaded event rather than waiting for the load event (which can take longer with images and style sheets).

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

This is simply called like this:

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

这篇关于如何访问 iFrame 中的 DOM 元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆