IE の メモリリーク不具合 まとめ (サンプルコード)

0 件のコメント

元ネタは こちら をご参照ください。

IE8 における JavaScript でメモリーリークを起こすコードパターンです。 ここにあげているパターンはすべてパッチリリース済みなので、パッチをあてれば解消できます。 逆に、パッチがあてられない場合、絶対書いてはいけないコードです。

  • innerHTML プロパティを頻繁に書き換える
  • frameset 要素を使う
  • window.open した先のウィンドウ内で循環参照を含む iframe 要素を使う
  • window.createPopup した先のウィンドウ内で window.createStyleSheet を使う
  • 循環参照があるスクリプトで、onload、onerror、onunload イベントハンドラーを接続する
  • iframe から モーダルダイアログ を開いて、iframe を閉じる

innerHTML プロパティを頻繁に書き換える

この不具合の詳細は KB975623 を参照してください。

DOM要素の innerHTML プロパティを繰り返し書き換えるコードを Internet Explorer 8 で実行すると、 メモリリークします。

(あまりに、書いてしまいやすいコードすぎて涙が出てきます。。)

サンプルコード

var i, element
element = document.getElementById('hoge');
for (i = 0; i < 5000; i++) {
    element.innerHTML = '' + (Math.random() * 100);
}

frameset 要素を使う

frameset を利用したページの更新を繰り返すと、メモリリークします。 この不具合の詳細については KB982094 を参照してください。

今さら、 frameset ページのサンプルですが…、一応掲載します。 ちなみに、frameset要素、frame要素、noframes要素 は HTML5 で廃止されています。 「ユーザビリティやアクセシビリティに悪影響を及ぼすとみなされ」たためだそうです。

サンプルコード

index.html

<html>
<head>
    <title>frameset sample page.</title>
</head>
<body>
    <frameset cols="50%,*">
        <frame src="frame1.html" name="menu"></frame>
        <frame src="frame2.html" name="contents"></frame>
        <noframes>
            このページはフレーム対応のブラウザで閲覧ください。
        </noframes>
    </frameset>
</body>
</html>

frame1.html

<html>
<head>
    <title>frame1 - menu page.</title>
</head>
<body>
    <a href="frame2.html" target="contents">コンテンツ更新</a>
</body>
</html>

frame2.html

<html>
<head>
    <title>frame2 - contents page.</title>
</head>
<body>
    <h1>コンテンツ</h1>
</body>
</html>

window.open した先のウィンドウ内で循環参照を含む iframe 要素を使う

この不具合の詳細については KB975736 を参照してください。 循環参照については こちら を参照してください。

サンプルコード

index.html

<html>
<head>
    <script type="text/javascript">
        var btn_onclick = function (event) {
            window.open('./childwindow.html');
        };
        
        window.lonload = function () {
            window.document.getElementById('btn').onclick = btn_onclick;
        };
    </script>
</head>
<body>

<input type="button" id="btn" value="子ウィンドウを開く" />

</body>
</html>

childwindow.html

<html>
<head>
    <style type="text/css">
        iframe {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
        }
    </style>
</head>
<body>

<iframe src="./childcontent.html"></iframe>

</body>
</html>

childcontent.html

<html>
<head>
    <script type="text/javascript" src="./childcontent.js"></script>
</head>
<body>
    <input type="button" id="setup" value="setup leak" />
    <input type="button" id="teardown" value="teardown leak" />
    <div id="foo"></div>
    <div id="bar"></div>
</body>
</html>

childcontent.js

// グローバル変数
var globalObject;
 
// メモリリークするコードを呼び出し
function setupLeak() {
    var element;
     
    // グローバル変数に DOM要素 への参照を保持
    globalObject = document.getElementById('foo');
     
    // "bar"要素の expandProperty に DOM要素 への参照を保持
    element = document.getElementById('bar');
    element.expandProperty = globalObject;
};
 
