SafariとChromeの判別があやしくなったので改善してみる ― 2011年08月25日 20時10分51秒
夏も終わり間近というこのタイミングで今年最初のエントリになっちゃうんだけども、デスクトップ版Safariが5.1になったことで以前書いた「SafariとChromeを判別してみる」の方法が通用しなくなっちゃったのであわてて更新してみる。ほんとは中華パッド3号とか4号とかのこと書きたかったんだけども。
前回のおさらい
前回のアプローチは、SafariとChromeでwindow.constructorが違っている(SafariはObject、ChromeはDOMWindow)ことを利用してこんなアプローチをしてた。
// Safariはtrue、Chromeはfalseを返す関数 <script type="text/javascript"> window.check1 = function() { try { return window.constructor.prototype.alert == undefined; } catch(e) { // IEとかエラーでるし。 alert(e.message || e.description); } } </script>
<button type="button" onclick="alert(check1())">check1</button>
ところが、Safari5.1からはwindow.constructorがDOMWindowConstructorとやらに変わっちゃったので、Chromeと判別されちゃう。さて、困りました。
Safari Developer Libraryを探したら
んで、急いでAppleのSafari Developer LibraryのDocument Additions Referenceでプロパティを確認したところ、「webkitCurrentFullScreenElement」「webkitFullScreenKeyboardInputAllowed」「webkitIsFullScreen」なんてのが見つかった。
念のためChromeのJavaScriptコンソールで「document.webkit」まで入力してタブキーで候補をさらってみたところ「Current~」とか「FullScreen~」、「Is~」とかは表示されなかったのでなんとなくで「webkitIsFullScree」を採用することに決定。
ただし、プロパティの名前からしてbooleanを返すだろうから、単純に
!document.webkitIsFullScreenなんて試しても判別がつかないので、undefinedかどうかをテストすることにして、こんな感じに。
<script type="text/javascript"> window.check2 = function() { try { return window.constructor.prototype.alert == undefined || document.webkitIsFullScreen !== undefined; } catch(e) { return e.message || e.description; } } </script>
<button type="button" onclick="alert(check2())">check2</button>
で、こうなりました。
この変更を適用すると、こんな感じに。前回同様is_webkitとis_mobileの判定が出来る前提で。
<script type="text/javascript"> // Safariかどうか window.is_safari2 = (function() { // WebKitじゃなきゃ当然Safariじゃないわな if(! window.is_webkit) return false; if(window.is_mobile) { // モバイル版の場合、SVGをサポートしてたらSafari return !! window.SVGColor; } else { // デスクトップ版の場合、windowコンストラクタのプロトタイプで判別 try { // * ココ修正!! * // どっちかtrueならSafari return window.constructor.prototype.alert == undefined || document.webkitIsFullScreen !== undefined; } catch(e) { // そもそもwindow.constructor とか そのプロトタイプにアクセスできない // ブラウザもあるので try - catch しとく return false; } } })(); // Chromeかどうか window.is_chrome2 = (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 && document.webkitIsFullScreen === undefined; } catch(e) { return false; } } })(); </script>
<button type="button" onclick="alert(window.is_safari2)">Safariっすか?(改)</button> <button type="button" onclick="alert(window.is_chrome2)">Chromeっすか?(改)</button>
Chrome 13まではこれで大丈夫だけど、フルスクリーンがどうのこうのなんて機能はOS X Lion向けっぽいのでいずれこれでも判別できなくなっちゃいそうではあるが。
つか、そろそろ中華パッドネタ書いときたいなぁ。もうすでに旬をすぎちゃってるんだけども。
追記
Safari且つChromeとか判定されたり、前回の記事と同時に表示すると上書きされちゃったりなど細々と考慮漏れがあったりしたのでちくちく修正してました。もう平気かな。
追記2(2011.12.28)
ありゃ、気付いたらChromeもdocument.webkitIsFullScreenとかサポートしちゃってるしorz さて、どうするべ…
→ window.chrome が使えそう。なぜこれに気付かないし。
ブラウザで開いているページの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じゃ化けちゃうなぁ...
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>
JSでブラウザを判別してみよう ― 2010年11月25日 05時07分32秒
油断してたらまた4ヶ月も更新してなかったのだが、なんとなくJSでブラウザ判別を行うという、わりとありがちな事に挑戦してみる。
ライブラリとしてまとめようと思ってたんだけど、そろそろ眠いので、今日のところは断片的な記述にしておこう。
何を判別する?
メジャーどころだったらたいていはレンダリングエンジン≒ブラウザが成立しそうなもんなんだけど、WebKitの場合はSafariとChromeの両方がなかなか普及してるので、「レンダリングエンジンが何であるか」と「ブラウザが何であるか」の2種類を判別することにする。で、Safari/Chromeの場合はモバイル版かどうかも見ないとね。
ということで、レンダリングエンジンは
- Trident
- Gecko
- Presto
- WebKit
- IE
- Firefox
- Opera
- Safari
- Chrome
まずは、Presto = Opera。
めちゃくちゃ手抜きなんだけど、window.operaがundefinedじゃなければPrestoっつーことで。しかもPrestoだったら何も考えずにOperaっつーことで。
<button type="button" onclick="alert(!! window.opera)">レンダリングエンジンははPresto?</button>
<button type="button" onclick="alert(!! window.opera)">ブラウザはOpera?</button>
次はGecko = Firefox。
まぁ、GeckoをみんなFirefoxとみなすのは乱暴なのはわかってるんだけども、こちらも手抜き。
昔、MooToolsの1.2.2のブラウザ判定部分のソース見たときは、window.document.getBoxObjectForがundefinedでなければGeckoてな判断してたみたいなんだけど、Firefox 3.6の環境で試したらこれが成立しなくなってたのでwindowのプロパティを総当りで調べたところ、window.mozInnerScreenXあたりが使えそうなので組み合わせで判別してみる。
<button type="button" onclick="alert(window.document.getBoxObjectFor != undefined || window.mozInnerScreenX != undefined);">レンダリングエンジンはGecko?</button>
<button type="button" onclick="alert(window.document.getBoxObjectFor != undefined || window.mozInnerScreenX != undefined);">ブラウザはFirefox?</button>
IEかどうか、までは簡単。
IEかどうかを判別するのは、window.ActiveXObjectをチェックするのが定番なんだけども、念のためMSDNを見てみたら、なんかIE9で廃止になっちゃいそうな記述が。
びっくりしてIE9ベータで確認したらnullは返してこなかったんだけど、ちょっと怖いのでもうひとつの定番「window.document.all」を使うことに。でもこれ、Operaも実装してるんだったよな、確か。 っつーことで、こんな感じに。
<button type="button" onclick="alert(!!((window.ActiveXObject || window.document.all) && !window.opera))">レンダリングエンジンはTrident?</button>
IEのバージョンは細かく見てみたい。
で、IEの場合は他のブラウザよりもバージョンの違いをなるべく厳密に判別したくなるので、
- IE6よりも前
- IE6
- IE7
- IE8
- IE9
Another IE version detector snippetなるほど、styleプロパティでチェックするのね。ただこのサンプルどおりbody.styleをチェックする方法だとページロードが完了しないと判定できないので不便なので、head要素でチェックをしてみることに。あ、あとIE9かどうかの判別はwindow.msPerformanceでチェックしたほうがよさげ。
function getIEVersion(odoc){
if (odoc.body.style.scrollbar3dLightColor!=undefined)
{
if (odoc.body.style.opacity!=undefined) {return 'IE9';}
else if (odoc.body.style.msBlockProgression!=undefined) {return 'IE8';}
else if (odoc.body.style.msInterpolationMode!=undefined) {return 'IE7';}
else if (odoc.body.style.textOverflow!=undefined) {return 'IE6'}
else {return 'IE5.5 or lower';}
}
}
で、こんなコードを実行して、
<script type="text/javascript"> window.detected_ie_name = (function() { if(window.ActiveXObject == undefined && window.document.all == undefined) return "IEじゃない"; if(window.msPerformance != undefined) return "IE9"; var h = document.getElementsByTagName("head")[0]; if(h.style.msBlockProgression != undefined) return "IE8"; if(h.style.msInterpolationMode != undefined) return "IE7"; if(h.style.textOverflow != undefined) return "IE6"; return "IE5.5かそれ以前"; })(); </script>こんな感じのボタンを貼ってみる。
<button type="button" onclick="alert(window.detected_ie_name)">IEのバージョンは?</button>
WebKitか否か。
MooTools 1.2.2では、navigator.taintEnabledのチェックでもって判別してた。これなんぞ?と思ったら、よい解説が見つかった。
navigator.taintEnabled() というのは、「ユーザーに非通知でデータ送信が可能かどうか(データテイント機能の使用有無)を返すメソッド」です。「何それ?」て思われた方、ご安心ください。Netscape Navigator 3.x の時代の、古い古ーい仕様で、データテイント機能は現在使われておりません。多くのブラウザは、ただfalseを返すだけです。だそうで、まんま使わせてもらおう。
ただしSafari(WebKit)では、このメソッドそのものを定義していません。
<button type="button" onclick="try { alert(window.navigator.taintEnabled == undefined) } catch(e) { alert(false); }">レンダリングエンジンはWebKit?</button>って、IETester使ってるせいか、デフォルトIE以外だとwindow.navigator.taintEnabledにアクセスしただけで例外吐くのでtry catchで囲まなきゃ。
Safariか、Chromeか。
これはあんまりいい方法が見つからなかったので、両方でSafari5とChrome7の両方でwindowのプロパティを洗いざらい列挙してめぼしいところをいくつか試してみたところ、window.TouchListの有無がよさそうだった。
と思って今試したら、Safariでもwindow.TouchListがundefined返してきた。あれぇ?会社で試したときと違うなあ。
ということで、ちょっと別の方法探さなきゃ。ちなみにモバイル版かの判別は今のところwindow.orientationがNaNじゃなければモバイルってな判定でよさげ。
SmartPadから書きこんでみるテスト。 ― 2010年07月28日 03時47分02秒
アクセスログ見てたら、解像度が800 x 480 x 16bitな人が見に来てた。
UAには「Android 1.6」て出てのでEKEN系かePad/FlyTouchの人だろうか。お仲間のようで、なんかうれしい。
てなことをスマパから書きこんでみているが、たったこれだけの文章でも結構ストレスだなあ。
フリック入力にはそれなりに馴れてるつもりだったんだけど位置の誤検出による入力ミスはちょっとイライラくる。
テンキーパッドで「た」はともかく「ま」を「あ」で検出しちゃうのと、DELキーの二重検出はしんどいなあ。
て、これだけで30分近くかかってるし orz
最近のコメント