Hebikuzure's Tech Memo

2009年10月25日

「操作は中断されました」を防止する

Filed under: Internet Explorer — hebikuzure @ 1:19 PM

Preventing Operation Aborted Scenarios
http://blogs.msdn.com/ie/archive/2009/09/03/preventing-operation-aborted-scenarios.aspx


IE8 でかなり減ったと言っても、相変わらずやっかいな「操作は中断されました」のエラーを防止するための情報です。

以下の文章は IE Blog の 9/3 の記事 Preventing Operation Aborted Scenarios) を hebikuzure が私的に試訳したものです。翻訳については Microsoft Corporation およびマイクロソフト株式会社とは無関係に hebikuzure が公開情報に基づき独自に行ったものであり、この文書の内容についての文責は公開者である hebikuzure にあります。翻訳の内容および技術的内容については正確を期すよう十分な注意を払っておりますが、誤りや不正確な部分が含まれている可能性がありますので、本文書を利用される際には原文も併せてご確認ください。


「操作は中断されました」を防止する

この記事は、以前の操作の中断についての記事を補い、Web サイトのオーナーやサードパーティーのスクリプト ライブラリに向けて追加の情報と支援を提供するものです。

まとめ

ほぼ 1 年半前、スクリプトを使ってコンテンツを生成しているある種の Web サイトで発生する可能性のあるエラーについてブログ記事を投稿しました。こうしたコンテンツは Internet Explorer の HTML パーサーを回復不能の状態に陥れる場合があり、このエラーの発生理由の分析と発見の両方を困難にしています。このような状態に陥ると、HTML パーサーは動作を継続できず、両手を上げて認めます: “操作は中断されました!”

IE8 の開発の初期段階で、この問題の最悪の副作用を軽減する緩和策を追加しました。モーダル ダイアログが表示され、OK ボタンを押した後は別のページに移動するしかない動作に代えて、ダイアログは廃止しエラーの通知をステータス バー (スクリプト エラーが通知される領域) に移動しました。その結果、ユーザーはダイアログに妨げられることなく、現在のページを見続ける事ができまようになりました。ユーザーがエラー発生に気付かなかったとしても、HTML パーサーは (そのタブだけですが) すっかり動作を停止し、追加のコンテンツについての処理は行われません。

IE8 のリリースから間もなく、IE8 のユーザーから依然として以前の操作中断のダイアログが表示されるという報告を受けました。ダイアログはナビゲーション スタックやネットワーキングなどの数多くのサブシステムから包括的に呼び出されるため、ダイアログが呼び出される可能性のある全ての状況に対して修正がされているのではない事は分かっていましたが、最悪のケースについては緩和されていると考えていました。IE8 での操作中断のダイアログが表示されたというユーザーの報告を基に、(スクリプト エラーの軽減よりも) ダイアログが表示される別の状況についてさらに調査を進めました。

シナリオ 1: 操作中断後のネストした解析

<html>
 <body>
  <div>
   <script type="text/javascript">
    document.body.appendChild(document.createElement('div'));
    document.write("Testing");
   </script>
  </div>
 </body>
</html>
上記の HTML では、スクリプトの最初の行により操作中断の問題が発生します。IE8 ではこの問題は前述のように緩和されるはずです。しかしスクリプトの 2 行目のように document.write API がその後に呼ばれると、IE8 含めすべてのバージョンの Internet Explorer は以前の操作中断のダイアログを表示します。

シナリオ 2: エラー ハンドラ中の操作中断

