16

私は大きな問題になるために爆発する小さな迷惑に遭遇しました。

問題1:window.open Internet Explorerで、(を介して開いた)ウィンドウを閉じると、ウィンドウもownerDocument一緒に消えます。

これは、またはなどのDOMの呼び出しが、appendChildまたはcreateElementで失敗することをSCRIPT70: Permission Denied意味しSCRIPT1717: The interface is unknownます。

Chromeなどの他のブラウザの動作を見てきました。ChromeではownerDocumentまだ参照して#documentownerDocument.defaultViewますが、最終的にはになりますundefined。これは私には理にかなっています。を呼び出して通過しますappendChild。直接createElement参照しない限り、すべて問題ないと思います。defaultView

問題2: Internet Explorerで、生成されたウィンドウの閉じるボタンをクリックすると、イベントループを尊重していないようです。unload生成されたウィンドウにイベントをアタッチすると、イベントループの最後でキューに入れるのではなく、すぐに発生します。これは私には意味がありませ。このかなり些細な問題に対処することはまったく不可能になります。

問題1が発生したばかりの場合は、-それでも苦痛ですが-簡単な解決策があります。存在するかどうかを確認し、ownerDocument存在しない場合はスキップします。ownerDocument同期JavaScriptコードの途中で消えてしまいます。

期待される動作: DOMノードを参照した場合、DOMノードが消えることはありません-ガベージコレクションの健全性。

予想される動作2: DOMノードは同期コードで消えてはなりません。(もちろん削除しない限り)。

既知の回避策: DOMと対話するすべてのコードをウィンドウに移動します。これにより、ウィンドウが閉じられると、JavaScriptランタイム環境も閉じられます。これは簡単な解決策ではなく、アーキテクチャに大幅な変更が必要になる場合があります。

安っぽい解決策: DOMと相互作用する関数を、要素のウィンドウが閉じられたことを検出した場合にエラーを消費する関数にラップします。これは非常に侵襲的であり、パフォーマンスに大きな影響を及ぼし、IEはすでに非常に低速です。

より良い解決策はありますか?

少なくとも、ユーザーがウィンドウを閉じたためにスローされたを無視する方法が必要です。問題1問題2は、JavaScriptコードに関する基本的な仮定であるガベージコレクションとイベントループを破っています。Error


デモスクリプト

<script type="text/javascript">
function go() {
    var popup = window.open('', 'open', 'width=500,height=300,scrollbars=yes,resizable=yes');
    popup.document.open();
    popup.document.write('<html><head></head><body></body></html>');
    popup.document.close();
    for (var i = 0; i < 10000; i += 1) {
        var node = popup.document.createTextNode(i + " ");
        popup.document.body.appendChild(node);
    }
}
</script>
<input type="button" onclick="go();" value="Open popup" />

(.htmlファイルとして保存)

指示:

  • InternetExplorer9で開く
  • 「ポップアップを開く」をクリックします
  • レンダリング中にウィンドウを閉じます
  • 「許可が拒否されました」を確認します

これがJSFiddleです:http://jsfiddle.net/C9p2R/1/

4

4 に答える 4

1

誰かがより良い解決策を持っていない限り、私はくだらない解決策を選びます。これが私のコードです:

function apply_window_close_fix(dom_element, wrapped_element) {
    var ignore_errors = false;
    dom_element.ownerDocument.defaultView.addEventListener("unload", function () {
        ignore_errors = true;
    });
    return map(wrapped_element, function (key, func) {
        return function () {
            try {
                return func.apply(this, arguments);
            } catch (e) {
                if (ignore_errors === false) {
                    throw e;
                }
            }
        };
    });
}

wrapped_elementDOMを変更するために返すAPIです。ウィンドウが閉じられていることを確認した場合にエラーを無視するtry-catchですべての関数をラップしました。この関数は、InternetExplorerと同じように動作するブラウザーに対してのみ呼び出します。

パフォーマンスへの影響はごくわずかなようです。もちろん、これはこのAPIをどれだけ集中的に呼び出すかによって異なります。

マイナーな欠点の1つは、現在一部のエラーを再スローすることが一部のブラウザで壊れていることです。DOMExceptionを再スローすると、Internet ExplorerとChrome(および場合によってはその他)のスタックがリセットされます。また、InternetExplorerのDOMExceptionからファイル名と行番号を取得する方法も見つかりませんでした。繰り返しになりますが、全体的な見落としは、すべての人の時間を無駄にすることになります。

于 2013-02-19T11:21:24.730 に答える
0

「CrappySolution」を変更できます。DOMと相互作用する関数をラップする代わりに、unloadイベントが発生したときにDOMと相互作用しないように関数を再定義できますmyOldFunction = function (){};

于 2013-02-18T05:24:22.123 に答える
0

IE9で常に問題が発生するいくつかのこと(postMessage、Worker、...)を試した後、私は素晴らしい解決策を見つけました。setInterval関数を使用してParallel.Forループを作成しました。キャッチはここにあります:

setInterval()メソッドは、clearInterval()が呼び出されるか、ウィンドウが閉じられるまで、関数の呼び出しを続行します。

ノードを作成するためのロジックは子ウィンドウにありますが、親ウィンドウからトリガーされます。ループはチャンクで実行され、setIntervalが使用されるため、どの時点でも子ウィンドウを閉じてもエラーは発生しません。さらに、ブラウザはハングしません(これが実行されている間は親でも子でもありません)。そしてそれはこのように見えます:

ここに画像の説明を入力してください

parent-ie.html、child-ie.html、小さなparallel.jsファイルの3つのコンポーネントがあります。1つの癖は、すべてのブラウザが機能するためにsetInterval(function、-1)が使用されたことです。正の値はIEにブレーキをかけ、2番目に省略されたパラメーターはOperaを混乱させるため、関数の最初のチャンクのみを作成します。とにかく、コードはここにあります:

