♪NAI NAI NAI IEじゃない (c) シブがき隊

IE11はIEであってIEじゃない、IEとして扱おうとすると…♪そこが危NAI

IE11から削除されたdocument.createStyleSheetによる不具合例|Web制作 W3Gの補遺。

件のW3Gさんの記事では、どんなことをするとIE11でトラブルを引き起こすのか、そのバグ自体が明示されていなかったので書き添えておくと(リンク先を参照せよということだと思うけど)、IE11では document.styleSheets[0].cssText += "div { color: yellow; }"; のように、スタイルシート・オブジェクトのcssTextに、文字列としてCSSルールを動的に追加すると、既存の@-ms-viewportや@keyframesでの指定を壊してしまう(無効化される)。

もう1点。

  1. これまでIE専用だったdocument.createStyleSheet()関数がIE11より削除された
  2. IE11にdocument.createStyleSheet()関数が存在しなくなったことで、各種スクリプト側でフォールバックとして用意されているコードが走ることになる
  3. フォールバック用のコードの中でIE11の既存のスタイルオブジェクト(キーフレームアニメーションや@-ms-viewport)を破壊する(cssTextプロパティに文字列としてCSSを追加している)コードが実行されている場合があり、IE11では既存のスタイルオブジェクト(キーフレームアニメーションや@-ms-viewport)を認識できなくなる不具合が発生する

それはその通りなのだが、その根本的な原因は、FBなどのスクリプトではIE11をこれまでのIEと同様のブラウザとして扱っている点にあると思う。

FBのスクリプトの一部をIE11 bug with dynamic CSS stylesから再掲すると

Code snippet (from the minified src code of that SDK):

... if(!j.ie()){ var ea=document.createElement('style'); ea.type='text/css'; ea.textContent=z; document.getElementsByTagName('head')[0].appendChild(ea); } else try { document.createStyleSheet().cssText=z; } catch(fa) { if(document.styleSheets[0]) document.styleSheets[0].cssText+=z; } ...

この処理に典型的な、「IEかIEじゃないか」で振り分けるという形。 このような方法はIE11が出た今となってはさっさとやめた方がいいと思う。

IE11 の互換性の変更点 (Windows)にあるように、IE11で削除されたMS独自実装のメソッドやプロパティは何もdocument.createStyleSheetに限らず

  • attachEvent
  • window.execScript
  • window.doScroll
  • document.all
  • document.fileSize、img.fileSize
  • script.onreadystatechange と script.readyState
  • document.selection
  • document.createStyleSheet
  • style.styleSheet
  • window.createPopup
  • バイナリ ビヘイビアー
  • 従来のデータ バインディング

…と、結構多い。

ついでに言うと、 <!--[if ie]> (IE向けのコードやスタイルシートへのリンクなど) <[endif]--> といったMS独自実装のコンディショナルコメントも、たとえドキュメントモードをIE8とかにしてもIE11は認識しないでコメントとして無視するようになっている。

IE11におけるコンディショナル・コメントの振る舞いについては別記事をたてましたのであわせてご参照を

つまり、IE11はIEであっても最早IEじゃない

document.createStyleSheetに限らず、これらのMS独自実装をIE11でも適用しようとすると、今回のように思わぬトラブルが発生する可能性は十分にある。 以前、IE11はたったの1行では判別できなくなる!?という記事やあんたのお名前何ァんてェの (c)トニー谷なんて記事も公開しているので、掌を返すようだがw、むしろ、IE判別してIE11もこれまでのIEと同じように扱おうとするのは大いに問題あり、ということである。

で、その解決方法は何も難しいことではなく(スクリプトを修正するのは手間かもしれないがw)、MS自身が言っているように、Web標準の使いたいメソッドやプロパティをサポートしているかどうか、で振り分ければいいだけの話。

先の処理もちょっと変えて if(document.createElement) { var ea=document.createElement('style'); ea.type='text/css'; ea.textContent=z; document.getElementsByTagName('head')[0].appendChild(ea); } else { try { document.createStyleSheet().cssText=z; } catch(fa) { if(document.styleSheets[0]) document.styleSheets[0].cssText+=z; } } とすれば問題はなくなるはず(もうちょっとまともな(丁寧な)書き方ができるとは思うけど、そこは置いておくw)。

FBなど外部ライブラリはともかく、少なくとも自サイトに置いているJavaScriptを、この際、見直してみたらいかがかと。 …ということで、結論。