// メモリリークしたオブジェクトを破棄して解放
function teardownLeak() {
    document.getElementById('bar').expandProperty = null;
};
 
window.onload = function () {
    document.getElementById('setup').onclick = setupLeak;
    document.getElementById('teardown').onclick = teardownLeak;
};

window.createPopup した先のウィンドウ内で window.createStyleSheet を使う

この不具合に関する詳細は KB2539352 を参照してください。 関連するメソッドの詳細は下記リンクをご参照ください。

サンプルコード

var html = '';
html += "<p>ポップアップ ウィンドウ の 内容</p>";
html += "<script>";
html += "var ss = document.createStyleSheet();";
html += "ss.addRule('body', 'background-color: gray;');";
html += "</script>";

// この関数を呼び出すとリーク(するはず…)
var showPopup = function () {
    var windowObject = window.createPopup();
    var body = windowObject.document.body;
    body.innerHTML = html;
    windowObject.show(100, 100, 300, 400);
};

循環参照があるスクリプトで、onload、onerror、onunload イベントハンドラーを接続する

この不具合に関する詳細は KB2711084 を参照してください。 循環参照については こちら を参照してください。

循環参照がある JavaScript において、onload、onerror、onunload を使うとメモリリークするということは… JavaScript でクラスっぽい書き方をしたとき書きたくなる、下記のようなコードはダメと言うことだと思います。

サンプルコード

/**
* @class
* @constructor
*/
var SampleClass = function () {
    // 循環参照の原因となる DOM要素
    this.window = window;
    this.document = window.document;
    this.body = window.document.body;

    // 初期化処理の実行
    this.initialize();
};

/**
* SampleClass の初期化処理
* NOTE: このメソッド内のクロージャで循環参照が起こっています。
*/
SampleClass.prototype.initialize = function () {
    var self = this;
    
    this.body.onload = function (event) {
        self.onload(event);
    };
    this.window.onunload = function (event) {
        self.onunload(event);
    };
    this.window.onerror = function (message, url, line) {
        self.onerror(message, url, line);
    };
};

/**
* onload の呼び出し
*/
SampleClass.prototype.onload = function () {
};

/**
* onunload の呼び出し
*/
SampleClass.prototype.onunload = function () {
};

/**
* onerror の呼び出し
*/
SampleClass.prototype.onerror = function (message, url, line) {
};

// インスタンスの生成と実行
new SampleClass();

iframe から モーダルダイアログ を開いて、iframe を閉じる

この不具合に関する詳細は KB2695422 を参照してください。 showModalDialog 関数に関しては下記のリンクを参照してください。

サンプルコードを作っていて感じたのですが、このコードを実装することは少し難しい気がします。 モーダルダイアログを開くと親ウィンドウがさわれなくなるので、元の iframe を閉じづらい… おそらく、「window.open でモードレスダイアログにした上で、 window.showModalDialog のような実装が行われているのではないか」 という、個人的な意見です。

サンプルコード

index.html

<html>
<head>
    <script type="text/javascript">
var removeContents = function (target) {
    var n, children;
    children = target.childNodes;
    for (n = children.length; n--;) {
        target.removeChild(target.firstChild);
    }
};

var button_onclick = function (event) {
    var target = document.getElementById('content');
    var iframe = document.createElement('iframe');
    iframe.onload = function () {
        removeContents(target);
    }
    iframe.src = './content.html';
    target.appendChild(iframe);
};

window.onload = function () {
    document.getElementById('btn').onclick = button_onclick;
};
    </script>
</head>
<body>

<div id="control">
    <input type="button" id="btn" value="開く" />
</div>
<div id="content">
</div>

</body>
</html>

content.html

<html>
<head>
    <script type="text/javascript">
var url = 'http://www.yahoo.co.jp/';
window.showModalDialog(url, this);
    </script>
</head>
<body>
iframe内
</body>
</html>

…と、いろいろサンプルコードを書いていきましたが、実際にはすべてのコードを IE8 で評価していないので、間違っていたらごめんなさい。。