ユーザースクリプト(UserScript)作成時の覚書
ユーザースクリプトとは
ユーザースクリプトとは、ブラウザの対象ウェブページでユーザ作成のスクリプトを実行する為の仕組みです。元々は、Firefoxのアドオンとして作成されたGreasemonkey上で使用できるスクリプトでした。その後、後続の拡張機能も同様の仕様で実行可能なユーザースクリプトとして一般化しました。
拡張機能の種類
ユーザースクリプト用の拡張機能として、主に次の3種類が開発されています。
※AdGuardなどの例外も存在します。
インストール
- Firefox
- Chrome
- Greasemonkey
- 標準機能としてChrome本体と統合
- ただし、Chromeウェブストア以外からのインストール禁止の余波で、使用不可
- エラーメッセージ「この拡張機能は Chrome Web Storeで提供されていません。知らないうちに追加された可能性があります。」
- Chrome によって無効にされた拡張機能 - Chrome ウェブストア ヘルプ
- Tampermonkey - Chrome ウェブストア
- Violentmonkey - Chrome ウェブストア
- Greasemonkey
- Edge
- Safari
- Tampermonkey on the Mac App Store
- Mac App Store経由の場合、有料($1.99)
- (無料アプリでも)AppStoreの登録料が高額なため、致し方ないことです
- 公式ページから直接ダウンロードする場合、無料
- Mac App Store経由の場合、有料($1.99)
- Tampermonkey on the Mac App Store
ユーザースクリプトの公開と検索
- Greasy Fork
- ユーザースクリプトホスティングサービス
- OpenUserJS
- GitHub Gist
- GitHubでのユーザースクリプトの検索
※ブログ等で公開されていることもあります。
※「==UserScript==」「.user.js」等のキーワードで検索できます。
デバッグ
ログ出力する
console.log()関数を使用する- Webコンソール上に出力する
alert()関数を使用する- ページ上にアラームを表示する
GM_log()関数を使用する(廃止済み)- Webコンソール上に出力する
- 使用には、明示的な@grant指定が必要です「
// @grant GM_log」
エラー出力を確認する
ユーザースクリプトがエラーした場合、Webコンソール上に出力されます。ただし、確実にエラー出力されるとは限りません。
デバッガーを使用する
- Greasemonkey
- Firefox標準のデバッガーを使用する
- [開発ツール] > [デバッガー] > [ソースファイル] > [user-script://] > [対象ユーザースクリプト]
- Firefox標準のデバッガーを使用する
- Tampermonkey
- ブラウザ標準のデバッガーを使用する
- [Firefox] > [開発ツール] > [デバッガー] > [Tampermonkey] > [userscripts] > [対象ユーザースクリプト]
- [Chrome] > [開発ツール] > [Sources] > [Tampermonkey] > [userscript.html?name=対象ユーザースクリプト]
- スクリプト開始直後にデバッガーを起動する
- [オプション] > [設定] > [全般]
- [設定のモード] を [上級者] に設定する
- [スクリプトをデバッグする] を有効にする
- [オプション] > [設定] > [全般]
- ブラウザ標準のデバッガーを使用する
- Violentmonkey
- ブラウザ標準のデバッガーを使用する
- [Firefox] > [開発ツール] > [デバッガー] > [Violentmonkey] > [対象ユーザースクリプト]
- [Chrome] > [開発ツール] > [Sources] > [Violentmonkey] > [対象ユーザースクリプト]
- ブラウザ標準のデバッガーを使用する
ブラウザ標準デバッガーの使用方法
- ブレークポイント: 行番号をクリック(選択)
- ステップ実行(Play/pause): F8
- 次のブレークポイントまで実行する
- ステップオーバー(Step over): F10
- 次の行まで実行する
- ステップイン{Step in}: F11
- 関数呼び出し以外、次の行まで実行する。関数呼び出し、呼び出した関数へ入る
- ステップアウト(Step out): Shift+F11
- 現在の関数の終端まで実行する
ドキュメント
メタデータブロック
メタデータブロックを記述することでユーザースクリプトにメタ的な情報を付与できます。
メタデータブロックの記述例// ==UserScript==
// @name スクリプト名
// @description スクリプト説明
// @match *://example.com/*
// @author You
// @version 1.2
// @since 1.0 - 20210416 - 初版
// @since 1.1 - 20210418 - fix 〇〇の修正
// @since 1.2 - 20210420 - △△の機能拡張
// @grant none
// ==/UserScript==主なメタタグ
| メタタグ | 概要 |
|---|---|
| @name | スクリプト名 |
| @description | スクリプトの説明 |
| @include | 対象のページ |
| @match | 対象のページ |
| @exclude | 対象外のページ |
| @run-at | 実行タイミング |
| @grant | 特権の付与 |
参考
実行タイミング(@run-at)
ユーザースクリプトの実行タイミングは、@run-atで指定できます。
| @run-at | 概要 |
|---|---|
| document-start | スクリプトをできるだけ早く実行する(※) |
| document-end | DOMContentLoaded時に実行する(既定) |
| document-idle | DOMContentLoaded起動後に実行する |
※拡張機能のコンテンツスクリプトは、ウェブページの読み込みより先に実行できます。<head>を読み込むより早く実行することができます。ただし、WebExtensionAPIにアクセスする場合、非同期処理の影響で最終的に<head>読み込み中や<head>より後に実行することになります。そのため、「できるだけ早く実行する」と言う曖昧な表現になります。
別のタイミングで実行する
ページ読み込み時以外でユーザースクリプトを実行したいことがあります。拡張機能のユーザースクリプト実行タイミミングをこれ以上変更することはできませんが、JavaScriptの各種イベントのタイミングで処理を実行することはできます。
ユーザースクリプトで使えそうな主なイベント種類を次に示します。
- クリックしたタイミング
clickイベント- ページ上の既存の要素にイベントを設定することもできます
- イベント設定のない画像やアイコンに新たなイベントを追加する
- 要素そのものをユーザースクリプトで挿入することもできます
- 既存の要素間に新たな要素(ボタンなど)を配置する
z-index等で浮遊した要素(ボタンなど)を配置する
- ページ全体のクリックイベントを監視することもできます
window.addEventListener('click', func);
- 要素の状態が変化したタイミング
input/select/changeイベント
- コピーペーストしたタイミング
cut/copy/pasteイベント
- キーボード入力したタイミング
keydownイベント- ページ独自のショートカットキーを設定することができます
- ジェスチャー操作したタイミング
mousedown/mousemove/mouseup/draggestureイベント- ページ独自のジェスチャーを設定することができます
- 文字列選択したタイミング
mouseupイベントwindow.getSelection();
- スクロールしたタイミング
scrollイベントIntersectionObserver- 無限スクロールを設定することができます
- DOM要素の変更したタイミング
MutationObserver- ページ読み込み完了後に動的追加される要素を処理することができます
- ページを閉じるタイミング
pagehideイベント- データを保存して、次の訪問時に使用することができます
- データ保存には、
window.localStorage / GM.setValue()などが使用できます
- データ保存には、
- コンテキストメニューを選択したタイミング
GM.registerMenuCommand()- ブラウザアクションのコンテキストメニュー
API(GM関数)
| API | 概要 |
|---|---|
| GM.info | ユーザースクリプトに関する情報 |
| GM.setValue() / GM.getValue() | データの保存と取得 |
| GM.setClipboard() | クリップボードへの書き込み |
| GM.xmlHttpRequest() | XMLHttpRequestオブジェクトと同様の機能 |
| unsafeWindow | ページのwindow |
※GM関数は、拡張機能の権限を利用して処理されます。
例:GM.setValue()は、データを Cookie や localStorage とは異なる拡張機能の領域に保持します。
例:GM.xmlHttpRequest()は、CSP や CROS の問題を回避できます。
JavaScriptの通常API
console.log():コンソールへ出力するalert() / confirm() / prompt():ダイアログを表示するdocument.querySelector():要素を取得するdocument.elementFromPoint():マウスの位置にある要素を取得する- EventListener / Observer:各種実行タイミングを設定する
- WebWorker:ページから隔離されたプロセスで実行する(重い処理用)
- ShadowDOM:対象ページのスタイルに影響を受けない要素を作成する
参考
よくある失敗事例
@run-at document-startでドキュメント要素へアクセスする
document-startのタイミングは、DOMContentLoadedより前のタイミングです。ドキュメントの解析が完了していません。そのため、ドキュメント要素を取得できず、エラー終了や意図しない動作をすることがあります。document-endまたは、document-idleの指定、もしくは@run-atを未指定とすることで問題を解決できます。
※対象要素をドキュメントの解析完了後に動的追加している場合、MutationObserverでドキュメント変更を監視して、対象要素が追加されたタイミングで処理を実行する必要があります。
@include/@matchの指定ミス
@include/@matchの指定ミスにより、ユーザースクリプトが実行されない。または、意図しないページで実行される問題が発生します。
※@include/@matchが未指定の場合、「すべてのサイトにユーザースクリプトを適用する」設定になります。
簡単なバグフィックス・仕様の揺らぎ
@grant
@grant未指定@grant noneの動作となる
@grant noneと@grant GMの重複指定@grant noneの動作となるGM関数は使用できない
@require のローカルファイル指定
- ローカルファイル(
file:)の使用- Greasemonkey / Violentmonkey
- 不可
- Tampermonkey
- 要設定
- Chrome設定([ファイルの URL へのアクセスを許可する])
- 拡張機能設定([セキュリティ] > [スクリプトによるローカル ファイルへのアクセスを許可する])
- 要設定
- Greasemonkey / Violentmonkey
※ローカルファイルへのアクセスは、セキュリティ的にとても危険な機能です。不用意に許可しないことを推奨します。
ポート番号(@match)
- '@match'のポート番号
- Greasemonkey / Tampermonkey
- ポート番号ありが動作しない
- Violentmonkey
- ポート番号なしが動作しない(明示的なポート番号指定が必要)
- ポート番号あり・なしを併記することで回避できる
- Greasemonkey / Tampermonkey
ページのスクリプト無効
NoScriptなどを使用してページのスクリプトを無効とした場合、WebWorkerが動作しません。また、FirefoxのTampermonkeyが動作しません。
Firefox のコンテナが GM.xmlhttpRequest() に適用されない
Firefox でコンテナで表示中のページから GM.xmlhttpRequest() を使用しても、コンテナの Cookie を使用してリクエストを取得できません。
クロスオリジンの問題がない場合、 XMLHttpRequest を直接使用することで問題を回避できます。
後方互換性の問題
2014年6月~:Greasemonkey2.0
@grant noneがデフォルト設定になるGM_の関数が@grant指定なしで使用不可になる
- ユーザースクリプトの実行がウェブページのJavaScriptから分離される
- ユーザースクリプト→ウェブページは、unsafeWindowでアクセスが可能
- ウェブページ→ユーザースクリプトは、アクセス不可
- ただし、
cloneInto(), exportFunction(), createObjectIn()を使用すれば可能
- ただし、
※詳細:Greasespot: Greasemonkey 2.0 Release
2017年9月~:Greasemonkey4.0 WebExtension対応
GM_関数の廃止GM.関数への移行GM_とGM.では関数の仕様が一部異なります- 同期関数だったものが、非同期関数へ移行しています
GM_log()関数の廃止- 代替:
console.log()
- 代替:
GM_addStyle()関数の廃止GM_registerMenuCommand()関数の廃止GM.registerMenuCommand()へ移行- v4.11(2021年1月)で復活
GM_getResourceText()関数の完全な廃止
ブックマークレット
ユーザースクリプトと類似するユーザー作成のスクリプトを実行する為の仕組みとして、ブックマークレットがあります。ユーザースクリプトと比べた場合のブックマークレトの利点と欠点を次に示します。
ブックマークレットの利点
ブックマークレットは、拡張機能なしで実行できます。拡張機能を利用できないIEなどの古いブラウザ環境で動作します。また、拡張機能が提供されていないモバイル環境でも動作します。
ブックマークレットは、ブックマークの選択時に実行できます。これは、GUIとしてとてもわかり易い実行タイミングです。(GM.registerMenuCommand()が類似的な機能を提供しています)
ブックマークレットの欠点
ブックマークレットは、CSP (Content-Security-Policy) 問題に対して無力です。CSPが設定されたページでは、ブックマークレットを起動できなくなります。
ブックマークレットは、CORS (Cross-Origin Resource Sharing) 問題に対して無力です。ブックマークレットでは、外部リソースの Access-Control-Allow-Origin しだいでダウンロードができません。ユーザースクリプトであれば、GM.xmlHttpRequest()関数を使用することでこの問題を回避できます。
ブックマークレットは、ページのスクリプト無効環境に対して無力です。NoScriptなどを利用してスクリプトを無効化した環境でブックマークレットは、動作しません。
備考
ユーザースクリプトの名称
ユーザースクリプトの名称は、「スクリプト名.user.js」です。
※@nameの名称に「.user.js」は付けない。
※userChrome.jsの「スクリプト名.uc.js」と混同してはいけない。
※@nameは、@name:ja/@name:enと言語毎に個別で指定できる。
無名関数で囲む
無名関数で囲むの例(function() {
// コード
})();旧バージョンのGreasemonkeyは、ユーザースクリプトを対象ページに直接書き込むことで、ユーザースクリプトの機能を実現していました。そのため、対象ページ本来のスクリプトと不用意に干渉しないよう無名関数で囲むことが一般的です。
ですが、現在のGreasemonkeyは、ユーザースクリプトを対象ページ本来のスクリプトとは隔離された環境で実行しています。そのため、無名関数で囲まなかったとしても特段問題が発生することはありません。
ユーザースクリプトとコンテキスト
- ページスクリプト
- ウェブページのスクリプト
- WebExtension API にアクセスできません
- 例:ウェブページへの
<script>を使用したスクリプトの埋め込み処理
- ウェブページのスクリプト
- コンテンツスクリプト
- 拡張機能のコンテンツスクリプト
- WebExtension API の一部のみアクセスできる(一定の制約がある)
- 拡張機能のコンテンツスクリプト
- バックグラウンドスクリプト
- 拡張機能のバックグラウンドスクリプト
- WebExtension API の全体にアクセスできる
- 拡張機能のバックグラウンドスクリプト
ユーザースタイル(UserCSS)
ユーザースクリプトと同様にユーザースタイルも存在します。スクリプトによる動的処理が不要で、CSS的な静的処理だけで問題を解決できる場合、特に有用です。
使用例として、次のようなものが考えられます。
- 特定要素を目立たせる
- 特定要素を非表示にする
- ダークモード対応する
- etc
ブロッカーのフィルター
要素の削除や非表示のみを実現したい場合、ブロッカーのフィルターを利用することもできます。
ブロッカーは、一般的に「広告ブロッカー」として知られていますが、その用途は広告ブロックだけにとどまりません。対象要素がCSSセレクターで表現可能であれば、ユーザースクリプトやユーザースタイルを記述するよりも簡単にフィルターを記述することができます。