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

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

问题描述

我编写一个jQuery插件,需要能够对iFrame中的DOM元素运行。我只是测试这个本地现在(即url是file://.../example.html),在Chrome我一直打安全错误:无法从'HTMLIFrameElement'读取'contentDocument'属性:阻止一个帧其中原点null无法访问跨原点帧。在Safari中我只是得到一个空文档。



由于父文件和iFrame的文件都离开我的本地磁盘(在开发中),并将脱离同样的服务器(在生产中)我原以为我不会遇到跨源的问题。



有什么方法可以说服浏览器



< aside> 有趣的是,在Safari中,直接使用控制台,我可以键入 $(iframe)。get(0).contentDocument.find(ol),它很高兴找到我的列表。



更新 / p>

根据下面的建议,我已经启动了一个简单的本地Web服务器来测试这个,我现在没有得到跨原点错误 - YAY - 但我也不是任何内容。



我的Javascript看起来像

  $ .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 空。 (Chrome中的 [] 或Chrome中的 jQuery.fn.init [0]



但是如果我手动输入到控制台:

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



如预期。



这样我的问题就变成了:为什么我的脚本看不到DOM元素,但是直接输入到浏览器的控制台可以很高兴地看到DOM元素?

解决方案

Chrome默认的安全限制不允许您访问其他窗口硬盘即使它们在技术上是相同的起源。在Chrome中有一个标志可以放宽安全限制(在Windows上的命令行参数是我记得),但我不建议使用该标志运行多个快速测试。请参见此信息本文了解有关命令行参数的信息。



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



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






我们把问题改成不同的东西,你必须等待一个iframe窗口加载,然后才能访问它的内容,你不能使用jQuery的 .ready()在不同的文档(它不工作在另一个文档)。

  $(document).ready(function(){
//获取我的documnet中的iframe
var iframe = document.getElementById(testFrame);
//获取与该iframe关联的窗口
var iWindow = iframe.contentWindow;

//等待窗口在访问内容之前加载
iWindow.addEventListener(load,function(){
//从窗口获取文档
var doc = iframe.contentDocument || iframe.contentWindow .document;

//在iframe内容中查找目标
var target = doc.getElementById(target);
target.innerHTML =Found It!
});
});

测试页此处






编辑我发现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的实现,它真正做的是设置一个<$ c $






如果你想知道nfr



在我试图解决这个问题,我发现一些很奇怪的东西(在Chrome)与iFrames。当你第一次看到iframe的窗口,有一个文档,它说它的 readyState ===完成,以便你认为它完成加载,但它说谎。实际文档和通过URL加载到iframe的文档中的实际< body> 标记实际上并不存在。我通过在< body data-test =hello> 上放置自定义属性并检查该自定义属性来证明了这一点。 Lo和看。即使 document.readyState ===complete,该自定义属性不在< body> 标签。因此,我得出结论(至少在Chrome中)iFrame最初有一个虚假的空文档和正文,这不是实际的,一旦URL加载到iFrame就会到位。这使得这个整个过程检测什么时候准备好相当混乱(它花费我小时想出这一点)。事实上,如果我设置一个间隔定时器和poll iWindow.document.body.getAttribute(data-test),我会看到它显示为 undefined ,然后最终它将显示正确的值,所有这一切与 document.readyState ===完成这意味着它完全躺着。



我想发生的是,iFrame开始一个虚拟的空文档和正文,然后在内容开始加载后替换。另一方面,iFrame 窗口是真实的窗口。所以,我发现实际等待内容加载的唯一方法是监视加载事件在iFrame 窗口因为这似乎不是谎言。如果您知道您有某些特定内容,您还可以轮询,直到该内容可用。但是,即使你必须小心,因为你不能提取 iframe.contentWindow.document 太快,因为它将是错误的文档,如果你提取太快。整个事情都很糟糕。我找不到任何方式使用 DOMContentLoaded 从外部的iFrame文档本身,因为你没有办法知道然后实际的文档对象就位,你可以附加事件处理程序到它。所以...我在iFrame窗口的加载事件中解决了,它似乎工作。






如果你实际上控制了iFrame中的代码,那么你可以通过使用jQuery和 $(document).ready()来更容易地从iFrame中触发事件。 )在iFrame代码中使用它自己的jQuery版本,或者通过从位于目标元素之后的脚本调用父窗口中的函数(从而确保目标元素已加载并就绪)。






进一步编辑



更多的研究和测试,这里是一个函数,将告诉你什么时候iFrame击中 DOMContentLoaded 事件,而不是等待加载

  //此函数仅适用于相同原点的iFrames作为他们的父
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 ;
}
}

//与旧IE版本兼容的跨平台事件处理程序
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);
});
}
}

//使用iFrame加载作为备份 - 尽管其他事件应首先发生
addEvent(iFrame,load,function(){
ready.call(iFrame.contentDocument || iFrame.contentWindow.document);
});

function checkLoaded(){
var doc = iFrame.contentDocument || iFrame.contentWindow.document;
//我们可以知道是否安装了一个虚拟文档,因为虚拟文档
//将有一个以about:开头的URL。真正的文档不会有这个URL
if(doc.URL.indexOf(about:)!== 0){
if(doc.readyState ===complete){
ready.call(doc);
} else {
//为新文档上的DOMContentLoaded设置事件监听器
addEvent(doc,DOMContentLoaded,ready);
addEvent(doc,readystatechange,readyState);
}
} else {
//仍然相同的旧原始文档,所以继续寻找内容或新文档
timer = setTimeout(checkLoaded,1);
}
}
checkLoaded();
}

这样简单调用:

  //当你知道iFrame已经在jQuery中加载了
//的时候调用它,你会把它放在一个$(document).ready
iFrameReady(document.getElementById(testFrame),function(){
var target = this.getElementById(target);
target.innerHTML =Found It! b $ b});


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天全站免登陆