--- title: ユーザーエージェント文字列を用いたブラウザーの判定 slug: Web/HTTP/Browser_detection_using_the_user_agent tags: - Compatibility - HTTP - Web Development translation_of: Web/HTTP/Browser_detection_using_the_user_agent ---
ブラウザーによって異なるウェブページまたはサービスを提供するのは、ふつうは良いことではありません。ウェブは使用しているブラウザーや機器に関係なく、誰からでもアクセスできるものです。ウェブサイトを開発する方法として、特定のブラウザーを対象にするのではなく、機能が利用できるかどうかに基づいたプログレッシブエンハンスメントにする方法があります。
しかし、ブラウザーや標準は完全ではなく、ブラウザーの判定を必要とする場合も稀にあります。ユーザーエージェント文字列を使用してブラウザーを判定することは簡単に見えますが、うまく行くようにするのは、実はとても難しい問題です。この文書では、これをできるだけ正しく行う方法を案内します。
繰り返します。ユーザーエージェントを調べるのが良いことはめったにありません。問題を解決するには、もっと良い、もっと広く互換性のある方法が見つかるはずです。
ユーザーエージェント文字列を使用して、使用されているブラウザーを判定することを検討している場合は、できればまずは回避するようにしてください。なぜそれをやりたいのかを見分けることから始めましょう。
ユーザーエージェントの検出を使用しないようにするのであれば、いくつかの選択肢があります。
// this code snippet splits a string in a special notation if (navigator.userAgent.indexOf("Chrome") !== -1){ // YES! The user is suspected to support look-behind regexps // DO NOT USE /(?<=[A-Z])/. It will cause a syntax error in // browsers that do not support look-behind expressions // because all browsers parse the entire script, including // sections of the code that are never executed. var camelCaseExpression = new RegExp("(?<=[A-Z])"); var splitUpString = function(str) { return (""+str).split(camelCaseExpression); }; } else { /*This fallback code is much less performant, but works*/ var splitUpString = function(str){ return str.replace(/[A-Z]/g,"z$1").split(/z(?=[A-Z])/g); }; } console.log(splitUpString("fooBare")); // ["fooB", "are"] console.log(splitUpString("jQWhy")); // ["jQ", "W", "hy"]
上記のコードでは、いくつかの間違った仮定をするでしょう。
このような問題は、機能自体の対応をテストすることで回避することができます。
var isLookBehindSupported = false; try { new RegExp("(?<=)"); isLookBehindSupported = true; } catch (err) { // If the agent doesn't support lookbehinds, the attempted // creation of a RegExp object using that syntax throws and // isLookBehindSupported remains false. } var splitUpString = isLookBehindSupported ? function(str) { return (""+str).split(new RegExp("(?<=[A-Z])")); } : function(str) { return str.replace(/[A-Z]/g,"z$1").split(/z(?=[A-Z])/g); };
上記のコードが示すように、ブラウザーの互換性をユーザーエージェントの判定なしに行う方法は常にあります。このためにユーザーの文字列をチェックする理由は決してありません。
最後に、上記のコードスニペットは、常に考慮しなければならないクロスブラウザーのコーディングで重大な問題を引き起こします。サポート対象外のブラウザーでテストしている API を意図せず使用しないでください。これは明らかでシンプルに聞こえるかもしれませんが、そうでない時もあります。たとえば、上記のコードスニペットでは、短い regexp 表記 (たとえば /reg/igm) で lookbehind を使用すると、サポートされていないブラウザーで parser エラーが発生します。したがって、あなたのコードの lookbehind がサポートされているセクションであっても、上記の例では new RegExp("(?<=look_behind_stuff)"); を /(?<=look_behind_stuff)/ の代わりに使用します。
var hasTouchScreen = false; if ("maxTouchPoints" in navigator) { hasTouchScreen = navigator.maxTouchPoints > 0; } else if ("msMaxTouchPoints" in navigator) { hasTouchScreen = navigator.msMaxTouchPoints > 0; } else { var mQ = window.matchMedia && matchMedia("(pointer:coarse)"); if (mQ && mQ.media === "(pointer:coarse)") { hasTouchScreen = !!mQ.matches; } else if ('orientation' in window) { hasTouchScreen = true; // deprecated, but good fallback } else { // Only as a last resort, fall back to user agent sniffing var UA = navigator.userAgent; hasTouchScreen = ( /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) || /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA) ); } } if (hasTouchScreen) document.getElementById("exampleButton").style.padding="1em";
(max-width: 25em)
, not all and (min-width: 25em)
, (max-width: 24.99em)
の間には大きな違いがあることにも注意してください。 (max-width: 25em)
は (max-width: 25em)
を除外し、 not all and (min-width: 25em)
は (max-width: 25em)
を含みます。 (max-width: 24.99em)
は、 not all and (min-width: 25em)
の哀れな人のバージョンです。 (max-width: 24.99em)
を使用しないでください。適切なメディアクエリを選択し、対応する JavaScript の中で適切な >=, <=, >, < を選択すると、これらが混ざってしまいやすく、結果として、レイアウトが変更された画面サイズでウェブサイトを見てしまうことになるからです。そのため、レイアウト変更が適切に行われているかどうかを確認するために、レイアウト変更が行われている正確な幅/高さでウェブサイトを徹底的にテストしてください。ユーザーエージェントの判定のよりよい代替案をすべて検討したうえで、ユーザーエージェントの判定が適切で正当化される可能性のある場合がまだいくつかあります。
そのような場合のひとつに、端末にタッチパネルがあるかどうかを検出する際に、ユーザーエージェントの判定をフォールバックとして使用するというものがあります。詳細は モバイル端末の検出の節を参照してください。
もう一つの例として、自動更新されないブラウザーのバグを修正する場合があります。 Internet Explorer (Windows) と Webkit (iOS) がその好例です。バージョン 9 以前のInternet Explorer は、レンダリングのバグ、 CSS のバグ、 API のバグなど、信じられないほどの問題を抱えていました。しかし、バージョン 9 以前の Internet Explorer は特別な小さな wasp 例外だったため、利用可能なブラウザー固有の機能に基づいてブラウザーを検出するのは非常に簡単でした。 Webkit は、 Apple が iOS 上のすべてのブラウザーに内部的に Webkit を使用するように強制しているため、ユーザーは古い端末上でより良い、より更新されたブラウザーを取得する方法がありません。ほとんどのバグは検出できますが、バグによっては他よりも検出に手間がかかる場合があります。そのような場合は、ユーザーエージェント検出を使用してパフォーマンスを節約するのが有効です。たとえば、 Webkit 6 にはデバイスの向きが変わると、ブラウザーが {{domxref("MediaQueryList")}} リスナーを実行しない場合があるというバグがあります。このバグを回避するには、以下のコードを参照してください。
var UA=navigator.userAgent, isWebkit=/\b(iPad|iPhone|iPod)\b/.test(UA) && /WebKit/.test(UA) && !/Edge/.test(UA) && !window.MSStream; var mediaQueryUpdated = true, mqL = []; function whenMediaChanges(){mediaQueryUpdated = true} var listenToMediaQuery = isWebkit ? function(mQ, f) { if(/height|width/.test(mQ.media)) mqL.push([mQ, f]); mQ.addListener(f), mQ.addListener(whenMediaChanges); } : function(){}; var destroyMediaQuery = isWebkit ? function(mQ) { for (var i=0,len=mqL.length|0; i<len; i=i+1|0) if (mqL[i][0] === mQ) mqL.splice(i, 1); mQ.removeListener(whenMediaChanges); } : listenToMediaQuery; var orientationChanged = false; addEventListener("orientationchange", function(){ orientationChanged = true; }, PASSIVE_LISTENER_OPTION); addEventListener("resize", setTimeout.bind(0,function(){ if (orientationChanged && !mediaQueryUpdated) for (var i=0,len=mqL.length|0; i<len; i=i+1|0) mqL[i][1]( mqL[i][0] ); mediaQueryUpdated = orientationChanged = false; },0));
ユーザーエージェント文字列のそれぞれの部分には統一性がないので、これは難しい部分です。
開発者が「ブラウザーを判定したい」という場合、実際は「レンダリングエンジンを判定したい」場合であることがしばしばあります。実際に SeaMonkey と Firefox を、または Chromium と Chrome を区別したいのでしょうか。それとも、実際にはブラウザーがレンダリングエンジンに Gecko を使用しているか、 WebKit を使用しているかを確認したいのしょうか。これが必要なのであれば、ページのもっと下を見てください。
ほとんどのブラウザーは、 Internet Explorer の例外を除いて、名前とバージョンを BrowserName/VersionNumber の形式で設定します。しかし、ユーザーエージェント文字列はこのような形式の名前だけから成っている訳ではないので、ブラウザーの名前が分かるわけではなく、探している名前があるかどうかを確認することしかできません。しかし、ブラウザーによってはうそをつくこともあります。例えば Chrome は、 Chrome と Safari の両方の文字列を含みます。ですから Safari を判定するには、 Safari の文字列があって Chrome の文字列がないことを確認する必要がありますし、 Chromium は自分自身を Chrome と報告することがよくあり、 Seamonkey は自分自身を Firefox として報告することが時々あります。
また、 BrowserName に単純な正規表現を使用しないように注意してください。ユーザーエージェント文字列には、 Keyword/Value 構文以外の文字列も含まれています。例えば、 Safari や Chrome では、 'like Gecko' のような文字列が含まれています。
必ず含む | 決して含まない | ||
---|---|---|---|
Firefox | Firefox/xyz | Seamonkey/xyz | |
Seamonkey | Seamonkey/xyz | ||
Chrome | Chrome/xyz | Chromium/xyz | |
Chromium | Chromium/xyz | ||
Safari | Safari/xyz | Chrome/xyz または Chromium/xyz | Safari はバージョン番号を2つ提供しており、一方は技術的な Safari/xyz のトークン、もう一方はユーザーに分かりやすい Version/xyz のトークンです |
Opera |
OPR/xyz [1] Opera/xyz |
[1] Opera 15+ (Blink ベースのエンジン) [2] Opera 12- (Presto ベースのエンジン) |
|
Internet Explorer |
; MSIE xyz; [1] Trident/7.0; .*rv:xyz [2] |
[1] Internet Explorer 10- [2] Internet Explorer 11 |
もちろん、他のブラウザーがこれらの一部をハイジャックしないという絶対的な保証はありません (過去に Chrome が Safari の文字列をハイジャックしたように)。そのため、ユーザーエージェント文字列を使用したブラウザーの判定は信頼性が低いので、バージョン番号をチェックするのみにしてください (過去のバージョンをハイジャックすることはあまりありません)。
ブラウザーのバージョンは、例外はあるものの、多くがユーザーエージェント文字列の BrowserName/VersionNumber トークンの値の部分に入れられます。もちろんこれは Internet Explorer の場合は当てはまらず (MSIE トークンの直後にバージョン番号を入れる)、 Opera のバージョン10以降では、 Version/VersionNumber トークンが追加されています。
ここで再度、探しているブラウザーの正しいトークンを取得していることを確認してください。他には妥当な番号が含まれているという保証はありません。
前述のように、多くの場合はレンダリングエンジンを探した方が良い方法になります。これは、あまり知られていないブラウザーを除外しないためにも役立つでしょう。共通のレンダリングエンジンを持つブラウザーはページを同じ方法で表示します。一方で動作するものはもう一方でも動作するということを想定することができます。
主なレンダリングエンジンには、 Trident, Gecko, Presto, Blink, WebKit の5つがあります。レンダリングエンジンの名前を探すのが一般的であるため、たくさんのレンダリングエンジンが他のレンダリングエンジンの名前も追加して検出されるようにしています。したがって、レンダリングエンジンを判定する際には誤判定をしないように注意を払うことが重要です。
必ず含む | ||
---|---|---|
Gecko | Gecko/xyz | |
WebKit | AppleWebKit/xyz | 注意: WebKit ブラウザーは 'like Gecko' の文字列を追加するので、判定時に注意しないと Gecko と誤認することがあります。 |
Presto | Opera/xyz | 注: Presto は Opera ブラウザーのバージョン 15 以降では使用されない ('Blink' を参照) |
Trident | Trident/xyz | Internet Explorer はこのトークンをユーザーエージェント文字列のコメント部分に入れる |
EdgeHTML | Edge/xyz | Chromium ではない Edge は、 Edge/ のトークンの後にアプリケーションのバージョンではなく、エンジンのバージョンを入れる。 注: EdgeHTML は Edge ブラウザーのバージョン 79 以降では使用されない ('Blink' を参照)。 |
Blink | Chrome/xyz |
ほとんどのレンダリングエンジンは、 Gecko を除いて RenderingEngine/VersionNumber のトークンにバージョン番号を入れています。 Gecko はユーザーエージェント文字列のコメント部分の中で、 rv:
文字列の後にバージョン番号を入れます。モバイル版の Gecko 14 とデスクトップ版の Gecko 17 から、この値を Gecko/version
のトークン (以前のバージョンではビルド日付、その後は GeckoTrail と呼ばれる固定日付) に置きます。
オペレーティングシステムは、多くのユーザーエージェント文字列で提供されますが (ただし Firefox OS のようなウェブ用のプラットフォームでは提供されません)、書式は大幅に異なります。これはユーザーエージェント文字列のコメント部分にある2つのセミコロン間の固定文字列です。これらの文字列はブラウザーに依存します。これは OS を示しますが、しばしばバージョンや依存するハードウェア (32/64ビットや、 Mac の場合の Intel / PPC) も示します。
すべての場合と同様に、これらの文字列は将来変更される可能性があり、既にリリースされているブラウザーの判定と組み合わせて使用する必要があります。新しいバージョンのブラウザーが出現したときは、スクリプトを適合させるための技術調査が必要です。
ユーザーエージェントの判別を行う最も一般的な理由は、ブラウザーが実行されている端末の種類を判別することです。目的は、それぞれの種類の端末に別々な HTML を提供することです。
次の表は主要なブラウザーのベンダーが、ブラウザーがモバイル端末上で動作していることを示す方法をまとめたものです。
ブラウザー | ルール | 例 |
---|---|---|
Mozilla (Gecko, Firefox) | Mobile または Tablet のトークンがコメントの中にある。 | Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0 |
WebKit ベースのもの (Android, Safari) | Mobile Safari のトークンがコメントの外にある。 | Mozilla/5.0 (Linux; U; Android 4.0.3; de-ch; HTC Sensation Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30 |
Blink ベースのもの (Chromium, Google Chrome, Opera 15 以降) | Mobile Safari のトークンがコメントの外にある。 | Mozilla/5.0 (Linux; Android 4.4.2); Nexus 5 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Mobile Safari/537.36 OPR/20.0.1396.72047 |
Presto ベースのもの (Opera 12 まで) |
Opera Mobi/xyz のトークンがコメントの中にある。 (Opera 12 まで) |
Opera/9.80 (Android 2.3.3; Linux; Opera Mobi/ADR-1111101157; U; es-ES) Presto/2.9.201 Version/11.50 |
Internet Explorer | IEMobile/xyz のトークンがコメントの中にある。 | Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0) |
Edge on Windows 10 Mobile | Mobile/xyz & Edge/ tokens outside the comment. | Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1; Xbox; Xbox One) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36 Edge/16.16299 |
要するに、モバイル端末を検出するには、ユーザーエージェント文字列のどこかに文字列 “Mobi” があるかどうかを探すことをお勧めします。
端末が大きくて “Mobi” と表示されていない場合は、デスクトップサイトを提供してください (ベストプラクティスとして、デスクトップ機にタッチ画面が採用されつつあるので、どちらにしてもタッチ入力に対応するようにしてください)。