开发者

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:

  1. Make ajax request (via getHTTPObject(), no libraries is used)
  2. Create an iframe with script, src is "blank.html".
  3. use iframe.document.write() to execute scripts (inkluding document.write based scripts) in the iframe.
  4. call parent window's document to clone the iframe content.
  5. 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>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