<html> <body> <script type=”text/javascript”> window.onerror = function() { var el = document.getElementById(“div2”); el.appendChild(document.createElement(“div”)); } </script> <div id=”div1″></div> <div id=”div2″ onclick=”alert(‘hi’;“></div> </body> </html>

この HTML ファイルでは、(onclick イベント ハンドラ中の) スクリプト エラーはライタイム エラーを発生させ、window オブジェクトの onerror ハンドラが起動されます。このシナリオで、操作の中断がエラー ハンドラ中で発生すると、IE8 でもダイアログが表示されます。

操作の中断をプログラムから検知する

このエラー ダイアログが表示される場合、Web 開発者が問題を発見して修正するのは非常に困難でした。この問題はしばしば (そして最もよく確認されているのは) 、問題の起きるページから参照されているサードパーティーのスクリプトに起因しています。Web 開発者が問題を素早く見つけて修正できるよう、その助けとなる短いスクリプトを作成しました。

このスクリプトは操作中断の問題が発生するページ内で実行される最初のスクリプトでなければなりません。このスクリプトは innerHTML と appendChild について、動作が許可される前に最初に解析の境界をチェックする事により、使用方法をオーバーライドしています。AppendChild は操作中断を引き起こす、断然最も一般的な DOM エントリーであり、innerHTML がそれに次いでいます。このスクリプトは false positive の検知動作を行いますが、これは間違えるのであれば用心深い側であるようにしたかったからです。

このスクリプトはIE8 標準モードでのみ有効な機能 — Mutable (変更可能) DOM プロトタイプに依存しています。そのためこれは IE の最も標準に準拠したモードのページでのみ動作します。IE がページを解釈するモードについての詳細は、互換表示についてのこの記事を参照してください。ただしこのスクリプトが (IE8 標準モードで) 検知する操作中断の問題は IE6 と IE7 でも同様なので、どのバージョンの IE についても問題を解析し修正するのに役立ちます。

下記のスクリプトを利用するには、次の手順を実行します:

  1. 問題のページの head セクションに script 要素を追加します。この script 要素はページ上の他のどの script 要素よりも前でなければなりません。
  2. 下記のスクリプトのテキストを script 要素内に記述します (または下記のスクリプトを含むファイルを src 属性で参照します)
  3. “f1” と “f2” の機能に値を設定します
    1. “f1” を true に設定すると操作中断を発生させる可能性のある DOM 呼び出しをスキップします。これによりプログラム フローが変化する事になり、結果として他のスクリプト エラーが発生します。
    2. “f2” を true に設定すると操作中断を発生させる可能性のある場所でプログラム フローを停止し、デバッガー (外部デバッガーまたは組み込みの JavaScript デバッガー) にブレークします。この部分が、それぞれの現象についてどのような仮定をしていたのか、また問題の抑止のためにどのようなプログラム フローの変更が可能か分析すべき箇所です。
  • IE で問題のページに移動します。
  • “F12” を押して JavaScript デバッガーを開始し、開発者ツールの “スクリプト” タブを選択し、“デバッグ開始” ボタンを押します。
(function() { // Feature switches
    // WARNING: 'true' may cause alternate program flow.
    var f1 = PREVENT_POTENTIAL_OCCURANCES = false;
    var f2 = BREAK_INTO_DEBUGGER_AT_POTENTIAL_OCCURANCES = true;
    if (!window.console) {
        window.console = {};
        window.console.warn = function() { };
    }
    var frontierCheck = function(host) {
        // Is host on the frontier?
        while (host && (host != document.documentElement)) {
            if (host.parentNode && (host.parentNode.lastChild != host))
            // This is not on the frontier
                return true;
            host = host.parentNode;
        }
        if (!host || (host != document.documentElement))
            return true; // This node is not on the primary tree
        // This check is overly cautious, as appends to 
        // the parent of the running script element are 
        // OK, but the asynchronous case means that the 
        // append could be happening anywhere and intrinsice
        // knowledge of the hosting application is required
        console.warn("Potential case of operation aborted");
        if (f2)
            debugger;
        // Step up two levels in the call stack 
        // to see the problem source!!
        if (f1)
            return false;
        else
            return true;
    }
    var nativeAC = Element.prototype.appendChild;
    Element.prototype.appendChild = function() {
        // call looks like this:
        //    object.appendChild(object)
        // Go back one more level in the call stack!!
        if (frontierCheck(this))
            return nativeAC.apply(this, arguments);
    }
    var nativeIH = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML").set;
    Object.defineProperty(Element.prototype, "innerHTML", { set: function() {
        if (frontierCheck(this))
            nativeIH.apply(this, arguments);
    }
    });
})();
IE8 での操作中断のダイアログやその多少ましな同類は、依然として Web 開発者の顕著な悩みの種である事を理解しています。この記事の情報と予防のためのスクリプトが、IE8 (と以前のバージョンの IE) での操作中断に関連した問題を分析し修正する役に立てば幸いです。

-Travis Leithead
プログラム マネージャー

コメントする »

まだコメントはありません。

RSS feed for comments on this post. TrackBack URI

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

WordPress.com Blog.

%d人のブロガーが「いいね」をつけました。