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をEvernoteにメールするブックマークレット2010年12月18日 06時26分37秒

需要があるかはわかりませんが...

半年くらい前からちょろちょろとEvernote使ってるんだけども、dara-j的にはWebの内容をクリップするのが主目的なんです。たとえばTumblrのダッシュボードに流れてきたQuoteの元記事見に行って気に入った部分があったらクリップするとか。FirefoxのWebクリッパーなんかはかなり出来がいいので、なかなか快適に使えるわけです。

が、、内容をじっくり呼んでる時間がないときなんかは後回しにするためにちゃっちゃとタイトルとURLだけクリップしときたい、ってケースもままあるわけで。そんな時はメールで飛ばしちゃうのが手っ取り早いので、ページタイトルとURLをクリップボードに入れるJSActionスクリプトとか作ってみてたんだけど、これだと既定のノートブックに入るだけでしばらくすると後で探しづらかったり。

そこはEvernote、ちゃんとサブジェクトの書き方でターゲットのノートブックやタグを指定はできるんだけど、いちいち書くのも結構面倒になってくるわけで。

で、こういう用途ならブックマークレットを経由してmailtoで開いてやれば投稿用のtoアドレスやらタグをつけたサブジェクトを指定できて結構クイックに扱えるかな、と思って作ってみました。前置きが長かったな。

まずはソース

こんな感じ。

(function(opt){
    var a = '', t = document.title, u = location.href, e = encodeURIComponent, s = ' ';
    location.href =
        'mailto:' + a +
        '?subject=' + e(t + s + opt) +
        '&body=' + e([t, u].join('\n'));
})(prompt('input #tag and/or @notebook', '') || '');


で、実際のブックマークレットはこんなの。

Evernoteにメール
クリックしたらソースを表示

「クリックしたらソースを表示」のチェックボックスをOnにすると、クリック時にpromptでソースをポップアップするので、iPhoneなんかのドラッグドロップでブックマークレット登録できないブラウザ使ってる場合にちょっぴり便利。

ざっと解説

メインの関数式に渡す引数はprompt()の実行結果から受け取るようにしてて、これをページタイトルとくっつけたものをサブジェクトに指定してます。なので、「未読」ってタグをつけたい場合はprompt()に「#未読」って入れてやればOKと。

タグやノートブックを複数指定する場合はそれぞれの間をスペースで区切ります。ノートブック「個人用」にタグ「未読」と「url」をつけるなら、「#未読 #url @個人用」みたいな感じで。

改造とか

関数内の変数'a'はtoアドレスに使われるので、ここに自分のEvernoteメールアドレスをあらかじめ入れておけば送信先を選択やら入力やらしなくてもよくなります。さらに関数式への引数もリテラルで書いておけば、ブックマークレットを起動すればメーラーがすぐに起動するので後は送信するだけで目的を果たせるようになります。

ただし引数をリテラル記述する場合、マルチバイト文字はそのまま使えないので、あらかじめescapeしたものを使ってください。

改造が面倒だろうからジェネレータ

って、ブックマークレットのソースを改造するのはちとめんどくさいので、ジェネレータを作って起きました。自分が良く使うパターンで生成してブックマークしておくと便利じゃないかと。

  • toアドレス:
  • ターゲットのノートブック:
  • タグ(スペース区切り):
  • リンクタイトル:
(ここにブックマークレットが作成されます)
クリックしたらソースを表示

使い方は、

  • toアドレスに自分のEvernoteメールアドレス(@m.evernote.com)を入力
  • ターゲットのノートブックやタグを入力。@や#は不要、タグは半角スペースで区切って複数指定可能
  • リンクタイトルに入れた文字列が、生成されるブックマークレットの見出しになる
てな感じ。ジェネレータがうまく動かなかったらコメントください。

追記

あー、ジェネレータでノートブック名やタグにマルチバイト文字使うと、IEじゃ化けちゃうなぁ...