parent-id.html

<!DOCTYPE html>
<html>
<head>
    <title>Parent</title>
</head>
<body>
    <script type="text/javascript">
        'use strict';
        (function(){
            var popup;
            var pathname = 'child-ie.html';

            window.openPopup = function _open() {
                popup = window.open(pathname, 'open', 'width=500,height=300,scrollbars=yes,resizable=yes');
            }

            window.createElements = function _createElements() {
                if (popup == null || popup.closed == true) {
                    alert("Open new popup window first.");
                    return;
                }
                var numberOfElements = parseInt(document.getElementById('numberOfElements').value);
                popup.writeElements(numberOfElements);
            }
        })();
    </script>
    <button onclick="openPopup()">Open popup</button><br />
    <button onclick="createElements()">Create elements</button>
    <input id="numberOfElements" type="number" value="10000" />
</body>
</html>

child-ie.html

<!doctype html>
<html>
<head>
    <title>Child window</title>
</head>
<body>
<script src="/Scripts/Parallel.js"></script>
<script>
    (function(){
        function _iterator(index) {
            var node = document.createTextNode(index + " ");
            document.body.appendChild(node);
        }

        window.writeElements = function (numberOfElements) {
            document.body.innerHTML = '';
            Parallel.For(0, numberOfElements, 100, _iterator);
        }
    })();
</script>
</body>
</html>

/Scripts/Parallel.js

'use strict';
var Parallel;
(function (Parallel) {
    var Iterator = (function () {
        function Iterator(from, to, step, expression) {
            this._from = from;
            this._to = to;
            this._step = step;
            this._expression = expression;
        }
        Object.defineProperty(Iterator.prototype, "intervalHandle", {
            set: function (value) {
                this._intervalHandle = value;
            },
            enumerable: true,
            configurable: true
        });
        Iterator.prototype.next = function () {
            var max = this._to > this._step + this._from ? this._step + this._from : this._to;
            for(var i = this._from; i < max; i += 1) {
                this._expression(i);
            }
            if(max === this._to) {
                clearInterval(this._intervalHandle);
            } else {
                this._from = max;
            }
        };
        return Iterator;
    })();    
    function For(from, to, step, expression) {
        var _iterator = new Iterator(from, to, step, expression);
        _iterator.intervalHandle = setInterval(function () {
            _iterator.next();
        }, -1);
    }
    Parallel.For = For;
})(Parallel || (Parallel = {}));

Javascriptはtypescriptファイルから生成されました(おそらくjavascriptよりも少し明確です):

'use strict';
module Parallel {
    class Iterator {
        private _from: number;
        private _to: number;
        private _step: number;
        private _expression: (index: number) => {};
        private _intervalHandle: number;

        public set intervalHandle(value: number) {
            this._intervalHandle = value;
        }

        constructor(from: number, to: number, step: number, expression: (index: number) => {}) {
            this._from = from;
            this._to = to;
            this._step = step;
            this._expression = expression;
        }

        next() {
            var max: number = this._to > this._step + this._from ? this._step + this._from : this._to;
            for (var i = this._from; i < max; i += 1) {
                this._expression(i);
            }
            if (max === this._to) {
                clearInterval(this._intervalHandle);
            }
            else {
                this._from = max;
            }
        }
    }
    export function For(from: number, to: number, step: number, expression: (index: number) => {}) {
        var _iterator = new Iterator(from, to, step, expression);
        _iterator.intervalHandle = setInterval(function () {
            _iterator.next();
        }, -1);
    }
}

それで全部です。

于 2013-02-23T02:18:27.290 に答える
0

OK、単純化してみましょう。ウィンドウの応答性を高め、子が閉じているときにアプリケーションが中断しないようにするには、いくつかのことを組み合わせる必要があります。

  1. 親は子DOMを直接変更しないでください。関数は子自体にある必要があります。ただし、関数が親からトリガーされて同期的に実行される場合、これだけでは何も解決されません。ぶら下がっている窓と例外はまだそこにあります。
  2. 親が子に対して呼び出すメイン関数は、setTimeoutまたは を使用setIntervalして、DOM変更関数をスケジュールし、実行を子に委任してすぐに戻る必要があります。この手順により、自動クリーンアップが可能になります。
  3. 子での長期的なDOM操作は、setTimeoutまたはを使用してさらに高速なチャンクに分割する必要がありますsetInterval。これにより、events-loopが長時間ロックされなくなります。これにより、両方のウィンドウに応答性が与えられます

子JavaScriptの最も単純な例。親から呼び出された関数はLongLastingOperation、4つのチャンクに分割されています。

<script>
    function wasteTime(message) {
        // TODO: waste time
    }
    function FirstPartOfLongOperation(){
        wasteTime('long... part 1');
        setTimeout(SecondPartOfLongOperation, 0);
    }
    function SecondPartOfLongOperation() {
        wasteTime('long... part 2');
        setTimeout(ThirdPartOfLongOperation, 0);
    }
    function ThirdPartOfLongOperation() {
        wasteTime('long... part 3');
        setTimeout(FourthPartOfLongOperation, 0);
    }
    function FourthPartOfLongOperation() {
        wasteTime('long... part 4');
        alert('Done!');
    }

    // Main entry, called from parent.
    // Ex: popup.LongLastingOperation({ data: ....})
    function LongLastingOperation(parametersPassedFromParentWindow) {
        // decompose long operation
        setTimeout(FirstPartOfLongOperation, 0);
    }
</script>
于 2013-02-23T11:46:42.040 に答える