ユーザーエージェント文字列を用いたブラウザーの判定

この翻訳は不完全です。英語から この記事を翻訳 してください。

ブラウザーによって異なるウェブページまたはサービスを提供するのは、ふつうは良いことではありません。ウェブは使用しているブラウザーや機器に関係なく、誰からでもアクセスできるものです。ウェブサイトを開発する方法として、特定のブラウザーを対象にするのではなく、機能が利用できるかどうかに基づいたプログレッシブエンハンスメントにする方法があります。

しかし、ブラウザーや標準は完全ではなく、ブラウザーの判定を必要とする場合も稀にあります。ユーザーエージェント文字列を使用してブラウザーを判定することは簡単に見えますが、うまく行くようにするのは、実はとても難しい問題です。この文書では、これをできるだけ正しく行う方法を案内します。

繰り返します。ユーザーエージェントを調べるのが良いことはめったにありません。問題を解決するには、もっと良い、もっと広く互換性のある方法が見つかるはずです。

ブラウザーの判定を用いる前に考えること

ユーザーエージェント文字列を使用して、使用されているブラウザーを判定することを検討している場合は、できればまずは回避するようにしてください。なぜそれをやりたいのかを見分けることから始めましょう。

ブラウザーのあるバージョンの特有のバグを回避しようとしているのですか?
専門のフォーラムで見たり尋ねたりしてみましょう。この問題に最初に遭遇したのがあなたであることはほとんどありません。また専門家、あるいは単に別の視点を持つ人々が、バグを回避するためのアイディアをくれるかもしれません。珍しい問題である場合は、このバグがバグ追跡システム (Mozilla; WebKit; Blink; Opera) を介してブラウザーのベンダーに報告されているかどうかを確認することが重要です。ブラウザーメーカーはバグレポートに注意を払い、そのバグの他の回避策を分析するかもしれません。
特定の機能が存在するかどうか確認しようとしているのですか?
一部のブラウザーがまだ対応していない特定のウェブ機能をサイトで使用する必要がある場合、未対応のブラウザーのユーザーには、より機能が少ないものの動作することが分かっている古いウェブサイトを送信したい場合があります。これはユーザーエージェントの検出を使用する最悪の理由です。なぜなら、この差は最終的に他のすべてのブラウザーが追いつくからです。さらに、それほど一般的でないブラウザーを1つ1つ全てテストするのは現実的ではありません。ユーザーエージェントによる判定は決して行うべきではありません。その代わりに常に機能の検出を行う代替手段があります。
使用されているブラウザーによって異なる HTML を提供する予定ですか?
通常では悪い慣例ですが、これが必要な場合もあります。このような場合は、まず自分の状況を分析してそれが本当に必要であることを確認してください。形式的な <div> または <span> 要素を追加することで、それを防ぐことができませんか?ユーザーエージェントの検出を正しく行うことは、 HTML の純度をいくらか損なっても構わないくらい難しいものです。また、デザインを考え直してください。プログレッシブエンハンスメントや流体レイアウトを使用することで、回避することはできませんか?

ユーザーエージェントの判定の回避

ユーザーエージェントの検出を使用しないようにするのであれば、いくつかの選択肢があります。

機能の検出
機能の検出とは、ページを表示しているブラウザーを特定するのではなく、必要な機能が利用可能であるかどうかを確認することです。利用できない場合は代替手段を使用します。ブラウザーによって異なる動作をする場合は稀ですが、ユーザーエージェント文字列をチェックする代わりに、ブラウザーがその API をどう実装しているか検査する処理を実装し、そこから使い方を判断しましょう。機能の検出の今のところ良い例は次の通りです。最近、 Chrome は正規表現に実験的な後方参照の対応を追加しましたが、他のブラウザーは今のところ対応していません。ですから、間違って以下のようにするべきと思うかもしれません。
// 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"]

しかし、上記のコードは絶対的に劣悪で、分かりにくいものです。 Chrome がこの後方参照機能を削除したらどうなりますか?他のブラウザーが正規表現で後方参照を実装したらどうなりますか?別なブラウザーがユーザーエージェント文字列に Chrome を使用したらどうなりますか?このリストはひどい間違いであることを繰り返します。したがって、次のような機能の検出を代わりに使用してください。

var isLookBehindSupported = false;
try { isLookBehindSupported = !!new RegExp("(?<=)"); }catch(e){
    // In unsupported browsers, lookbehind expressions err
}
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 がサポートされているセクションであっても、上記の例では /(?<=look_behind_stuff)/ の代わりに new RegExp("(?<=look_behind_stuff)"); を使用します。

