IE10のJavaScriptでスゴいバグ見つけた

こんにちわ、腰痛に耐えながら何とか生きてるkudoxです。久々のブログ更新になってしまいましたが、Windows8 Internet Explorer10のJavaScriptで深刻なバグを発見しましたので、レポートしたいと思います。このバグは、特定の状況下でしか発生しないことを確認していますが、かなりクリティカルな内容です。

バグの発生する環境

Windows8のInternet Explorer10(現時点の最新バージョン)で確認しました。IE8, 9, 11を含め、その他のブラウザでは発生しません。

使用しているライブラリは、下記の通りです。

  • jquery-1.11.2.min.js
  • underscore-min.js (1.8.3)
  • backbone-min.js (1.1.2)

バグの発生するサンプル

バグの発生するサンプルを下記にアップしています。サンプルでは、タブをクリックすると表示するパネルを切り替えることができます。IE8, 9, 11を含め、その他のブラウザでは何度タブをクリックしても問題ありませんが、IE10では16回目のクリック以降、js/ie10_js_bug.js の68行目が機能しないため、レイアウトが崩れます。

何が起こっているのか?

IE10では何が起こっているのか? 調査してみました。下記のサンプルは、js/ie10_js_bug.js にログ出力を加えたものです。if文内とif文後で同じローカル変数xの値を出力するようにしています。これをIE10で実行してみると驚愕の事実が判明します。

JavaScript

var row = 0;
var x = originX;
var $item, w, y;
for (var i = 0, l = $items.length; i < l; i++) {
  $item = $items.eq(i);
  if ($item.hasClass('hidden')) {
    $item.removeClass('hidden');
  }
  w = $item.innerWidth();
  if (containerWidth < (x + w)) {
    row++;
    x = originX;
    console.log('inner if x:' + x);
  }
  console.log('after if x:' + x);
  y = row * (gridHeight + marginY);
  $item.css({
    left: x,
    top: y
  });
  x += w + marginX;
}

下記の画像は、IE10の開発者ツールのキャプチャです。確認すると、バグが発生した後では同じローカル変数xが、if文内とif文後で違う値になっていることがわかります。こうなってくると、もう何も信用できません。

原因についての推察

このバグの原因について色々と調査してみたのですが、私の力不足とやる気の問題で残念ながら特定には至っていません。一応、現時点の私の推察を述べておきたいと思います。

backbone.jsを使わずに同様の処理を行った場合、バグが発生しないことを確認しました。それでも私は、原因がbackbone.jsにあるとは思えません。IE8, 9, 11を含め、その他のブラウザでは問題ないことやIE10でも15回目までは問題ないことが説明できません。原因は、やはりIE10のJavaScriptにあると考えています。

興味深いのは、IE10でも開発者ツールでデバッグを開始している状態では、バグが再現しないことです(デバッグの意味なくね?)。この点から、IE10がJavaScriptの高速化のためにやっている何かが悪さをしてそうと考えています。

暫定的な対処法

バグの原因が特定できていない以上、あくまで暫定的とはなりますが、対処法をご紹介したいと思います。今回のバグは、ローカル変数を別のローカル変数に代入している箇所で起こっています。そこで片方をオブジェクトのプロパティにしたところ、IE10でもバグが発生しなくなりました。もし、IE10で同様の症状が出た場合は、試してみる価値があるのではないでしょうか?

下記のサンプルは、代入するローカル変数をオブジェクトのプロパティに書き換えたものです。これだとIE10でもバグが発生しなくなります。

JavaScript

var origin = {};
origin.x = (containerWidth - ((gridWidth + marginX) * cols - marginX)) >> 1;
var $items = $thumbs;
if (selector !== '') {
  $items = $thumbs.filter(selector);
  $thumbs.not(selector).addClass('hidden');
}
var row = 0;
var x = origin.x;
var $item, w, y;
for (var i = 0, l = $items.length; i < l; i++) {
  $item = $items.eq(i);
  if ($item.hasClass('hidden')) {
    $item.removeClass('hidden');
  }
  w = $item.innerWidth();
  if (containerWidth < (x + w)) {
    row++;
    x = origin.x;
  }
  y = row * (gridHeight + marginY);
  $item.css({
    left: x,
    top: y
  });
  x += w + marginX;
}

このバグの厄介なところは、n回実行しないと再現しない点です。このnは、常に一定の値ではなく、スクリプトの量などによって変化します。事実、最初にバグに気付いた時は、21回目でした。このJSer泣かせのバグが、一刻も早く修正されることを切に願います。