Script in Internet Explorer (IE) hangs and becomes unresponsible with empty cache (works fine with cache)
Ok, I have put together a script which does this:
- Make ajax request (via getHTTPObject(), no libraries is used)
- Create an iframe with script, src is "blank.html".
- use iframe.document.write() to execute scripts (inkluding document.write based scripts) in the iframe.
- call parent window's document to clone the iframe content.
- Append the content clone to parent body.
Works like a charm in all browsers but IE, where every version - including IE9 beta - hangs on iframeWindow.document.close() with empty cache, leaving the window/tab unresponsible. When I force quit, restart and load the page again it works.
What I've tried already:
- Googled.
- called the ajax request callback manually with string instead of request.responseText - it works even with empty cache here.
- Removed document.close() - resulting in scripts in iframe not executing at all (again, only with empty cache, cached pages works fine).
- Tested to make the ajax request synchronous - no difference.
Any ideas?
Live example here: http://labs.adeprimo.se/~adupanyt/ot/unlimited_scroll/
Here is the code. The install(), finish() and append()-functions manages the iframe.
/*!
* Cross browser unlimited scroll snippet
* Copyright (c) 2010 by Adeprimo.
* Released under the MIT license.
*/
/* Code assumptions:
*
* <div id="unlimited-scroll-wrapper">
*
* ... first content ...
*
* <a id="load-more-unlimited-content" href="/nyheter">
* Ladda mer innehåll</a>
* </div>
*/
(function(window, document, undefined){
/**
* This snippet has two running modes, and it is the load_on_scroll
* directive which rules it.
*
* true = a scroll event is initiated, which will wait
* until the bottom is reached before it load any new content.
* false = this script will continue loading more content, one piece
* at the time, until there is no more to get.
*
*/
load_on_scroll = false;
var request; // results of getHTTPObject()
var wrapper; // reference to the element which stores the new contents.
var callsCount; // keep the count of ajax calls, helps css targeting.
var attachEvent;// stores result of window.attachEvent for performance
// locks, these are updated by the script.
window.mkt_nothing_more_to_load = false;// true when end of queue reached.
window.mkt_prevent_new_loading = false; // true when ajax in progress.
wrapper = document.getElementById('unlimited-scroll-wrapper');
callsCount = 1;
attachEvent = window.attachEvent;
/**
* Customize this function for your need.
* It is called each time a new document fragment
* shall load.
* In here, you might add some nifty jQuery or Prototype if your app
* has it.
*/
function load_and_append() {
var src; // href attribute of the #load-more-unlimited-content A element
// get the source
src = document.getElementById('load-more-unlimited-content').href;
// mktwebb specific, will only return the content and
// not the header or footer.
src = src.replace(/\?.+?$/, '') + '?segment=content,footer';
if (!request) {
request = getHTTPObject(); // getHTTPObject must be declared
} // separately!
if (request) {
request.onreadystatechange = function() {
if (request.readyState == 4) {
if (request.status == 200 || request.status == 304) {
console.log('append() begin');
append(request.responseText, src);
console.log('append() done');
}
}
};
request.open("GET", src, false);
request.send(null);
}
}
function finish(iframe_window) {
var acc_elm = (function(doc){
return doc.getElementsByTagName('div')[0].cloneNode(true);
})(iframe_window.document);
document.getElementById('unlimited-scroll-wrapper'开发者_运维问答).appendChild(acc_elm);
window.mkt_prevent_new_loading = false; // we are ready to more content.
// we are done with the iframe, let's remove it.
var iframe_container = document.getElementById('mkt_iframe-container');
iframe_container.parentNode.removeChild(iframe_container);
// basically, the script continues as long as it finds a new
// #load-more-unlimited-content element in the newly added content.
// if it can't find the #load-more-unlimited-content,
// the script will stop and unattach itself.
if (document.getElementById('load-more-unlimited-content')) {
// if load_on_scroll, the new content is added under
// the control of the scroll event. There is no need to call
// load_and_append here since the scroll event will manage
// that fine.
// however, when not load_on_scroll,
// new content should be loaded asap.
if (!load_on_scroll) {
window.mkt_prevent_new_loading = true;
// give the browser some time to reflow and rest, then continue.
setTimeout(load_and_append, 2 * 1000);
}
} else {
nothing_more_to_load = true; // tell the scroll event to stop.
// remove scroll event since it is not needed anymore
if (attachEvent) {
window.detachEvent('onscroll',
look_for_trouble); // ie
} else {
window.removeEventListener('scroll',
look_for_trouble, false); // w3c
}
}
};
window['mkt_importFromIframe'] = finish;
// ------------------------------------------------------------------------
// We are now ready to start.
// ------------------------------------------------------------------------
// see head section in this file for how to configure load_on_scroll
function init() {
if (load_on_scroll) {
if (attachEvent) {
window.attachEvent('onscroll', look_for_trouble);
} else {
window.addEventListener('scroll', look_for_trouble, false);
}
} else {
load_and_append();
}
}
// we are using window.onload since we want "everything" to run
// in the first screeen before we continue.
if (attachEvent) {
window.attachEvent('onload', init);
} else {
window.addEventListener('load', init, false);
}
// ------------------------------------------------------------------------
// the script has started. Below functions are supporting it.
// ------------------------------------------------------------------------
// loaded in load_and_append after a successful ajax call.
function append(txt, src) {
// remove previously #load-more-unlimited-content links since
// it i not needed anymore.
(function(elm_to_remove){
elm_to_remove.parentNode.removeChild(elm_to_remove);
})(document.getElementById('load-more-unlimited-content'));
console.log('install() begin');
install(txt, src);
console.log('install() done');
}
/**
* cleaning function to strip unecessary tags(oup) out.
* also attach and execute scripts with the help of an
* handy little snippet.
*/
function install(ajax_result, src) {
var acc_elm; // final wrapper DIV,
var acc_class; // and it's unique class.
// remove the footer since we don't want that in our result.
ajax_result = ajax_result.match(/^([\n\s\S]+?)<div id="mainBottom/im)
|| ['', ajax_result];
// rename #startPageContainer to avoid css conflicts.
ajax_result = ajax_result[1].replace(/startpageContainer/m,
'startPageContainer_'
+ Math.floor(Math.random() * 1000));
ajax_result = ajax_result.replace(/<scr/ig, '\x3Cscr').replace(/<\/scr/ig, '\x3C/scr');
acc_class = 'aCC-' + src.match(/:\/\/.+?\/(.+?)\?/)[1].replace(/[\/.]/g, '_');
acc_class += ' aCC'+ ++callsCount;
acc_class = 'allColumnsContainer ' + acc_class;
// mount ajax response in a temporary iframe
// and send the markup upwards when done. by doing
// this, all scripts in the ajax response is executed
// correctly.
console.log('iframe begin');
(function(iframe_container){
var iframe_window; // reference
var splitted; // for cross browser script execution
iframe_window = getIFrameWindow(document.getElementById('unlimited-loader'));
iframe_window.document.open();
// open wrappers DIVs.
iframe_window.document.write(
'<div class="' + acc_class +
'"><div class="allColumnsContainer-inner">');
// now comes a tricky part: all but IE will execute
// script tags without any complains. To make the
// last stubborn one work with us, we need to split
// strings to mimic the rusty ol':
// '<scr' + 'ipt>doAmazingStuff()</scr' + 'ipt>';
splitted = ajax_result.split('<scr');
// first chunk can be added right away.
iframe_window.document.write(splitted[0]);
// we are done now unless there was a script in
// the ajax response, which we know if we have more
// than one chunk.
if (splitted.length > 1) {
for (var extracted, i = 1, max = splitted.length; i < max; i++) {
// this is necessary since we need to
// split the end tags as well.
extracted = splitted[i].split('</scr');
// now we can put it together
iframe_window.document.write('<scr'
+ extracted[0] + '</scr' + extracted[1]);
}
}
// close wrapper DIVs
iframe_window.document.write('</div></div>');
// finally, we ask the iframe to send the html
// up to the parent.
iframe_window.document.write('<scr');
iframe_window.document.write('ipt>setTimeout(function(){parent.mkt_importFromIframe(this);}, 999);</scr' + 'ipt>');
iframe_window.document.write('<h1>bu!</h1>');
console.log('iframe document.close begin');
console.log('iframe document.close done');
})((function(){
var div = document.createElement('div');
// the iframe should be visually hidden so
// lets add some css for that.
div.style.position = "absolute";
div.style.left = "-9999px";
div.style.top = "-99px";
div.id = 'mkt_iframe-container'; // do not change this!
div.innerHTML = '<iframe id="unlimited-loader" name="unlimited-loader" src="inner.html"></iframe>';
(function(scr){
scr.parentNode.insertBefore(div, scr);
})(document.getElementsByTagName('script')[0]);
return div;
})());
console.log('iframe done');
}
/**
* callback function which is called when we are using
* scrollbased loading and fires a scroll event.
* It makes sure we are not loading anything
* until it is necessary.
*/
function look_for_trouble(e) {
// first, check to see if we should continue.
if (window.mkt_nothing_more_to_load || window.mkt_prevent_new_loading) {
return; // one or more locks is still active, so we wait.
}
// second, we only want to load new content
// if we are at the bottom of the page.
if (getDocHeight() - getScrollTop() <= window.outerHeight) {
window.mkt_prevent_new_loading = true;
load_and_append();
}
};
//
// borrowed functions.
//
// found at http://james.padolsey.com/javascript/get-document-height-cross-browser/
function getDocHeight() {
return Math.max(
Math.max(document.body.scrollHeight,
document.documentElement.scrollHeight),
Math.max(document.body.offsetHeight,
document.documentElement.offsetHeight),
Math.max(document.body.clientHeight,
document.documentElement.clientHeight)
);
}
// found at http://stackoverflow.com/questions/871399/cross-browser-method-for-detecting-the-scrolltop-of-the-browser-window
function getScrollTop(){
if(typeof pageYOffset!= 'undefined'){
//most browsers
return pageYOffset;
}
else{
var B= document.body; //IE 'quirks'
var D= document.documentElement; //IE with doctype
D= (D.clientHeight)? D: B;
return D.scrollTop;
}
}
// http://av5.com/docs/changing-parent-window-s-url-from-iframe-content.html
function getIFrameWindow(iframe) {
return (iframe.contentWindow) ? iframe.contentWindow : (iframe.contentDocument.document) ? iframe.contentDocument.document : iframe.contentDocument;
}
function getHTTPObject() {
var xhr = false;//set to false, so if it fails, do nothing
if(window.XMLHttpRequest) {//detect to see if browser allows this method
var xhr = new XMLHttpRequest();//set var the new request
} else if(window.ActiveXObject) {//detect to see if browser allows this method
try {
var xhr = new ActiveXObject("Msxml2.XMLHTTP");//try this method first
} catch(e) {//if it fails move onto the next
try {
var xhr = new ActiveXObject("Microsoft.XMLHTTP");//try this method next
} catch(e) {//if that also fails return false.
xhr = false;
}
}
}
return xhr;//return the value of xhr
}
})(window, document);
Maybe I'm wrong, but as far as I know in accordance to the DOM-specification it's not possible in MSIE to move nodes between documents.(and that's what you do, regarding to your description)
for testing:
<html>
<head>
<script type="text/javascript">
<!--
function fx(o)
{
var doc=o.contentWindow.document;
doc.body.appendChild(doc.createTextNode('This works with nodes \
from the same document'));
try{
doc.body.appendChild(document.createTextNode(' and also with nodes \
from another document'));
}
catch(e)
{
doc.body.appendChild(doc.createTextNode(' but not with nodes from \
another document=>['+e.description+']'));
}
}
//-->
</script>
</head>
<body>
<iframe onload="fx(this)" src="about:blank"></iframe>
</body>
</html>
精彩评论