SafariとChromeを判別してみる2010年12月18日 04時32分45秒

※:Safari 5.1がChromeと区別できなくなったので修正版作ってみました。(11.08.25)

はい、またも3週間もほったらかしにしてたわけですが、ようやく前回の続きで、レンダリングエンジンがWebKitだった場合にAppleのSafariなのか、GoogleのChromeなのかを判別してみようかと。

当初の考えでは、まずSafariなのかChromeなのかを判断して、それからモバイル版なのかを見ようと思ってたんだけど、「SafariとモバイルSafariにはあって、ChromeとモバイルChromeには存在しないプロパティ」またはその逆ってのが見つからなかったのよね。

仕方ないのでデスクトップ版同士、モバイル版同士の差異を探して、

  • まずはモバイル版かどうか判断し
  • その後にそれぞれSafariかChromeかをチェックする
という方法に落ち着いた。

あ、この記事では便宜上、Androidの標準ブラウザを「モバイル版Chrome」とか呼んじゃってるけど、これたぶん正式な名前じゃないみたい。あくまで便宜上の呼称と思ってくださいな。

まずはモバイル版かどうか見分ける

これは前回の最後にふれてたけど、モバイル版ブラウザにはデバイスの向きを示す「window.orientation」というNumberなプロパティがあり、これはデスクトップ版には今のところ実装されてないのでこれを使ってみる。

<script type="text/javascript">
// WebKitかどうか。
window.is_webkit = (function() {
    try {
        return window.navigator.taintEnabled == undefined;
    } catch(e) {
        return false;
    }
})();

// モバイルブラウザかどうか
window.is_mobile = (function() {
    return window.is_webkit && ! isNaN(window.orientation);
})();
</script>

事前にWebKitであるかもあわせて判別しておく。

あとはこの情報を参照すればいいだけ。
<button type="button" onclick="alert(window.is_mobile)">モバイル版のWebKit?</button>

Safariであるかどうか - モバイル編

まずモバイル版の違い。エミュレータベースでしか確認してないけど、Android 2.3(Gingerbread)になってもモバイル版のChromeはまだSVGをサポートしてない模様。しかもありがたいことにWebKitはDOMとかブラウザ由来のオブジェクトのコンストラクタがちゃんとwindowのプロパティとしてアクセスできるので、SVG関連のコンストラクタの有無をチェックすればいいわけだ。

が、モバイルSafariのほうもSVGの実装が段階的に進んでいるため、バージョンによって存在するものとしないものがあるので、なるべく基本的なオブジェクトを使う必要がある。

ここではSVGColorを使うことにする。要はwindow.SVGColor が undefined かどうかを見るというわけ。コードは後述。

Safariであるかどうか - デスクトップ編

で、今度はデスクトップ版の場合だが、これがなかなか苦しかった。さすがに各ブラウザともHTML5街道へまっしぐら状態なのでなかなかブラウザ固有のものが見つからないのだ。windowのプロパティをfor..inで列挙してみると、たまに片方にしか存在しないプロパティ名が見つかるのだが、単にDontEnum属性がついてるだけでオブジェクトとしては存在してたりするし。

そんなわけで単純な存在の有無のチェックじゃ実現できないんだけど、windowプロパティの列挙時に値までチェックしてみたところちょっと面白いことに気づいた。たとえばデベロッパコンソール上でwindowのコンストラクタを出力してみると、Safariの場合は

> window.constructor
  function Object() {
      [native code]
  }

Chromeの場合は
> window.constructor
  function DOMWindow() { [native code] }

と、コンストラクタ関数がまったく異なっているようなのだ。

Safariの場合、コンストラクタがObject()ってことは、window固有のプロパティはコンストラクタから継承してるわけではないと推測できるし、ChromeのほうはモロにDOMWindowという名前なのでこれにwindow固有プロパティが実装されてそうだと推測できる。ってことで、Chromeで試してみた。

> window.constructor.prototype.alert
  function alert() { [navite code] }

お!いい感じですよ?じゃ、Safariでは?
> window.constructor.prototype.alert
  undefined

よし!予想通り!!要するに、window.constructor.prototype.alert が undefined ならSafari、そうでないならChromeと判断して間違いない、と。

これ試したのはChrome 8だったので、以前のバージョンがどういう実装になっていたのか、USBブート可能なポータブル版のChrome 4やら5やらを探し出してきて試してみたところ、まったく同じ状況だったのでこれで問題なさそう。

で、こうなる、と。

ようやく判別方法が固まったので、以下のような感じで。あ、先ほどのis_webkitとis_mobileの判断がでてることが前提でね。

<script type="text/javascript">
// Safariかどうか
window.is_safari = (function() {
    // WebKitじゃなきゃ当然Safariじゃないわな
    if(! window.is_webkit) return false;
    if(window.is_mobile) {
        // モバイル版の場合、SVGをサポートしてたらSafari
        return !! window.SVGColor;
    } else {
        // デスクトップ版の場合、windowコンストラクタのプロトタイプで判別
        try {
            // undefinedならSafari
            return window.constructor.prototype.alert == undefined;
        } catch(e) {
            // そもそもwindow.constructor とか そのプロトタイプにアクセスできない
            // ブラウザもあるので try - catch しとく
            return false;
        }
    }
})();

// Chromeかどうか
window.is_chrome = (function() {
    // WebKitじゃなきゃ(ry
    if(! window.is_webkit) return false;
    // 後は基本的にSafariと逆
    if(window.is_mobile) {
        return ! window.SVGColor;
    } else {
        try {
            return window.constructor.prototype.alert != undefined;
        } catch(e) {
            return false;
        }
    }
})();

</script>

<button type="button" onclick="alert(window.is_safari)">Safariっすか?</button>
<button type="button" onclick="alert(window.is_chrome)">Chromeっすか?</button>

そのうちライブラリにするよ。多分。

ということで、ようやく当初の目的どおりの判別ができるようになりましたとさ。

ここまでのスクリプトはすべて、window.onloadを待たずに判断できるので、単独ライブラリで実装して一番最初に読み込むスクリプトに仕立て上げちゃえばいいんだけど、ちょっとまとめるのがめんどくさいので、まぁそのうち。

追記

ちなみに、window.constructor.prototype のプロパティでの判断は、モバイル版では通用しません。モバイル版ChromeはSafariと同じで window.constructor.prototype.alert は undefined になります。と思いました。確か。

<button type="button" onclick="try { alert(window.constructor.prototype.alert); } catch(e) { alert(e); }">window.constuctor.prototype.alert は?</button>

コメント

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※なお、送られたコメントはブログの管理者が確認するまで公開されません。

名前:
メールアドレス:
URL:
コメント:

トラックバック

このエントリのトラックバックURL: http://dara-j.asablo.jp/blog/2010/12/18/5588728/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。