♪ジタバタするなよ 世紀末が来るぜ
(まともな動作が)欲しけりゃ 今すぐ
(Web標準に)すがりつけ~

…なんつーてw

補遺の補遺

ところで、IE11 bug with dynamic CSS stylesのコメント欄で提示されている回避策だが、よくよく見ると多少問題ありかもしれない。

というのも、回避策のコードではstyle要素を生成してURLがあったらhrefを足しているけど、そもそもstyle要素にhref属性はないので実際にCSSファイルへのURLが渡されてもそれは反映されない(URLが渡された場合には、本来ならばstyle要素ではなくlink要素を生成すべき)。

また、MSのドキュメントを見ると、第1引数にはCSSファイルへのURLだけではなく、例えば、 document.createStyleSheet(document.body.style.backgroundColor = 'blue'); を渡せる、というなかなか豪快な仕様になっている。 さらに、document.createStyleSheet()の第2引数には、 styleSheetsコレクションの中で挿入される位置を整数で指定する ための値も渡せるらしい。 CSSは後から書かれた指定で上書きするので、なぜ新たに生成したスタイルシートをわざわざスタイルシート・コレクションの「途中」に挿入するのか、その意味が自分にはさっぱりわからないけど…。

いずれにせよ、彼の回避策コードは、FB対策としては問題は表面化しないけど、ほかのライブラリ等でIE11にcreateStyleSheet()が適用された場合に不具合が出る可能性がある。

そこでちょっと発想を変えて、いっそdocument.createStyleSheet()をWeb標準で再定義してIEに適用しちゃった方が♪ぴたりあっちゃうかもね(ってこれは別の歌かw)と考えて、屋上屋を架してみた。 テストケース

…と思ったけど、あれこれ追試してみたら色々と不都合が出てきそうな感じだったので、結局はIE11のみに適用することにした。 やはりMS謹製ソフトは一筋縄ではいかなかった…orz


/*
*   Redefine document.createStyleSheet() in IE11
*   Free and unencumbered, released into the public domain.
*/
(function(d) {
    'use strict';
    d.documentMode === 11 && (d.createStyleSheet = function(s, i) {
        var o = d.createElement('style'), x;
        s && (x = new XMLHttpRequest(), x.onreadystatechange = function() {
            x.readyState === 4 && x.status === 200 && (o = d.createElement('link'), o.setAttribute('rel', 'stylesheet'), o.setAttribute('href', s));
        }, x.open('GET', s, false), x.send(null));
        i && (x = d.styleSheets, x[i]) ? (x = x[i].ownerNode, x.parentNode.insertBefore(o, x)) : d.querySelector('head').appendChild(o);
        return o.sheet;
    });
})(document);

適用対象はIE11のみに限定。

なお、第2引数のindex値(i)に関しては、0から始まるのか、それとも1から始まるのか、はたまた-1が指定された場合はどうなるのか、など全く情報がないので、とりあえずの実装となっている。

ただし、こんなもの作っておいてまたまた掌を返すようでアレですが…w

harrihaさんが示した回避策を使うにせよ、今回私が作ったものを使うにせよ、当面(FBのスクリプトが更新されるまでとか)の使用にとどめておいた方がいいとは思う。(一応FBのスクリプトが更新された後でも問題がでないように作ったつもりだけど)

http://connect.facebook.net/ja_JP/all.js を確認してみたら、いつ改修されたのかはしらないが、今はこのようになっていた。

if(h.ie()<11){
    try{
        document.createStyleSheet().cssText=z;
    }
    catch(ea){
        if(document.styleSheets[0])document.styleSheets[0].cssText+=z;
    }
}else{
    var fa=document.createElement('style');
    fa.type='text/css';
    fa.textContent=z;
    document.getElementsByTagName('head')[0].appendChild(fa);
}

ということで、FBに関しては上記のポリフィルはもう不要です。

"♪NAI NAI NAI IEじゃない (c) シブがき隊"へのTwitter上でのコメントやRT

18件のツイートがあります。

ツイート 1

ツイート 2

ツイート 3

ツイート 4

ツイート 5

ツイート 6

ツイート 7

ツイート 8

ツイート 9

ツイート 10

ツイート 11

ツイート 12

ツイート 13

ツイート 14

ツイート 15

ツイート 16

ツイート 17

ツイート 18