プログレッシブエンハンスメント
この設計手法は、ウェブサイトを「階層」で開発し、ボトムアップのアプローチを使用して、より簡単な階層から始め、階層が上がるに従ってより多くの機能を使用して、サイトの機能を向上させる方法です。
グレイスフルデグラデーション
これはトップダウンのアプローチで、必要なすべての機能を使用して最適なサイトを構築し、それを古いブラウザーでも使用できるように調整する方法です。これは、プログレッシブエンハンスメントよりも難しく、有効性が低くなりますが、場合によっては有用であることがあります。
モバイル端末の検出
おそらく、ユーザーエージェントの判定で最も一般的かつ誤った使用法は、その端末がモバイル端末であるかどうかを検出することです。しかし、それが後で本当にどうなるかは見過ごされがちです。開発者はユーザーエージェントの判定を使用して、ユーザーの端末がタッチ操作であることや小さい画面であることを判別し、それに応じてウェブサイトを最適化することがあります。ユーザーエージェントの判定でこれらを検出できることもありますが、すべての端末が同じではありません。モバイル端末が大きなサイズの画面を持っている場合もあるし、デスクトップが小さなタッチ画面を持っている場合もあるし、まったく異なる球技であるスマートテレビで見ている場合もあるし、タブレットの向きを回転させて画面の幅や高さを動的に変化させている人もいます。よって、ユーザーエージェントの検出は決定的な方法ではありません。幸い、はるかに良い代替方法があります。ユーザーの端末にタッチ画面があるかどうかを検出するには、 Navigator.maxTouchPoints を使用しましょう。そして、 if (!("maxTouchPoints" in navigator)) { /*Code here*/} の場合のみ、ユーザーエージェントの画面をチェックする既定に戻します。この情報を使用して端末にタッチ画面があるかどうかを確認することができますが、タッチ端末であるだけのためにウェブサイトのレイアウト全体を変更しないようにしてください。これは自分の作業とメンテナンスの量を増やすだけです。それよりも、タッチに適したより大きい、よりクリックしやすいボタンを追加しましょう (CSS を使用してフォントサイズを上げるだけです)。モバイル端末で #exampleButton のパディングを 1em に拡張するコードの例を示します。
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; // depedicated, 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";
画面のサイズについては、 window.innerWidth と window.addEventListener("resize", function(){ /*refresh screen size dependent things*/ }) を使用するだけです。画面サイズの対策としてやりたいことは、小さな画面の時に情報を削らないことです。デスクトップバージョンを使用するようになるので、人々を悩ますだけです。むしろ、小さい画面で長いページでは情報の列数を少なくし、画面サイズが大きいほど短いページで多くの列を持つようにしてください。この効果は、 CSS フレックスボックスを使用し、時には部分的な代替手段として浮動レイアウトを用いることで簡単に達成できます。
Also try to move less relevant/important information down to the bottom and group the page's content together meaningfully. Although it is off-topic, perhapse the following detailed example might give you insights and ideas that persuade you to forgo user agent sniffing. Let us imagine a page composed of boxes of information; each box is about a different feline breed or canine breed. Each box has an image, an overview, and a historical funfact. The pictures are kept to a maximum reasonable size even on large screens. For the purposes of grouping the content meaningfully, all the cat boxes are separated from all the dog boxes such that the cat and dog boxes are not intermixed together. On a large screen, it saves space to have multiple columns to reduce the space waisted to the left and to the right of the pictures. The boxes can be separated into multiple columns via two equally fair method. From this point on, we shall assume that all the dog boxes are at the top of the source code, that all the cat boxes are at the bottom of the source code, and that all these boxes have the same parent element. There a single instance of a dog box immediately above a cat box, of course. The first method uses horizontal Flexboxs to group the content such that when the page is displayed to the end user, all the dogs boxs are at the top of the page and all the cat boxes are lower on the page. The second method uses a Column layout and resents all the dogs to the left and all the cats to the right. Only in this particular scenario, it is appropriate to provide no fallback for the flexboxes/multicolumns, resulting in a single column of very wide boxes on old browsers. Also consider the following. If more people visit the webpage to see the cats, then it might be a good idea to put all the cats higher in the source code than the dogs so that more people can find what they are looking for faster on smaller screens where the content collapses down to one column.
次に、コードを常に動的にしましょう。ユーザーは携帯端末の向きを回転させて、ページの幅と高さを変更することがあります。または、将来は広げると画面が拡張できるような、折り畳み式電話のような奇妙な端末が現れるかもしれません。折り畳み式電話のような端末をどのように扱うかということに頭を悩ませないでください。開発者ツールのサイドパネルを開いて画面のサイズを変更してみて、ウェブページが滑らかに、流動的に、動的にサイズ変更されるようになるまで満足しないようにしてください。 The simplest way to do this is to separate all the code that moves content around based on screen size to a single function that is called when the page is loaded and at each resize event thereafter. If there is a lot calculated by this layout function before it determines the new layout of the page, then consider debouncing the event listener such that it is not called as often. Also note that there is a huge difference between the media queries (max-width: 25em), not all and (min-width: 25em), and (max-width: 24.99em): (max-width: 25em) excludes (max-width: 25em), whereas not all and (min-width: 25em) includes (max-width: 25em). (max-width: 24.99em) is simply a poor man's version of  not all and (min-width: 25em): do not use (max-width: 24.99em) because the layout might break on very high font sizes on very high definition devices in the future. Always be very deliberate about choosing the right media query and choosing the right >=, <=, >, or < in any corresponding Javascript because it is very easy to get these mixed up, resulting in the website looking wonking right at the screen size where the layout changes.  Thus, thouroughly test the website at the exact widths/heights where layout changes occur to ensure that the layout changes occur properly.

最適なユーザーエージェントの判定

After reviewing all of the above better alternatives to user agent sniffing, there are still some potential cases where user agent sniffing is appropriate and justified.

One such case is using user agent sniffing as a fallback when detecting if the device has a touch screen. See the Mobile Device Detection section for more information.

Another such case is for fixing bugs in browsers that do not automatically update. Internet Explorer (on Windows) and Webkit (on iOS) are two perfect examples. Prior to version 9, Internet Explorer had unbelievable issues with rendering bugs, css bugs, API bugs, and so forth. However, Internet Explorer was such a special little wasp exception prior to version 9 that it was very easy to detect the browser based upon the browser-specific features avalable. Webkit is a bit worse because Apple forces all of the browsers on iOS to use Webkit internally, thus the user has no way to get a better more updated browser on older devices. Most bugs can be detected, but some bugs take more effort to detect than others. In such cases, it might be beneficial to use user agent sniffing to save on performance. For example, Webkit 6 has a bug whereby when the device orientation changes, the browser might not fire MediaQueryList listeners when it should. To overcome this bug, observe the code below.

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 [2]

[1] Opera 15+ (Blink-based engine)

[2] Opera 12- (Presto-based engine)

Internet Explorer ; MSIE xyz; Internet Explorer は名前を BrowserName/VersionNumber の書式では入れません

もちろん、他のブラウザーがこれらの一部をハイジャックしないという絶対的な保証はありません (過去に 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 Note: Presto is no longer used in Opera browser builds >= version 15 (see 'Blink')
Trident Trident/xyz Internet Explorer put this token in the comment part of the User Agent String
Blink Chrome/xyz

レンダリングエンジンのバージョン

ほとんどのレンダリングエンジンは、 Gecko を除いて RenderingEngine/VersionNumber のトークンにバージョン番号を入れています。 Gecko はユーザーエージェント文字列のコメント部分の中で、 rv: 文字列の後にバージョン番号を入れます。モバイル版の Gecko 14 とデスクトップ版の Gecko 17 から、この値を Gecko/version のトークン (以前のバージョンではビルド日付、その後は GeckoTrail と呼ばれる固定日付) に置きます。

OS

オペレーティングシステムは、多くのユーザーエージェント文字列で提供されますが (ただし Firefox OS のようなウェブ用のプラットフォームでは提供されません)、書式は大幅に異なります。これはユーザーエージェント文字列のコメント部分にある2つのセミコロン間の固定文字列です。これらの文字列はブラウザーに依存します。これは OS を示しますが、しばしばバージョンや依存するハードウェア (32/64ビットや、 Mac の場合の Intel / PPC) も示します。

すべての場合と同様に、これらの文字列は将来変更される可能性があり、既にリリースされているブラウザーの判定と組み合わせて使用する必要があります。新しいバージョンのブラウザーが出現したときは、スクリプトを適合させるための技術調査が必要です。

モバイルか、タブレットか、デスクトップか

ユーザーエージェントの判別を行う最も一般的な理由は、ブラウザーが実行されている端末の種類を判別することです。目的は、それぞれの種類の端末に別々な HTML を提供することです。

  • ブラウザーやレンダリングエンジンは、1種類の端末でしか動作しないと想定しないでください。ブラウザーやレンダリングエンジンごとに、異なる既定値を設定しないでください。
  • ブラウザーがモバイル、タブレット、デスクトップのいずれであるかを定義するために、 OS トークンを使用しないでください。 OS は複数の種類の端末 (Android はタブレットや携帯電話など) で動作します。

次の表は主要なブラウザーのベンダーが、ブラウザーがモバイル端末上で動作していることを示す方法をまとめたものです。

各ブラウザーのユーザーエージェント文字列
ブラウザー ルール
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)

要するに、モバイル端末を検出するには、ユーザーエージェント文字列のどこかに文字列 “Mobi” があるかどうかを探すことをお勧めします。

端末が大きくて “Mobi” と表示されていない場合は、デスクトップサイトを提供してください (ベストプラクティスとして、デスクトップ機にタッチ画面が採用されつつあるので、どちらにしてもタッチ入力に対応するようにしてください)。

ドキュメントのタグと貢献者

このページの貢献者: mfuji09, mdnwebdocs-bot, silverskyvicto, karaage-kun
最終更新者: mfuji09,