tag:blogger.com,1999:blog-2794476860308762522024-03-29T12:29:17.474+09:00バグ取りの日々プログラミング関連の記事を中心に好きなことを書いてるブログです。toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.comBlogger288125tag:blogger.com,1999:blog-279447686030876252.post-12540395088374087742024-03-03T09:32:00.004+09:002024-03-27T08:44:35.732+09:00runtime.onStartup を有効無効切り替え時にも呼び出す<section id="toc-1"><h3>はじめに</h3><p>Manifest V3 で拡張機能のバックグラウンド処理は、無期限に生存できなくなりました。</p><p>それに伴い、<code>chrome.runtime.onInstalled</code>/<code>chrome.runtime.onStartup</code>が重要な処理を担うようになりました。ただし、これには問題があります。拡張機能の有効無効切り替えを認識できないことです。</p><p>無効状態の拡張機能が有効状態に遷移した時、<code>chrome.runtime.onStartup</code>は発火しません。この問題を解決する必要があります。</p></section><section id="toc-2"><h3>問題</h3><pre><span class="pre-code-title">background.js</span><code class="language-js:background.js">chrome.runtime.onInstalled.addListener(onInstalled);
chrome.runtime.onStartup.addListener(onStartup);</code></pre><p>拡張機能の有効無効切り替え時に<code>onStartup</code>が動作しません。</p></section><section id="toc-3"><h3>解決策1</h3><pre><span class="pre-code-title">background.js</span><code class="language-js:background.js">chrome.runtime.onInstalled.addListener(onInstalled);
//chrome.runtime.onStartup.addListener(onStartup);
onStartup();
// 備考:Service Worker の復帰毎に処理を実施します</code></pre><p>background.js の起動毎に<code>onStartup</code>処理を実行します。<br>愚かな行為であることは認識していますが、これはある程度有効に機能します。</p></section><section id="toc-4"><h3>解決策2</h3><pre><span class="pre-code-title">background.js</span><code class="language-js:background.js">chrome.runtime.onInstalled.addListener(onInstalled);
chrome.runtime.onStartup.addListener(onStartup);
chrome.management.onEnabled.addListener((info) => {
if (info.id === chrome.runtime.id) {
onStartup();
}
// 備考:起動時にブラウザ内の有効な拡張機能毎に呼び出されます
// もちろん、有効無効切り替え時にも呼び出されます
});</code></pre><p>この方法は、有効無効の切り替えに対して有効に機能しますが、問題を抱えています。</p><p><code>onStartup</code>は、ブラウザ起動時に2回呼び出されます。<code>chrome.runtime.onStartup</code>,<code>chrome.management.onEnabled</code>の両方から<code>onStartup</code>が呼び出されることが原因です。</p><p><code>management</code>権限を必要とします。<code>chrome.management</code>へアクセスするために追加で権限が必要になります。この権限は、ブラウザ上の他の拡張機能の状態を取得する権限です。これは、拡張機能に分不相応な権限を付与することになります。</p><h4>参考</h4><ul><li><a href="https://stackoverflow.com/questions/13979781/chrome-extension-how-to-handle-disable-and-enable-event-from-browser">Chrome Extension : How to handle disable and enable event from browser - Stack Overflow</a></li></ul></section><section id="toc-5"><h3>解決策3</h3><pre><span class="pre-code-title">background.js</span><code class="language-js:background.js">const startup = async () => {
const sessionStorage = await chrome.storage.session?.get({startup:false});
if (!sessionStorage?.startup) {
await chrome.storage.session?.set({startup:true});
await onStartup();
}
// 備考:有効無効時は chrome.runtime.onStartup が呼ばれない対策
// 備考:chrome.storage.session
// 更新時、内容は消える
// 有効無効時、内容は消える
// Service Worker 復帰時、内容は残る
// 対応時期:Chrome 102, Firefox115
};
chrome.runtime.onInstalled.addListener(onInstalled);
//chrome.runtime.onStartup.addListener(onStartup);
startup();</code></pre><p>新機能の<code>chrome.storage.session</code>を使用する方法です。<code>storage</code>権限を必要としますが、一般的にこの権限が問題となることはないはずです。</p><h4>参考</h4><ul><li><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/session">storage.session - Mozilla | MDN</a></li><li><a href="https://developer.chrome.com/docs/extensions/reference/api/storage">chrome.storage | API | Chrome for Developers</a></li></ul></section><section id="toc-6"><h3>最終案</h3><pre><span class="pre-code-title">background.js</span><code class="language-js:background.js">let startupingPromise = null;
const startuping = async () => {
const sessionStorage = await chrome.storage.session?.get({startup:false});
if (!sessionStorage?.startup) {
await chrome.storage.session?.set({startup:true});
const manifest = chrome.runtime.getManifest();
const localStorage = await chrome.storage.local.get({extension_version:''});
if (manifest.version != localStorage.extension_version) {
await chrome.storage.local.set({extension_version:manifest.version});
await onInstalled();
}
// 備考:無効状態の拡張機能が更新しても chrome.runtime.onInstalled が呼ばれない対策(Chrome 限定?)
// see https://issues.chromium.org/issues/41116832
await onStartup();
}
// 備考:有効無効時は chrome.runtime.onStartup が呼ばれない対策
// 備考:chrome.storage.session
// 更新時、内容は消える
// 有効無効時、内容は消える
// Service Worker 復帰時、内容は残る
// 対応時期:Chrome 102, Firefox115
// 備考:onInstalled, onStartup の実行順・実行タイミングを保証します
// 標準機能では、 onStartup > onInstalled の順で実行されることがあります
// また、並行(非同期)に実行されることがあります
};
const startup = async () => {
if (startupingPromise) {
await startupingPromise;
} else {
startupingPromise = startuping();
await startupingPromise;
//startupingPromise = null;
}
// 備考:並行実行を阻止する。完了を待機する。シングルトン
};
chrome.runtime.onInstalled.addListener(startup);
chrome.runtime.onStartup.addListener(startup);
startup();
// 備考:chrome.runtime.onStartup を呼び出さないと、
// 起動時に background.js が動作しない対策(Firefox 限定?)</code></pre><p>※<code>onInstalled</code>は、バージョンアップ毎に呼び出されます。<br> 更新毎には、呼び出されません。<br> 通常は、問題になりませんがデバッグで問題になることがあります。<br>※次の要素名は変更可能です。<br> <code>chrome.storage.session</code>: <code>{startup}</code><br> <code>chrome.storage.local</code>: <code>{extension_version}</code></p></section><section id="toc-7"><h3>備考</h3><p><code>onStartup</code>は、次の機能の初期化に必要になります。</p><pre><code class="language-js">chrome.action.setPopup({popup});
// 備考:クリックとポップアップ両方に対応する場合、設定が必要になります
chrome.contextMenus.create();
// 備考:起動時は、メニューがないため、作成する必要があります</code></pre></section><section id="toc-8"><h3>備考:無効状態で更新されても更新イベントが発火しない</h3><p>無効状態の拡張機能が更新しても<code>chrome.runtime.onInstalled</code>が呼び出されない。<br>(Chrome 限定?)</p><ul><li><a href="https://issues.chromium.org/issues/41116832">https://issues.chromium.org/issues/41116832</a></li></ul></section><section id="toc-9"><h3>備考:スタートアップキャッシュクリア済みの起動時</h3><p>スタートアップキャッシュクリア済みの起動時は、 Firefox のコンテキストメニューの初期化が必要になる。だが、<code>chrome.runtime.onStartup</code>なしの起動時は、 background.js が呼び出されない。(スタートアップキャッシュクリアなしの Firefox 起動時は、コンテキストメニューがあるため、問題とはならない)</p><ul><li>Profile folder(xxx.default)/startupCache (手動削除)</li><li>[about:support] > [Clear startup cache..]</li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-53509418666739721602022-01-22T11:46:00.007+09:002024-03-26T10:33:22.722+09:00beforeunload のダイアログが出現しないことがある<section id="toc-1"><h3>beforeunload とは?</h3><p><code>beforeunload</code>イベントは、ページがアンロードされる直前に発生します。<code>beforeunload</code>イベントが発生時は、ページがまだ表示されており、イベントもキャンセル可能です。</p><p><code>beforeunload</code>イベントは、ページにダイアログを表示し、ユーザーにページを閉じるか確認を求めることができます。ユーザーが確認すれば、ブラウザは新しいページへ遷移し、そうでなければ遷移をキャンセルします。(ユーザーの確認なしにページ遷移をキャンセルすることはできません)</p></section><section id="toc-2"><h3>サンプル(確認ダイアログを表示する)</h3><pre><span class="pre-code-title">beforeunload.html</span><code class="language-html:beforeunload.html"><script>
window.addEventListener('beforeunload', function(event) {
event.preventDefault();
event.returnValue = '';
});
</script></code></pre><p>※ HTML の仕様では、<code>event.preventDefault();</code>を使用する必要があります。<br> ただし、 Chrome では<code>event.returnValue = '';</code>を設定する必要があります。<br> そのため、 Chrome系とそれ以外に対応するため、両方の記載を併記しています。<br>※<code>returnValue</code>に空文字以外の値を設定するとユーザー確認に使用されます。<br> ただし、これは歴史的な機能であり現在は値を設定しても定型文が使用されます。<br> <a href="https://developers.google.com/web/updates/2016/04/chrome-51-deprecations?hl=en#remove_custom_messages_in_onbeforeunload_dialogs">Remove Custom Messages in onbeforeload Dialogs after Chrome 51</a></p><h4>ダイアログの表示例</h4><pre><code>Chrome
「このサイトを離れますか?」
「行った変更が保存されない可能性があります。」
「このページを離れる」「キャンセル」</code></pre><hr><pre><code>Firefox
「(host)」
「このページから移動しますか? 入力した情報は保存されません。」
「このページから移動する」「このページに留まる」</code></pre></section><section id="toc-3"><h3>ダイアログが出現しないことがある</h3><p>上記のサンプルを使用してもダイアログが出現しないことがあります。</p><h4>JavaScript が無効になっているため</h4><p><code>beforeunload</code>イベントの登録には JavaScript の実行が必要です。そのため、 JavaScript 無効環境ではダイアログが出現しません。</p><p>また、下記のようにタグに <code>onbeforeunload</code> を記述する方法でも内部的には JavaScript が使用されているため、 JavaScript 無効環境ではダイアログが出現しません。</p><pre><code><body onbeforeunload="return 'TEST'"></code></pre><h4>ブラウザが対応していないため</h4><p>Safari on iOS / WebView Android などの一部のブラウザは、<code>beforeunload</code>イベントのダイアログ表示(<code>event.preventDefault()</code> / <code>event.returnValue</code>)に対応していません。そのため、ダイアログ出現させることができません。</p><hr><p>また、 Firefox の about:config 設定で次の変更がある場合、<code>beforeunload</code>イベントのダイアログ表示が無効化されます。ただし、この設定は初期値でダイアログ表示が有効となっています。</p><pre><code>dom.disable_beforeunload
false → true</code></pre><h4>ページとユーザーの対話が存在しないため</h4><p>ページを表示後にユーザーとの対話が存在しない場合(ジェスチャ操作が存在しない場合)、ダイアログは出現しません。元々、ページに入力した情報を保護する機能であるため、ユーザーによる入力が存在しないのであれば、保護する必要もありません。</p><p>ユーザーとの対話は、ユーザーイベントだと考えられます。FID(First Input Delay:初回ユーザーイベント)などでは、<code>click / mousedown / keydown / touchstart / pointerdown</code>をユーザーイベントとして扱います。初回ユーザーイベントが発生していない状態ではユーザーとの対話がないものと考えられます。</p><p><code>Element.click()</code>等で JavaScript から意図的にイベントを発生させたとしてもそれは、ユーザーイベントとしてはカウントされない点に注意してください。また、ユーザーイベントに <code>scroll / mousemove</code> を含まない点も考慮してください。</p><p>そしてまた、<code>window.alert()</code> / <code>window.confirm()</code> / <code>window.prompt()</code> ダイアログへのユーザー入力と確認も考慮されません。<code>window.prompt()</code>でデータを入力しても他のユーザー操作がなしであればダイアログは出現しません。(これは仕様バグなのでは?)</p><hr><p>ユーザーイベントがある場合、ダイアログを表示しますが、ユーザーイベントがない場合、ダイアログを表示せず、デベロッパーツールに次のエラーを出力します。デベロッパーツールにエラーを出力したくない場合、ユーザーイベント発生後に<code>beforeunload</code>を登録する方法などが考えられます。</p><pre><code>Chrome
[Intervention] Blocked attempt to show a 'beforeunload' confirmation panel for a frame that never had a user gesture since its load. https://www.chromestatus.com/feature/5082396709879808</code></pre><aside class="ads-inarticle"><ins class="adsbygoogle ads-ad ads-pending"></ins></aside></section><section id="toc-4"><h3>意図的にダイアログを出現させない</h3><p><code>beforeunload</code>イベントのダイアログは、ユーザーにとって常に有益とは限りません。この機能は、ときに邪魔に思うユーザーがいるかもしれません。次のユーザースクリプトで問題を解決できるかもしれません。</p><pre><code class="language-js">// 他の beforeunload イベントへのイベント伝搬を阻害します
// このコードより早く window のキャプチャフェーズへイベント登録された場合、効果がありません
window.addEventListener('beforeunload', function(event) {
event.stopImmediatePropagation();
}, true);
// 既に登録済みの window.onbeforeunload を除去します
window.onbeforeunload = function(event) {};</code></pre><p>※上記のコードを常に使用することはおすすめできません。<br> 下記の「備考(キャッシュの不使用)」のため、ブラウザ体験が劣化します。<br> 導入するのであれば、特定のページ(サイト)に限定すべきです。</p></section><section id="toc-5"><h3>備考(キャッシュの不使用)</h3><p><code>beforeunload / unload</code>イベントが設定されている場合、ブラウザは bfcache (Back Forward Cache) or WebKit のページキャッシュを使用しなくなります。</p><p>キャッシュの使用により、ユーザー体験が向上します。不必要に <code>beforeunload / unload</code> を使用せずに <code>pagehide</code> の使用を検討してください。</p></section><section id="toc-6"><h3>備考(beforeunload 内では、ダイアログを表示できない)</h3><p><code>beforeunload</code>イベント中は、<code>window.alert()</code> / <code>window.confirm()</code> / <code>window.prompt()</code>などのメソッドが無視(ダイアログを表示しない)されます。</p></section><section id="toc-7"><h3>参考</h3><ul><li><a href="https://developer.mozilla.org/ja/docs/Web/API/Window/beforeunload_event">Window: beforeunload イベント - Web API | MDN</a></li><li><a href="https://developer.mozilla.org/ja/docs/Mozilla/Firefox/Releases/1.5/Using_Firefox_1.5_caching">Using Firefox 1.5 caching - Mozilla | MDN</a></li><li><a href="https://webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/">WebKitページキャッシュII–アンロードイベント| WebKit</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-38001912043364455082021-02-24T12:47:00.010+09:002024-03-26T10:30:39.514+09:00ユーザースクリプト(UserScript)作成時の覚書<script type="application/json" id="post-data-json">{
"title": "ユーザースクリプト(UserScript)作成時の覚書"
,"labels": ["JavaScript", "UserScript", ""]
,"url": "https://www.bugbugnow.net/2021/02/user-script.html"
}</script><div id="toc"><div id="toc-title">目次</div><div id="toc-content"><ol><li><a class="toc-link" href="#toc-1">ユーザースクリプトとは</a></li><li><a class="toc-link" href="#toc-2">拡張機能の種類</a></li><li><a class="toc-link" href="#toc-3">インストール</a></li><li><a class="toc-link" href="#toc-4">ユーザースクリプトの公開と検索</a></li><li><a class="toc-link" href="#toc-5">デバッグ</a></li><li><a class="toc-link" href="#toc-6">ドキュメント</a></li><li><a class="toc-link" href="#toc-7">メタデータブロック</a></li><li><a class="toc-link" href="#toc-8">実行タイミング(@run-at)</a></li><li><a class="toc-link" href="#toc-9">API(GM関数)</a></li><li><a class="toc-link" href="#toc-10">よくある失敗事例</a></li><li><a class="toc-link" href="#toc-11">簡単なバグフィックス・仕様の揺らぎ</a></li><li><a class="toc-link" href="#toc-12">後方互換性の問題</a></li><li><a class="toc-link" href="#toc-13">ブックマークレット</a></li><li><a class="toc-link" href="#toc-14">備考</a></li></ol></div></div><section id="toc-1"><h3>ユーザースクリプトとは</h3><p>ユーザースクリプトとは、ブラウザの対象ウェブページでユーザ作成のスクリプトを実行する為の仕組みです。元々は、Firefoxのアドオンとして作成されたGreasemonkey上で使用できるスクリプトでした。その後、後続の拡張機能も同様の仕様で実行可能なユーザースクリプトとして一般化しました。</p></section><section id="toc-2"><h3>拡張機能の種類</h3><p>ユーザースクリプト用の拡張機能として、主に次の3種類が開発されています。</p><ul><li><a href="https://github.com/greasemonkey/greasemonkey">Greasemonkey - GitHub</a></li><li><a href="https://github.com/Tampermonkey/tampermonkey">Tampermonkey - GitHub</a></li><li><a href="https://github.com/violentmonkey/violentmonkey">Violentmonkey - GitHub</a></li></ul><p>※AdGuardなどの例外も存在します。</p></section><section id="toc-3"><h3>インストール</h3><ul><li>Firefox<ul><li><a href="https://addons.mozilla.org/ja/firefox/addon/greasemonkey/">Greasemonkey – 🦊 Firefox (ja) 向け拡張機能を入手</a></li><li><a href="https://addons.mozilla.org/ja/firefox/addon/tampermonkey/">Tampermonkey – 🦊 Firefox (ja) 向け拡張機能を入手</a></li><li><a href="https://addons.mozilla.org/ja/firefox/addon/violentmonkey/">Violentmonkey – 🦊 Firefox (ja) 向け拡張機能を入手</a></li></ul></li><li>Chrome<ul><li>Greasemonkey<ul><li>標準機能としてChrome本体と統合</li><li>ただし、Chromeウェブストア以外からのインストール禁止の余波で、使用不可<ul><li>エラーメッセージ「この拡張機能は Chrome Web Storeで提供されていません。知らないうちに追加された可能性があります。」</li><li><a href="https://support.google.com/chrome_webstore/answer/2811969">Chrome によって無効にされた拡張機能 - Chrome ウェブストア ヘルプ</a></li></ul></li></ul></li><li><a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ja">Tampermonkey - Chrome ウェブストア</a></li><li><a href="https://chrome.google.com/webstore/detail/violentmonkey/jinjaccalgkegednnccohejagnlnfdag">Violentmonkey - Chrome ウェブストア</a></li></ul></li><li>Edge<ul><li><a href="https://microsoftedge.microsoft.com/addons/detail/tampermonkey/iikmkjmpaadaobahmlepeloendndfphd">Tampermonkey - Microsoft Edge Addons</a></li><li><a href="https://microsoftedge.microsoft.com/addons/detail/violentmonkey/eeagobfjdenkkddmbclomhiblgggliao">Violentmonkey - Microsoft Edge Addons</a></li></ul></li><li>Safari<ul><li><a href="https://apps.apple.com/us/app/tampermonkey/id1482490089">Tampermonkey on the Mac App Store</a><ul><li>Mac App Store経由の場合、有料($1.99)<ul><li>(無料アプリでも)AppStoreの登録料が高額なため、致し方ないことです</li></ul></li><li>公式ページから直接ダウンロードする場合、無料<ul><li><a href="https://www.tampermonkey.net/?browser=safari">Tampermonkey • Home</a></li></ul></li></ul></li></ul></li></ul></section><section id="toc-4"><h3>ユーザースクリプトの公開と検索</h3><ul><li><a href="https://greasyfork.org/ja">Greasy Fork</a><ul><li>ユーザースクリプトホスティングサービス</li></ul></li><li><a href="https://openuserjs.org/">OpenUserJS</a></li><li><a href="https://gist.github.com/search?l=JavaScript&o=desc&q=%22==UserScript==%22&s=updated">GitHub Gist</a><ul><li>GitHubでのユーザースクリプトの検索</li></ul></li></ul><p>※ブログ等で公開されていることもあります。<br>※「<code>==UserScript==</code>」「<code>.user.js</code>」等のキーワードで検索できます。</p></section><section id="toc-5"><h3>デバッグ</h3><h4>ログ出力する</h4><ul><li><code>console.log()</code>関数を使用する<ul><li>Webコンソール上に出力する</li></ul></li><li><code>alert()</code>関数を使用する<ul><li>ページ上にアラームを表示する</li></ul></li><li><code>GM_log()</code>関数を使用する(廃止済み)<ul><li>Webコンソール上に出力する</li><li>使用には、明示的な@grant指定が必要です「<code>// @grant GM_log</code>」</li></ul></li></ul><h4>エラー出力を確認する</h4><p>ユーザースクリプトがエラーした場合、Webコンソール上に出力されます。ただし、確実にエラー出力されるとは限りません。</p><h4>デバッガーを使用する</h4><ul><li>Greasemonkey<ul><li>Firefox標準のデバッガーを使用する<ul><li>[開発ツール] > [デバッガー] > [ソースファイル] > [user-script://] > [対象ユーザースクリプト]</li></ul></li></ul></li><li>Tampermonkey<ul><li>ブラウザ標準のデバッガーを使用する<ul><li>[Firefox] > [開発ツール] > [デバッガー] > [Tampermonkey] > [userscripts] > [対象ユーザースクリプト]</li><li>[Chrome] > [開発ツール] > [Sources] > [Tampermonkey] > [userscript.html?name=対象ユーザースクリプト]</li></ul></li><li>スクリプト開始直後にデバッガーを起動する<ul><li>[オプション] > [設定] > [全般]<ul><li>[設定のモード] を [上級者] に設定する</li><li>[スクリプトをデバッグする] を有効にする</li></ul></li></ul></li></ul></li><li>Violentmonkey<ul><li>ブラウザ標準のデバッガーを使用する<ul><li>[Firefox] > [開発ツール] > [デバッガー] > [Violentmonkey] > [対象ユーザースクリプト]</li><li>[Chrome] > [開発ツール] > [Sources] > [Violentmonkey] > [対象ユーザースクリプト]</li></ul></li></ul></li></ul><hr><p>ブラウザ標準デバッガーの使用方法</p><ul><li>ブレークポイント: 行番号をクリック(選択)</li><li>ステップ実行(Play/pause): F8<ul><li>次のブレークポイントまで実行する</li></ul></li><li>ステップオーバー(Step over): F10<ul><li>次の行まで実行する</li></ul></li><li>ステップイン{Step in}: F11<ul><li>関数呼び出し以外、次の行まで実行する。関数呼び出し、呼び出した関数へ入る</li></ul></li><li>ステップアウト(Step out): Shift+F11<ul><li>現在の関数の終端まで実行する</li></ul></li></ul><p>※<a href="https://developer.mozilla.org/ja/docs/Tools/Debugger/How_to">デバッガの使い方 - 開発ツール | MDN</a><br> <a href="https://developer.mozilla.org/ja/docs/Tools/Debugger/How_to/Step_through_code">コードをステップ実行する - 開発ツール | MDN</a></p></section><section id="toc-6"><h3>ドキュメント</h3><ul><li><a href="https://wiki.greasespot.net/Main_Page">GreaseSpot Wiki</a></li><li><a href="https://www.tampermonkey.net/documentation.php">Tampermonkey • Documentation</a></li><li><a href="https://violentmonkey.github.io/">Violentmonkey</a></li></ul></section><section id="toc-7"><h3>メタデータブロック</h3><p>メタデータブロックを記述することでユーザースクリプトにメタ的な情報を付与できます。</p><pre><span class="pre-code-title">メタデータブロックの記述例</span><code class="language-js:メタデータブロックの記述例">// ==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==</code></pre><h4>主なメタタグ</h4><div class="responsive-table"><table><thead><tr><th>メタタグ</th><th>概要</th></tr></thead><tbody><tr><td>@name</td><td>スクリプト名</td></tr><tr><td>@description</td><td>スクリプトの説明</td></tr><tr><td>@include</td><td>対象のページ</td></tr><tr><td>@match</td><td>対象のページ</td></tr><tr><td>@exclude</td><td>対象外のページ</td></tr><tr><td>@run-at</td><td>実行タイミング</td></tr><tr><td>@grant</td><td>特権の付与</td></tr></tbody></table></div><h4>参考</h4><ul><li><a href="https://wiki.greasespot.net/Metadata_Block">Metadata Block - GreaseSpot Wiki</a></li><li><a href="https://www.tampermonkey.net/documentation.php">Tampermonkey • Documentation</a></li><li><a href="https://violentmonkey.github.io/api/metadata-block/">Metadata Block - Violentmonkey</a></li></ul></section><section id="toc-8"><h3>実行タイミング(@run-at)</h3><p>ユーザースクリプトの実行タイミングは、<code>@run-at</code>で指定できます。</p><div class="responsive-table"><table><thead><tr><th>@run-at</th><th>概要</th></tr></thead><tbody><tr><td>document-start</td><td>スクリプトをできるだけ早く実行する(※)</td></tr><tr><td>document-end</td><td><code>DOMContentLoaded</code>時に実行する(既定)</td></tr><tr><td>document-idle</td><td><code>DOMContentLoaded</code>起動後に実行する</td></tr></tbody></table></div><p>※拡張機能のコンテンツスクリプトは、ウェブページの読み込みより先に実行できます。<code><head></code>を読み込むより早く実行することができます。ただし、WebExtensionAPIにアクセスする場合、非同期処理の影響で最終的に<code><head></code>読み込み中や<code><head></code>より後に実行することになります。そのため、「できるだけ早く実行する」と言う曖昧な表現になります。</p><h4>別のタイミングで実行する</h4><p>ページ読み込み時以外でユーザースクリプトを実行したいことがあります。拡張機能のユーザースクリプト実行タイミミングをこれ以上変更することはできませんが、JavaScriptの各種イベントのタイミングで処理を実行することはできます。</p><p>ユーザースクリプトで使えそうな主なイベント種類を次に示します。</p><ul><li>クリックしたタイミング<ul><li><code>click</code>イベント</li><li>ページ上の既存の要素にイベントを設定することもできます<ul><li>イベント設定のない画像やアイコンに新たなイベントを追加する</li></ul></li><li>要素そのものをユーザースクリプトで挿入することもできます<ul><li>既存の要素間に新たな要素(ボタンなど)を配置する</li><li><code>z-index</code>等で浮遊した要素(ボタンなど)を配置する</li></ul></li><li>ページ全体のクリックイベントを監視することもできます<ul><li><code>window.addEventListener('click', func);</code></li></ul></li></ul></li><li>要素の状態が変化したタイミング<ul><li><code>input/select/change</code>イベント</li></ul></li><li>コピーペーストしたタイミング<ul><li><code>cut/copy/paste</code>イベント</li></ul></li><li>キーボード入力したタイミング<ul><li><code>keydown</code>イベント</li><li>ページ独自のショートカットキーを設定することができます</li></ul></li><li>ジェスチャー操作したタイミング<ul><li><code>mousedown/mousemove/mouseup/draggesture</code>イベント</li><li>ページ独自のジェスチャーを設定することができます</li></ul></li><li>文字列選択したタイミング<ul><li><code>mouseup</code>イベント</li><li><code>window.getSelection();</code></li></ul></li><li>スクロールしたタイミング<ul><li><code>scroll</code>イベント</li><li><code>IntersectionObserver</code></li><li>無限スクロールを設定することができます</li></ul></li><li>DOM要素の変更したタイミング<ul><li><code>MutationObserver</code></li><li>ページ読み込み完了後に動的追加される要素を処理することができます</li></ul></li><li>ページを閉じるタイミング<ul><li><code>pagehide</code>イベント</li><li>データを保存して、次の訪問時に使用することができます<ul><li>データ保存には、<code>window.localStorage / GM.setValue()</code>などが使用できます</li></ul></li></ul></li><li>コンテキストメニューを選択したタイミング<ul><li><code>GM.registerMenuCommand()</code><ul><li>ブラウザアクションのコンテキストメニュー</li></ul></li></ul></li></ul></section><section id="toc-9"><h3>API(GM関数)</h3><div class="responsive-table"><table><thead><tr><th>API</th><th>概要</th></tr></thead><tbody><tr><td>GM.info</td><td>ユーザースクリプトに関する情報</td></tr><tr><td>GM.setValue() / GM.getValue()</td><td>データの保存と取得</td></tr><tr><td>GM.setClipboard()</td><td>クリップボードへの書き込み</td></tr><tr><td>GM.xmlHttpRequest()</td><td>XMLHttpRequestオブジェクトと同様の機能</td></tr><tr><td>unsafeWindow</td><td>ページの<code>window</code></td></tr></tbody></table></div><p>※GM関数は、拡張機能の権限を利用して処理されます。<br> 例:<code>GM.setValue()</code>は、データを Cookie や localStorage とは異なる拡張機能の領域に保持します。<br> 例:<code>GM.xmlHttpRequest()</code>は、CSP や CROS の問題を回避できます。</p><h4>JavaScriptの通常API</h4><ul><li><code>console.log()</code>:コンソールへ出力する</li><li><code>alert() / confirm() / prompt()</code>:ダイアログを表示する</li><li><code>document.querySelector()</code>:要素を取得する</li><li><code>document.elementFromPoint()</code>:マウスの位置にある要素を取得する</li><li>EventListener / Observer:各種実行タイミングを設定する</li><li>WebWorker:ページから隔離されたプロセスで実行する(重い処理用)</li><li>ShadowDOM:対象ページのスタイルに影響を受けない要素を作成する</li></ul><h4>参考</h4><ul><li><a href="https://wiki.greasespot.net/Greasemonkey_Manual:API">Greasemonkey Manual:API - GreaseSpot Wiki</a></li><li><a href="https://www.tampermonkey.net/documentation.php">Tampermonkey • Documentation</a></li><li><a href="https://violentmonkey.github.io/api/gm/">GM_* APIs - Violentmonkey</a></li></ul></section><section id="toc-10"><h3>よくある失敗事例</h3><h4><code>@run-at document-start</code>でドキュメント要素へアクセスする</h4><p><code>document-start</code>のタイミングは、<code>DOMContentLoaded</code>より前のタイミングです。ドキュメントの解析が完了していません。そのため、ドキュメント要素を取得できず、エラー終了や意図しない動作をすることがあります。<code>document-end</code>または、<code>document-idle</code>の指定、もしくは<code>@run-at</code>を未指定とすることで問題を解決できます。</p><p>※対象要素をドキュメントの解析完了後に動的追加している場合、<code>MutationObserver</code>でドキュメント変更を監視して、対象要素が追加されたタイミングで処理を実行する必要があります。</p><h4><code>@include/@match</code>の指定ミス</h4><p><code>@include/@match</code>の指定ミスにより、ユーザースクリプトが実行されない。または、意図しないページで実行される問題が発生します。</p><p>※<code>@include/@match</code>が未指定の場合、「すべてのサイトにユーザースクリプトを適用する」設定になります。</p></section><section id="toc-11"><h3>簡単なバグフィックス・仕様の揺らぎ</h3><h4>@grant</h4><ul><li><code>@grant</code>未指定<ul><li><code>@grant none</code>の動作となる</li></ul></li><li><code>@grant none</code>と<code>@grant GM</code>の重複指定<ul><li><code>@grant none</code>の動作となる</li><li><code>GM</code>関数は使用できない</li></ul></li></ul><h4>@require のローカルファイル指定</h4><ul><li>ローカルファイル(<code>file:</code>)の使用<ul><li>Greasemonkey / Violentmonkey<ul><li>不可</li></ul></li><li>Tampermonkey<ul><li>要設定<ul><li>Chrome設定([ファイルの URL へのアクセスを許可する])</li><li>拡張機能設定([セキュリティ] > [スクリプトによるローカル ファイルへのアクセスを許可する])</li></ul></li></ul></li></ul></li></ul><p>※ローカルファイルへのアクセスは、セキュリティ的にとても危険な機能です。不用意に許可しないことを推奨します。</p><h4>ポート番号(@match)</h4><ul><li>'@match'のポート番号<ul><li>Greasemonkey / Tampermonkey<ul><li>ポート番号ありが動作しない</li></ul></li><li>Violentmonkey<ul><li>ポート番号なしが動作しない(明示的なポート番号指定が必要)</li></ul></li><li>ポート番号あり・なしを併記することで回避できる</li></ul></li></ul><h4>ページのスクリプト無効</h4><p>NoScriptなどを使用してページのスクリプトを無効とした場合、WebWorkerが動作しません。また、FirefoxのTampermonkeyが動作しません。</p><h4>Firefox のコンテナが GM.xmlhttpRequest() に適用されない</h4><p>Firefox でコンテナで表示中のページから <code>GM.xmlhttpRequest()</code> を使用しても、コンテナの Cookie を使用してリクエストを取得できません。</p><p>クロスオリジンの問題がない場合、 <code>XMLHttpRequest</code> を直接使用することで問題を回避できます。</p><p>参考:<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1670278">https://bugzilla.mozilla.org/show_bug.cgi?id=1670278</a></p></section><section id="toc-12"><h3>後方互換性の問題</h3><h4>2014年6月~:Greasemonkey2.0</h4><ul><li><code>@grant none</code>がデフォルト設定になる<ul><li><code>GM_</code>の関数が<code>@grant</code>指定なしで使用不可になる</li></ul></li><li>ユーザースクリプトの実行がウェブページのJavaScriptから分離される<ul><li>ユーザースクリプト→ウェブページは、unsafeWindowでアクセスが可能</li><li>ウェブページ→ユーザースクリプトは、アクセス不可<ul><li>ただし、<code>cloneInto(), exportFunction(), createObjectIn()</code>を使用すれば可能</li></ul></li></ul></li></ul><p>※詳細:<a href="https://www.greasespot.net/2014/06/greasemonkey-20-release.html">Greasespot: Greasemonkey 2.0 Release</a></p><h4>2017年9月~:Greasemonkey4.0 WebExtension対応</h4><ul><li><code>GM_</code>関数の廃止<ul><li><code>GM.</code>関数への移行</li><li><code>GM_</code>と<code>GM.</code>では関数の仕様が一部異なります<ul><li>同期関数だったものが、非同期関数へ移行しています</li></ul></li></ul></li><li><code>GM_log()</code>関数の廃止<ul><li>代替:<code>console.log()</code></li></ul></li><li><code>GM_addStyle()</code>関数の廃止</li><li><code>GM_registerMenuCommand()</code>関数の廃止<ul><li><code>GM.registerMenuCommand()</code>へ移行<ul><li>v4.11(2021年1月)で復活</li></ul></li></ul></li><li><code>GM_getResourceText()</code>関数の完全な廃止</li></ul><p>※詳細:<a href="https://www.greasespot.net/2017/09/greasemonkey-4-for-script-authors.html">Greasespot: Greasemonkey 4 For Script Authors</a></p></section><section id="toc-13"><h3>ブックマークレット</h3><p>ユーザースクリプトと類似するユーザー作成のスクリプトを実行する為の仕組みとして、ブックマークレットがあります。ユーザースクリプトと比べた場合のブックマークレトの利点と欠点を次に示します。</p><h4>ブックマークレットの利点</h4><p>ブックマークレットは、拡張機能なしで実行できます。拡張機能を利用できないIEなどの古いブラウザ環境で動作します。また、拡張機能が提供されていないモバイル環境でも動作します。</p><p>ブックマークレットは、ブックマークの選択時に実行できます。これは、GUIとしてとてもわかり易い実行タイミングです。(<code>GM.registerMenuCommand()</code>が類似的な機能を提供しています)</p><h4>ブックマークレットの欠点</h4><p>ブックマークレットは、CSP (Content-Security-Policy) 問題に対して無力です。CSPが設定されたページでは、ブックマークレットを起動できなくなります。</p><p>ブックマークレットは、CORS (Cross-Origin Resource Sharing) 問題に対して無力です。ブックマークレットでは、外部リソースの Access-Control-Allow-Origin しだいでダウンロードができません。ユーザースクリプトであれば、<code>GM.xmlHttpRequest()</code>関数を使用することでこの問題を回避できます。</p><p>ブックマークレットは、ページのスクリプト無効環境に対して無力です。NoScriptなどを利用してスクリプトを無効化した環境でブックマークレットは、動作しません。</p></section><section id="toc-14"><h3>備考</h3><h4>ユーザースクリプトの名称</h4><p>ユーザースクリプトの名称は、「<code>スクリプト名.user.js</code>」です。</p><p>※<code>@name</code>の名称に「<code>.user.js</code>」は付けない。<br>※<code>userChrome.js</code>の「<code>スクリプト名.uc.js</code>」と混同してはいけない。<br>※<code>@name</code>は、<code>@name:ja</code>/<code>@name:en</code>と言語毎に個別で指定できる。</p><h4>無名関数で囲む</h4><pre><span class="pre-code-title">無名関数で囲むの例</span><code class="language-js:無名関数で囲むの例">(function() {
// コード
})();</code></pre><p>旧バージョンのGreasemonkeyは、ユーザースクリプトを対象ページに直接書き込むことで、ユーザースクリプトの機能を実現していました。そのため、対象ページ本来のスクリプトと不用意に干渉しないよう無名関数で囲むことが一般的です。</p><p>ですが、現在のGreasemonkeyは、ユーザースクリプトを対象ページ本来のスクリプトとは隔離された環境で実行しています。そのため、無名関数で囲まなかったとしても特段問題が発生することはありません。</p><h4>ユーザースクリプトとコンテキスト</h4><ul><li>ページスクリプト<ul><li>ウェブページのスクリプト<ul><li>WebExtension API にアクセスできません</li></ul></li><li>例:ウェブページへの<code><script></code>を使用したスクリプトの埋め込み処理</li></ul></li><li>コンテンツスクリプト<ul><li>拡張機能のコンテンツスクリプト<ul><li>WebExtension API の一部のみアクセスできる(一定の制約がある)</li></ul></li></ul></li><li>バックグラウンドスクリプト<ul><li>拡張機能のバックグラウンドスクリプト<ul><li>WebExtension API の全体にアクセスできる</li></ul></li></ul></li></ul><p>※<a href="https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/Content_scripts">コンテンツスクリプト - Mozilla | MDN</a></p><h4>ユーザースタイル(UserCSS)</h4><p>ユーザースクリプトと同様にユーザースタイルも存在します。スクリプトによる動的処理が不要で、CSS的な静的処理だけで問題を解決できる場合、特に有用です。</p><p>使用例として、次のようなものが考えられます。</p><ul><li>特定要素を目立たせる</li><li>特定要素を非表示にする</li><li>ダークモード対応する</li><li>etc</li></ul><h4>ブロッカーのフィルター</h4><p>要素の削除や非表示のみを実現したい場合、ブロッカーのフィルターを利用することもできます。</p><p>ブロッカーは、一般的に「広告ブロッカー」として知られていますが、その用途は広告ブロックだけにとどまりません。対象要素がCSSセレクターで表現可能であれば、ユーザースクリプトやユーザースタイルを記述するよりも簡単にフィルターを記述することができます。</p><ul><li>参考:<a href="https://www.bugbugnow.net/2021/09/ublock-filter.html">uBlock Origin フィルター覚書(書き方・サンプル)</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-20395823734478651762022-01-23T10:46:00.002+09:002024-03-26T10:16:46.464+09:00Disable Service Worker.<script type="application/json" id="post-data-json">{
"title": "Disable Service Worker."
,"labels": ["English", "ServiceWorker", "UserScript"]
,"url": "https://www.bugbugnow.net/2022/01/disable-service-worker.html"
}</script><script id="post-body-script" type="text/plain">(function() {
let text = '';
text += '<link rel="alternate" hreflang="ja" href="https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html">';
text += '<link rel="alternate" hreflang="en" href="https://www.bugbugnow.net/2022/01/disable-service-worker.html">';
text += '<link rel="alternate" hreflang="x-default" href="https://www.bugbugnow.net/2022/01/disable-service-worker.html">';
document.head.insertAdjacentHTML('beforeend', text);
document.getElementById('post-body').lang = 'en';
})();</script><p>This article is a translation of the following Japanese article.</p><ul><li><a href="https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html">ServiceWorker を無効化する</a></li></ul><section id="toc-1"><h3>Introduction</h3><p>ServiceWorker, a much talked about feature these days, has been added to the browser. This is a great feature that allows you to do functions like smartphone apps in the browser. It's a great feature that allows you to achieve smartphone app-like functionality in your browser. However, it can be registered without prior approval from the user.</p><p>ServiceWorker is a convenient but very dangerous feature because it allows you to perform smartphone app-like functions without approval. Anti-virus software (such as Windows Defender) may detect some ServiceWorkers as viruses. ServiceWorkers can continue to run even after the site is closed, which can make the browser slow. ServiceWorkers also store large amounts of cache, which can lead to disk and memory overload.</p></section><section id="toc-2"><h3>Check your registered ServiceWorkers</h3><p>You can check them by accessing the following address. You can also unsubscribe manually.</p><pre><code>// Chrome
chrome://serviceworker-internals
// Edge
edge://serviceworker-internals
// Firefox
about:serviceworkers
about:debugging#workers</code></pre><p>It is not possible to manually unsubscribe from Google related services in Chrome.<br>It is possible to unregister Google related services in Chrome using the following method.</p></section><section id="toc-3"><h3>Deny ServiceWorker registration in user script.</h3><p>By replacing the ServiceWorker registration function, new registrations will be rejected. Also, ServiceWorker can be unregistered.</p><h4>Specifications</h4><ul><li>Replace the ServiceWorker's registration function to make new registration impossible.</li><li>If the ServiceWorker is already registered, it will be unregistered.</li><li>Clear the cache if the ServiceWorker is already registered.</li><li>Can be used as a whitelist by setting <code>@exclude</code>.</li><li>Complete rejection of registration is not possible with the timing of user script execution.<ul><li>Due to a slight delay in inserting the script into the page</li><li>ServiceWorker will be registered temporarily, but will be unregistered immediately afterwards<ul><li>However, if the unregistration is interfered with, it may not be possible to unregister</li></ul></li><li>The extension version works more powerfully in this area than the userscript version.</li><li>However, since function replacement definitely occurs in the extended function version, if the page script detects the function replacement and behaves abnormally, it will not be able to deal with it.</li><li>Therefore, the user script version is better when operating a whitelist that includes sites that behave abnormally.</li></ul></li></ul><h4>User script code</h4><pre><span class="pre-code-title">RejectServiceWorker.user.js</span><code class="language-js:RejectServiceWorker.user.js">// ==UserScript==
// @name RejectServiceWorker
// @description Reject to register a service worker.
// Reject to register new service worker by overwriting the register function.
// If service worker was registered, it unregister the registered service worker.
// If service worker was registered, it clears the cache.
// You can use it as a whitelist by setting @exclude.
// As long as user script execution timing is used, complete reject cannot be achieved.
// @note ↓↓↓ Add target page URL ↓↓↓
// @include https://*/*
// @exclude https://example.com/*
// @note ↑↑↑ Add target page URL ↑↑↑
// @author toshi (https://github.com/k08045kk)
// @license MIT License | https://opensource.org/licenses/MIT
// @version 0.3.0
// @since 0.1.0 - 20200328 - 初版
// @since 0.1.1 - 20200415 - 修正
// @since 0.2.0 - 20200926 - Greasemonkey対応(unsafeWindow経由でwindowのオブジェクトを書き換え)
// @since 0.2.1 - 20210125 - RejectServiceWorkers.user.js → RejectServiceWorker.user.js
// @since 0.2.2 - 20210828 - comment メタデータの見直し
// @since 0.2.3 - 20211013 - comment 権限不足エラーの注意書きを追記
// @since 0.3.0 - 20240326 - Proxy 方式に対応(他、動作不可問題修正)
// @see https://github.com/k08045kk/UserScripts
// @see https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html
// @run-at document-start
// @grant unsafeWindow
// ==/UserScript==
;(async function(win) {
let isExec = false;
try { isExec = !!navigator.serviceWorker; } catch {}
if ( isExec === false) {
// http environmental measures
return;
}
// Reject to register a service worker
const register = new Proxy(win.ServiceWorkerContainer.prototype.register, {
apply: () => win.Promise.reject(new win.Error('Reject to register a service worker.')),
});
try {
exportFunction(register,
win.ServiceWorkerContainer.prototype,
{defineAs: 'register'});
} catch {
win.ServiceWorkerContainer.prototype.register = register;
}
// Unregister the registered service worker
const registrations = await navigator.serviceWorker.getRegistrations();
if (registrations.length) {
// Unregister service worker
const unregisterPromises = registrations.map(registration => registration.unregister());
await Promise.all(unregisterPromises);
// Delete all cache storage
const keys = await caches.keys();
const cacheDeletePromises = keys.map(key => caches.delete(key));
await Promise.all(cacheDeletePromises);
}
})(unsafeWindow || window);</code></pre></section><section id="toc-4"><h3>Block (or manage) ServiceWorker with an extension</h3><p>The following extension has already been created. This is a feature that is still in its infancy, so there may be better ones if you look for them.</p><h4>author's extension</h4><p>It works in the same way as the above user script. The extension works more powerfully than the user script because the execution timing is faster.</p><ul><li><a href="https://chrome.google.com/webstore/detail/reject-service-worker/falajmifjcihbmlokgomiklbfmgmnopd">Reject Service Worker - Chrome Web Store</a></li><li><a href="https://addons.mozilla.org/en-US/firefox/addon/reject-service-worker/">Reject Service Worker – Get this Extension for 🦊 Firefox (en-US)</a></li></ul><h4>Similar extensions by other authors</h4><ul><li><a href="https://chrome.google.com/webstore/detail/block-service-workers/ceokjgeibfjfcboemhdpkdalankbmnej">Block Service Workers - Chrome Web Store</a></li><li><a href="https://addons.mozilla.org/en-US/firefox/addon/block-service-workers/">Block Service Workers – Get this Extension for 🦊 Firefox (en-US)</a></li><li><a href="https://chromewebstore.google.com/detail/no-service-worker/mbhfklemgegigbfbfmfdmijkcnabgpmf">No Service Worker - Chrome Web Store</a></li></ul></section><section id="toc-5"><h3>Disable ServiceWorker in browser settings.</h3><h4>Chrome (cannot be disabled)</h4><p>We have not found a way to disable ServiceWorker in Chrome. If you want to disable ServiceWorker in Chrome, it is best to use the above user script or extension.</p><h4>Firefox</h4><p>Firefox can disable ServiceWorkers by going to <code>about:config</code> and setting <code>dom.serviceWorkers.enabled</code> to <code>false</code>. However, it is necessary to unregister all ServiceWorkers from <code>about:serviceworkers</code> in advance, because already registered ServiceWorkers will still work.</p><p>※ You will need to restart Firefox to reflect the settings.</p></section><section id="toc-6"><h3>Remarks (Disable with script disable)</h3><p>The registration of ServiceWorkers requires JavaScript. Therefore, you can reject ServiceWorker registration by disabling JavaScript.</p><p>Therefore, you can reject the registration of ServiceWorker by disabling JavaScript with NoScript.</p><p>If it is already registered, ServiceWorker will continue to work.</p></section><section id="toc-7"><h3>Remarks (Disabling Workers using CSP)</h3><ul><li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">Content Security Policy (CSP) - HTTP | MDN</a></p><p> Content-Security-Policy: worker-src 'none'</p><meta http-equiv="Content-Security-Policy" content="worker-src 'none'"></li></ul><p>By adding a Content-Security-Policy (CSP) header or meta tag like the one above, you can block the Worker / SharedWorker / ServiceWorker source code as invalid code. As the source code of the worker, it will not be loaded.</p><p>In CSP, Worker/SharedWorker is also blocked.<br> CSP cannot block only ServiceWorkers.<br> CSP cannot block only ServiceWorker. Blocking Worker/SharedWorker may cause unstable page operation.<br>If you have already registered, the ServiceWorker will continue to work.<br>It also blocks the dataURL.</p><h4>Example: uBlock Origin's filter</h4><pre><code>! Deny worker registration for some pages.
||example.com^$csp=worker-src 'none'
! Deny worker registration for all pages
*$csp=worker-src 'none' !
! Cancel denial of worker registration for some pages.
@@||example.com^$csp=worker-src 'none' !</code></pre><p>Simple explanation</p><ul><li><code>!</code>: comment line</li><li><code>||example.com^</code>: Specify per-domain (including subdomains)</li><li><code>*</code>: wildcard (all domains)</li><li><code>@@</code>: cancel effect of filter (exception rule)</li><li><code>$csp=</code>: Add a meta tag for Content-Security-Policy.</li><li><code>worker-src 'none'</code>: Set empty set for worker-src directive</li></ul><h4>Example: User script</h4><pre><span class="pre-code-title">BlockingWorkerWithCSP.user.js</span><code class="language-js:BlockingWorkerWithCSP.user.js">// ==UserScript==
// @name BlockingWorkerWithCSP
// @description Use CSP to block Worker.
// @note ↓↓↓ Add target page URL ↓↓↓
// @include https://example.com/*
// @note ↑↑↑ Add target page URL ↑↑↑
// @author toshi (https://github.com/k08045kk)
// @license MIT License | https://opensource.org/licenses/MIT
// @version 0.1.0
// @since 0.1.0 - 20210828 - 初版
// @see https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html
// @run-at document-end
// @grant none
// ==/UserScript==
(function() {
// Add CSP meta tag
var meta = document.createElement('meta');
meta.setAttribute('http-equiv', 'Content-Security-Policy');
meta.setAttribute('content', "worker-src 'none'");
document.head.appendChild(meta);
// Note: This user script will block all Worker, not just ServiceWorker.
// Note: It is not possible to stop a Worker that is already running.
})();</code></pre><p>Simple explanation</p><ul><li>Insert CSP meta tags into the target page.</li><li>The CSP meta tag blocks the loading of the worker source code.<ul><li>If ServiceWorker is registered before the meta tags are inserted, it is not tested.</li></ul></li><li>Blocks not only ServiceWorker but also Worker/SharedWorker.</li><li>Cannot stop a ServiceWorker that is already running</li></ul><p>※ uBlock Origin is internally similar to user script.</p></section><section id="toc-8"><h3>Remarks (Disabling Cache only)</h3><p>Let's consider how to disable only Cache while running ServiceWorker.</p><p>As it turns out, this is not possible at present. (It is more practical to disable ServiceWorker.)</p><p>At first glance, it seems that it is possible to override the Cache functions as well as the ServiceWorker registration functions. However, there is no way to override the Cache inside the ServiceWorker. Embedded scripts, user scripts, and extensions cannot override the ServiceWorker process.</p><p>The practical solution is to remove the Cache immediately, rather than disabling it. However, there is no event in place to notify you of cache changes such as the storage event. Therefore, deleting the entire cache every time the page is loaded and unloaded is a realistic approach.</p><p>However, if the ServiceWorker itself works on the assumption that the cached data exists, this may cause problems. This is not a problem if the ServiceWorker is flexible enough to perform a fetch operation when a fetch from the cache fails, but depending on the design of the ServiceWorker, the page will not work.</p><h4>Code example (automatic deletion of the entire cache)</h4><pre><span class="pre-code-title">DeleteCache.user.js</span><code class="language-js:DeleteCache.user.js">// ==UserScript==
// @name DeleteCache
// @description Delete all caches.
// @note ↓↓↓ Add target page URL ↓↓↓
// @include https://example.com/*
// @note ↑↑↑ Add target page URL ↑↑↑
// @author toshi (https://github.com/k08045kk)
// @license MIT License | https://opensource.org/licenses/MIT
// @version 0.1
// @since 0.1 - 20211231 - 初版
// @see https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html
// @grant none
// ==/UserScript==
(() => {
const deleteCache = async () => {
for (let key of await caches.keys()) {
await caches.delete(key);
}
};
window.requestIdleCallback(deleteCache);
window.addEventListener('pagehide', deleteCache, {capture:true, passive:true});
})();</code></pre></section><section id="toc-9"><h3>Remarks (Disabling ServiceWorker in mobile environment)</h3><p>From the previous methods, you can disable ServiceWorker in a mobile environment if the extension can be installed. However, only some browsers allow extensions to be installed in the mobile environment. You can install the extension in the following browsers</p><ul><li>Kiwi Browser (Android)</li><li>Firefox (Android)</li><li>Safari (iOS)</li></ul><p>※ When ServiceWorker is disabled, A2HS (Add to Home screen) cannot be selected.<br>※ For Firefox (Android), only recommended extensions can be installed.<br> Since uBlock Origin is available, the above CSP method can be used.<br> Starting with Firefox 121, you can now install extensions other than those recommended.<br> Reject Service Worker is also compatible with Android Firefox.<br>※ Reject Service Worker is not supported in <del>Firefox (Android)</del> / Safari (iOS).</p></section><section id="toc-10"><h3>Reference</h3><ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/serviceWorker">Navigator.serviceWorker - Web APIs | MDN</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer">ServiceWorkerContainer - Web APIs | MDN</a></li><li><a href="https://developer.mozilla.org/ja/docs/Web/API/CacheStorage">CacheStorage - Web API | MDN</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-77296234229616868052020-03-28T19:49:00.021+09:002024-03-26T10:15:29.091+09:00ServiceWorker を無効化する<script type="application/json" id="post-data-json">{
"title": "ServiceWorker を無効化する"
,"labels": ["ServiceWorker", "Chrome", "Firefox", "UserScript"]
,"url": "https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html"
}</script><script id="post-body-script" type="text/plain">(function() {
let text = '';
text += '<link rel="alternate" hreflang="ja" href="https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html">';
text += '<link rel="alternate" hreflang="en" href="https://www.bugbugnow.net/2022/01/disable-service-worker.html">';
text += '<link rel="alternate" hreflang="x-default" href="https://www.bugbugnow.net/2022/01/disable-service-worker.html">';
document.head.insertAdjacentHTML('beforeend', text);
})();</script><section id="toc-1"><h3>はじめに</h3><p>ServiceWorker と言う、この頃話題の機能がブラウザに追加されました。既存のウェブページではできなかった。スマホアプリのような機能をブラウザで実現できる素晴らしい機能です。ただし、ユーザの事前承認なしに登録できます。</p><p>ServiceWorker は、スマホアプリのような機能を無承認で実行できるため、便利な半面でとても危険な機能です。ウイルス対策ソフト(Windows Defenderなど)では、一部の ServiceWorker をウイルスとして検出することがあります。 ServiceWorker は、サイトを閉じても動作し続けることが可能なため、ブラウザの動作を重くする原因にもなります。 ServiceWorker は、キャッシュを大量に保存するため、ディスクやメモリの圧迫につながることもあります。</p></section><section id="toc-2"><h3>登録済みの ServiceWorker を確認する</h3><p>次のアドレスにアクセスすれば確認できます。手動による登録解除も可能です。</p><pre><code>// Chrome
chrome://serviceworker-internals
// Edge
edge://serviceworker-internals
// Firefox
about:serviceworkers
about:debugging#workers</code></pre><p>※Chrome では、 Google 関連サービスの手動による登録解除ができません。<br>※次の方法を使用することで、 Chrome の Google 関連サービスを登録解除は可能です。</p><h4>注意(拡張機能の ServiceWorke を登録解除)</h4><p>Chrome 拡張機能の Manifest V3 対応により、拡張機能のバックグラウンドが Service Worker になりました。拡張機能の Service Worker も上記のページに表示されます。停止や登録解除が可能です。</p><p>拡張機能の ServiceWorker を登録解除してしまうと、拡張機能が動作しなくなる可能性があります。次のアドレスから始まる ServiceWorker を登録解除しないでください。</p><pre><code>chrome-extension://</code></pre></section><section id="toc-3"><h3>ユーザスクリプトで ServiceWorker の登録を拒否する</h3><p>ServiceWorker の登録関数を置き換えてしまうことで新規登録を拒否します。また、 ServiceWorker は登録解除が可能です。なので、登録済みの ServiceWorker を登録解除します。</p><h4>仕様</h4><ul><li>ServiceWorker の登録関数を置き換えて新規登録不可にする</li><li>ServiceWorker が登録済みならば登録解除する</li><li>ServiceWorker が登録済みならばキャッシュもクリアする</li><li><code>@exclude</code>を設定することでホワイトリストとして利用できる</li><li><code>@include</code>の設定を変更することでブラックリストとしても利用できる<ul><li>「<code>// @include https://*/*</code>」を指定のサイトに変更する</li></ul></li><li>ユーザスクリプトの実行タイミングでは、完全な登録拒否はできない<ul><li>ページへのスクリプト挿入が少し遅れるため</li><li>一時的にServiceWorkerを登録されますが、直後に登録解除します<ul><li>ただし、登録解除を妨害された場合、解除できないことがあります</li></ul></li><li>拡張機能版は、ユーザスクリプト版に比べてこの部分がより強力に動作します<ul><li>ただし、拡張機能版は関数の置き換えが確実に発生するため、関数置き換えをページスクリプトに検出されて異常動作を取られると対処できなくなります。</li><li>なので、異常動作するサイトを含めてホワイトリスト運用する場合、ユーザースクリプト版の方が優れています。</li></ul></li></ul></li></ul><h4>ユーザスクリプトのコード</h4><pre><span class="pre-code-title">RejectServiceWorker.user.js</span><code class="language-js:RejectServiceWorker.user.js">// ==UserScript==
// @name RejectServiceWorker
// @description Reject to register a service worker.
// Reject to register new service worker by overwriting the register function.
// If service worker was registered, it unregister the registered service worker.
// If service worker was registered, it clears the cache.
// You can use it as a whitelist by setting @exclude.
// As long as user script execution timing is used, complete reject cannot be achieved.
// @note ↓↓↓ Add target page URL ↓↓↓
// @include https://*/*
// @exclude https://example.com/*
// @note ↑↑↑ Add target page URL ↑↑↑
// @author toshi (https://github.com/k08045kk)
// @license MIT License | https://opensource.org/licenses/MIT
// @version 0.3.0
// @since 0.1.0 - 20200328 - 初版
// @since 0.1.1 - 20200415 - 修正
// @since 0.2.0 - 20200926 - Greasemonkey対応(unsafeWindow経由でwindowのオブジェクトを書き換え)
// @since 0.2.1 - 20210125 - RejectServiceWorkers.user.js → RejectServiceWorker.user.js
// @since 0.2.2 - 20210828 - comment メタデータの見直し
// @since 0.2.3 - 20211013 - comment 権限不足エラーの注意書きを追記
// @since 0.3.0 - 20240326 - Proxy 方式に対応(他、動作不可問題修正)
// @see https://github.com/k08045kk/UserScripts
// @see https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html
// @run-at document-start
// @grant unsafeWindow
// ==/UserScript==
;(async function(win) {
let isExec = false;
try { isExec = !!navigator.serviceWorker; } catch {}
if ( isExec === false) {
// http environmental measures
return;
}
// Reject to register a service worker
const register = new Proxy(win.ServiceWorkerContainer.prototype.register, {
apply: () => win.Promise.reject(new win.Error('Reject to register a service worker.')),
});
try {
exportFunction(register,
win.ServiceWorkerContainer.prototype,
{defineAs: 'register'});
} catch {
win.ServiceWorkerContainer.prototype.register = register;
}
// Unregister the registered service worker
const registrations = await navigator.serviceWorker.getRegistrations();
if (registrations.length) {
// Unregister service worker
const unregisterPromises = registrations.map(registration => registration.unregister());
await Promise.all(unregisterPromises);
// Delete all cache storage
const keys = await caches.keys();
const cacheDeletePromises = keys.map(key => caches.delete(key));
await Promise.all(cacheDeletePromises);
}
})(unsafeWindow || window);</code></pre></section><section id="toc-4"><h3>拡張機能で ServiceWorker をブロック(または、管理)する</h3><p>次のような拡張機能が既に作成されています。まだ、出始めの機能であるため、探せばもっと良いものがあるかもしれません。</p><h4>宣伝(筆者作成の拡張機能)</h4><p>上記ユーザスクリプトと同じような動作をします。拡張機能は、ユーザスクリプトより実行タイミングが早いため、より強力に動作します。</p><ul><li><a href="https://chrome.google.com/webstore/detail/reject-service-worker/falajmifjcihbmlokgomiklbfmgmnopd">Reject Service Worker - Chrome ウェブストア</a></li><li><a href="https://addons.mozilla.org/ja/firefox/addon/reject-service-worker/">Reject Service Worker – 🦊 Firefox (ja) 向け拡張機能を入手</a></li></ul><h4>他作者による類似の拡張機能</h4><ul><li><a href="https://chrome.google.com/webstore/detail/block-service-workers/ceokjgeibfjfcboemhdpkdalankbmnej">Block Service Workers - Chrome ウェブストア</a></li><li><a href="https://addons.mozilla.org/ja/firefox/addon/block-service-workers/">Block Service Workers – 🦊 Firefox (ja) 向け拡張機能を入手</a></li><li><a href="https://chromewebstore.google.com/detail/no-service-worker/mbhfklemgegigbfbfmfdmijkcnabgpmf">No Service Worker - Chrome ウェブストア</a></li></ul></section><section id="toc-5"><h3>ブラウザ設定で ServiceWorker を無効化する</h3><h4>Chrome(無効化できない)</h4><p>Chrome で ServiceWorker を無効にする方法は発見できませんでした。次の記事で紹介されている方法では無効化できないことを確認しています。記事内でも無効化できなかったと記載されています。そのため、現状 Chrome で ServiceWorker を無効化する場合、上記ユーザスクリプトや拡張機能を利用するのが懸命です。</p><ul><li><del><a href="https://qiita.com/rana_kualu/items/52d8cb7b200d6fefddc8">Chromeを使うなら、必ずServiceWorkersを無効化しよう</a></del></li></ul><h4>Firefox</h4><p>Firefox は、<code>about:config</code>から<code>dom.serviceWorkers.enabled</code>を<code>false</code>とすることで ServiceWorker を無効化できます。ただし、既に登録済みの ServiceWorker は動作するため、事前に<code>about:serviceworkers</code>からすべての ServiceWorker を登録解除する必要があります。</p><p>※設定の反映には、 Firefox の再起動が必要になります。</p></section><section id="toc-6"><h3>備考(スクリプト無効で無効化する)</h3><p>ServiceWorker の登録には、 JavaScript による登録作業が必要となります。そのため、 JavaScript を無効化することで ServiceWorker の登録を拒否できます。</p><p>そのため、 NoScript 等による JavaScript の無効化で ServiceWorker の登録を拒否できます。</p><p>※登録済みの場合、 ServiceWorker は動作し続けます。</p></section><section id="toc-7"><h3>備考(CSP を利用したワーカーの無効化)</h3><p><a href="https://developer.mozilla.org/ja/docs/Web/HTTP/CSP">コンテンツセキュリティポリシー (CSP) - HTTP | MDN</a></p><pre><code>Content-Security-Policy: worker-src 'none'
<meta http-equiv=Content-Security-Policy content="worker-src 'none'"></code></pre><p>上記のような Content-Security-Policy (CSP) のヘッダーまたはメタタグを追加することで、Worker / SharedWorker / ServiceWorker のソースコードを無効なコードとしてブロックできます。ワーカーのソースコードとして、読み込まれません。</p><p>※ CSP では、 Worker/SharedWorker もブロックします。<br> CSP では、 ServiceWorker だけをブロックできません。<br> Worker/SharedWorker のブロックでページ動作が不安定になる可能性があります。<br>※登録済みの場合、 ServiceWorker は動作し続けます。<br>※ dataURL もブロックします。</p><h4>例:uBlock Origin のフィルター</h4><pre><code>! 一部ページのワーカー登録を拒否する
||example.com^$csp=worker-src 'none'
! すべてのページのワーカー登録を拒否する
*$csp=worker-src 'none'
! 一部ページのワーカー登録拒否を打ち消す
@@||example.com^$csp=worker-src 'none'</code></pre><p>簡易説明</p><ul><li><code>!</code>: コメント行</li><li><code>||example.com^</code>: ドメイン単位の指定(サブドメインも含む)</li><li><code>*</code>: ワイルドカード(すべてのドメインを対象にする)</li><li><code>@@</code>: フィルターの効果を打ち消す(例外ルール)</li><li><code>$csp=</code>: Content-Security-Policy のメタタグを追加する</li><li><code>worker-src 'none'</code>: worker-src ディレクティブに空のセットを設定</li></ul><p>※詳しくは、別記事を参照:<a href="https://www.bugbugnow.net/2021/09/ublock-filter.html">uBlock Origin フィルター覚書(書き方・サンプル)</a></p><h4>例:ユーザースクリプト</h4><pre><span class="pre-code-title">BlockingWorkerWithCSP.user.js</span><code class="language-js:BlockingWorkerWithCSP.user.js">// ==UserScript==
// @name BlockingWorkerWithCSP
// @description Use CSP to block Worker.
// @note ↓↓↓ Add target page URL ↓↓↓
// @include https://example.com/*
// @note ↑↑↑ Add target page URL ↑↑↑
// @author toshi (https://github.com/k08045kk)
// @license MIT License | https://opensource.org/licenses/MIT
// @version 0.1.0
// @since 0.1.0 - 20210828 - 初版
// @see https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html
// @run-at document-end
// @grant none
// ==/UserScript==
(function() {
// Add CSP meta tag
var meta = document.createElement('meta');
meta.setAttribute('http-equiv', 'Content-Security-Policy');
meta.setAttribute('content', "worker-src 'none'");
document.head.appendChild(meta);
// Note: This user script will block all Worker, not just ServiceWorker.
// Note: It is not possible to stop a Worker that is already running.
})();</code></pre><p>簡易説明</p><ul><li>CSPのメタタグを対象ページに挿入する</li><li>CSPのメタタグにより、 Worker のソースコードの読み込みをブロックする<ul><li>メタタグ挿入前に ServiceWorker を登録された場合、どうなるか未検証</li></ul></li><li>ServiceWorker だけでなく、 Worker/SharedWorker もブロックする</li><li>既に実行中の ServiceWorker は停止できません</li></ul><p>※uBlock Origin も内部的にはユーザースクリプトと類似処理になります</p></section><section id="toc-8"><h3>備考(Cache のみを無効化する)</h3><p>ServiceWorker を動作させつつ Cache のみを無効化する方法について考えます。</p><p>結論から言えば、現状では不可能です。(ServiceWorker を無効化する方が現実的です)</p><p>一見すると ServiceWorker の登録関数同様に Cache の関数も上書きすることで実現可能なように思えます。ですが、 ServiceWorker 内部の Cache を上書きする方法がありません。埋め込みスクリプト・ユーザースクリプト・拡張機能は、 ServiceWorker のプロセスを上書きできません。</p><p>現実的な方法は、 Cache を無効化するのではなく、 Cache を即座に削除することです。ですが、storage イベントのようなキャッシュの変更を通知するイベントは準備されていません。なので、ページの読み込み・アンロード毎にキャッシュを全削除する方法が現実的です。</p><p>ただし、 ServiceWorker 自身でキャッシュしたデータが存在する前提で動作した場合、問題となる可能性があります。キャッシュからの取得失敗時にフェッチ処理を実施する柔軟性のある ServiceWorker であれば問題ありませんが、 ServiceWorker の設計次第ではページが動作しなくなります。</p><h4>コード例(キャッシュの自動全削除)</h4><pre><span class="pre-code-title">DeleteCache.user.js</span><code class="language-js:DeleteCache.user.js">// ==UserScript==
// @name DeleteCache
// @description Delete all caches.
// @note ↓↓↓ Add target page URL ↓↓↓
// @include https://example.com/*
// @note ↑↑↑ Add target page URL ↑↑↑
// @author toshi (https://github.com/k08045kk)
// @license MIT License | https://opensource.org/licenses/MIT
// @version 0.1
// @since 0.1 - 20211231 - 初版
// @see https://www.bugbugnow.net/2020/03/Reject-to-register-a-ServiceWorker.html
// @grant none
// ==/UserScript==
(() => {
const deleteCache = async () => {
for (let key of await caches.keys()) {
await caches.delete(key);
}
};
window.requestIdleCallback(deleteCache);
window.addEventListener('pagehide', deleteCache, {capture:true, passive:true});
})();</code></pre></section><section id="toc-9"><h3>備考(モバイル環境で ServiceWorker を無効化する)</h3><p>これまでの方法から、拡張機能がインストールできればモバイル環境でも ServiceWorker を無効化できます。ただし、モバイル環境で拡張機能がインストールできるのは一部のブラウザのみです。次に示すブラウザで拡張機能をインストールできます。</p><ul><li>Kiwi Browser (Android)</li><li>Firefox (Android)</li><li>Safari (iOS)</li></ul><p>※ ServiceWorker を無効化した場合、A2HS (Add to Home screen) を使用できません。<br>※ Firefox (Android) は、おすすめの拡張機能のみインストールできます。<br> uBlock Origin が利用できるため、上記 CSP の方法が使用できます。<br> Firefox 121 からおすすめ以外の拡張機能もインストールが可能になりました。<br> Reject Service Worker も Android Firefox に対応済みです。<br>※ <del>Firefox (Android)</del> / Safari (iOS) に Reject Service Worker は、未対応です。</p></section><section id="toc-10"><h3>備考(翻訳記事を追加しました)</h3><ul><li><a href="https://www.bugbugnow.net/2022/01/disable-service-worker.html">Disable Service Worker.</a></li></ul></section><section id="toc-11"><h3>参考</h3><ul><li><a href="https://qiita.com/igara/items/9564008194c74e12551e">閲覧しているサイトのServiceWorkerの登録削除とCache APIのキャッシュを全て削除するJSコード - Qiita</a></li><li><a href="https://scrapbox.io/nwtgck/%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E5%81%B4%E3%81%A7Service_Worker_(PWA)%E3%82%92%E7%84%A1%E5%8A%B9%E5%8C%96%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95">サーバー側でService Worker (PWA)を無効化する方法 - nwtgck / Ryo Ota</a></li><li><a href="https://developer.mozilla.org/ja/docs/Web/API/Navigator/serviceWorker">Navigator.serviceWorker - Web API | MDN</a></li><li><a href="https://developer.mozilla.org/ja/docs/Web/API/ServiceWorkerContainer">ServiceWorkerContainer - Web API | MDN</a></li><li><a href="https://developer.mozilla.org/ja/docs/Web/API/CacheStorage">CacheStorage - Web API | MDN</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com3tag:blogger.com,1999:blog-279447686030876252.post-28589732590912418832017-12-26T22:07:00.055+09:002024-03-14T20:52:09.053+09:00Firefox のコンテキストメニューを編集する<p>Firefox57 以降、旧アドオン廃止により使用できなくなった Menu Wizard の代替えについて考えます。残念ながら、 WebExtensions API では、コンテキストメニュー編集の拡張機能を作成できないため、自分なりに代替えしました。</p><section id="toc-1"><h3>実現方法</h3><p>これまでは、 Menu Wizard を使用してコンテキストメニューを編集していました。ですが、よくよく考えるとコンテキストメニュー(右クリックメニュー)の削除(非表示)しかしていませんでした。削除だけであれば、 userChrome.css による CSS の FirefoxUI 編集で実現できます。なので、 userChrome.css で実現します。</p></section><section id="toc-2"><h3>実現結果</h3><h4>変更前</h4><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7RhwRdtDMZ2cpReXPwAhUVB2uNrnuLn06L7mur3oWtmB3ofQhmX__AFLRvcCAb2xg6rrTpqe0Gz69LEBf8Z_w71yWnJWHqgIFRCeTHFz1fmqm2s2ESV1xypNxHCSQ32LTHMD3WWuCZR_J/s1600/20171226_01.png" alt="変更前" loading="lazy" width="250" height="271" decoding="async"><h4>変更後</h4><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsnH2NEfkWnCUV-8Aw2jQMIQCIZeLdNHeQSDIbBfmBq7yTsMU1a9LC2_TZ35C79N8C0Dgv3RMRowJOFurs1ka-VwxokZFnPtcEGb75lgYTEgffcADabvAjKujdUntJrdO55XBMr7VYhK9T/s1600/20171226_02.png" alt="変更後" loading="lazy" width="221" height="172" decoding="async"></section><section id="toc-3"><h3>userChrome.css への追加コード</h3><p>下記の追加コードを userChrome.css に導入することでコンテキストメニュー編集を実現できます。</p><p>利用者によってコンテキストメニューの好みが異なるため、お好みで設定を変更してください。表示非表示の切り替えは、行頭の<code>/*</code>を追加/削除でコンテキストメニューの表示/非表示を切り替えできます。<code>/*</code>の追加で表示、削除で非表示です。</p><p>※ userChrome.css 未導入の方は、「<a href="https://www.bugbugnow.net/2018/04/firefox-userchromecss.html">環境準備</a>」参照<br>※全角文字を含む場合(拡張機能用の記述を指定する場合)、<code>@charset</code>の指定が必要になります。また、 userChrome.css ファイルを指定文字コードで保存してください。<br>※コード量が多いため、<code>@import</code>構文で外部ファイル化することを推奨します。</p><pre><span class="pre-code-title">userChrome.css</span><code class="language-css:userChrome.css">@charset "UTF-8";
/* デフォルト名前空間をXULにする */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
/**
* @name userChrome.menus.css
* @description Firefox のコンテキストメニューを userChrome.css を利用して削除します。
* メニューの 表示/非表示 は、行頭の "/*" を 追加/削除 することで制御できます。
* お好みで変更してください。
* 不足がある場合、 README.css を確認してください。ここに含まれない項目を記載しています。
* @author toshi (https://github.com/k08045kk)
* @license MIT License | https://github.com/k08045kk/userChrome.menus.css/blob/master/LICENSE
* @version 123.20240314
* @since 57.20171227 - 初版
* @see https://github.com/k08045kk/userChrome.menus.css
* @see https://www.bugbugnow.net/2017/12/firefox-quantum-57.html
* @see https://www.bugbugnow.net/2018/04/firefox-quantum-context-menu-editing.html
*/
/* ========================================================================== */
/* #mainPopupSet, /* ポップアップ */
/* -------------------------------------------------------------------------- */
/* #tabContextMenu, /* タブ */
#context_openANewTab, /* [新しいタブ] 89+ */
#context_openANewTab + menuseparator, /* [----------------] 89+ */
#context_reloadTab, /* [タブを再読み込み] */
#context_reloadSelectedTabs, /* [タブを再読み込み] 67+ */
#context_toggleMuteTab, /* [タブをミュート] */
#context_toggleMuteSelectedTabs, /* [タブをミュート] 67+ */
/* #context_pinTab, /* [タブをピン留め] */
/* #context_unpinTab, /* [タブのピン留めを外す] */
/* #context_pinSelectedTabs, /* [タブをピン留め] 67+ */
/* #context_unpinSelectedTabs, /* [タブのピン留めを外す] 67+ */
/* #context_duplicateTab, /* [タブを複製] */
/* #context_duplicateTabs, /* [タブを複製] 67+ */
/* #context_duplicateTabs + menuseparator, /* [----------------] 67+ */
#context_bookmarkSelectedTabs, /* [タブをブックマーク...] */
#context_bookmarkTab, /* [タブをブックマーク] 64+ */
#context_moveTabOptions, /* [タブを移動] 64+ 67+ */
/* #context_moveToStart, /* [最初のタブへ移動] 67+ */
/* #context_moveToEnd, /* [最後のタブへ移動] 67+ */
/* #context_openTabInWindow, /* [新しいウィンドウへ移動] 67+ */
#context_sendTabToDevice, /* [n 個のタブを端末へ送信] n=選択タブ数 */
/* /* [接続された端末がありません] */
/* /* [----------------] */
/* /* [他の端末を接続...] */
/* /* [タブの送信について...] */
/* /* [すべての端末に送信] */
/* /* [端末を管理...] */
#tabContextMenu > .share-tab-url-item, /* [共有] 89+ 92+ */
#context_reopenInContainer, /* [新しいコンテナータブで開く] 88+ 89+ */
/* /* コンテナー */
#context_selectAllTabs, /* [すべてのタブを選択] 64+ 88+ */
#context_selectAllTabs + menuseparator, /* [----------------] 88+ */
#context_closeTab, /* [タブを閉じる] 88+ */
/* #context_closeTabOptions, /* [複数のタブを閉じる] 78+ */
/* #context_closeTabsToTheStart, /* [左側のタブをすべて閉じる] 88+ */
/* #context_closeTabsToTheEnd, /* [右側のタブをすべて閉じる] */
/* #context_closeOtherTabs, /* [他のタブをすべて閉じる] */
/* #context_undoCloseTab, /* [閉じたタブを開きなおす] 88+ */
/* #context_undoCloseTab + menuseparator, /* [----------------] 88+ */
/* /* Extensions(タブ) */
/* -------------------------------------------------------------------------- */
/* #toolbar-context-menu, /* ツールバー */
/* /* 拡張機能(ブラウザアクション) */
/* /* [----------------] */
/* /* [データの取得と変更はできません] 123+? */
/* /* [データの取得と変更を許可する条件:] 123+? */
/* /* [クリック時のみ] 123+? */
/* /* [〇〇 サイト上] 〇〇=ドメイン名 123+? */
/* /* [----------------] */
/* #toolbar-context-menu > .customize-context-manageExtension, /* [拡張機能を管理] */
#toolbar-context-menu > .customize-context-removeExtension, /* [拡張機能を削除] */
#toolbar-context-menu > .customize-context-reportExtension, /* [拡張機能を報告] */
/* /* [----------------] */
#toolbar-context-menu > .customize-context-moveToPanel, /* [オーバーフローメニューにピン留め] */
/* #toolbar-context-autohide-downloads-button, /* [履歴がないときはボタンを非表示にする] 88+? */
#toolbar-context-menu > .customize-context-removeFromToolbar, /* [ツールバーから削除] */
/* #toolbar-context-menu > .customize-context-pinToToolbar, /* [ツールバーにピン留め] 109+ */
/* #toolbarDownloadsAnchorMenuSeparator, /* [----------------] */
/* #toolbar-context-always-open-downloads-panel, /* [ダウンロード開始時にパネルを表示する] */
#toolbar-context-openANewTab, /* [新しいタブ] 89+ */
#toolbarNavigatorItemsMenuSeparator, /* [----------------] 89+ */
/* #toolbar-context-reloadSelectedTab, /* [選択したタブを再読み込み] */
/* #toolbar-context-reloadSelectedTabs, /* [選択したタブを再読み込み] */
/* #toolbar-context-bookmarkSelectedTab, /* [選択したタブをブックマーク...] */
/* #toolbar-context-bookmarkSelectedTabs, /* [選択したタブをブックマーク...] */
/* #toolbar-context-selectAllTabs, /* [すべてのタブを選択] */
/* #toolbar-context-undoCloseTab, /* [閉じたタブを開きなおす] 88+ */
/** #toolbarItemsMenuSeparator, /* [----------------] */
#toolbar-context-autohide-downloads-button[hidden] ~ #toolbar-context-undoCloseTab[hidden] + #toolbarItemsMenuSeparator, /* [----------------] **/
/** #toggle_toolbar-menubar, /* [メニューバー] */
/** #toolbar-context-menu > #toggle_PersonalToolbar,/* [ブックマークツールバー] 85+ */
/* /* [常に表示する] 85+ */
/* /* [表示しない] 85+ */
/* /* [新しいタブのみ表示する] 85+ */
/** #viewToolbarsMenuSeparator, /* [----------------] */
.customize-context-removeFromToolbar:not([hidden]):not([disabled]) ~ #toggle_toolbar-menubar, /* [メニューバー] **/
.customize-context-removeFromToolbar:not([hidden]):not([disabled]) ~ #toggle_PersonalToolbar, /* [ブックマークツールバー] **/
.customize-context-removeFromToolbar:not([hidden]):not([disabled]) ~ #viewToolbarsMenuSeparator,/* [----------------] **/
/* #toolbar-context-menu > .viewCustomizeToolbar, /* [ツールバーをカスタマイズ...] */
/* #toolbar-context-menu > menuseparator[contexttype="fullscreen"], /* [----------------] */
/* #toolbar-context-menu > .fullscreen-context-autohide, /* [ツールバーを隠す] */
/* #toolbar-context-menu > menuitem[data-l10n-id="full-screen-exit"], /* [全画面表示モードを終了] */
/* -------------------------------------------------------------------------- */
/* #contentAreaContextMenu, /* コンテンツエリア */
/* #context-navigation, /* ナビゲーション */
/* #context-back, /* [戻る] */
/* #context-forward, /* [進む] */
/* #context-reload, /* [更新] */
/* #context-stop, /* [停止] */
/* #context-bookmarkpage, /* [このページをブックマーク] */
/* #context-sep-navigation, /* [----------------] */
/* #context-viewsource-goToLine, /* [指定行へ移動...] 88+ */
/* #context-viewsource-wrapLongLines, /* [長い行を折り返す] 88+ */
/* #context-viewsource-highlightSyntax, /* [構文を強調表示] 88+ */
/* #context-sep-viewsource-commands, /* [----------------] 88+ */
/* #spell-no-suggestions, /* [修正候補なし] 88+ */
/* /* 修正候補 */
/* #spell-add-to-dictionary, /* [辞書に追加] */
/* #spell-undo-add-to-dictionary, /* [辞書への追加を元に戻す] */
/* #spell-suggestions-separator, /* [----------------] */
/* #context-openlinkincurrent, /* [選択した URL を開く] */
#context-openlinkincontainertab, /* [リンクを新しい 〇〇 タブで開く] 〇〇=コンテナ名 */
#context-openlinkintab, /* [リンクを新しいタブで開く] */
#context-openlinkinusercontext-menu, /* [リンクを新しいコンテナータブで開く] */
/* /* コンテナ */
#context-openlink, /* [リンクを新しいウィンドウで開く] */
#context-openlinkprivate, /* [リンクを新しいプライベートウィンドウで開く] */
/** #context-sep-open, /* [----------------] */
#context-openlinkincurrent[hidden] ~ #context-sep-open, /* [----------------] */
#context-bookmarklink, /* [このリンクをブックマーク] */
/* #context-savelink, /* [名前を付けてリンク先を保存...] */
#context-savelinktopocket, /* [リンクを Pocket に保存] */
/* #context-copyemail, /* [メールアドレスをコピー] */
/* #context-copyphone, /* [電話番号をコピー] 110+? */
/* #context-copylink, /* [リンクをコピー] 88+ */
#context-stripOnShareLink, /* [サイト追跡を除いたリンクをコピー] 120+ */
#context-sendlinktodevice, /* [リンクを端末へ送信] 89+ */
/* /* [接続された端末がありません] */
/* /* [----------------] */
/* /* [他の端末を接続...] */
/* /* [タブの送信について...] */
/* /* [すべての端末に送信] */
/* /* [端末を管理...] */
/* #context-sep-sendlinktodevice, /* [----------------] 89+ */
/* #context-sep-copylink, /* [----------------] */
/* #context-media-play, /* [再生] */
/* #context-media-pause, /* [一時停止] */
/* #context-media-mute, /* [ミュート] */
/* #context-media-unmute, /* [ミュート解除] */
/* #context-media-playbackrate, /* [再生速度] 110+? */
/* #context-media-playbackrate-050x, /* [0.5 倍] 110+? */
/* #context-media-playbackrate-100x, /* [1.0 倍] 110+? */
/* #context-media-playbackrate-125x, /* [1.25 倍] 110+? */
/* #context-media-playbackrate-150x, /* [1.5 倍] 110+? */
/* #context-media-playbackrate-200x, /* [2 倍] 110+? */
/* #context-media-loop, /* [連続再生] */
/* #context-leave-dom-fullscreen, /* [全画面表示モードを終了] 88+ */
/* #context-video-fullscreen, /* [全画面表示] 88+ */
/* #context-media-hidecontrols, /* [コントロールを隠す] 88+ */
/* #context-media-showcontrols, /* [コントロールを表示] 88+ */
/* #context-media-sep-video-commands, /* [----------------] 88+ */
#context-viewvideo, /* [動画を新しいタブで開く] 88+ 89+ */
/* #context-video-pictureinpicture, /* [ピクチャーインピクチャーで視聴] 85+? 102+? */
/* #context-media-sep-commands, /* [----------------] */
#context-reloadimage, /* [画像を再読み込み] */
#context-viewimage, /* [画像を新しいタブで開く] 88+ */
/* #context-saveimage, /* [名前を付けて画像を保存...] 87+ */
/* #context-video-saveimage, /* [スナップショットを保存...] 87+ 110+? */
/* #context-savevideo, /* [名前を付けて動画を保存...] 87+ */
/* #context-saveaudio, /* [名前を付けて音声を保存...] 87+ */
#context-copyimage-contents, /* [画像をコピー] */
/* #context-copyimage, /* [画像のリンクをコピー] 88+ */
/* #context-copyvideourl, /* [動画のリンクをコピー] 88+ */
/* #context-copyaudiourl, /* [音声のリンクをコピー] 88+ */
#context-sendimage, /* [画像の URL をメールで送信...] */
#context-sendvideo, /* [動画の URL をメールで送信...] */
#context-sendaudio, /* [音声の URL をメールで送信...] */
/* #context-imagetext, /* [画像からテキストをコピー] 110+? */
/* #context-viewimageinfo, /* [画像の情報を表示] 89+ (browser.menu.showViewImageInfo=true) */
/* #context-viewimagedesc, /* [画像の詳細情報を表示] 87+ */
/** #context-sep-setbackground, /* [----------------] 87+ */
#context-copyimage-contents:not([hidden]) ~ #context-sep-setbackground, /* [----------------] (#context-setDesktopBackground + 画像コンテキストメニュー用) */
#context-setDesktopBackground, /* [画像をデスクトップの背景に設定...] 87+ 88+ */
/* #context-ctp-play, /* [このプラグインを有効化] */
/* #context-ctp-hide, /* [このプラグインを非表示] */
/* #context-sep-ctp, /* [----------------] */
#context-savepage, /* [名前を付けてページを保存...] */
#context-pocket, /* [ページを Pocket に保存] */
#context-sendpagetodevice, /* [ページを端末へ送信] */
/* /* [接続された端末がありません] */
/* /* [----------------] */
/* /* [他の端末を接続...] */
/* /* [タブの送信について...] */
/* /* [すべての端末に送信] */
/* /* [端末を管理...] */
/* #fill-login, /* [保存したパスワードを使用] 84+? 88+? */
/* /* ログイン情報 84+? */
/* #fill-login-generated-password, /* [安全なパスワードを生成...] 84+? 88+ */
/* #manage-saved-logins, /* [ログイン情報を管理...] 88+ */
/* #passwordmgr-items-separator, /* [----------------] 88+ */
/* #context-undo, /* [元に戻す] */
/* #context-redo, /* [やり直し] 88+? */
/* #context-sep-undo, /* [----------------] */
/* #context-cut, /* [切り取り] */
/* #context-copy, /* [コピー] */
/* #context-paste, /* [貼り付け] */
/* #context-paste-no-formatting, /* [書式なしで貼り付け] 106+? */
/* #context-delete, /* [削除] */
/* #context-selectall, /* [すべて選択] */
/* #context-reveal-password, /* [パスワードを開示] 110+? */
#context-print-selection, /* [選択した部分を印刷] 84+ 88+ */
/* #context-sep-selectall, /* [----------------] 88+ */
/* #context-pdfjs-undo, /* [元に戻す] 110+? */
/* #context-pdfjs-redo, /* [やり直し] 110+? */
/* #context-sep-pdfjs, /* [----------------] 110+? */
/* #context-pdfjs-cut, /* [切り取り] 110+? */
/* #context-pdfjs-copy, /* [コピー] 110+? */
/* #context-pdfjs-paste, /* [貼り付け] 110+? */
/* #context-pdfjs-delete, /* [削除] 110+? */
/* #context-pdfjs-selectall, /* [すべて選択] 110+? */
/* #context-sep-pdfjs-selectall, /* [----------------] 110+? */
/* #context-take-screenshot, /* [スクリーンショットを撮影] 88+ 110+? */
/* #context-sep-screenshots, /* [----------------] 88+ 110+? */
#context-keywordfield, /* [この検索にキーワードを設定...] 88+ */
/** #context-searchselect, /* [〇〇 で検索: "△△"] 〇〇=検索エンジン、△△=選択文字列 88+ */
/* #context-searchselect-private, /* ??? 84+? 88+ */
/** #frame-sep, /* [----------------] */
#context-sep-selectall:not([hidden]) ~ #context-searchselect[hidden] ~ #frame-sep, /* [----------------] (#context-keywordfield + 入力コンテキストメニュー用) */
/* #frame, /* [このフレーム] */
#context-showonlythisframe, /* [このフレームだけを表示] */
/* #context-openframeintab, /* [フレームを新しいタブで開く] */
#context-openframe, /* [フレームを新しいウィンドウで開く] */
/* #open-frame-sep, /* [----------------] */
#context-reloadframe, /* [フレームの再読み込み] */
#context-reloadframe + menuseparator, /* [----------------] */
#context-bookmarkframe, /* [このフレームをブックマーク] */
#context-saveframe, /* [名前を付けてフレームを保存...] */
#context-saveframe + menuseparator, /* [----------------] */
#context-printframe, /* [フレームを印刷...] */
#context-printframe + menuseparator, /* [----------------] */
#context-take-frame-screenshot, /* [スクリーンショットを撮影] 110+? */
#context-sep-frame-screenshot, /* [----------------] 110+? */
/* #context-viewframesource, /* [フレームのソースを表示] */
/* #context-viewframeinfo, /* [フレームの情報を表示] */
/* #spell-separator, /* [----------------] */
/* #spell-check-enabled, /* [スペルチェックを行う] */
/* #spell-add-dictionaries-main, /* [辞書を追加...] */
/* #spell-dictionaries, /* [言語] */
/* /* 言語 */
/* #spell-language-separator, /* [----------------] */
/* #spell-add-dictionaries, /* [辞書を追加...] */
/* #context-sep-bidi, /* [----------------] */
/* #context-bidi-text-direction-toggle, /* [テキストの記述方向を切り替える] */
/* #context-bidi-page-direction-toggle, /* [ページの記述方向を切り替える] */
/* #inspect-separator, /* [----------------] */
#context-viewpartialsource-selection, /* [選択した部分のソースを表示] 88+ */
/* #context-viewsource, /* [ページのソースを表示] 88+ */
#context-inspect-a11y, /* [アクセシビリティプロパティを調査] 67+ 82+? */
/* #context-inspect, /* [調査] 82+? 89+? */
/* #context-media-eme-separator, /* [----------------] */
/* #context-media-eme-learnmore, /* [DRM の詳細...] */
/* #context-media-eme-learnmore + menuseparator, /* [----------------] */
/* /* 拡張機能(コンテンツエリア) */
/* -------------------------------------------------------------------------- */
/* #placesContext, /* ブックマーク・履歴 */
#placesContext_open, /* [開く] */
#placesContext_openBookmarkContainer\:tabs, /* [ブックマークをすべて開く] 89+ */
#placesContext_openBookmarkLinks\:tabs, /* [ブックマークをすべて開く] 89+ */
#placesContext_open\:newtab, /* [新しいタブで開く] 89+ */
#placesContext_open\:newcontainertab, /* [新しいコンテナータブで開く] 100+? */
/* /* コンテナー */
#placesContext_openContainer\:tabs, /* [タブですべて開く] 89+ */
#placesContext_openLinks\:tabs, /* [タブですべて開く] 89+ */
#placesContext_open\:newwindow, /* [新しいウィンドウで開く] 89+ */
#placesContext_open\:newprivatewindow, /* [新しいプライベートウィンドウで開く] 89+ */
#placesContext_openSeparator, /* [----------------] */
/* #placesContext_show_bookmark\:info, /* [ブックマークを編集...] 89+ */
/* #placesContext_show\:info, /* [編集...] 89+ */
/* #placesContext_show_folder\:info, /* [フォルダー名を変更...] 89+ */
/* #placesContext_deleteBookmark, /* [ブックマークを削除] 89+ */
/* #placesContext_deleteFolder, /* [フォルダーを削除] 89+ */
/* #placesContext_delete, /* [削除] 89+ */
/* #placesContext_delete_history, /* [ページを削除] 89+ */
/* #placesContext_deleteHost, /* [このサイトの履歴を消去] 89+ */
#placesContext_sortBy\:name, /* [名前順に並べ替える] 89+ */
/* #placesContext_deleteSeparator, /* [----------------] 89+ */
/* #placesContext_cut, /* [切り取り] 89+ */
/* #placesContext_copy, /* [コピー] 89+ */
/* #placesContext_paste_group, /* [貼り付け] 89+ */
/* #placesContext_editSeparator, /* [----------------] 89+ */
/* #placesContext_new\:bookmark, /* [ブックマークを追加...] */
/* #placesContext_new\:folder, /* [フォルダーを追加...] */
/* #placesContext_new\:separator, /* [区切りを追加] */
/* #placesContext_newSeparator, /* [----------------] */
/* #placesContext_paste, /* [貼り付け] 89+ */
/* #placesContext_pasteSeparator, /* [----------------] 89+ */
/* #placesContext_createBookmark, /* [ページをブックマークに追加] */
#placesContext > #toggle_PersonalToolbar, /* [ブックマークツールバー] 85+ */
/* /* [常に表示する] 85+ */
/* /* [新しいタブのみ表示する] 85+ */
/* /* [表示しない] 85+ */
/* #show-other-bookmarks_PersonalToolbar, /* [他のブックマークを表示] 85+ */
/* #placesContext_showAllBookmarks, /* [ブックマークを管理] 89+ */
/* #placesContext_showAllBookmarks + menuseparator,/* [----------------] 89+ */
/* /* 拡張機能(ブックマーク・履歴) */
/* -------------------------------------------------------------------------- */
/* #pageActionContextMenu, /* ページアクションコンテキストメニュー */
/* /* Extension (ページアクション) */
/* /* [----------------] */
/* #pageActionContextMenu > menuitem[data-l10n-id="page-action-manage-extension2"],/* [拡張機能を管理...] 110+? */
#pageActionContextMenu > menuitem[data-l10n-id="page-action-remove-extension2"],/* [拡張機能を削除] 110+? */
/* ========================================================================== */
/* 追加機能 */
/* ========================================================================== */
/* [〇〇 で検索: "△△"]を削除 */
/* リンクを右クリックした場合、削除する。 */
/* 選択中のテキストがある場合、削除しない。 */
/* -------------------------------------------------------------------------- */
/* #mainPopupSet, /* ポップアップ */
/* #contentAreaContextMenu, /* コンテンツエリア */
#context-copylink:not([hidden]) ~ #context-copy[hidden] ~ #context-searchselect,/* [〇〇 で検索: "△△"] */
#context-copylink:not([hidden]) ~ #context-copy[hidden] ~ #frame-sep, /* [----------------] */
/* ========================================================================== */
/* ブックマークツールバーメニューの[タブですべて開く]を削除 */
/* -------------------------------------------------------------------------- */
/* #navigator-toolbox, /* ナビゲーションツールボックス */
/* #PlacesToolbar, /* ブックマークツールバー */
/* /* ブックマーク */
#PlacesToolbar .bookmarks-actions-menuseparator,/* [----------------] */
#PlacesToolbar .openintabs-menuitem, /* [タブですべて開く] */
/* ========================================================================== */
/* コンテキストメニュー削除(拡張機能用)による不要な区切りを削除 */
/* ただし、[要素を調査]を拡張機能の区間に表示する。 */
/* -------------------------------------------------------------------------- */
/* #mainPopupSet, /* ポップアップ */
/* #contentAreaContextMenu, /* コンテンツエリア */
/* #context-media-eme-separator, /* [----------------] */
/* menuseparator:first-child, /* [----------------] */
/* menuseparator:last-child, /* [----------------] */
/* -------------------------------------------------------------------------- */
/* コンテキストメニューを削除(拡張機能用) */
/* コンテキストメニューのラベル名を直接指定して削除する。 */
/* メニューアイテムは、 menuitem で指定する。 */
/* メニューグループは、 menu で指定する。 */
/* -------------------------------------------------------------------------- */
/* menuitem[label="NoScript"], /* NoScript */
/* menu[label="Firefox Multi-Account Containers"], /* Firefox Multi-Account Containers */
/* ========================================================================== */
#context-dummy-dummy-dummy /* ダミー */
{
display: none !important;
}</code></pre><details><summary>その他(README.css)</summary><pre><span class="pre-code-title">README.css</span><code class="language-css:README.css">@charset "UTF-8";
/* デフォルト名前空間をXULにする */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
/**
* @name README.css
* @description userChrome.menus.css の補足情報を記載します。
* メニューの表示方法の覚書です。
* 表示しないとブラウザ開発ツールに表示されないため。
* userChrome.menus.css の削除理由の覚書です。
* 記載しておかないと忘れてしまうため。
* userChrome.menus.css に含めていない項目の覚書です。
* 不用意に項目数を増やさないため、メインファイルから分離しています。
* 必要に応じてコピー・ペーストして使用してください。
* @author toshi (https://github.com/k08045kk)
* @license MIT License | https://github.com/k08045kk/userChrome.menus.css/blob/master/LICENSE
* @version 120.20231124
* @note 57.20171227 - 初版
* @see https://github.com/k08045kk/userChrome.menus.css
* @see https://www.bugbugnow.net/2017/12/firefox-quantum-57.html
*/
/* ========================================================================== */
/* #mainPopupSet, /* ポップアップ */
/* -------------------------------------------------------------------------- *
* タブ
* 表示方法:タブを右クリック
* [削除項目] - 削除理由
* [タブを再読み込み] - 「ツールバーボタン」「<ボタンの右クリック」で代用可能
* [タブをミュート] - 「タブ上の音量ボタン」で代用可能
* [すべてのタブを選択] - 「Shift+クリック」「<ボタンの右クリック」で代用可能
* [タブをブックマーク] - 「ツールバーボタン」「ページアクション」「<ボタンの右クリック」で代用可能
* [コンテナーを開きなおす] - 「+ボタン」で代用可能
* [タブを移動] - 「タブのドラッグ」で代用可能
* [すべてのタブを再読み込み] - 「<ボタンの右クリック」で代用可能(又は、危険な操作のため)
* [すべてのタブをブックマーク...] - 「<ボタンの右クリック」で代用可能
* [タブを閉じる] - 「タブ上のXボタン」で代用可能
* -------------------------------------------------------------------------- */
/* #tabContextMenu, /* タブ */
/* userChrome.menus.css 参照 */
/* -------------------------------------------------------------------------- *
* 戻る・進むボタン
* 表示方法:戻る・進むボタンを右クリック
* -------------------------------------------------------------------------- */
/* #backForwardMenu, /* 戻る・進む */
/* /* 履歴 */
/* -------------------------------------------------------------------------- *
* 新規タブボタン
* 表示方法:タブバーの+ボタンを右クリック(Firefox Multi-Account Containersをインストールする)
* -------------------------------------------------------------------------- */
/* #new-tab-button-popup, /* 新規タブボタン(コンテナー) */
/* /* [コンテナーなし] */
/* /* [----------------] */
/* /* [個人] */
/* /* [仕事] */
/* /* [銀行取引] */
/* /* [ショッピング] */
/* /* その他 */
/* /* [----------------] */
/* /* [コンテナーを管理] */
/* -------------------------------------------------------------------------- *
* 検索バー検索エンジン
* 表示方法:検索バーに文字を入力してクリック、検索候補パネルの検索エンジンアイコンを右クリック
* see https://support.mozilla.org/ja/kb/add-or-remove-search-engine-firefox
* -------------------------------------------------------------------------- */
/* #PopupSearchAutoComplete .search-one-offs-context-menu, /* 検索バー検索エンジン */
/* #PopupSearchAutoComplete .search-one-offs-context-open-in-new-tab, /* [新しいタブで検索] */
/* #PopupSearchAutoComplete .search-one-offs-context-set-default, /* [既定の検索エンジンに設定] */
/* #PopupSearchAutoComplete .search-one-offs-context-set-default-private, /* [プライベートウィンドウの既定の検索エンジンに設定] */
/* -------------------------------------------------------------------------- *
* ???
* 表示方法:???
* -------------------------------------------------------------------------- */
/* #ContentSelectDropdown, /* ??? */
/* /* カスタム */
/* /* 今日 */
/* /* 昨日 */
/* /* 前週 */
/* /* 先月 */
/* /* 過去 7 日間 */
/* /* 過去 30 日間 */
/* -------------------------------------------------------------------------- *
* ブックマーク
* 表示方法:ブックマークのフォルダーをクリック
* -------------------------------------------------------------------------- */
/* #editBMPanel_folderMenuList, /* ブックマークフォルダー */
/* #editBMPanel_toolbarFolderItem, /* [ブックマークツールバー] */
/* #editBMPanel_bmRootItem, /* [ブックマークメニュー] */
/* #editBMPanel_unfiledRootItem, /* [他のブックマーク] */
/* #editBMPanel_chooseFolderSeparator, /* [----------------] */
/* #editBMPanel_chooseFolderMenuItem, /* [選択...] */
/* #editBMPanel_foldersSeparator, /* [----------------] */
/* /* ブックマーク */
/* -------------------------------------------------------------------------- *
* ???
* 表示方法:???
* -------------------------------------------------------------------------- */
/* #editBMPanel_folderTree, /* ??? */
/* /* [----------------] */
/* /* [列を元の順序に戻す] */
/* -------------------------------------------------------------------------- *
* サイドバーポップアップ
* 表示方法:サイドバーのvを右クリック
* -------------------------------------------------------------------------- */
/* #sidebarMenu-popup, /* サイドバーポップアップ */
/* #sidebar-switcher-bookmarks, /* [ブックマーク] */
/* #sidebar-switcher-history, /* [履歴] */
/* #sidebar-switcher-tabs, /* [同期タブ] */
/* #sidebar-switcher-tabs + toolbarseparator, /* [----------------] */
/* /* 拡張機能(サイドバー) */
/* #sidebar-extensions-separator, /* [----------------] */
/* #sidebar-reverse-position, /* [サイドバーを右側へ移動][サイドバーを左側へ移動] */
/* #sidebar-reverse-position + toolbarseparator, /* [----------------] */
/* /* [サイドバーを閉じる] */
/* -------------------------------------------------------------------------- *
* ツールバー
* 表示方法:ツールバーボタンを右クリック
* 表示方法:ブラウザアクションを右クリック
* 表示方法:ダウンロードツールチップを右クリック(#toolbar-context-autohide-downloads-button)
* [削除項目] - 削除理由
* [拡張機能を削除] - `about:addons` の […] > [削除] で代用可能
* [拡張機能を報告] - `about:addons` の […] > [報告] で代用可能
* -------------------------------------------------------------------------- */
/* #toolbar-context-menu, /* ツールバー */
/* userChrome.menus.css 参照 */
/* -------------------------------------------------------------------------- *
* ポップアップブロック
* 表示方法:ポップアップブロック通知バーのオプションボタンをクリック
* see https://support.mozilla.org/ja/kb/pop-blocker-settings-exceptions-troubleshooting
* -------------------------------------------------------------------------- */
/* #blockedPopupOptions, /* ポップアップブロック */
/* #blockedPopupAllowSite, /* [このサイト (〇〇) によるポップアップを許可する] 〇〇=ドメイン名 */
/* /* [ポップアップブロック設定を変更...] */
/* #blockedPopupDontShowMessage, /* [ポップアップをブロックするとき、このメッセージを表示しない] */
/* #blockedPopupsSeparator, /* [----------------] */
/* /* ['〇〇' を表示] 〇〇=URL */
/* -------------------------------------------------------------------------- *
* 自動非表示
* 表示方法:全画面表示時にタイトルバー、ツールバー、ブックマークバーの余白領域を右クリック
* see https://support.mozilla.org/ja/kb/how-make-firefox-and-websites-go-full-screen
* -------------------------------------------------------------------------- */
/* #autohide-context, /* 自動非表示 */
/* #autohide-context > menuitem[data-l10n-id="full-screen-autohide"], /* [ツールバーを隠す] */
/* /* [----------------] */
/* #autohide-context > menuitem[data-l10n-id="full-screen-exit"], /* [全画面表示モードを終了] */
/* -------------------------------------------------------------------------- *
* コンテンツエリア
* 表示方法:コンテンツエリアで右クリック(ページコンテキストメニュー)
* 表示方法:コンテンツエリアで文字列を選択して右クリック(選択コンテキストメニュー)
* 表示方法:コンテンツエリアでプレインのURL文字列を選択して右クリック
* 表示方法:コンテンツエリアで文字列を選択してリンクを右クリック
* 表示方法:コンテンツエリアで画像を右クリック(画像コンテキストメニュー)
* 表示方法:コンテンツエリアで読み込み失敗画像を右クリック
* 表示方法:コンテンツエリアで動画を右クリック
* 表示方法:コンテンツエリアで音声を右クリック
* 表示方法:コンテンツエリアでフレームを右クリック
* 表示方法:コンテンツエリアでリンクを右クリック(リンクコンテキストメニュー)
* 表示方法:コンテンツエリアでメールアドレス(mailto)を右クリック
* 表示方法:コンテンツエリアでリンク画像を右クリック
* 表示方法:コンテンツエリアでテキストフィールドを右クリック
* 表示方法:コンテンツエリアでテキストフィールド([name="name"])を右クリック
* 表示方法:コンテンツエリアでテキストフィールド([name="password"])を右クリック
* 表示方法:コンテンツエリアでテキストフィールドのスペルチェック状態にスペルミスして右クリック
* 表示方法:コンテンツエリアでテキストエリアを右クリック
* 表示方法:コンテンツエリアの画像ページで画像を右クリック(画像コンテキストメニュー)
* 表示方法:コンテンツエリアの画像ページで画像以外を右クリック
* 表示方法:コンテンツエリアのソースコードページを右クリック
* [削除項目] - 削除理由
* [リンクを開く] - クリック、中央クリックで代用可能
* [このリンクをブックマーク] - ページを開いてからブックマークで代用可能
* [リンクを Pocket に保存] - ページを開いてから[Pocket に保存]で代用可能
* [名前を付けてページを保存...] - メインメニュー([ファイル])・ショートカットキー[Ctrl+S]で代用可能
* [ページを Pocket に保存] - ページアクションで代用可能
* -------------------------------------------------------------------------- */
/* #contentAreaContextMenu, /* コンテンツエリア */
/* userChrome.menus.css 参照 */
/* -------------------------------------------------------------------------- *
* ピクチャーインピクチャー
* 表示方法:ピクチャーインピクチャー表示ボタンを右クリック
* 備考:[オプション] > [一般] > [ブラウズ] > [ピクチャーインピクチャーの動画操作を有効にする]
* -------------------------------------------------------------------------- */
/* #pictureInPictureToggleContextMenu, /* ピクチャーインピクチャー */
/* /* [ピクチャーインピクチャーの切り替えボタンを隠す] */
/* -------------------------------------------------------------------------- *
* ブックマーク・履歴
* 表示方法:メインメニューのブックマークを右クリック
* 表示方法:ハンバーガーメニューのブックマーク・履歴を右クリック
* 表示方法:ブラウザアクションのブラウジングライブラリー・ブックマーク・履歴を右クリック
* 表示方法:ブックマークツールバーを右クリック
* 表示方法:[他のブックマーク] にブックマークを保存する(#show-other-bookmarks_PersonalToolbar用)
* [削除項目] - 削除理由
* [開く] - クリック・中央クリックで代用可能
* -------------------------------------------------------------------------- */
/* #placesContext, /* ブックマーク・履歴 */
/* userChrome.menus.css 参照 */
/* -------------------------------------------------------------------------- *
* ページアクションコンテキストメニュー
* 表示方法:アドレスバー(URLバー)のページアクションボタンを右クリック
* 表示方法:アドレスバー(URLバー)の [︙] をクリック。メニュー項目を右クリック
* [削除項目] - 削除理由
* [拡張機能を削除] - `about:addons` の […] > [削除] で代用可能
* -------------------------------------------------------------------------- */
/* #pageActionContextMenu, /* ページアクションコンテキストメニュー */
/* userChrome.menus.css 参照 */
/* -------------------------------------------------------------------------- *
* ???
* 表示方法:???
* -------------------------------------------------------------------------- */
/* #webRTC-selectCamera-menupopup, /* ??? */
/* -------------------------------------------------------------------------- */
/* #webRTC-selectWindow-menupopup, /* ??? */
/* -------------------------------------------------------------------------- */
/* #webRTC-selectMicrophone-menupopup, /* ??? */
/* -------------------------------------------------------------------------- *
* カスタマイズ
* 表示方法:カスタマイズパレット上のツールチップを右クリック
* 表示方法:カスタマイズパネル or オーバーフローメニューの右クリック
* see https://support.mozilla.org/ja/kb/customize-firefox-controls-buttons-and-toolbars
* [削除項目] - 削除理由
* [拡張機能を削除] - `about:addons` の […] > [削除] で代用可能
* [拡張機能を報告] - `about:addons` の […] > [報告] で代用可能
* [ツールバーから削除] - カスタマイズで代用可能
* [備考] - Unified Extension Button により機能の陳腐化(109)
* -------------------------------------------------------------------------- */
/* #customizationPaletteItemContextMenu, /* カスタマイズパレット */
/* /* [ツールバーに追加] */
/* /* [オーバーフローメニューに追加] */
/* -------------------------------------------------------------------------- */
/* #customizationPanelItemContextMenu, /* カスタマイズパネルアイテム・オーバーフローメニュー */
/* #customizationPanelItemContextMenu > .customize-context-manageExtension,/* [拡張機能を管理] */
#customizationPanelItemContextMenu > .customize-context-removeExtension,/* [拡張機能を削除] */
#customizationPanelItemContextMenu > .customize-context-reportExtension,/* [拡張機能を報告] */
/* /* [----------------] */
#customizationPanelItemContextMenuPin, /* [オーバーフローメニューにピン留め] */
#customizationPanelItemContextMenuUnpin, /* [オーバーフローメニューからピン留めを外す] */
#customizationPanelItemContextMenu > .customize-context-removeFromPanel,/* [ツールバーから削除] */
#customizationPanelItemContextMenuUnpin + menuitem + menuseparator, /* [----------------] */
/* #customizationPanelItemContextMenu > .viewCustomizeToolbar, /* [カスタマイズ...] */
/* -------------------------------------------------------------------------- */
/* #customizationPanelContextMenu, /* カスタマイズパネル */
/* /* [その他の項目を追加...] */
/* -------------------------------------------------------------------------- *
* ダウンロード
* 表示方法:[ツールチップ(ブラウザアクション)] > [ダウンロード] > [ダウンロード項目] > [右クリック]
* -------------------------------------------------------------------------- */
/* #downloadsContextMenu, /* ダウンロード */
/* #downloadsContextMenu > .downloadPauseMenuItem, /* [中断] */
/* #downloadsContextMenu > .downloadResumeMenuItem, /* [再開] */
/* #downloadsContextMenu > .downloadUnblockMenuItem, /* [ダウンロードを許可] */
/* #downloadsContextMenu > .downloadUseSystemDefaultMenuItem, /* [〇〇 で開く] 98+ */
/* #downloadsContextMenu > .downloadAlwaysUseSystemDefaultMenuItem,/* [常に 〇〇 で開く] 98+ */
/* #downloadsContextMenu > .downloadAlwaysOpenSimilarFilesMenuItem,/* [常に既定のプログラムで開く] 98+ */
/* #downloadsContextMenu > .downloadShowMenuItem, /* [保存フォルダーを開く] */
/* #downloadsContextMenu > .downloadCommandsSeparator, /* [----------------] */
/* #downloadsContextMenu > menuitem[data-l10n-id="downloads-cmd-go-to-download-page"], /* [ダウンロード元のページを開く] */
/* #downloadsContextMenu > menuitem[data-l10n-id="downloads-cmd-copy-download-link"], /* [ダウンロード元の URL をコピー] */
/* /* [----------------] */
/* #downloadsContextMenu > .downloadDeleteFileMenuItem, /* [削除] 98+? */
/* #downloadsContextMenu > .downloadRemoveFromHistoryMenuItem, /* [履歴から削除] */
/* #downloadsContextMenu > menuitem[data-l10n-id="downloads-cmd-clear-list"], /* [プレビューパネルの消去] */
/* #downloadsContextMenu > menuitem[data-l10n-id="downloads-cmd-clear-downloads"], /* [ダウンロード履歴をすべて消去] 98+? */
/* -------------------------------------------------------------------------- *
* 同期タブ
* 表示方法:
* see https://support.mozilla.org/ja/kb/view-synced-tabs-other-devices
* 不具合:空白のメニューを表示する。
* see https://bugzilla.mozilla.org/show_bug.cgi?id=1683599
* -------------------------------------------------------------------------- */
/* #SyncedTabsSidebarContext, /* 同期タブ */
/* #syncedTabsOpenSelected, /* [開く] */
/* #syncedTabsOpenSelectedInTab, /* [新しいタブで開く] */
/* #syncedTabsOpenSelectedInWindow, /* [リンクを新しいウィンドウで開く] */
/* #syncedTabsOpenSelectedInPrivateWindow, /* [新しいプライベートウィンドウで開く] */
/* #syncedTabsOpenSelectedInPrivateWindow + menuseparator, /* [----------------] */
/* #syncedTabsBookmarkSelected, /* [このタブをブックマーク...] */
/* #syncedTabsCopySelected, /* [コピー] */
/* #syncedTabsCopySelected + menuseparator, /* [----------------] */
/* #syncedTabsOpenAllInTabs, /* [すべてをタブで開く] */
/* #syncedTabsManageDevices, /* [端末の管理...] */
/* #syncedTabsRefresh, /* [今すぐ同期] */
/* -------------------------------------------------------------------------- */
/* #SyncedTabsSidebarTabsFilterContext, /* 同期タブフィルター */
/* 「テキストボック」参照 /* [元に戻す] */
/* 「テキストボック」参照 /* [----------------] */
/* 「テキストボック」参照 /* [切り取り] */
/* 「テキストボック」参照 /* [コピー] */
/* 「テキストボック」参照 /* [貼り付け] */
/* 「テキストボック」参照 /* [削除] */
/* 「テキストボック」参照 /* [----------------] */
/* 「テキストボック」参照 /* [すべて選択] */
/* 「テキストボック」参照 /* [----------------] */
/* #syncedTabsRefreshFilter, /* ??? (空白の謎メニューを表示する。不具合) */
/* -------------------------------------------------------------------------- *
* 拡張機能ボタン(Unified Extensions Button)
* 表示方法:一覧の項目を右クリック
* 備考:109+対応
* see https://support.mozilla.org/en-US/kb/unified-extensions
* -------------------------------------------------------------------------- */
/* #unified-extensions-context-menu, /* 拡張機能ボタン */
/* menuitem[data-l10n-id="unified-extensions-context-menu-pin-to-toolbar"], /* [ツールバーにピン留め] */
/* menuitem[data-l10n-id="unified-extensions-context-menu-move-widget-up"], /* [上へ移動] 116+? */
/* menuitem[data-l10n-id="unified-extensions-context-menu-move-widget-down"], /* [下へ移動] 116+? */
/* menuseparator.unified-extensions-context-menu-management-separator, /* [----------------] */
/* menuitem[data-l10n-id="unified-extensions-context-menu-manage-extension"], /* [拡張機能を管理] */
/* menuitem[data-l10n-id="unified-extensions-context-menu-remove-extension"], /* [拡張機能を削除] */
/* menuitem[data-l10n-id="unified-extensions-context-menu-report-extension"], /* [拡張機能を報告] */
/* ========================================================================== */
/* #navigator-toolbox, /* ナビゲーションツールボックス */
/* -------------------------------------------------------------------------- *
* メインメニューバー
* 表示方法:[Alt]キー押下
* -------------------------------------------------------------------------- */
/* #main-menubar, /* メインメニューバー */
/* #file-menu, /* [ファイル] */
/* #edit-menu, /* [編集] */
/* #view-menu, /* [表示 */
/* #history-menu, /* [履歴] */
/* #bookmarksMenu, /* [ブックマーク] */
/* #tools-menu, /* [ツール] */
/* #helpMenu, /* [ヘルプ] */
/* 省略 */
/* -------------------------------------------------------------------------- *
* URLバー(アドレスバー)
* 表示方法:URLバーの右クリック
* -------------------------------------------------------------------------- */
/* #urlbar .textbox-contextmenu, /* URLバー(アドレスバー) */
/* 「テキストボック」参照 /* [元に戻す] */
/* 「テキストボック」参照 /* [やり直し] 88+? */
/* 「テキストボック」参照 /* [----------------] */
/* 「テキストボック」参照 /* [切り取り] */
/* 「テキストボック」参照 /* [コピー] */
/* #strip-on-share, /* [サイト追跡を除いてコピー] 120+ */
/* 「テキストボック」参照 /* [貼り付け] */
/* #paste-and-go, /* [貼り付けて移動] */
/* 「テキストボック」参照 /* [削除] */
/* 「テキストボック」参照 /* [すべて選択] */
/* #urlbar .textbox-contextmenu > .menuseparator-add-engine, /* [----------------] */
/* #urlbar .textbox-contextmenu > .context-menu-add-engine, /* ["〇〇" を追加] */
/* -------------------------------------------------------------------------- *
* URLバー(アドレスバー)
* 表示方法:表示方法不明(検索エンジンアイコンの右クリックで表示できない)
* -------------------------------------------------------------------------- */
/* #urlbar .search-one-offs-context-menu, /* URLバー(アドレスバー) */
/* #urlbar .search-one-offs-context-open-in-new-tab, /* [新しいタブで検索] */
/* #urlbar .search-one-offs-context-set-default, /* [既定の検索エンジンに設定] */
/* #urlbar .search-one-offs-context-set-default-private, /* [プライベートウィンドウの既定の検索エンジンに設定] */
/* -------------------------------------------------------------------------- *
* 検索バー
* 表示方法:検索バーの右クリック
* -------------------------------------------------------------------------- */
/* #searchbar .textbox-contextmenu, /* 検索バー */
/* 「テキストボック」参照 /* [元に戻す] */
/* 「テキストボック」参照 /* [やり直し] 88+? */
/* 「テキストボック」参照 /* [----------------] */
/* 「テキストボック」参照 /* [切り取り] */
/* 「テキストボック」参照 /* [コピー] */
/* 「テキストボック」参照 /* [貼り付け] */
/* #searchbar .searchbar-paste-and-search, /* [貼り付けて検索] */
/* 「テキストボック」参照 /* [削除] */
/* 「テキストボック」参照 /* [すべて選択] */
/* 「テキストボック」参照 /* [----------------] */
/* #searchbar .searchbar-clear-history, /* [検索履歴を消去] */
/* -------------------------------------------------------------------------- *
* ブックマークツールバー
* 表示方法:ブックマークのクリック
* [削除項目] - 削除理由
* [タブですべて開く] - 中央クリックで代用可能(誤動作防止)
* -------------------------------------------------------------------------- */
/* #PlacesToolbar, /* ブックマークツールバー */
/* /* ブックマーク */
/* #PlacesToolbar .bookmarks-actions-menuseparator,/* [----------------] */
/* #PlacesToolbar .openintabs-menuitem, /* [タブですべて開く] */
/* -------------------------------------------------------------------------- *
* カスタマイズ
* 表示方法:カスタマイズパレット
* -------------------------------------------------------------------------- */
/* #customization-container, /* カスタマイズ */
/* #customization-toolbar-menu, /* ツールバー */
/* #toggle_toolbar-menubar, /* [メニューバー] */
/* #customization-toolbar-menu > #toggle_PersonalToolbar, /* [ブックマークツールバー] */
/* #customization-toolbar-menu > #toggle_PersonalToolbar > menupopup > menuitem[data-l10n-id="toolbar-context-menu-bookmarks-toolbar-always-show-2"], /* [常に表示する] */
/* #customization-toolbar-menu > #toggle_PersonalToolbar > menupopup > menuitem[data-l10n-id="toolbar-context-menu-bookmarks-toolbar-on-new-tab-2"], /* [新しいタブのみ表示する] */
/* #customization-toolbar-menu > #toggle_PersonalToolbar > menupopup > menuitem[data-l10n-id="toolbar-context-menu-bookmarks-toolbar-never-show-2"], /* [表示しない] */
/* -------------------------------------------------------------------------- *
* テキストボックス
* 表示方法:コンテンツエリア・ブックマーク・履歴・同期タブ・URLバー・検索バー・他を右クリック
* 注意:影響範囲に注意
* #urlbar menuitem[data-l10n-id="text-action-undo"], などの子孫セレクタで
* 影響範囲を限定することを考慮してください。
* -------------------------------------------------------------------------- */
/* .textbox-contextmenu, /* テキストボックス */
/* menuitem[data-l10n-id="text-action-undo"], /* [元に戻す] */
/* menuitem[data-l10n-id="text-action-undo"] + menuseparator, /* [----------------] */
/* menuitem[data-l10n-id="text-action-redo"], /* [やり直し] 88+? */
/* menuitem[data-l10n-id="text-action-redo"] + menuseparator, /* [----------------] */
/* menuitem[data-l10n-id="text-action-cut"], /* [切り取り] */
/* menuitem[data-l10n-id="text-action-copy"], /* [コピー] */
/* menuitem[data-l10n-id="text-action-paste"], /* [貼り付け] */
/* menuitem[data-l10n-id="text-action-paste-no-formatting"], /* [書式なしで貼り付け] 106+? */
/* menuitem[data-l10n-id="text-action-delete"], /* [削除] */
/* menuitem[data-l10n-id="text-action-delete"] + menuseparator, /* [----------------] */
/* menuitem[data-l10n-id="text-action-select-all"], /* [すべて選択] */
/* menuitem[data-l10n-id="text-action-select-all"] + menuseparator, /* [----------------] */
/* ========================================================================== */
/* /* [----------------] */
/* #dummy, /* [] */
#context-dummy-dummy-dummy /* ダミー */
{
display: none !important;
}</code></pre></details></section><section id="toc-4"><h3>変更履歴</h3><div class="responsive-table"><table><thead><tr><th>更新日</th><th>内容</th></tr></thead><tbody><tr><td>2017/12/27</td><td>初版(Firefox57 / Firefox Quantum / PhotonUI)</td></tr><tr><td>2018/02/25</td><td><code>#context-sep-sendlinktodevice</code>を非表示で追記<br>リンクの区切りが2重になる問題を修正</td></tr><tr><td>2018/02/25</td><td><code>id</code>なしの [区切り] を隣接セレクタを使用して追加</td></tr><tr><td>2018/03/04</td><td><code>@charset</code>を追加</td></tr><tr><td>2018/03/04</td><td>拡張機能のコンテキストメニュー削除を追加<br>ラベル名が判明している場合、同様の方法で削除可能</td></tr><tr><td>2018/03/07</td><td>ブックマークメニューを追加</td></tr><tr><td>2018/03/07</td><td>ブックマークの [タブですべて開く] を非表示で追加<br>ブックマークメニューの [タブですべて開く] は表示のままとする</td></tr><tr><td>2018/04/14</td><td>[Take a Screenshot] を非表示で追加<br>国際化対策のため</td></tr><tr><td>2018/04/18</td><td>[[-をweb検索]] の削除を追加<br>テキスト選択時は削除しない</td></tr><tr><td>2018/04/19</td><td>ブックマークを別ページへ移動<br><a href="//www.bugbugnow.net/2018/04/firefox-userchromecss.html">Firefox userChrome.css のコードまとめ</a></td></tr><tr><td>2018/04/19</td><td>連続再生を表示で追加<br>漏れていたため</td></tr><tr><td>2018/04/19</td><td>タブですべて開くを表示で追加<br>漏れていたため</td></tr><tr><td>2018/04/19</td><td>以下の範囲のラベルがズレていたため修正<br><code>#context-sep-copylink,</code><br>...<br><code>#context-media-playbackrate,</code></td></tr><tr><td>2018/04/20</td><td>Added English version article.<br><a href="//www.bugbugnow.net/2018/04/firefox-quantum-context-menu-editing.html">Firefox Quantum context menu editing</a></td></tr><tr><td>2018/08/14</td><td>下記の項目を追加<br><code>#context-sendlink</code><br><code>#context-blockimage</code><br><code>#context-sendpage</code><br><code>#context-metadata</code><br><code>#context-spell-check-enabled,</code><br>ただし、筆者の環境では未確認の項目です。<br><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Firefox_menus">Menus - Mozilla | MDN</a> に記載があるため、追加する。</td></tr><tr><td>2018/12/12</td><td>Firefox64 対応</td></tr><tr><td>2019/01/17</td><td>Firefox64 対応、翻訳漏れを翻訳文字列に変更</td></tr><tr><td>2019/06/30</td><td>Firefox67 対応</td></tr><tr><td>2020/01/18</td><td><code>#context_toggleMuteSelectedTabs</code><br>直下のセパレーターを削除(なくなっていたため)</td></tr><tr><td>2020/01/18</td><td><code>#context_closeSelectedTabs</code><br>直下のセパレーターを追加(漏れ)</td></tr><tr><td>2020/07/18</td><td>Firefox78 対応</td></tr><tr><td>2020/09/02</td><td>「…で検索: "…"」を変更<br><code>visibility: collapse !important;</code><br>↓<br><code>display: none !important;</code></td></tr><tr><td>2020/10/01</td><td>「ページアクション」を追記</td></tr><tr><td>2020/10/31</td><td>[アクセシビリティプロパティを調査] と [要素の調査] を入れ替え<br>正確な修正バージョンは不明</td></tr><tr><td>2020/10/31</td><td>試験的にバージョン表記を追記<br>v[Firefoxバージョン].[更新日]</td></tr><tr><td>2020/12/18</td><td>Firefox84 対応 [選択した部分を印刷] 追加</td></tr><tr><td>2020/12/18</td><td>Firefox84 対応? [ログイン情報を入力] +他追加</td></tr><tr><td>2020/12/19</td><td>大規模改定(ツールバー、サイドバー、その他、追記)</td></tr><tr><td>2020/12/21</td><td>大規模改定(ポップアップブロック、<code>data-l10n-id</code>属性、追記)</td></tr><tr><td>2021/01/27</td><td>Firefox85 対応 ブックマークツールバー+他</td></tr><tr><td>2021/02/10</td><td><code>@namespace</code>を追記</td></tr><tr><td>2021/02/10</td><td>Firefox57 より前の項目削除(Firefox57 以降を対象とする)<br><del>Firefox57 以降の削除済み項目は後方互換性のため、保持する。</del></td></tr><tr><td>2021/02/11</td><td>大規模改定(1つにまとめる、インデント、他)</td></tr><tr><td>2021/02/11</td><td><a href="https://github.com/k08045kk/userChrome.menus.css">k08045kk/userChrome.menus.css - GitHub</a><br>GitHubのリポジトリを作成。英語版と管理を一括化。<br>英語版は、 GitHub へ移行。日本語版は、 GitHub を追従予定。</td></tr><tr><td>2021/02/14</td><td>大規模改定(初期値の変更、MITライセンス指定、他)</td></tr><tr><td>2021/03/24</td><td>Firefox87 対応</td></tr><tr><td>2021/04/20</td><td>Firefox88 対応</td></tr><tr><td>2021/04/21</td><td>Firefox88 対応(漏れ追記)</td></tr><tr><td>2021/04/21</td><td>指摘対応([やり直し] 追加、 [すべて選択] 直前のセパレータ廃止)</td></tr><tr><td>2021/04/21</td><td>テキストボックスを共通化<br>戻る進むボタン・ブックマークフォルダー・カスタマイズツールバー追加<br>ダウンロードパネル廃止(88-対応?)</td></tr><tr><td>2021/04/23</td><td>Firefox88 対応(漏れ追記)</td></tr><tr><td>2021/04/30</td><td>追加機能 ブックマークツールバーメニューの[タブですべて開く]を削除</td></tr><tr><td>2021/05/02</td><td>Firefox88 対応([背景画像だけを表示]廃止、他)</td></tr><tr><td>2021/06/01</td><td>Firefox89 対応(ProtonUI)</td></tr><tr><td>2021/06/02</td><td>Firefox89 対応(名称変更漏れ)</td></tr><tr><td>2021/08/06</td><td>記事タイトルを変更(Quantum の呼称を削除、既に時代遅れであるため)<br>旧題「Firefox Quantum コンテキストメニュー編集の代替え」</td></tr><tr><td>2021/09/07</td><td>Firefox92 対応([共有]のID消失)</td></tr><tr><td>2021/11/20</td><td>macOS のネイティブコンテキストメニュー用 userChrome.js を作成<br><a href="https://www.bugbugnow.net/2021/11/edit-contenxtmenu-macos-firefox.html">macOSNativeContextMenuHidden.uc.js</a></td></tr><tr><td>2021/11/27</td><td>fix 読込中画像のメニューに二重のセパレータを表示する</td></tr><tr><td>2022/03/09</td><td>Firefox98 対応(ダウンロード関連)</td></tr><tr><td>2022/03/09</td><td>[選択した URL を開く] を表示に戻す (#8)</td></tr><tr><td>2022/05/13</td><td>[ブックマーク・履歴] > [新しいコンテナータブで開く] を追加 (#11)</td></tr><tr><td>2022/10/19</td><td>fix ツールバー関連の対応漏れ修正 (#13)</td></tr><tr><td>2022/10/31</td><td>fix [書式なしで貼り付け] の対応漏れ (#14)</td></tr><tr><td>2022/11/25</td><td>fix [ピクチャーインピクチャーで視聴] 対応漏れ (#15)</td></tr><tr><td>2023/01/19</td><td>Firefox109 対応(拡張機能ボタン)</td></tr><tr><td>2023/02/16</td><td>Firefox110 対応</td></tr><tr><td>2023/08/29</td><td>Firefox116 対応?(拡張機能ボタン)</td></tr><tr><td>2023/11/24</td><td>Firefox120 対応(サイト追跡を除いたリンクをコピー)</td></tr><tr><td>2024/03/14</td><td>add [データの取得と変更を許可する条件:]</td></tr></tbody></table></div><p>※英語版・過去バージョンは、 GitHub のリポジトリを参照してください。<br> <a href="https://github.com/k08045kk/userChrome.menus.css">k08045kk/userChrome.menus.css - GitHub</a><br>※漏れや名称の食い違いなどを発見されましたら、指摘頂ければ幸いです。</p></section><section id="toc-5"><h3>備考</h3><ul><li>Firefox userChrome.css のコードまとめ<ul><li><a href="https://www.bugbugnow.net/2018/04/firefox-userchromecss.html">userChrome.css 環境準備</a></li><li><a href="https://www.bugbugnow.net/2018/04/firefox-userchromecss.html#import-css">userChrome.css の一部を外部ファイル化</a></li><li><a href="https://www.bugbugnow.net/2018/04/firefox-userchromecss.html#menu-text">メニュー項目のテキストを変更</a></li><li><a href="https://www.bugbugnow.net/2018/04/firefox-userchromecss.html#menu-order">メニュー項目の表示順序を編集</a></li><li><a href="https://www.bugbugnow.net/2018/04/firefox-userchromecss.html#menu-icon">アイコンの領域を確保する(Firefox89+ 対応)</a></li></ul></li><li>コンテキストメニュー編集の英語版<ul><li><a href="https://www.bugbugnow.net/2018/04/firefox-quantum-context-menu-editing.html">Firefox Quantum context menu editing</a><ul><li>GitHub へ移行済み</li></ul></li></ul></li><li>userChrome.js<ul><li><a href="https://www.bugbugnow.net/2018/02/CopyTabTitleAndURL.uc.js.html">Firefox userChrome.js 用ユーザスクリプトを作成する</a><ul><li>CopyTabTitleUrl.uc.js<ul><li>タブコンテキストメニューにタイトルと URL コピーの項目を追加する。</li></ul></li></ul></li><li><a href="https://www.bugbugnow.net/2018/03/nostyleucjsuserchromejs.html">タブメニューで CSS の有効無効を切替える</a><ul><li>NoStyle.uc.js<ul><li>タブコンテキストメニューに CSS 無効化の項目を追加する。</li></ul></li></ul></li><li><a href="https://www.bugbugnow.net/2021/05/bookmarkbar-not-editable-uc-js.html">Firefox のブックマークバーを編集不可にする</a><ul><li>BookmarkbarNotEditable.uc.js<ul><li>ブックマークツールバーのドラックによる編集を不可にする。</li></ul></li></ul></li></ul></li><li><a href="https://www.bugbugnow.net/2021/11/edit-contenxtmenu-macos-firefox.html">macOS版 Firefox のコンテキストメニューを編集する</a><ul><li>about:config(<code>widget.macos.native-context-menus</code>を<code>false</code>)<ul><li>ネイティブコンテキストメニューの無効化</li></ul></li><li>macOSNativeContextMenuHidden.uc.js<ul><li>macOS のネイティブコンテキストメニューに対応する。</li></ul></li></ul></li></ul></section><section id="toc-6"><h3>参考</h3><ul><li><a href="https://developer.mozilla.org/ja/docs/Tools/Browser_Toolbox">ブラウザーツールボックス - 開発ツール | MDN</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com34tag:blogger.com,1999:blog-279447686030876252.post-7737893514001060292019-05-22T06:30:00.002+09:002024-03-06T08:27:34.503+09:00古いブラウザに自動で polyfill を適用する<p>古いブラウザへのes6対応です。</p><p>古いブラウザにも一応対応したい。ただし、煩わしいことはしたくない。最新ブラウザでpolyfillによるコード量増加などの悪影響を防ぎたい。とかの場合に使うといいかも…。</p><p>ただし、HTML5やCSS3、JavaScriptの最新構文を使用できるわけではないです。polyfillなので…。</p><section id="toc-1"><h3>polyfill.io の中国企業への売却</h3><ul><li><a href="https://github.com/polyfillpolyfill/polyfill-service/issues/2834">Is it true that polyfill.io hosting is going to be owned by a Chinese company? · Issue #2834 · polyfillpolyfill/polyfill-service · GitHub</a></li></ul><p>上記の Issue の通り。 polyfill.io ドメインは、中国の CDN ベンダーに売却されました。そのため、政治的なリスク等から polyfill.io の使用を不安視する声が上がっています。</p><p>それに伴い、 Cloudflare / Fastly が polyfill.io のフォークを提供することを発表しています。本記事では、コードを Cloudflare のコードに差し替えることで対応します。</p><ul><li><a href="https://blog.cloudflare.com/polyfill-io-now-available-on-cdnjs-reduce-your-supply-chain-risk">polyfill.io now available on cdnjs: reduce your supply chain risk</a></li><li><a href="https://community.fastly.com/t/new-options-for-polyfill-io-users/2540">New options for Polyfill.io users - General - Fastly Community</a></li></ul></section><section id="toc-2"><h3>コード</h3><p>以下のコードを他<code><script></code>より先(先頭)に宣言する。</p><p>特に何も気にしない場合</p><pre><code class="language-html"><!--script src="https://polyfill.io/v3/polyfill.min.js?features=default%2Ces5%2Ces6"></script-->
<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=default%2Ces5%2Ces6"></script></code></pre><p>最新ブラウザでスクリプトの外部読み込みによるパース解析をブロックしたくない場合</p><pre><span class="pre-code-title">polyfill-loader.js</span><code class="language-js:polyfill-loader.js">/**
* polyfill-loader.js
* 同期的にES6のpolyfillを読み込む(IE9+対応)
*/
(function() {
// アローファンクション構文有無(簡易ES6判定)
function canUseArrowFunction() {
try {
Function('x=>1');
return true;
} catch (e) {
return false;
}
}
// スクリプト同期ローダー
function scriptSynchronousLoader(url) {
// IE7+
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.send(null);
if (xhr.status === 200) {
var script = document.createElement('script');
script.text = xhr.responseText;
var sc = document.getElementsByTagName('script')[0];
sc.parentNode.insertBefore(script, sc);
}
xhr = null;
}
function main() {
if (!canUseArrowFunction()) {
// 他にほしいpolyfillがあれば、URLを変更する
// see https://polyfill.io/v3/url-builder/
//scriptSynchronousLoader('https://polyfill.io/v3/polyfill.min.js?features=default%2Ces5%2Ces6')
scriptSynchronousLoader('https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=default%2Ces5%2Ces6')
}
}
main();
})();</code></pre></section><section id="toc-3"><h3>補足</h3><h4>polyfill</h4><p><code>polyfill.io</code>を利用しています。<code>polyfill.io</code>は、リクエストの User-Agent ヘッダーから、要求元ブラウザに適した polyfill を返してくれます。アクセスしたブラウザを認識して必要最小限の polyfill を自動で返してくれます。</p><h4>IE8</h4><p>IE8で<code>Object.defineProperty</code>が非DOMオブジェクトのプロパティに対する取得/設定関数はサポートされていません。そのため、polyfillの置き換えの際にエラーとなります。そのため、IE9+でしかpolyfillが効きません。(たとえ、<code>Object.defineProperty</code>のpolyfillを先に適用しても、DocumentFragment, Element, getOwnPropertySymbolsで問題が発生します)</p><h4>es5対応</h4><pre><code class="language-js">// strictモード有無(簡易ES5判定)
function canUseStrictMode() {
return (function() {"use strict"; return typeof this;}).call(1) === 'number';
}</code></pre><p>※IE9-を判定する</p><h4>Babel</h4><p>コード量の増加や事前コンパイルできる環境であれば、Babelを使用してもいいかも。</p></section><section id="toc-4"><h3>参考</h3><ul><li><a href="https://qiita.com/moriyuu/items/061de38704687fef93b9">polyfill.io が便利すぎた - Qiita</a></li><li><a href="https://qiita.com/mascii/items/5d35d4a02778946b7c8b">【JavaScript】ES6やES2017の構文が使えるか判定する方法 - Qiita</a></li><li><a href="http://sujoyu.hatenadiary.com/entry/2017/03/03/script%E3%82%BF%E3%82%B0%E3%82%92%E5%8B%95%E7%9A%84%E3%81%AB%E8%BF%BD%E5%8A%A0%E3%81%97%E3%81%A6%E3%80%81%E5%90%8C%E6%9C%9F%E3%81%97%E3%81%A6%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E3%80%82">scriptタグを動的に追加して、同期して実行する。 - とある地味なブログ</a></li><li><a href="https://w3g.jp/blog/ie_supportlist">Internet ExplorerのどのバージョンからどのHTML/CSS/JSに対応しているかの一覧</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-31654267291131688752022-02-09T14:19:00.008+09:002024-03-03T09:57:43.627+09:00Chrome の拡張機能を Manifest V3 に対応する<section id="toc-1"><h3>はじめに</h3><p>「<a href="https://www.bugbugnow.net/2018/02/chromecopytabtitleurl.html">Chromeの拡張機能を作成する</a>」で作成した Manifest V2 用の拡張機能を Manifest V3 用に変更します。</p><p>開発から既に数年経過しているため、最終的なコードからはかけ離れていますが、 Manifest V3 用のサンプルとしての位置付けでこの記事は作成されています。</p><p>※CopyTabTitleUrl の最新コードは、次のサイトで確認できます。<br> (最新コードはまだ Manifest V3 には対応していません)<br> <a href="https://github.com/k08045kk/CopyTabTitleUrl">k08045kk/CopyTabTitleUrl - GitHub</a></p></section><section id="toc-2"><h3>コード</h3><h4>新バージョン(オフスクリーン方式 Chrome109+)</h4><pre><span class="pre-code-title">manifest.js</span><code class="language-js:manifest.js">{
"manifest_version": 3,
"name": "TinyCopyTabTitleUrl",
"description": "コンテキストメニューを追加して、クリップボードへタイトルとURLをコピーします。",
"version": "3.0.2",
"background": {
"service_worker": "background.js"
},
"permissions": [
"contextMenus",
"clipboardWrite",
"offscreen"
]
}</code></pre><hr><pre><span class="pre-code-title">background.js</span><code class="language-js:background.js">// Chrome 109+
// see https://developer.chrome.com/docs/extensions/reference/offscreen/
// see #example-maintaining-the-lifecycle-of-an-offscreen-document
let creatingOffscreenDocument = null;
const hasOffscreenDocument = async (path) => {
const offscreenUrl = chrome.runtime.getURL(path);
if (chrome.runtime.getContexts) {
// Chrome 116+
const contexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [offscreenUrl],
});
return !!contexts.length;
} else {
// Chrome 109-115
// see #before-chrome-116-check-if-an-offscreen-document-is-open
for (const client of await clients.matchAll()) {
if (client.url === offscreenUrl) {
return true;
}
}
return false;
}
};
const setupOffscreenDocument = async (path) => {
// 備考:オフスクリーンドキュメントは、1つしか開けない。
// そのため、正常にクローズされなければならない。
// 別パスのオフスクリーンドキュメントが生存していてはならない。
if (!(await hasOffscreenDocument(path))) {
if (creatingOffscreenDocument) {
await creatingOffscreenDocument;
} else {
creatingOffscreenDocument = chrome.offscreen.createDocument({
url: path,
reasons: ['CLIPBOARD'],
justification: 'Used for writing to the clipboard.',
});
await creatingOffscreenDocument;
creatingOffscreenDocument = null;
}
}
};
const copyToClipboard = async (tab, text) => {
// オフスクリーン方式(Chrome 109+)
await setupOffscreenDocument('offscreen.html');
await chrome.runtime.sendMessage({
target: 'offscreen',
type: 'clipboardWrite',
text: text,
});
await chrome.offscreen.closeDocument();
// インジェクションファンクション方式
// const injectedFunction = async function(text) {
// try {
// await navigator.clipboard.writeText(text);
// //console.log('successfully');
// } catch (e) {
// //console.log('failed', e);
// }
// }
// chrome.scripting.executeScript({
// target: {tabId: tab.id},
// func: injectedFunction,
// args: [text]
// });
};
const updateContextMenus = async () => {
await chrome.contextMenus.removeAll();
chrome.contextMenus.create({
id: "context-copytab-title-url",
title: "タブのタイトルとURLをコピー",
contexts: ["all"]
});
chrome.contextMenus.create({
id: "context-copytab-title",
title: "タブのタイトルをコピー",
contexts: ["all"]
});
chrome.contextMenus.create({
id: "context-copytab-url",
title: "タブのURLをコピー",
contexts: ["all"]
});
};
chrome.runtime.onInstalled.addListener(updateContextMenus);
chrome.runtime.onStartup.addListener(updateContextMenus);
chrome.contextMenus.onClicked.addListener((info, tab) => {
switch (info.menuItemId) {
case 'context-copytab-title-url':
copyToClipboard(tab, tab.title+'\n'+tab.url);
break;
case 'context-copytab-title':
copyToClipboard(tab, tab.title);
break;
case 'context-copytab-url':
copyToClipboard(tab, tab.url);
break;
}
});</code></pre><hr><pre><span class="pre-code-title">offscreen.html</span><code class="language-html:offscreen.html"><!DOCTYPE html>
<script src="offscreen.js"></script></code></pre><hr><pre><span class="pre-code-title">offscreen.js</span><code class="language-js:offscreen.js">chrome.runtime.onMessage.addListener(async (data, sender) => {
if (data.target === 'offscreen' && data.type === 'clipboardWrite') {
const text = data.text;
/**/// a. document.execCommand('copy') ----------------------------------------
document.addEventListener('copy', () => {
event.preventDefault();
event.stopImmediatePropagation();
event.clipboardData.setData('text/plain', text);
}, {capture:true, once:true});
document.execCommand('copy');
/** // b. navigator.clipboard.writeText ---------------------------------------
// 次のエラーが発生するため、この方法は使用できません。
// 「DOMException: Document is not focused.」
try {
await navigator.clipboard.writeText(text);
//console.log('successfully');
} catch (e) {
//console.log('failed', e);
}
/**/// ------------------------------------------------------------------------
}
});</code></pre><h4>旧バージョン(インジェクションファンクション方式)</h4><details><pre><span class="pre-code-title">manifest.js</span><code class="language-js:manifest.js">{
"manifest_version": 3,
"name": "CopyTabTitleUrl",
"description": "コンテキストメニューを追加して、クリップボードへタイトルとURLをコピーします。",
"version": "3.0.1",
"background": {
"service_worker": "background.js"
},
"permissions": [
"activeTab",
"contextMenus",
"clipboardWrite",
"scripting",
"tabs"
]
}</code></pre><hr><pre><span class="pre-code-title">background.js</span><code class="language-js:background.js">const copyToClipboard = (tab, text) => {
function injectedFunction(text) {
try {
navigator.clipboard.writeText(text);
//console.log('successfully');
} catch (e) {
//console.log('failed');
}
}
chrome.scripting.executeScript({
target: {tabId: tab.id},
func: injectedFunction,
args: [text]
});
};
const updateContextMenus = async () => {
await chrome.contextMenus.removeAll();
chrome.contextMenus.create({
id: "context-copytab-title-url",
title: "タブのタイトルとURLをコピー",
contexts: ["all"]
});
chrome.contextMenus.create({
id: "context-copytab-title",
title: "タブのタイトルをコピー",
contexts: ["all"]
});
chrome.contextMenus.create({
id: "context-copytab-url",
title: "タブのURLをコピー",
contexts: ["all"]
});
};
chrome.runtime.onInstalled.addListener(updateContextMenus);
chrome.runtime.onStartup.addListener(updateContextMenus);
chrome.contextMenus.onClicked.addListener((info, tab) => {
switch (info.menuItemId) {
case 'context-copytab-title-url':
copyToClipboard(tab, tab.title+'\n'+tab.url);
break;
case 'context-copytab-title':
copyToClipboard(tab, tab.title);
break;
case 'context-copytab-url':
copyToClipboard(tab, tab.url);
break;
}
});</code></pre></details></section><section id="toc-3"><h3>コードの補足</h3><h4>chrome.runtime.onInstalled / chrome.runtime.onStartup</h4><p>元々、コンテキストメニューの登録処理は、バックグラウンドページに直接記入していました。ですが、バックグラウンドが ServiceWorker となった関係でバックグラウンドの存続期間に大きな変更がありました。</p><p>バックグラウンドの存続期間は、 Manifest V2 であればブラウザが開いてから閉じるまでです。ですが、 Manifest V3 では数分程度処理がない場合、バックグラウンドの処理は停止してしまいます。再開する場合、バックグラウンド処理を再度実施します。</p><p>Manifest V3 でもコンテキストメニューの登録処理を直接記述することはできます。ですが、バックグラウンド処理の実行毎に実行されるのはあまり気持ちが良いものではありません。そのため、<code>chrome.runtime.onInstalled / chrome.runtime.onStartup</code>で特定のタイミング(インストール時・更新時・起動時)に実行しています。</p><p><strong>備考</strong><br>この処理は、有効無効時に問題を抱えています。<br>次の記事で問題の回避策について考察します。</p><ul><li><a href="https://www.bugbugnow.net/2024/03/webextensions-onstartup.html">runtime.onStartup を有効無効切り替え時にも呼び出す</a></li></ul><h4>chrome.offscreen</h4><p>Chrome109 で実装された新機能です。 Service Worker から<code>document</code>にアクセスするためにオフスクリーンドキュメントを作成して、 Service Worker から DOM API を使用できるようにします。</p><p>ただし、オフスクリーンであるため、フォーカスが取得できないようです。そのため、<code>navigator.clipboard.writeText()</code>が使用できないようです。なので、<code>document.execCommand('copy')</code>を使用する必要があります。</p><p>オフスクリーンは、 Service Worker 同様に残存期間があります。そのため、オフスクリーンへアクセス毎にオフスクリーンのセットアップを実施する必要があります。セットアップ後、<code>chrome.runtime.sendMessage</code>でバックグランドからオフスクリーンへ通信してクリップボードコピーを実行しています。また、オフスクリーンは1つしか使用できません。そのため、複数のオフスクリーンを使用する場合、速やかにクローズする必要があります。1つしか使用しない場合、勝手にクローズするまで放置しても良いかもしれません。再オープンの時間を(メモリ容量と引き換えに)節約できるかもしれません。</p><p>※<a href="https://developer.chrome.com/docs/extensions/reference/offscreen/">chrome.offscreen - Chrome Developers</a></p><h4>chrome.scripting.executeScript</h4><p><strong>旧バージョン(インジェクションファンクション方式)のコードです。</strong></p><p>ServiceWorker には、<code>document</code>がありません。そのため、古いクリップボードコピーの方式(<code>document.execCommand("copy")</code>)が利用できません。</p><p>新しいクリップボードコピーの方式(<code>navigator.clipboard.writeText()</code>)も ServiceWorker 上からはアクセスできません。</p><p>拡張機能の方式(<code>chrome.clipboard</code>)は、テキストのコピーに対応していません。</p><p>上記の通り Manifest V3 でのクリップボードコピーは、現状八方塞がりです。ですが、アクティブタグにスクリプトを挿入することでこの問題を回避しています。ただし、システム系のタブ(<code>chrome://</code>)などで機能しないなど問題も多くあります。</p><p>別解として、新しいタブを開いてスクリプトを挿入する方法があります。より安全にスクリプトを注入できます。ただし、タブのオープン・クローズによる画面のちらつきがあります。</p><p>※正式な実装時には、要検討対象です。<br>※<a href="https://developer.chrome.com/docs/extensions/mv3/content_scripts/#programmatic">Content scripts - Chrome Developers</a></p></section><section id="toc-4"><h3>その他の Manifest V3 の問題</h3><h4>chrome.i18n.getMessage が ServiceWorker で使用できない</h4><del><p><code>chrome.i18n.getMessage</code> がバックグラウンド上の ServiceWorker からアクセスできません。</p><p>この問題は、アクションボタンのラベルやコンテキストメニューのラベルの国際化に影響します。</p><p>この問題は、まだ解決していませんが、今後解決する可能性がああります。(現在Chrome98)</p></del><p><strong>Chrome103 で問題が解決したようです。</strong></p><ul><li><a href="https://github.com/w3c/webextensions/issues/93">https://github.com/w3c/webextensions/issues/93</a></li></ul></section><section id="toc-5"><h3>関連記事</h3><ol><li><a href="https://www.bugbugnow.net/2018/02/CopyTabTitleAndURL.uc.js.html">Firefox userChrome.js用ユーザスクリプトを作成する</a></li><li><a href="https://www.bugbugnow.net/2018/02/firefoxwebextensionscopytabtitleurl.html">Firefox用WebExtensions拡張機能を作成する</a></li><li><a href="https://www.bugbugnow.net/2018/02/chromecopytabtitleurl.html">Chromeの拡張機能を作成する</a></li></ol></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-53261621002525043282024-03-01T07:09:00.001+09:002024-03-01T07:09:19.676+09:00JavaScript で paste 内容を書き換える<section id="toc-1"><h3>はじめに</h3><p>テキストフィールド、テキストエリア、編集可能な要素(contenteditable)へのペースト処理でペースト文字列を上書きして別の文字列や一部変更した文字列をペーストする処理を実現します。</p></section><section id="toc-2"><h3>サンプル</h3><pre><code class="language-js">function onPaste(event) {
const data = event.clipboardData || window.clipboardData;
const paste = data.getData('text');
// 上書き文字列の作成(改行文字を空文字に置換する)
const text = paste.replace(/\r?\n/g, '');
if (paste != text) {
if (document.execCommand('insertText', false, text)) {
// 上書きに成功した場合、元々の貼り付けイベントを中断(伝搬阻止)する
event.preventDefault();
event.stopImmediatePropagation();
}
}
};
document.getElementById('input_id').addEventListener('paste', onPaste, true);</code></pre></section><section id="toc-3"><h3>使用案</h3><ul><li>電話番号のハイフンを削除する</li><li>住所の半角数字を全角数字に置き換える</li><li>行超行末のスペースを削除する</li><li>...</li></ul></section><section id="toc-4"><h3>備考</h3><p>Text 形式だけでなく、 HTML 形式で書き込むこともできます。</p><pre><code>document.execCommand('insertText', false, text)
document.execCommand('insertHTML', false, text)</code></pre></section><section id="toc-5"><h3>参考</h3><ul><li><a href="https://developer.mozilla.org/ja/docs/Web/API/Document/execCommand">Document: execCommand() メソッド - Web API | MDN</a></li><li><a href="https://developer.mozilla.org/ja/docs/Web/API/Element/paste_event">Element: paste イベント - Web API | MDN</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-15810928503360896572020-03-02T10:09:00.014+09:002024-02-15T23:33:08.286+09:00GoogleAdsense での確定申告の書き方<section id="toc-1"><h3>はじめに</h3><p>GoogleAdSense の確定申告についてです。<br>ただし、次の内容は対象外です。(詳しく解説している他サイト等を参照してください)</p><ul><li>確定申告: 必要な人 or 不要な人</li><li>申告方法: 青色申告 or 白色申告</li><li>所得区分: 事業所得 or 雑所得</li><li>計上基準: 発生主義 or 現金主義 or 実現主義</li><li>按分計算</li><li>減価償却</li></ul><p>※わからない用語があれば、別途調べておいたほうが良いです。<br>※当記事の主な対象は、確定申告する所得税の雑所得です。</p></section><section id="toc-2"><h3>所得の内訳書</h3><p>所得の内訳書の記載例は次の通りです。</p><ul><li>所得の種類<ul><li>「雑所得」</li></ul></li><li>種目<ul><li>「その他」<ul><li>「広告収入」</li></ul></li></ul></li><li>業務に該当しますか?<ul><li>「はい」</li><li>現金主義による所得計算の特例の適用を受けますか?<ul><li>「いいえ」</li><li>詳細は、下記「<a href="#cash-basis">現金主義による所得計算の特例</a>」参照</li></ul></li></ul></li><li>収入金額<ul><li>1年間の収入合計</li></ul></li><li>必要経費<ul><li>1年間の経費合計<ul><li>ドメイン費用</li><li>サーバー費用</li><li>ネット接続費用</li><li>PC 費用</li><li>etc...(説明できる範囲でほどほどに…)</li></ul></li></ul></li><li>源泉徴収額<ul><li>未記入(記載なし)</li><li>詳細は、下記「<a href="#consumption-tax">源泉徴収は発生しているか?</a>」参照</li></ul></li><li>所得の生ずる場所又は法人番号<ul><li><del>「8 Marina View Asia Square 1 #30-01 Singapore 018960」</del></li><li><del>「Mapletree Business City II 70 Pasir Panjang Road, #03-71 Singapore 117371」</del></li><li>「法人番号:4700150006045」</li><li>詳細は、下記「<a href="#who-is-contractor">契約者はだれか?</a>」参照</li></ul></li><li>報酬などの支払者の氏名・名称<ul><li>「Google Asia Pacific Pte. Ltd.」</li><li>詳細は、下記「<a href="#who-is-contractor">契約者はだれか?</a>」参照</li></ul></li></ul><p>※収入、経費の証明書の添付は一般的に不要です。<br> ただし、税務署から書類提示を求められるため、手元に保存する必要があります。<br> 令和4年から前々年の雑所得の収入金額が1000万円超の場合、添付義務が発生します。<br> 詳細は、下記「<a href="#reiwa2">令和2年の税制改正</a>」参照</p></section><section id="toc-3"><h3><a id="who-is-contractor"></a>契約者はだれか?</h3><h4>契約会社</h4><p>「報酬などの支払者の氏名・名称」「所得の生ずる場所」を記載するために契約者の会社名と住所が必要になります。ただし、 GoogleAdSense の場合、 Google 日本法人(グーグル合同会社)と単純に契約しているわけではないため、いろいろと面倒です。</p><p>GoogleAdSense と契約している場合、契約先の会社は次の場所に記載されています。</p><ul><li>[GoogleAdSense 管理画面] > [利用規約]</li></ul><p>次のいずれかの契約先の事業体が書かれているはずです。</p><ul><li>Google LLC</li><li>Google Ireland Limited</li><li>Google Advertising (Shanghai) Company Limited</li><li>Google Asia Pacific Pte. Ltd.</li></ul><p>筆者の環境では、利用規約の序文に『「Google」とは、〇〇を意味し、』というような記述で記載されていました。</p><p>以降は、「Google Asia Pacific Pte. Ltd.」を前提にして記載します。(契約先の事業体は、地域毎に異なるようなので日本在住であれば同一の会社と契約しているものと思われます。ただし、個々人で契約内容が異なる可能性もあるため、ご自身で契約内容を再度確認してください)</p><p>※<a href="https://support.google.com/adsense/answer/1322028?hl=ja">契約先の事業体の税務情報を確認する - Google AdSense ヘルプ</a></p><h4>契約会社の所在地(Google Asia Pacific Pte. Ltd.)</h4><p>次は、契約会社の所在地はどこかです。<br>AdSense ヘルプに記載があるため、次に引用します。</p><blockquote><p>Google Asia Pacific Pte. Ltd.<br>Mapletree Business City II<br>70 Pasir Panjang Road, #03-71<br>Singapore 117371<br><cite>"<a href="https://support.google.com/adsense/answer/3025029?hl=ja">契約先の事業体が Google Asia Pacific Pte. Ltd. の場合 - AdSense ヘルプ</a>"<br>最終閲覧日: 2023年01月11日</cite></p></blockquote><p>※<a href="https://www.houjin-bangou.nta.go.jp/henkorireki-johoto.html?selHouzinNo=4700150006045">Google Asia Pacific Pte. Ltd. の情報|国税庁法人番号公表サイト</a></p><p>上記の通り、契約会社の所在地は「<strong>Mapletree Business City II 70 Pasir Panjang Road, #03-71 Singapore 117371</strong>」です。</p><h4>過去の契約会社の所在地(Google Asia Pacific Pte. Ltd.)</h4><p>法人情報を確認する限り、2016年09月01日に所在地が上記の所在地へ変更されています。<br>ですが、 AdSense ヘルプ上には最短でも2022年04月05日(<a href="https://web.archive.org/web/20220405102954/https://support.google.com/adsense/answer/3025029?hl=en">Wayback Machine</a> 参照)までは次の旧所在地が記載されていたようです。</p><del><blockquote><p>Google Asia Pacific Pte. Ltd.<br>8 Marina View<br>Asia Square 1 #30-01<br>Singapore 018960</p><p><cite>"<a href="https://support.google.com/adsense/answer/3025029?hl=ja">契約先の事業体が Google Asia Pacific Pte. Ltd. の場合 - AdSense ヘルプ</a>"<br>最終閲覧日: 2020年03月02日</cite></p></blockquote></del><p>※<a href="https://www.houjin-bangou.nta.go.jp/henkorireki-johoto.html?selHouzinNo=4700150006045">Google Asia Pacific Pte. Ltd. の情報|国税庁法人番号公表サイト</a></p><h4>e-Tax の入力(全角28文字以内に収める)</h4><p>会社名は、29文字ですが、スペースを削ることで25文字にできます。</p><pre><code>GoogleAsiaPacificPte.Ltd.</code></pre><hr><p>所在地は、法人番号が許可されているため、法人番号を記載します。</p><pre><code>4700150006045</code></pre><p>※下記のサイトで法人番号を確認できます。<br> <a href="https://www.houjin-bangou.nta.go.jp/henkorireki-johoto.html?selHouzinNo=4700150006045">Google Asia Pacific Pte. Ltd. の情報|国税庁法人番号公表サイト</a><br>※印刷して手書きで所在地を記載することも一つの方法です。</p><aside class="ads-inarticle"><ins class="adsbygoogle ads-ad ads-pending"></ins></aside></section><section id="toc-4"><h3><a id="consumption-tax"></a>源泉徴収は発生しているか?</h3><p>利用規約の源泉徴収に係る部分を次に引用します。</p><blockquote><p>お客様に対する支払いについて源泉徴収を行う義務が Google にある場合には、Google はこれをお客様に通知し、源泉徴収額を控除後、支払いを行います。Google は、このような税金の納付を行った場合には、税金納付書の原本もしくは認証付写し(または税金納付のその他の十分な証拠)をお客様に提供します。</p><p><cite>"<a href="https://www.google.com/adsense/new/localized-terms?rc=JP&ce=19">Google AdSense - 利用規約</a>"<br>最終閲覧日: 2021年02月22日</cite></p></blockquote><p>上記の通り、 Google 側で源泉徴収した場合、通知されるとあります。もしも、源泉徴収が発生した場合、何らかの通知を受け取っているはずです。ですが、筆者の知る限り通知を受け取っていません。なので源泉徴収は発生していないものと考えられます。</p><h4>消費税は発生するか?</h4><p>上記の通り GoogleAdSense との契約は、シンガポールにある外国法人との契約です。 Google の日本法人(グーグル合同会社)との契約ではありません。そのため、国外取引の扱いとなり消費税は、不課税になります。なので、消費税は発生しません。</p><p>※<a href="https://www.nta.go.jp/publication/pamph/shohi/cross/01.htm">国境を越えた役務の提供に係る消費税の課税関係について|国税庁</a></p><h4>補足(平成27年度税制改正)</h4><p>平成27年度税制改正の消費税法の一部改正が影響しています。改正前後でルールが変更されていますが、 GoogleAdSense との契約で消費税が発生しない結果に変更はありません。</p><p>※<a href="https://www.gyokaikei.com/google-adsense-consumption-tax/1959.html">Google Adsense(アドセンス)の消費税の取り扱い</a></p><h4>補足(GoogleAds)</h4><p>GoogleAdSense とは異なり、 GoogleAds(旧GoogleAdWords)は、2019年4月から契約先がグーグル合同会社(Google の日本法人)に変更され、消費税が発生するように変更されています。</p><p>※<a href="https://www.sakai-zeimu.jp/blog/archives/26023">2019年4月以降グーグルアドワーズ広告費の消費税計算は通常通りに</a><br>※「GoogleAds」は、広告出稿サービスです。<br> 「GoogleAdSense」は、広告配信サービスです。</p><h4>補足(源泉徴収票)</h4><p>源泉徴収票は、「給与所得」として確定申告書に記載した額が、適正なことを証明するための書類です。アドセンスは、給与所得ではないため、源泉徴収票はありません。</p></section><section id="toc-5"><h3>Google への税務情報の送信</h3><p>日本の確定申告とは直接関係はありませんが、 GoogleAdSense の税金に関する重大な問題であるため、触りだけ触れておきます。</p><p>GoogleAdSense のお知らせ欄に2021年3月10日から「米国での確定申告に備えましょう」が掲載されています。米国の話であり日本で活動しているため関係ないと考えてしまいがちですが、大間違いです。 YouTube の GoogleAdSense から米国外で収益を得ている場合、特に影響を受けます。具体的には、収益全体の 24% を源泉徴収されます。ただし、適切な申請をした対象の範囲外の方であれば、源泉徴収額を 0% にできます。申請の有無によって収益が大きく異なるため、適切に処理ことが重要です。</p><ul><li><a href="https://support.google.com/adsense/answer/10415189?hl=ja">米国での確定申告に備えましょう - AdSense ヘルプ</a></li><li><a href="https://support.google.com/paymentscenter/answer/10349995?hl=ja#zippy=">米国の税務情報の報告と源泉徴収 - Google お支払いセンター ヘルプ</a></li></ul><p>具体的な申請方法は、既に多くの紹介記事があるため、そちらを参照ください。<br>「GoogleAdSense 米国 源泉徴収」などで検索すれば見つかるはずです。</p></section><section id="toc-6"><h3><a id="cash-basis"></a>現金主義による所得計算の特例</h3><p>発生主義・現金主義どちらで所得計算するのかで選択してください。発生主義で所得計算できるのであれば、発生主義(「いいえ」を選択)でよいと考えます。</p><hr><p>確定申告では、一般的に「発生主義」で処理します。<br>特例で「現金主義」で処理することもできます。<br>企業などでは、主に「実現主義」で処理します。</p><p>「発生主義」は、商品を相手に引き渡したときに売上を計上します。<br>(例:広告を表示して、売上が確定したとき)</p><p>「現金主義」は、現金を受け取ったときに売上を計上します。<br>(例:Google からお金が銀行口座に入金されたとき)</p><p>「実現主義」は、会計期間内の売上を計上します。<br>(例:数年に及ぶ契約の売上がある場合、売上全体の今年分の売上のみ計上する)</p></section><section id="toc-7"><h3><a id="reiwa2"></a>令和2年の税制改正</h3><p>令和2年の税制改正では、「雑所得」に関して次のような改正が行われました。(確定申告の変更は令和4年分からです)</p><ul><li>前々年の雑所得の収入金額が300万以下の場合、当年は現金主義で計上できる</li><li>前々年の雑所得の収入金額が300万超の場合、当年の領収書等は5年間保存義務がある</li><li>前々年の雑所得の収入金額が1000万超の場合、当年の確定申告書には収入・経費の内容を記載した書類の添付義務がある</li></ul><p>※改正後の初年度(令和4年)の前々年は、令和2年です。<br>※<a href="https://www.jimin.jp/news/policy/140786.html">令和2年度税制改正大綱 | 政策 | ニュース | 自由民主党</a><br> 41ページ「(6) 雑所得を生ずべき業務に係る所得の金額の計算や確定申告について、次の見直しを行う。」参照</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-46051377659130624172024-02-09T14:06:00.004+09:002024-02-09T14:15:47.480+09:00Android Firefox 新拡張機能をデバッグする<section id="toc-1"><h3>はじめに</h3><p>Android Firefox 拡張機能のデバッグについてです。</p><p>次のバージョンから導入された新拡張機能が対象です。</p><ul><li>Android Firefox Bate 107.0+</li><li>Android Firefox 121.0+</li></ul><p>数年前に廃止済みの旧拡張機能とは異なります。</p><p>旧拡張機能のデバッグ記事は次のリンク先を参照してください。</p><ul><li><a href="https://www.bugbugnow.net/2019/01/Android-Firefox-debug.html">Android Firefox 旧拡張機能をデバッグする</a></li></ul></section><section id="toc-2"><h3>Android Firefox のリモートデバッグ環境を作成する</h3><ol><li>Android 環境に開発用の Android Firefox をインストールする<ul><li>Android Firefox / Android Firefox Beta / Android Firefox Nightly</li><li>Android Firefox でもデバッグできるようになりました</li><li>Android Firefox だけ動作しないことが稀によくありました。試験しましょう</li></ul></li><li>PC 環境に開発用の Firefox をインストールする<ul><li>Firefox / Firefox Beta / Firefox Nightly</li></ul></li><li>Android 環境で Android 端末の [開発者向けオプション] を有効にする<ul><li>[⚙] > [デバイス情報] > [ビルド番号] を7回タップする</li><li>一般的な Android のデバッグと同様です</li></ul></li><li>Android 環境で Android 端末の [USB デバッグ] を有効にする<ul><li>[開発者向けオプション] > [USB デバッグ] を有効にする</li></ul></li><li>Android 環境で Firefox の USB 経由のリモートデバッグを許可する<ul><li>[︙] > [設定] > [詳細設定] > [USB 経由でリモートデバッグする] を有効にする</li></ul></li><li>USB ケーブルで Android 端末と PC を接続する</li><li>PC 環境の Firefox で [USB デバイスを有効化] する<ul><li>[Firefox] > [about:debugging] > [セットアップ] > [USB デバイスを有効化]</li></ul></li><li>PC 環境の Firefox でデバイスが表示される<ul><li>[接続] して Android 端末で表示中のタブ等を表示できれば成功です</li></ul></li></ol><p>※備考:<code>xpinstall.signatures.required</code>の変更は不要です。<br>※ USB デバッグを前提にしていますが、ワイヤレスデバッグ等で問題ないはずです。<br>※ 実機がない場合、エミュレーター(emulator)を使用することもできます。</p></section><section id="toc-3"><h3>Android Firefox に拡張機能をインストールする</h3><p>以前は、<code>file:</code>URL 経由で拡張機能(.xpi ファイル)をインストールすることができましたが、最新環境の Firefox では file: プロトコルは使用不可になったため、使用できません。また、 Firefox は<code>content:</code>プロトコルにも対応していません。</p><p>そのため、<code>web-ext</code>経由で拡張機能をインストールします。</p><h4>web-ext をインストールする</h4><ul><li><a href="https://github.com/mozilla/web-ext">mozilla/web-ext - GitHub</a></li></ul><p><code>web-ext</code>は、<code>npm</code>からインストールできます。</p><pre><code>npm install --global web-ext</code></pre><h4>adb をインストールする</h4><p><code>web-ext</code>の動作には、<code>adb</code>を必要とします。<br><code>adb</code>(Android Debug Bridge)は、 Android 開発用のツールです。</p><p>次の場所のいずれかからインストールできます。</p><ul><li>Android Studio SDK Manager</li><li><a href="https://developer.android.com/studio/command-line/sdkmanager">sdkmanager</a> (cmdline-tools)</li><li><a href="https://developer.android.com/studio/releases/platform-tools">SDK Platform-Tools</a></li></ul><p>Android 開発に興味がない場合、 SDK Platform-Tools をダウンロードして、対象フォルダに PATH を通すとよいでしょう。実機がない場合、<code>emulator</code>で代用できます。</p><p>次のコマンドでデバイスが表示されれば<code>adb</code>の準備は完了です。</p><pre><code>adb devices</code></pre><pre><code>> adb devices
List of devices attached
123456789012345 device</code></pre><h4>web-ext で拡張機能をインストールする</h4><p>次のコマンドで拡張機能をインストールできます。 Android 端末でインストール許可を求められるため、許可すればインストールは完了です。</p><pre><code>web-ext run -t firefox-android --adb-device <adb device> --firefox-apk <firefox-apk>
web-ext run -t firefox-android --adb-device 123456789012345 --firefox-apk org.mozilla.firefox</code></pre><p>※<code><adb device></code>: <code>adb devices</code>で表示されるデバイスID<br>※<code><firefox-apk></code>: <code>org.mozilla.firefox</code>/<code>org.mozilla.firefox_beta</code>/<code>org.mozilla.fenix</code></p><h4>備考:web-ext を使用した lint</h4><p>web-ext には、 Android Firefox 用の lint 機能があります。</p><p>次のコマンドを manifest.json のあるフォルダで実行してください。</p><pre><code>web-ext lint</code></pre><h4>備考:Kiwi Browser</h4><p>Kiwi Browser は、<code>content:</code>プロトコル経由で拡張機能(.crx)をインストールすることができます。</p><ol><li>[拡張機能] > [デベロッパーモード] を有効にする</li><li>(form .zip/.crx/.user.js) からファイルを選択する</li></ol><p><code>.crx</code>ファイルは、[拡張機能をパッケージ化]から作成できます。作成した<code>.crx</code>/<code>.pem</code>を Android 端末に USB 経由で移動すればよいです。</p><h4>参考</h4><ul><li><a href="https://blog.mozilla.org/addons/2020/09/29/expanded-extension-support-in-firefox-for-android-nightly/">Expanded extension support in Firefox for Android Nightly | Mozilla Add-ons Community Blog</a></li><li><a href="https://extensionworkshop.com/documentation/develop/developing-extensions-for-firefox-for-android/">Developing extensions for Firefox for Android | Firefox Extension Workshop</a></li></ul></section><section id="toc-4"><h3>リモートデバッグする</h3><ol><li>PC と Android を USB で接続する</li><li>web-ext で拡張機能をインストールする</li><li>PC 側の Firefox からリモートデバッグする<ul><li>[about:debugging] > [セットアップ] > [USB デバイスを有効化]</li><li>デバイスが表示されたら [接続] を選択、接続先を再度選択する。</li><li>デバイスのタブ / 拡張機能 / Service Worker 等が表示されれば成功</li></ul></li><li>調査でバックグラウンドのコンソールを表示する<ul><li>拡張機能の [調査] タブを1度でも閉じると2度目からエラーになる</li><li>閉じてしまった場合、[切断] -> [接続]で再度開くことができる。</li></ul></li><li>マルチプロセスツールボックスでコンテンツスクリプトのエラーを表示する<ul><li>Firefox は、コンテンツスクリプトのエラーを拡張機能のコンソールに表示しない</li><li>[プロセス] > [マルチプロセスツールボックス] > [調査] のコンソールに表示する</li><li>大量の情報があるため、スクリプトのソース名(〇〇.js)でフィルターをかける</li><li>デスクトップ版は、 [Ctrl+Shift+J] で表示できる</li></ul></li></ol><p>※ ソース変更による[再読み込み]は、不要。 web-ext がファイルの変更を随時監視して更新するため、常に最新の状態が維持される。<br> ただし、大きな変更?をすると自動更新されないことがある。問題発生時は web-ext を再起動して確認する。</p></section><section id="toc-5"><h3>覚書</h3><ul><li>Android で動作しない機能にアクセスしているとエラーになる<ul><li><code>chrome.contextMenus</code>、<code>chrome.commands</code>でエラーになる</li><li><code>chrome.contextMenus?.〇〇</code>(オプショナルチェーン)で簡易に対応する</li><li><code>if (!chrome.contextMenus) { return; }</code> で処理から離脱して対応する</li></ul></li><li>モバイル処理の切り分け<ul><li>ユーザエージェントで判定する</li><li><code>chrome.contextMenus</code>/<code>chrome.commands</code>の有無で判定する</li></ul></li><li>ポップアップページの文字が小さい<ul><li>viewport を設定することで見やすくなる</li><li><code><head></code>内に下記を記載する<ul><li><code><meta name="viewport" content="width=device-width, initial-scale=1"></code></li></ul></li></ul></li><li>ポップアップページをクローズする<ul><li><code>window.close()</code>でクローズする</li></ul></li><li>コンテンツスクリプトが実行されない<ul><li><code>host_permissions</code>に問題を抱えてると思われる</li><li><code>content_scripts.matches</code>と<code>host_permissions</code>で2重記載で回避できる</li></ul></li></ul></section><section id="toc-6"><h3>関連記事</h3><ol><li><a href="https://www.bugbugnow.net/2018/02/CopyTabTitleAndURL.uc.js.html">Firefox userChrome.js用ユーザスクリプトを作成する</a></li><li><a href="https://www.bugbugnow.net/2018/02/firefoxwebextensionscopytabtitleurl.html">Firefox用WebExtensions拡張機能を作成する</a></li><li><a href="https://www.bugbugnow.net/2018/02/chromecopytabtitleurl.html">Chromeの拡張機能を作成する</a></li><li><a href="https://www.bugbugnow.net/2018/04/firefoxwebextensionscopytabtitleurl.html">Firefox用WebExtensions拡張機能を国際化する</a></li><li><a href="https://www.bugbugnow.net/2018/05/webextensionschromefirefox.html">Chrome、Firefox、Edgeの拡張機能を判定する</a></li><li><a href="https://www.bugbugnow.net/2018/07/blog-post_22.html">Chrome、Firefox拡張機能のブラウザアクションでポップアップあり・なしを共存する</a></li><li><a href="https://www.bugbugnow.net/2019/01/webextensions-shortcut.html">Chrome、Firefoxの拡張機能にショートカットを実装</a></li><li><a href="https://www.bugbugnow.net/2019/01/Android-Firefox-debug.html">Android Firefox 拡張機能をデバッグする</a></li></ol></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-45869019348156940472019-01-18T21:30:00.002+09:002024-02-09T14:13:37.903+09:00Android Firefox 旧拡張機能をデバッグする<p>Android Firefox 拡張機能デバッグの忘却録です。</p><p><strong>この記事は既に最新ではありません。</strong></p><p>最新の Android Firefox 拡張機能をこの記事の内容ではデバッグできません。<br>最新のデバッグ記事は次のリンク先を参照してください。</p><ul><li><a href="https://www.bugbugnow.net/2024/02/android-firefox-debug.html">Android Firefox 新拡張機能をデバッグする</a></li></ul><section id="toc-1"><h3>モバイルで拡張機能をインストールする</h3><ol><li>開発用Firefoxをインストールする<ul><li>Firefox Beta</li><li>Firefox Nightly</li></ul></li><li>未署名の拡張機能をインストール可能にする<ul><li>URLバーに<code>about:config</code>を入力する</li><li><code>xpinstall.signatures.required</code>の値を<code>false</code>にする</li></ul></li><li>作成したxpiをFirefoxにインストールする<ol><li>Android端末にxpiファイルを保存する<ul><li>USB等でファイル転送する</li></ul></li><li>xpiファイルと同一ディレクトリにインストール用のHTMLファイルを配置する<ul><li>HTMLファイル例は、「install.html」参照</li></ul></li><li>FirefoxからローカルのHTMLファイルにアクセスする<ul><li>PCで下記にファイルを配置した場合<ul><li><code>/Download/filename</code></li></ul></li><li>Firefoxのアドレス欄に下記でアクセスする<ul><li><code>file://mnt/sdcard/Download/filename</code></li></ul></li></ul></li><li>HTMLファイルのリンクをタップしてインストールする</li></ol></li><li>試験する<ul><li>下記の「リモートデバッグする」を参照</li></ul></li></ol><h4>install.html</h4><pre><span class="pre-code-title">install.html</span><code class="language-html:install.html"><html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<a href="src.xpi">XPI</a>
</body>
</html></code></pre></section><section id="toc-2"><h3>リモートデバッグする</h3><ol><li>PC側に開発用のFirefoxを立ち上げる</li><li>Android側に開発用のFirefoxを立ち上げる</li><li>Android側でUSBデバッグを許可する<ul><li>[メニュー] > [設定] > [高度な設定] > [USB経由でリモートデバッグする]<ul><li>チェックする</li></ul></li></ul></li><li>PCとAndroidをUSBで接続する</li><li>PC側にFirefoxでWebIDEを立ち上げる<ul><li>[三] > [Web Developer] > [WebIDE]</li></ul></li><li>Android側で承認する<ul><li>Android側で承認ポップアップがでるため、承認する</li></ul></li><li>PC側のWebIDEのUSB DEVICESを更新する<ul><li>右上の更新アイコン</li><li>Androidで実行中のFirefoxが表示されるので選択する<ul><li>左側に表示中のタブ情報出てくれば成功</li></ul></li></ul></li></ol><p>※左側の<code>Main Process</code>のConsoleを見ると拡張機能の(エラー)ログがでる</p></section><section id="toc-3"><h3>覚書</h3><ul><li>モバイル処理の切り分け<ul><li>ユーザエージェントで判定する</li></ul></li><li>Androidで動作しない機能にアクセスしているとエラーになる<ul><li><code>chrome.contextMenus</code>、<code>chrome.commands</code>でエラーになる</li></ul></li><li>ポップアップページの文字が小さい<ul><li>viewportを設定することで見やすくなる</li><li><code><head></code>内に下記を記載する<ul><li><code><meta name="viewport" content="width=device-width, initial-scale=1"></code></li></ul></li></ul></li><li>ポップアップページをクローズできない<ul><li><code>window.close()</code>がエラーとなる</li><li>「スクリプトはスクリプトによって開かれたウィンドウ以外を閉じることができません。」エラーを出力する</li><li>ポップアップのクローズは諦める</li></ul></li><li>ポップアップなしを再設定できない<ul><li>ポップアップなしを指定してもポップアップありで動作する<ul><li><code>chrome.browserAction.setPopup({popup: ''});</code>が動作しない</li></ul></li><li><code>setPopup</code>を1度もコールしなければ、<code>onClicked</code>が呼び出される<ul><li><code>browser_action.default_popup</code>は未指定とする</li></ul></li><li>ポップアップなしは、再起動後に有効となるものとして扱えばなんとかなる</li></ul></li><li>クリップボードコピーが動作しない<ul><li><code>execCommand('copy')</code>でコピーできない<ul><li>エラーなく処理は、通過する</li></ul></li><li>Clipboard APIで代用可能</li></ul></li></ul></section><section id="toc-4"><h3>参考</h3><ul><li><a href="https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/Browser_compatibility_for_manifest.json">manifest.json のブラウザー互換性 | MDN</a></li><li><a href="https://developer.mozilla.org/ja/docs/Tools/Remote_Debugging/Debugging_Firefox_for_Android_with_WebIDE">USB 経由で Android 版 Firefox のデバッグを行う | MDN</a></li></ul></section><section id="toc-5"><h3>関連記事</h3><ol><li><a href="https://www.bugbugnow.net/2018/02/CopyTabTitleAndURL.uc.js.html">Firefox userChrome.js用ユーザスクリプトを作成する</a></li><li><a href="https://www.bugbugnow.net/2018/02/firefoxwebextensionscopytabtitleurl.html">Firefox用WebExtensions拡張機能を作成する</a></li><li><a href="https://www.bugbugnow.net/2018/02/chromecopytabtitleurl.html">Chromeの拡張機能を作成する</a></li><li><a href="https://www.bugbugnow.net/2018/04/firefoxwebextensionscopytabtitleurl.html">Firefox用WebExtensions拡張機能を国際化する</a></li><li><a href="https://www.bugbugnow.net/2018/05/webextensionschromefirefox.html">Chrome、Firefox、Edgeの拡張機能を判定する</a></li><li><a href="https://www.bugbugnow.net/2018/07/blog-post_22.html">Chrome、Firefox拡張機能のブラウザアクションでポップアップあり・なしを共存する</a></li><li><a href="https://www.bugbugnow.net/2019/01/webextensions-shortcut.html">Chrome、Firefoxの拡張機能にショートカットを実装</a></li><li><a href="https://www.bugbugnow.net/2019/01/Android-Firefox-debug.html">Android Firefox 拡張機能をデバッグする</a></li></ol></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-31370107377433976442022-01-24T15:33:00.008+09:002024-01-29T18:27:39.868+09:00iOS / Android で拡張機能を利用できるブラウザ<section id="toc-1"><h3>はじめに</h3><p>読者の方は、パソコンのブラウザで拡張機能を利用していると思います。</p><p>モバイルでもブラウザで拡張機能を利用したいと考えてこのページにやってきたはずです。ですが、世にあるモバイル版のブラウザは、拡張機能を提供していません。広告ブロックやプライバシーブロック機能を内蔵するブラウザは多くあります。ですが、拡張機能(WebExtensionsAPI)を使用できるブラウザは限られています。</p><p>本ページでは、iOS / Android の有名所のブラウザの対応状態を記載します。もちろん、ここには書ききれていないブラウザもあります。ですが、大局は理解できるはずです。</p></section><section id="toc-2"><h3>iOS (iPhone / iPad)</h3><div class="responsive-table"><table><thead><tr><th>ブラウザ</th><th>拡張機能</th><th>広告ブロック</th><th>ブラウザ固有機能</th></tr></thead><tbody><tr><td>Safari</td><td>〇</td><td>-※1</td><td>-</td></tr><tr><td>Google Chrome</td><td>-</td><td>-※2</td><td>-</td></tr><tr><td>Microsoft Edge</td><td>-</td><td>〇※3</td><td>〇</td></tr><tr><td>Firefox</td><td>-</td><td>-</td><td>-</td></tr><tr><td>Firefox Focus</td><td>-</td><td>〇</td><td>〇</td></tr><tr><td>Brave Browser</td><td>-</td><td>〇</td><td>〇</td></tr><tr><td>DuckDuckGo</td><td>-</td><td>-</td><td>〇</td></tr><tr><td>Sleipnir Mobile</td><td>-</td><td>〇</td><td>〇</td></tr><tr><td>Opera Browser</td><td>-</td><td>〇</td><td>〇</td></tr><tr><td>Yandex Browser</td><td>-</td><td>〇</td><td>〇</td></tr><tr><td>Orion Browser</td><td>〇</td><td>〇</td><td>〇</td></tr><tr><td>...</td><td>...</td><td>...</td><td>...</td></tr></tbody></table></div><p>※1:拡張機能で代用できます。<br>※2:「煩わしい広告や誤解を招く広告」をブロックできます。<br>※3:設定を明示的に有効にする必要があります。</p></section><section id="toc-3"><h3>Android</h3><div class="responsive-table"><table><thead><tr><th>ブラウザ</th><th>拡張機能</th><th>広告ブロック</th><th>ブラウザ固有機能</th></tr></thead><tbody><tr><td>Google Chrome</td><td>-</td><td>-※2</td><td>-</td></tr><tr><td>Microsoft Edge</td><td>-</td><td>〇※3</td><td>〇</td></tr><tr><td>Firefox</td><td>△※4</td><td>-</td><td>-</td></tr><tr><td>Firefox Focus</td><td>△※4</td><td>〇</td><td>〇</td></tr><tr><td>Kiwi Browser</td><td>〇</td><td>-※1,2</td><td>〇</td></tr><tr><td>Yandex Browser</td><td>〇</td><td>〇</td><td>〇</td></tr><tr><td>Sleipnir Mobile</td><td>-※5</td><td>〇</td><td>〇</td></tr><tr><td>Opera Browser</td><td>-</td><td>〇</td><td>〇</td></tr><tr><td>Samsung Internet</td><td>-</td><td>〇</td><td>〇</td></tr><tr><td>DuckDuckGo</td><td>-</td><td>-</td><td>〇</td></tr><tr><td>Brave Browser</td><td>-</td><td>〇</td><td>〇</td></tr><tr><td>...</td><td>...</td><td>...</td><td>...</td></tr></tbody></table></div><p>※1:拡張機能で代用できます。<br>※2:「煩わしい広告や誤解を招く広告」をブロックできます。<br>※3:設定を明示的に有効にする必要があります。<br>※4:一部の拡張機能のみインストールできます。<br>※5:「エクステンション」機能を利用できます。詳細は後述。<br>※他のブラウザ<br> 例:Phoenix Browser, Adblock Browser, Tor Browser, Yahoo! Browser, Vivaldi Browser, Ghostery Privacy, Sleipnir Mobile, ...</p></section><section id="toc-4"><h3>レンダリングエンジン</h3><div class="responsive-table"><table><thead><tr><th>ブラウザ</th><th>備考</th></tr></thead><tbody><tr><td>Safari on iOS</td><td>WebKit</td></tr><tr><td>WebView on iOS</td><td>WebKit (WKWebView / UIWebView / SFSafariViewController)</td></tr><tr><td>Chrome Android</td><td>27-:WebKit / 28+:Blink</td></tr><tr><td>WebView Android</td><td>4.3-:WebKit / 4.4+:Blink / 7.0+:Chrome Android に統合</td></tr><tr><td>Firefox for Android</td><td>68:Gecko / 69+:GeckView</td></tr><tr><td>Opera Android</td><td>12-:Presto / 14+:Chronium(WebKit / Blink)</td></tr><tr><td>Samsung Internet</td><td>Chronium系(一部独自実装)</td></tr></tbody></table></div><h4>iOS のサードパーティーブラウザ</h4><p>iOS(iPhone/iPad)では、OS同封のレンダリングエンジン以外の提供が認められていません。そのため、サードパーティー製のブラウザは、 Apple 開発の WebView(WebKit) 機能を使用してブラウザ機能を実現しています。</p><p>Chrome, Firefox などの独自のレンダリングエンジンを持つブラウザも、例外なく Apple 開発の WebView(WebKit) 機能を使用しています。</p><p>※Safari と WebView で一部機能の対応状態が異なることがあります。<br>※<a href="https://qiita.com/yoshitake_1201/items/05a13fd77c18ff380eb6">iOSのバージョンと Safariバージョンの対応表(2021/12/29 現在 - Qiita</a></p><h4>他のサードパーティーブラウザ</h4><p>上記に記載のないサードパーティーブラウザは、基本的にOS標準の WebView を使用しています。もしくは、WebKit / Blink / GeckView を使用しています。</p><h4>Apple によるブラウザエンジン規制の緩和(iOS17.4~)</h4><p>iOS17.4 から WebKit 以外のブラウザエンジンが利用可能になる。</p><p>規制緩和の条件が厳しく設定されているため、現状では Chronium / Gecko ベース以外のブラウザを使用することは難しいと思われる。ただし、 Chronium / Gecko が市場の大半を占めているため、一般ユーザーが意識することはない。</p><p>今後、対応ブラウザがリリースされれば、拡張機能の動作有無にも変化があるかもしれない。</p><ul><li><a href="https://blog.jxck.io/entries/2024-01-28/apple-sideloading.html">Apple によるブラウザエンジン規制の緩和 | blog.jxck.io</a></li></ul><h4>Apple のミニアプリ規制緩和</h4><p>アプリ内のミニアプリに変更があった場合、毎回アプリの審査をやり直す必要がある、というルールだった。これを、「アプリ内で提供されるアプリについても、App Storeのガイドラインに沿う必要がある」の制約はあるものの、ミニアプリの変更に伴う審査が不要になる。</p><p>これにより、 Safari 以外のブラウザで拡張機能実装の可能性ががでてきた。</p><ul><li><a href="https://www.watch.impress.co.jp/docs/series/nishida/1564318.html">アップル、EUで外部ストア解放 日本への影響と「もう1つの解放」【西田宗千佳のイマトミライ】-Impress Watch</a></li></ul><h4>Android 4.4 より前の標準ブラウザ</h4><p>Android 4.4 から Android の標準ブラウザとして、 Chrome が標準搭載されています。それ以前には、 Chrome とは異なるブラウザが標準ブラウザとして搭載されていました。</p><h4>参考</h4><ul><li><a href="https://ja.wikipedia.org/wiki/Android%E6%A8%99%E6%BA%96%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6">Android標準ブラウザ - Wikipedia</a></li><li><a href="https://ja.wikipedia.org/wiki/Google_Chrome">Google Chrome - Wikipedia</a></li><li><a href="https://ja.wikipedia.org/wiki/Firefox_for_Mobile">Firefox for Mobile - Wikipedia</a></li><li><a href="https://ja.wikipedia.org/wiki/Opera_Mobile">Opera Mobile - Wikipedia</a></li><li><a href="https://ja.wikipedia.org/wiki/Galaxy%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6">Galaxyブラウザ - Wikipedia</a></li></ul><aside class="ads-inarticle"><ins class="adsbygoogle ads-ad ads-pending"></ins></aside></section><section id="toc-5"><h3>Safari</h3><ul><li>インストール<ul><li>iOS: 標準インストール済み</li><li>Android: なし</li></ul></li><li>拡張機能<ul><li>Safari15 以降は、拡張機能(WebExtensionsAPI)を利用できます。</li><li>Apple App Store から Safari 用の拡張機能をインストールできます。<ul><li>インストールした拡張機能は、Safari側で許可することで使用できます。</li><li>インストールだけでは、Safariで拡張機能を使用できません。</li><li>[設定] > [Safari] > [拡張機能] から許可することができます。</li></ul></li><li>Chrome Web Store の拡張機能は、インストールできません。<ul><li>Chrome系の拡張機能でも変換ツールにより、Safari用の拡張機能に変換できます。</li><li>自作アプリ(拡張機能)として、導入することができます。</li></ul></li></ul></li><li>広告ブロック<ul><li>Safari9 以降は、コンテンツブロック拡張機能(Content Blocking API)を利用できます。<ul><li>静的なコンテンツブロックを利用できます。</li></ul></li><li>Safari15 以降は、拡張機能(WebExtensionsAPI)を利用できるため、より柔軟な機能を利用できます。<ul><li>コンテンツスクリプトなどの機能を利用できます。</li></ul></li><li>例<ul><li><a href="https://apps.apple.com/jp/app/id1047223162">「AdGuard ー Safariでしっかり広告ブロック」をApp Storeで</a></li><li><a href="https://apps.apple.com/us/app/id1335413823">Ka-Block! on the App Store</a></li><li><a href="https://apps.apple.com/us/app/id1436953057">Ghostery – Privacy Ad Blocker on the App Store</a></li></ul></li></ul></li><li>備考<ul><li>最新の iOS版 Safari の uBlock Origin はありません。<ul><li>Safari12 までのレガシー拡張機能には存在しました。</li><li><a href="https://github.com/el1t/uBlock-Safari/issues/158">Explanation of the state of uBlock Origin (and other blockers) for Safari</a></li></ul></li></ul></li></ul></section><section id="toc-6"><h3>Google Chrome</h3><ul><li>インストール<ul><li>iOS: <a href="https://apps.apple.com/jp/app/id535886823">「Google Chrome - ウェブブラウザ」をApp Storeで</a></li><li>Android: <a href="https://play.google.com/store/apps/details?id=com.android.chrome&hl=ja&gl=US">Google Chrome - Google Play の Android アプリ</a></li></ul></li><li>拡張機能<ul><li>モバイル版の Chrome では、拡張機能はインストールできません。</li><li>ですが、一部の Chronium 系のブラウザは拡張機能をインストールできます。<ul><li>Kiwi Browser, Yandex Browser 参照</li></ul></li></ul></li></ul></section><section id="toc-7"><h3>Microsoft Edge</h3><ul><li>インストール<ul><li>iOS: <a href="https://apps.apple.com/jp/app/id1288723196">「Microsoft Edge」をApp Storeで</a></li><li>Android: <a href="https://play.google.com/store/apps/details?id=com.microsoft.emmx&hl=ja&gl=US">Microsoft Edge: Webブラウザー - Google Play のアプリ</a></li></ul></li><li>広告ブロック<ul><li>設定 > プライバシーとセキュリティ > 広告ブロック<ul><li>[広告のブロック] を有効にする</li><li>[受け入れることができる広告を許可] を無効にする</li></ul></li><li>iOS / Android 対応</li></ul></li><li>ブラウザ固有機能<ul><li>広告ブロック、コレクション、Microsoft Rewards、...</li></ul></li><li>備考<ul><li>Chromium系のブラウザ</li></ul></li></ul></section><section id="toc-8"><h3>Firefox</h3><ul><li>インストール<ul><li>iOS: <a href="https://apps.apple.com/jp/app/id989804926">「Firefox ウェブブラウザー」をApp Storeで</a></li><li>iOS: <a href="https://apps.apple.com/jp/app/id1055677337">「Firefox Focus: プライバシーブラウザー」をApp Storeで</a></li><li>Android: <a href="https://play.google.com/store/apps/details?id=org.mozilla.firefox&hl=ja&gl=US">Firefox ブラウザー - Google Play のアプリ</a></li><li>Android: <a href="https://play.google.com/store/apps/details?id=org.mozilla.focus&hl=ja&gl=US">Firefox Focus - Google Play の Android アプリ</a></li></ul></li><li>拡張機能(アドオン)<ul><li>一部の拡張機能のみインストールできます。</li><li>Firefox79 時点で、次の9つのみインストールできます。<ul><li>Dark Reader, Decentraleyes, HTTPS Everywhere, NoScript, Privacy Badger, Privacy Possum, Search by Image, uBlock Origin, YouTube High Definition</li></ul></li><li>Firefox120 から addons.mozilla.org (AMO) のアドオンをインストールできます。<ul><li>ただし、すべてのアドオンではなくモバイル対応済みのアドインに限られます。</li></ul></li><li>iOS版は、拡張機能を利用できません。</li></ul></li><li>広告ブロック<ul><li>広告ブロックを標準搭載した Firefox Focus があります。<ul><li>iOS / Android 対応</li></ul></li></ul></li><li>ブラウザ固有機能(Firefox Focus)<ul><li>コンテンツトラッカー(広告、アクセス解析、ソーシャルトラッカー)、...</li></ul></li><li>エンジン<ul><li>Firefox68(初期Android版)は、Geckoエンジンを採用しています。</li><li>Firefox69(Android版)以降は、GeckoViewエンジンを採用しています。</li><li>iOS版は、WebKitエンジンを採用しています。</li></ul></li></ul></section><section id="toc-9"><h3>Kiwi Browser</h3><ul><li>インストール<ul><li>iOS: なし</li><li>Android: <a href="https://play.google.com/store/apps/details?id=com.kiwibrowser.browser&hl=ja&gl=US">Kiwi Browser - Fast & Quiet - Google Play のアプリ</a></li></ul></li><li>拡張機能<ul><li>Chrome 用の拡張機能を利用できます。<ul><li>iOS 版はありません。</li></ul></li></ul></li><li>ブラウザ固有機能<ul><li>ナイトモード、AMPを無効にする、...</li></ul></li><li>備考<ul><li>Chromium系のブラウザ</li></ul></li></ul></section><section id="toc-10"><h3>Yandex Browser</h3><ul><li>インストール<ul><li>iOS: <a href="https://apps.apple.com/jp/app/id483693909">「Yandex Browser」をApp Storeで</a></li><li>Android: <a href="https://play.google.com/store/apps/details?id=com.yandex.browser&hl=ja">Яндекс.Браузер — с Алисой - Google Play のアプリ</a></li></ul></li><li>拡張機能<ul><li>Chrome 用の拡張機能を利用できます。<ul><li>iOS 版では使用できません。</li></ul></li></ul></li><li>ブラウザ固有機能<ul><li>広告ブロック、自動翻訳、...</li></ul></li><li>備考<ul><li>Chromium系のブラウザ</li></ul></li></ul></section><section id="toc-11"><h3>Sleipnir Mobile</h3><ul><li>インストール<ul><li>iOS: <a href="https://apps.apple.com/jp/app/id404732112">「Sleipnir Mobile」をApp Storeで</a></li><li>Android: <a href="https://play.google.com/store/apps/details?id=jp.co.fenrir.android.sleipnir&hl=ja">Sleipnir Mobile - ウェブブラウザ - Google Play のアプリ</a></li></ul></li><li>拡張機能?<ul><li>「エクステンション」という名称となっていますが、実質的にはユーザースクリプトです。<ul><li><a href="https://blog.fenrir-inc.com/jp/2012/02/sleipnir-mobile-for-android-extension120228.html">Sleipnir Mobile for Android をよりパワフルにするエクステンション紹介</a></li></ul></li><li>独自の <a href="http://extensions.fenrir-inc.com/">Extensions Gallery</a> からエクステンションをインストールできます。<ul><li>Chrome Web Store の拡張機能は、インストールできません。</li><li>Android 版のみの機能です。 iOS 版では使用できません。</li></ul></li></ul></li><li>ブラウザ固有機能<ul><li>広告ブロック、ジェスチャー、Extensions、...</li></ul></li><li>備考<ul><li>Windows 版の Sleipnir は、Chrome 拡張機能を使用できますが、モバイル版は使用できません。</li></ul></li></ul></section><section id="toc-12"><h3>Orion Browser</h3><ul><li>インストール<ul><li>iOS: <a href="https://browser.kagi.com/">Orion Browser by Kagi</a></li><li>Android: なし</li></ul></li><li>拡張機能<ul><li>Firefox / Chrome / Safari 用の拡張機能を利用できます。</li></ul></li><li>広告ブロック<ul><li>標準でネイティブコンテンツブロッカーを使用できます。<ul><li>in Preferences > Website Settings > Content Blocker</li></ul></li><li>uBlock Origin を別途インストールすることもできます。</li></ul></li><li>ブラウザ固有機能<ul><li>ネイティブコンテンツブロッカー、...</li></ul></li><li>備考<ul><li>WebKit系のブラウザ</li><li>ベータテスト中です。</li><li>Google Play の Orion Browser Pro とは別物です。</li></ul></li><li>参考<ul><li><a href="https://browser.kagi.com/faq.html">Orion F.A.Q.</a></li></ul></li></ul></section><section id="toc-13"><h3>他のブラウザ</h3><ul><li>Opera Browser<ul><li>インストール<ul><li>iOS: <a href="https://apps.apple.com/jp/app/id1411869974">「Opera: 高速でプライベートなブラウザ」をApp Storeで</a></li><li>Android: <a href="https://play.google.com/store/apps/details?id=com.opera.mini.native&hl=ja&gl=US">VPN を備えた Opera ブラウザ - Google Play のアプリ</a></li></ul></li><li>ブラウザ固有機能<ul><li>広告ブロック、ナイトモード、データ節約、...</li></ul></li></ul></li><li>Samsung Internet<ul><li>インストール<ul><li>iOS: なし</li><li>Android: <a href="https://play.google.com/store/apps/details?id=com.sec.android.app.sbrowser&hl=ja&gl=US">Internet Browser - Google Play のアプリ</a></li></ul></li><li>ブラウザ固有機能<ul><li>コンテンツブロッカー、スマートアンチトラッキング、HTTPSの有線接続、...</li></ul></li></ul></li><li>DuckDuckGo Privacy Browser<ul><li>インストール<ul><li>iOS: <a href="https://apps.apple.com/jp/app/id663592361">「DuckDuckGo Privacy Browser」をApp Storeで</a></li><li>Android: <a href="https://play.google.com/store/apps/details?id=com.duckduckgo.mobile.android&hl=ja&gl=US">DuckDuckGo Privacy Browser - Google Play のアプリ</a></li></ul></li><li>ブラウザ固有機能<ul><li>広告トラッカーネットワークの回避、暗号化保護の強化、...</li></ul></li></ul></li><li>Brave Browser<ul><li>インストール<ul><li>iOS: <a href="https://apps.apple.com/jp/app/id1052879175">「Brave - 広告ブロック ウェブブラウザ アプリ」をApp Storeで</a></li><li>Android: <a href="https://play.google.com/store/apps/details?id=com.brave.browser&hl=ja&gl=US">Brave 広告をブロックする高速ブラウザ adblock - Google Play のアプリ</a></li></ul></li><li>ブラウザ固有機能<ul><li>広告ブロック、バッテリーとデータの無駄遣いを防止、...</li></ul></li></ul></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-6323644214485986872022-11-21T13:53:00.006+09:002023-11-27T09:55:08.660+09:00ビニール袋の容量を計算する<script type="application/json" id="post-data-json">{
"title": "ビニール袋の容量を計算する"
,"labels": ["JavaScript", "ウェブアプリ", ""]
,"url": "https://www.bugbugnow.net/2022/11/plastic-bag-capacity.html"
}</script><div><style> #app-form {
margin: 1em 0;
padding: 1em 0;
border: 1px solid #aaa;
background: #F8F8F0;
text-align: center;
}
#app-input-a,
#app-input-b,
.app-input {
width: 8em;
margin: 0 0.5em;
}
#app-input-a:invalid,
#app-input-b:invalid {
border: solid 2px red;
} </style><form id="app-form"> 横幅 a <input id="app-input-a" type="number" min="0" pattern="^[0-9]+$"> mm<br> 高さ b <input id="app-input-b" type="number" min="0" pattern="^[0-9]+$"> mm<br><br><button id="app-button" type="button">計算</button><br><br> 最大容積 V <input class="app-input" id="app-input-V1" type="text" readonly=""> L<br> 適量容積 V <input class="app-input" id="app-input-V2" type="text" readonly=""> L<br> 円柱容積 V <input class="app-input" id="app-input-V3" type="text" readonly=""> L<br></form><script type="none" id="post-body-script">//<![CDATA[
(function() {
const calc = function() {
const pi = 3.14;//Math.PI;
const a = document.getElementById('app-input-a').value / 10; // cm ← mm
const b = document.getElementById('app-input-b').value / 10; // cm ← mm
const V1 = (0.33 * a * b * b) - (0.11 * a * a * a); // 最大容積 cm3
const V2 = a/2 * a/2 * (b - a/2); // 最適容積 cm3
const r = a / pi;
const h = b - r - r;
const V3 = r * r * pi * h; // 円柱容積 cm3
document.getElementById('app-input-V1').value = Math.floor(V1) / 1000; // L ← ml(cm3)
document.getElementById('app-input-V2').value = Math.floor(V2) / 1000; // L ← ml(cm3)
document.getElementById('app-input-V3').value = Math.floor(V3) / 1000; // L ← ml(cm3)
};
document.getElementById('app-button').addEventListener('click', calc);
const keypress = function(event) {
if (event.keyCode === 13) {
calc();
}
return false;
};
document.getElementById('app-input-a').addEventListener('keypress', keypress);
document.getElementById('app-input-b').addEventListener('keypress', keypress);
const clear = function() {
document.getElementById('app-input-V1').value = '';
document.getElementById('app-input-V2').value = '';
document.getElementById('app-input-V3').value = '';
};
document.getElementById('app-input-a').addEventListener('input', clear);
document.getElementById('app-input-b').addEventListener('input', clear);
const focus = function(event) {
event.target.select();
};
document.getElementById('app-input-V1').addEventListener('focus', focus);
document.getElementById('app-input-V2').addEventListener('focus', focus);
document.getElementById('app-input-V3').addEventListener('focus', focus);
})();
//]]></script></div><section id="toc-1"><h3>はじめに</h3><p>ゴミ袋や規格袋の容量を計算します。</p><p>袋には、ゴミ袋のようにリットル数が明記されているものと、規格袋のようにサイズだけが記載されているものがあります。サイズだけが記載されている袋にどの程度の容積があるのか知りたかったため、計算しました。</p></section><section id="toc-2"><h3>早見表:袋の容積</h3><div class="responsive-table"><table><thead><tr><th align="right">袋</th><th align="right">サイズ</th><th align="right">最大容積</th><th align="right">適量容積</th><th align="right">円柱容積</th></tr></thead><tbody><tr><td align="right">規格袋 1号</td><td align="right">70 x 100 mm</td><td align="right">193 ml</td><td align="right">79 ml</td><td align="right">86 ml</td></tr><tr><td align="right">規格袋 2号</td><td align="right">80 x 120 mm</td><td align="right">323 ml</td><td align="right">128 ml</td><td align="right">140 ml</td></tr><tr><td align="right">規格袋 3号</td><td align="right">80 x 150 mm</td><td align="right">537 ml</td><td align="right">176 ml</td><td align="right">201 ml</td></tr><tr><td align="right">規格袋 4号</td><td align="right">90 x 170 mm</td><td align="right">778 ml</td><td align="right">253 ml</td><td align="right">290 ml</td></tr><tr><td align="right">規格袋 5号</td><td align="right">100 x 190 mm</td><td align="right">778 ml</td><td align="right">253 ml</td><td align="right">290 ml</td></tr><tr><td align="right">規格袋 6号</td><td align="right">100 x 210 mm</td><td align="right">1,081 ml</td><td align="right">350 ml</td><td align="right">402 ml</td></tr><tr><td align="right">規格袋 7号</td><td align="right">120 x 230 mm</td><td align="right">1,904 ml</td><td align="right">612 ml</td><td align="right">704 ml</td></tr><tr><td align="right">規格袋 8号</td><td align="right">130 x 250 mm</td><td align="right">2,439 ml</td><td align="right">781 ml</td><td align="right">899 ml</td></tr><tr><td align="right">規格袋 9号</td><td align="right">150 x 250 mm</td><td align="right">2,722 ml</td><td align="right">984 ml</td><td align="right">1,106 ml</td></tr><tr><td align="right">規格袋 10号</td><td align="right">180 x 270 mm</td><td align="right">3.6 L</td><td align="right">1.4 L</td><td align="right">1.6 L</td></tr><tr><td align="right">規格袋 11号</td><td align="right">200 x 300 mm</td><td align="right">5.0 L</td><td align="right">2.0 L</td><td align="right">2.1 L</td></tr><tr><td align="right">規格袋 12号</td><td align="right">230 x 340 mm</td><td align="right">7.4 L</td><td align="right">2.9 L</td><td align="right">3.2 L</td></tr><tr><td align="right">規格袋 13号</td><td align="right">260 x 380 mm</td><td align="right">10 L</td><td align="right">4.2 L</td><td align="right">4.6 L</td></tr><tr><td align="right">規格袋 14号</td><td align="right">280 x 410 mm</td><td align="right">13 L</td><td align="right">5.2 L</td><td align="right">5.7 L</td></tr><tr><td align="right">規格袋 15号</td><td align="right">300 x 450 mm</td><td align="right">17 L</td><td align="right">6.7 L</td><td align="right">7.4 L</td></tr><tr><td align="right">規格袋 16号</td><td align="right">340 x 480 mm</td><td align="right">21 L</td><td align="right">8.9 L</td><td align="right">9.6 L</td></tr><tr><td align="right">規格袋 17号</td><td align="right">360 x 500 mm</td><td align="right">24 L</td><td align="right">10 L</td><td align="right">11 L</td></tr><tr><td align="right">規格袋 18号</td><td align="right">380 x 530 mm</td><td align="right">31 L</td><td align="right">12 L</td><td align="right">14 L</td></tr><tr><td align="right">規格袋 19号</td><td align="right">400 x 550 mm</td><td align="right">32 L</td><td align="right">14 L</td><td align="right">15 L</td></tr><tr><td align="right">規格袋 20号</td><td align="right">460 x 600 mm</td><td align="right">43 L</td><td align="right">19 L</td><td align="right">20 L</td></tr></tbody></table></div><div class="responsive-table"><table><thead><tr><th align="right">袋</th><th align="right">サイズ</th><th align="right">最大容積</th><th align="right">適量容積</th><th align="right">円柱容積</th></tr></thead><tbody><tr><td align="right">ゴミ袋 10L</td><td align="right">400 x 500 mm</td><td align="right">25 L</td><td align="right">12 L</td><td align="right">12 L</td></tr><tr><td align="right">ゴミ袋 15L</td><td align="right">450 x 550 mm</td><td align="right">34 L</td><td align="right">16 L</td><td align="right">16 L</td></tr><tr><td align="right">ゴミ袋 20L</td><td align="right">500 x 600 mm</td><td align="right">45 L</td><td align="right">21 L</td><td align="right">22 L</td></tr><tr><td align="right">ゴミ袋 30L</td><td align="right">500 x 700 mm</td><td align="right">67 L</td><td align="right">28 L</td><td align="right">30 L</td></tr><tr><td align="right">ゴミ袋 45L</td><td align="right">650 x 800 mm</td><td align="right">107 L</td><td align="right">50 L</td><td align="right">51 L</td></tr><tr><td align="right">ゴミ袋 70L</td><td align="right">800 x 900 mm</td><td align="right">157 L</td><td align="right">80 L</td><td align="right">79 L</td></tr><tr><td align="right">ゴミ袋 90L</td><td align="right">900 x 1,000 mm</td><td align="right">216 L</td><td align="right">111 L</td><td align="right">110 L</td></tr><tr><td align="right">ゴミ袋 100L</td><td align="right">1,000 x 1,000 mm</td><td align="right">220 L</td><td align="right">125 L</td><td align="right">115 L</td></tr><tr><td align="right">ゴミ袋 120L</td><td align="right">1,000 x 1,200 mm</td><td align="right">365 L</td><td align="right">175 L</td><td align="right">179 L</td></tr><tr><td align="right">ゴミ袋 150L</td><td align="right">1,300 x 1,200 mm</td><td align="right">376 L</td><td align="right">232 L</td><td align="right">200 L</td></tr></tbody></table></div><p>※ゴミ袋のサイズは、各市町村により異なります。</p></section><section id="toc-3"><h3>計算式</h3><h4>最大容積</h4><pre><code>V = (0.33 × S × b) - (0.11 × a^3)
= (0.33 × a × b × b) - (0.11 × a × a × a)
S: 袋の表面積
a, b: 辺の長さ(b ≧ a)
V: 体積</code></pre><p>※参考:<a href="https://www.sanko-shoji.jp/lecture/cn8/pg128383.html">袋の大きさと適正内容量 of サンコー商事</a><br>※上記の計算機では、 <code>b ≧ a</code> の制約を考慮しません。</p><h4>適量容積</h4><pre><code>V = a/2 × a/2 × (b - a/2)
a, b: 辺の長さ(b ≧ a)
V: 体積</code></pre><p>※参考:<a href="https://www.sanko-shoji.jp/lecture/cn8/pg128383.html">袋の大きさと適正内容量 of サンコー商事</a><br>※上記の計算機では、 <code>b ≧ a</code> の制約を考慮しません。</p><h4>円柱容積</h4><pre><code>r = 2a / 2π
= a / 3.14
h = b - r - r
V = 2r × π × h
= r × r × 3.14 × h
r: 半径
π: 円周率
h: 高さ(袋を閉じる分補正)
a, b: 辺の長さ
V: 体積</code></pre><p>※参考:<a href="https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1368950683">ゴミ袋の容積を数学的に求めるにはどうしたらいいでしょうか? - Yahoo!知恵袋</a><br>※円柱容積の公式 <code>体積 = 底面積×高さ = 半径×半径×円周率×高さ</code><br>※円周の公式 <code>円周 = 直径×円周率 = 2×半径×円周率</code><br>※上記の計算機では、 <code>π</code> を <code>3.14</code> で計算します。</p></section><section id="toc-4"><h3>備考:マチ(奥行き)の扱い</h3><p>マチ(奥行き)がある場合、横幅に加算して計算してください。</p><p>例えば、横幅が <code>450 mm</code> でマチが <code>200 mm</code> の場合、 <code>450 + 200 = 600 (mm)</code> を横幅として代用できます。</p></section><section id="toc-5"><h3>備考:持ち手の扱い</h3><p>上記の計算には、持ち手のサイズは考慮されていません。</p><p>袋の高さに持ち手を含む場合、持ち手分の高さを補正する必要があります。</p></section><section id="toc-6"><h3>備考:実測の結果</h3><p>実際に手元にあった規格袋10号(180 x 270 mm)に水を入れて確認したところ、適量容積である <code>1.4 L</code> の水道水をギリギリ入れることができました。</p><img alt="実測結果" loading="lazy" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXh7XlLeYyN_1eGov3Jd0zV1j9G0UmSjuQja58fALRww21No3Dqk94MxR-KVJMWDYjYTxzADBMZPYxKibu99ofTlF6XavMBqTPh89DKHYqUR7dBgH4dGZsWlmpk-2sZjTuCcJNY0HL7KHyfS8y_kbOnVkLJO_zZOekRACwe94c0qIWIMtDqMzbSssHSQ/s632/20221121.jpg" width="600" height="632" decoding="async"><p>この結果から、ある程度妥当な計算結果であることを確認しました。ただし、サンプル数があまりに少ないことは否めません。</p><p>ちなみに、円柱容積の <code>1.6 L</code> を入れることもできましたが口を縛るのが極めて難しくなります。最大容積の <code>3.6 L</code> に至ってはどのように入れるのかわかりません。口を縛らない前提で入るだけ入れてみましたが <code>2.5 L</code> 程度が限界のようです。</p><p>※45Lなど袋で実測する場合、強度(袋の厚み)の問題に留意してください。</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-5725967790609331192018-12-14T00:58:00.005+09:002023-10-30T11:58:59.318+09:00GoogleAppsScriptでよく使うコードまとめ<section id="toc-1"><h3>基本処理</h3><h4>ショートカット</h4><div class="responsive-table"><table><thead><tr><th>ショートカット</th><th>説明</th></tr></thead><tbody><tr><td>Ctrl+R</td><td>選択している関数を実行</td></tr><tr><td>Ctrl+Enter</td><td>ログ表示</td></tr></tbody></table></div><p>※基本的なテキストエディタのショートカットは使用できます<br> [Ctrl+Z]:元に戻す、[Ctrl+F]:検索、[Ctrl+S]:保存など</p><h4>ログ出力</h4><p>Logger.log(data);<br><a href="https://developers.google.com/apps-script/reference/base/logger#log(Object)">Logger.log | Google Developers</a></p><pre><code class="language-js">Logger.log('Hello World.');</code></pre><h4>日付フォーマット</h4><p>Utilities.formatDate(date, timeZone, format);<br><a href="https://developers.google.com/apps-script/reference/utilities/utilities#formatDate(Date,String,String)">Utilities.formatDate | Google Developers</a></p><pre><code class="language-js">var string = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd HH:mm:ss");</code></pre><h4>スリープ(遅延処理)</h4><p>Utilities.sleep(milliseconds);<br><a href="https://developers.google.com/apps-script/reference/utilities/utilities#sleep(Integer)">Utilities.sleep | Google Developers</a></p><pre><code class="language-js">Utilities.sleep(1000);</code></pre><h4>メール送信</h4><p>MailApp.sendEmail(recipient, subject, body);<br><a href="https://developers.google.com/apps-script/reference/mail/mail-app#sendEmail(String,String,String)">MailApp.sendEmail | Google Developers</a></p><pre><code class="language-js">MailApp.sendEmail('hoge@gmail.com', '件名', '本文');</code></pre><p>※自分自身のGMailに送信すると受信トレイには届かないため、注意(すべてのメールにはある)</p></section><section id="toc-2"><h3>プロパティ</h3><p><a href="https://developers.google.com/apps-script/reference/properties/properties-service">Class PropertiesService | Google Developers</a></p><pre><code class="language-js">// ドキュメントプロパティ
var prop = PropertiesService.getDocumentProperties();
// スクリプトプロパティ
var prop = PropertiesService.getScriptProperties();
// ユーザプロパティ
var prop = PropertiesService.getUserProperties();</code></pre><h4>スクリプトプロパティ読込み</h4><pre><code class="language-js">var prop = PropertiesService.getScriptProperties();
var value = prop.getProperty('key');</code></pre><h4>スクリプトプロパティ書込み</h4><pre><code class="language-js">var prop = PropertiesService.getScriptProperties();
prop.setProperty('key', 'value');</code></pre></section><section id="toc-3"><h3>キャッシュ</h3><p><a href="https://developers.google.com/apps-script/reference/cache/cache-service">Class CacheService | Apps Script | Google Developers</a></p><pre><code class="language-js">// ドキュメントキャッシュ
var cache = CacheService.getDocumentCache();
// スクリプトキャッシュ
var cache = CacheService.getScriptCache();
// ユーザキャッシュ
var cache = CacheService.getUserCache();</code></pre><h4>キャッシュ読込み</h4><pre><code class="language-js">var cache = CacheService.getScriptCache();
var value = cache.get('key');</code></pre><h4>キャッシュ書込み</h4><pre><code class="language-js">var cache = CacheService.getScriptCache();
cache.put('key', 'value', 21600);</code></pre><p>※キャッシュの最大保持時間は、6時間(21600秒)です。</p></section><section id="toc-4"><h3>ドキュメント</h3><h4>GASの実行ログを出力</h4><pre><code class="language-js">// 既存のドキュメントを削除して、GASの実行ログをドキュメントに出力する
function log() {
var docid = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
var doc = DocumentApp.openById(docid);
doc.getBody().clear();
doc.getBody().appendParagraph(Logger.getLog());
}</code></pre><p>※Logger.logで長すぎて表示しきれない文字列(8192文字より多い文字列)は、<code>Logger.getLog()</code>から取得できない</p><h4>GASの長過ぎる文字列の出力</h4><pre><code class="language-js">// 既存のドキュメントを削除して、指定文字列をドキュメントに出力する
var text = '長過ぎる文字列...';
var docid = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
var doc = DocumentApp.openById(docid);
doc.getBody().clear();
doc.getBody().appendParagraph(text);</code></pre><p>※Googleドキュメントの文字数制限の半角文字で1,020,000文字まで出力できる</p></section><section id="toc-5"><h3>スプレッドシート</h3><h4>基本</h4><pre><code class="language-js">// 関連付けられたスプレッドシートにアクセス
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
// 別スプレッドシートにアクセス
// https://docs.google.com/spreadsheets/d/{id}/edit
var spreadsheet = SpreadsheetApp.openById('{id}');
// シート取得
var sheet = spreadsheet.getSheetByName('シート1');
// 列数と行数取得
var row = sheet.getLastRow();
var col = sheet.getLastColumn();</code></pre><h4>独自メニュー追加</h4><pre><code class="language-js">function onOpen() {
var ui = SpreadsheetApp.getUi();
var menu = ui.createMenu('追加メニュー');
menu.addItem('アイテム1', 'onClickItem1');
menu.addItem('アイテム2', 'onClickItem2');
menu.addToUi();
}
function onClickItem1() {
Browser.msgBox('アイテム1がクリックされました。');
}
function onClickItem2() {
SpreadsheetApp.getUi().alert('アイテム2がクリックされました。');
}</code></pre><h4>メッセージボックス</h4><p>Browser.msgBox(prompt);<br><a href="https://developers.google.com/apps-script/reference/base/browser#msgBox(String)">Class Browser | Google Developers</a><br>alert<br><a href="https://developers.google.com/apps-script/guides/dialogs">Dialogs and Sidebars in G Suite Documents | Google Developers</a></p><pre><code class="language-js">Browser.msgBox('Hello World');
SpreadsheetApp.getUi().alert('Hello World');</code></pre><p>※スプレッドシート上にメッセージボックスを表示する。<br>※メニューから呼び出された関数で実行する。<br>※カスタム関数では、権限がなくメッセージボックスを表示しない。<br>※6分以内にダイアログを閉じないとタイムアウトエラーとなる。<br>※他と共通で利用可能なUIを使用したほうが良い。</p><h4>シートの読み書き</h4><pre><code class="language-js">var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('シート1');
var row = sheet.getLastRow();
var col = sheet.getLastColumn();
// 読込み
var values = row && sheet.getRange(1, 1, row, col).getValues() || [];
// データ編集
// ...
//values[1][0] = 'xxx'; // 2行目1列目の値を書換える
//values.push([1, 2, 3 ,4, 5]); // 最終行に追加
//values.splice(2, 1); // 3行目を削除
// 書込み + データ消去
// 行数が減少する場合、消去が必要
values.length && sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
(row-values.length > 0) && sheet.deleteRows(values.length+1, row-values.length);</code></pre><p>※列数が増加する可能性がある場合、最大列数で取得すると書込み時にエラーしない<br> エラー例:「The number of columns in the data does not match the number of columns in the range. The data has 2 but the range has 1.」<br> 解決策例:<code>var col = Math.max(sheet.getLastColumn(), 2);</code></p><h4>シートの末尾に追加する</h4><pre><code class="language-js">var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('シート1');
var row = sheet.getLastRow();
var values = [];
// データ追加
//values.push(data);
// シートの末尾に追加
sheet.getRange(row+1, 1, values.length, values[0].length).setValues(values);</code></pre><h4>シートの先頭に追加する</h4><pre><code class="language-js">var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('シート1');
var values = [];
// データ追加
//values.push(data);
// シートの先頭に追加する
// 新規行を追加して、追加行に上書きする
sheet.insertRows(1, values.length);
sheet.getRange(1, 1, values.length, values[0].length).setValues(values);</code></pre><h4>シートの先頭行を削除する</h4><pre><code class="language-js">var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('シート1');
sheet.deleteRows(1, 1);</code></pre><h4>シートを全削除する</h4><pre><code class="language-js">var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('シート1');
sheet.clear();</code></pre><h4>valuesの列数を最大列数に統一する</h4><pre><code class="language-js">function valuesToReshape(values) {
var mcol = 0;
for (var r=0; r<values.length; r++) {
mcol = Math.max(values[r].length, mcol);
}
for (var r=0; r<values.length; r++) {
for (var c=values[r].length; c<mcol; c++) {
values[r].push('');
}
}
}</code></pre><p>※setValues時に、列数が統一されていない場合、エラーとなるため、列数を統一する</p><h4>補足</h4><p>GoogleAppsScriptからスプレッドシートへのアクセスは低速です。特に、複数回の読込み/書込みが非常に遅くなります。そのため、スプレッドシートへのアクセスは、セル毎のアクセスではなく選択範囲全体のアクセスとしたほうが懸命です。ただし、GoogleAppsScriptには、メモリの上限があります。あまりにも大量のデータを一度に処理するとメモリ上限に達してエラー終了します。</p><p>onOpen関数は、スプレッドシートを開いたときに実行します。opOpen関数以外に類似の関数としてonEdit関数、onInstall関数があります。</p><h4>参考</h4><ul><li><a href="https://developers.google.com/apps-script/guides/triggers/">シンプルトリガ | Google Developers</a></li><li><a href="https://qiita.com/howdy39/items/46ca1f2fd9d27eaba0c3">Google Apps Scriptを使った独自メニューの作り方 - Qiita</a></li><li><a href="https://tonari-it.com/gas-spreadsheet-speedup/">Google Apps Scriptのスプレッドシート読み書きを格段に高速化をする方法</a></li></ul><aside class="ads-inarticle"><ins class="adsbygoogle ads-ad ads-pending"></ins></aside></section><section id="toc-6"><h3>doGet, doPost</h3><h4>doGet</h4><pre><code class="language-js">function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}</code></pre><pre><span class="pre-code-title">index.html</span><code class="language-html:index.html"><!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h1>Hello HTML!</h1>
</body>
</html></code></pre><h4>doPost</h4><pre><code class="language-js">function doPost(e) {
var name = e.parameter.name;
var email = e.parameter.email;
var message = e.parameter.message;
// ...
}</code></pre><h4>JSON出力</h4><pre><code class="language-js">function doGet() {
var json = {"text": "Hello World."};
return ContentService.createTextOutput(JSON.stringify(json))
.setMimeType(ContentService.MimeType.JSON);
}</code></pre><ul><li><a href="https://developers.google.com/apps-script/reference/content/mime-type">Enum MimeType</a></li></ul><h4>補足</h4><p>パラメータの場合、<code>e.parameter.xxx</code>で受け取る。<br>JSONの場合、<code>JSON.parse(e.postData.getDataAsString());</code>で受け取る。</p><p>doGet, doPostのHTML出力は、「このアプリケーションは、Google ではなく、別のユーザーによって作成されたものです。」の帯が挿入される。<br>JavaScriptが無効の場合、帯のみが表示されHTML出力の結果が表示されない。<br>doGet, doPostで戻り値を指定しない場合、「スクリプトが完了しましたが、何も返されませんでした。」のメッセージを出力する。</p></section><section id="toc-7"><h3>HTMLファイル出力</h3><h4>テンプレートファイル読込み</h4><pre><code class="language-js">var temp = HtmlService.createTemplateFromFile("ファイル名").evaluate().getContent();</code></pre><h4>テンプレートファイル読込み(Scriptlets)</h4><pre><code class="language-js">var tempfile = HtmlService.createTemplateFromFile("test");
tempfile.flag = true;
tempfile.data1 = 'Hello World.';
tempfile.data2 = 2;
tempfile.data3 = 3;
var temp = tempfile.evaluate().getContent();</code></pre><pre><span class="pre-code-title">test.html</span><code class="language-html:test.html"><div>
<? if (flag) { ?>
<p>flag=true</p>
<? } else { ?>
<p>flag=false</p>
<? } ?>
<p><?= data1 ?></p>
</div>
<button onclick="pow(<?= data2 ?>)">
<button onclick="pow(<?!= data3 ?>)">
<script>
function pow(num){
alert(num * num);
}
</script></code></pre><pre><span class="pre-code-title">結果.html</span><code class="language-html:結果.html"><div>
<p>flag=true</p>
<p>Hello World.</p>
</div>
<button onclick="pow('2')">
<button onclick="pow(3)">
<script>
function pow(num){
alert(num * num);
}
</script></code></pre><h4>補足</h4><p>テンプレートファイルは、通常の文字列結合と比べるとかなり低速なため、大規模ファイル作成の際はパフォーマンスを考慮する。</p><h4>参考</h4><ul><li><a href="https://dackdive.hateblo.jp/entry/2015/02/01/010540">[GAS] Google Apps Script のHtmlServiceまとめ - dackdive's blog</a></li></ul></section><section id="toc-8"><h3>フェッチ(外部ファイル読込み)</h3><p>UrlFetchApp.fetch(url, params)<br><a href="https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String,Object)">UrlFetchApp.fetch | Google Developers</a></p><h4>HTMLファイル取得(GET)</h4><pre><code class="language-js">var url = "https://exmple.com/";
var response = UrlFetchApp.fetch(url);
var html = response.getContentText("UTF-8");</code></pre><h4>HTMLファイル取得(POST)</h4><pre><code class="language-js">var url = "https://exmple.com/";
var options = {
"method" : "POST",
"payload" : {
"userid": "userid",
"password": "passwd"
}
};
var response = UrlFetchApp.fetch(url, options);
var html = response.getContentText("UTF-8");</code></pre></section><section id="toc-9"><h3>関連記事</h3><ul><li><a href="https://www.bugbugnow.net/2018/12/GoogelAppsScript-restriction.html">GoogleAppsScriptでよくはまる制約まとめ</a></li><li><a href="https://www.bugbugnow.net/2019/03/GoogleAppsScript-speed-up.html">GoogleAppsScriptのスプレッドシート処理を高速化する</a></li><li><a href="https://www.bugbugnow.net/p/app-google-apps-script-day-run-time.html">GoogleAppsScriptの1日トリガー実行時間チェッカー</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-17405371359730610432023-10-03T15:37:00.002+09:002023-10-03T15:37:59.394+09:00【CSS】contain: content; の考慮漏れ<section id="toc-1"><h3>はじめに</h3><p>CSS の <code>contain</code> プロパティに関する個人的な失敗談です。</p></section><section id="toc-2"><h3>問題の対象</h3><pre><code class="language-css">.post-title {
contain: content;
}</code></pre><p><code>contain: paint;</code> (<code>content</code> / <code>strict</code>)は、要素が領域外へはみ出さないことを保証します。そのため、再描画が領域内に収まり、描画のコストが減少します。</p><p>基本的には、意図的にはみ出しにいかない限り要素が領域をはみ出すことはありません。そのため、ある程度気軽に追加できる項目のひとつです。ですが、落とし穴がありました。</p></section><section id="toc-3"><h3>問題</h3><p>問題は、<code><a></code> / <code><input></code>などの <code>focus</code> を取得する要素です。要素が <code>focus</code> を取得するとブラウザは標準で要素に <code>outline</code> を描画します。そして、この <code>outline</code> は要素の領域範囲外に描画することがあります。</p><p><code>contain: paint;</code> ありで <code>outline</code> を領域範囲外に描画した場合、 <code>outline</code> が描画されないか又は描画が途中で切れる表現になります。そのため、フォーカスを取得する要素に <code>contain: paint;</code> を使用するべきではありませんでした。</p><p>本件では、 <code>contain: content;</code> の使用に問題あったため、項目を削除することで問題を解決しました。</p></section><section id="toc-4"><h3>備考(outline の非表示)</h3><p><code>outline</code> を非表示にすることで問題を解決することもできます。ですが、キーボードユーザーを考慮するのであれば、標準のインタラクティブを削除すべきではありません。</p></section><section id="toc-5"><h3>備考(overflow: hidden)</h3><p><code>overflow: hidden;</code> も同様に <code>outline</code> に影響します。そのため、フォーカスを取得する要素に使用するべきではありません。</p></section><section id="toc-6"><h3>参考</h3><ul><li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/contain">contain - CSS: カスケーディングスタイルシート | MDN</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com1tag:blogger.com,1999:blog-279447686030876252.post-23304662476466628262023-08-23T01:59:00.002+09:002023-10-03T15:31:55.040+09:00ブラウザを判定する(ブラウザスニッフィング)<div id="toc"><div id="toc-title">目次</div><div id="toc-content"><ol><li><a class="toc-link" href="#toc-1">はじめに</a></li><li><a class="toc-link" href="#toc-2">ブラウザを判定する</a></li><li><a class="toc-link" href="#toc-3">IE を判定する</a></li><li><a class="toc-link" href="#toc-4">Trident を判定する</a></li><li><a class="toc-link" href="#toc-5">Gecko 判定について</a></li><li><a class="toc-link" href="#toc-6">WebKit を判定する</a></li><li><a class="toc-link" href="#toc-7">Blink を判定する</a></li><li><a class="toc-link" href="#toc-8">Presto を判定する</a></li><li><a class="toc-link" href="#toc-9">備考:スマホ・タブレット・デスクトップを判定する</a></li><li><a class="toc-link" href="#toc-10">備考:機能毎に判定する</a></li><li><a class="toc-link" href="#toc-11">参考</a></li></ol></div></div><section id="toc-1"><h3>はじめに</h3><p>ブラウザをユーザーエージェントの使用なしに、ブラウザの固有動作を元に判定します。</p><p>これは、ユーザーエージェント偽装などへの対策として優秀です。また、今後のブラウザの増加などで意図せずユーザーエージェント文字列の衝突による誤判定を防ぐこともできます。(ただし、動作の変更により問題が発生する可能性があります)</p></section><section id="toc-2"><h3>ブラウザを判定する</h3><pre><span class="pre-code-title">ua.js</span><code class="language-js:ua.js">var ua = (function() {
return {
IE: /*@cc_on!@*/false || !!document.documentMode, // IE 4-11
// Trident:!!document.uniqueID, // IE (MSHTML), Edge Legacy (EdgeHTML)
Gecko: 'MozAppearance' in document.documentElement.style,
// Firefox *
WebKit: /constructor/i.test(window.HTMLElement) || (!window.chrome && !!window.webkitAudioContext),
// Safari, iOS Third Party Browser, Chrome 27-
Blink: !!window.chrome, // Chrome 1+, Edge 79+, Opera 14+
// Presto: !!window.opera, // Opera 12-
Mobile: 'orientation' in window,
Touch: 'ontouchstart' in window
};
})();</code></pre><h4>実行例</h4><div><style> #app-form {
margin: 1em 0;
padding: 1em 0;
border: 1px solid #aaa;
background: #F8F8F0;
text-align: center;
}
#app-output {
width: 300px;
margin: 0.25em;
padding: 0.25em;
} </style><form id="app-form"><textarea id="app-output" rows="10"></textarea></form><script id="post-body-script" type="plain/text">var ua = (function() {
return {
IE: /*@cc_on!@*/false || !!document.documentMode, // IE 4-11
Trident:!!document.uniqueID, // IE (MSHTML), Edge Legacy (EdgeHTML)
Gecko: 'MozAppearance' in document.documentElement.style,
// Firefox *
WebKit: /constructor/i.test(window.HTMLElement) || (!window.chrome && !!window.webkitAudioContext),
// Safari, iOS Third Party Browser, Chrome 27-
Blink: !!window.chrome, // Chrome 1+, Edge 79+, Opera 14+
Presto: !!window.opera, // Opera 12-
Mobile: 'orientation' in window,
Touch: 'ontouchstart' in window
};
})();
var element = document.getElementById('app-output');
element.value = JSON.stringify(ua, null, ' ');</script></div></section><section id="toc-3"><h3>IE を判定する</h3><pre><code>// IE 4-10
var isIE = /*@cc_on!@*/false;
// IE 8-11 (EmulateIE 5, 7-11)
var isIE = !!document.documentMode;
// IE 4-11
var isIE = /*@cc_on!@*/false || !!document.documentMode;</code></pre><h4>@cc_on</h4><p><code>@cc_on ~ @</code>は、条件付きコンパイル機能を有効にする記述です。</p><p>条件付きコンパイルは、 JScript の機能でバージョン毎に互換性を維持しながら JScript の新機能を使用するための機能です。 IE 4 (JScript 3.0) で登場して、 IE 11 で廃止された。当然ながら、 IE 以外では実装されていません。</p><p><code>/*@cc_on!@*/</code>は、<code>!</code>(否定)の処理を実行するという意味です。 そのため、<code>/*@cc_on!@*/false</code>は<code>!false</code>となり。対応ブラウザでは<code>true</code>として処理されます。非対応ブラウザでは、<code>/*@cc_on!@*/</code>はコメント扱いであるため、<code>false</code>として処理されます。</p><p>※対応時期については、諸説あるようです(IE 10-, IE 4-10)</p><ul><li><a href="https://ja.wikipedia.org/wiki/%E6%9D%A1%E4%BB%B6%E4%BB%98%E3%81%8D%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88">条件付きコメント - Wikipedia</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Microsoft_JavaScript_extensions/at-cc-on">@cc_on - JavaScript | MDN</a></li><li><a href="https://web.archive.org/web/20181006122114/https://developer.mozilla.org/en-US/docs/Web/JavaScript/Microsoft_JavaScript_extensions/at-cc-on">@cc_on - JavaScript | MDN - Wayback Machien</a></li></ul><h4>document.documentMode</h4><p><code>document.documentMode</code>は、 IE 8 で実装されたドキュメントモードの機能です。ドキュメントモードで指定したバージョンの IE でエミュレートしてウェブページを表示します。</p><p>IE 5, 7-11 をエミュレートできます。<br>ドキュメントモードの指定は、<code><meta http-equiv="X-UA-Compatible"></code>を使用してください。</p><p>※<code>document.documentMode</code>は、ブラウザのバージョンではありません。<br> エミュレート中のドキュメントモードのバージョンです。<br>※ Edge Legacy は、<code>document.documentMode</code>に非対応です。<br> <code><meta http-equiv="X-UA-Compatible"></code>は、対応しています。<br>※ Windows 10 以降では、ドキュメントモードは非推奨です。<br>※<a href="https://learn.microsoft.com/ja-jp/internet-explorer/ie11-deploy-guide/fix-compat-issues-with-doc-modes-and-enterprise-mode-site-list">ドキュメント モードとエンタープライズ モード | Microsoft Learn</a><br>※<a href="https://www.bugbugnow.net/2020/02/X-UA-CompatibleIE-edge.html">IE対策の「X-UA-Compatible:IE=edge」は必要か?</a></p><h4>document.all</h4><p>この方法は既に利用できません。 IE 以外のブラウザが <code>document.all</code> に対応したため、この方法は利用できません。</p></section><section id="toc-4"><h3>Trident を判定する</h3><pre><code>// IE, Edge Legacy
var isTrident = !!document.uniqueID;
// IE 4-11, Edge Legacy
var isTrident = /*@cc_on!@*/false || !!document.uniqueID;</code></pre><p>※「Trident」は、 IE, Edge Legacy のレタリングエンジンの名称です。<br>※ Trident は、ライブラリファイルの名称から MSHTML とも呼ばれています。<br>※ Edge Legacy は、 Trident からフォークされた EdgeHTML を使用しています。</p><h4>document.uniqueID の対応時期</h4><p>正確な対応時期は不明だが、 IE 9 で実装済みであることは次ソースからうかがえる。実際はそれよりもまえに実装されているものと考えられます。</p><ul><li><a href="https://w3g.jp/blog/js_browser_sniffing">JavaScript ユーザエージェント条件分岐便利スニペット</a></li></ul></section><section id="toc-5"><h3>Gecko 判定について</h3><pre><code>// Firefox *
var isFF = 'MozAppearance' in document.documentElement.style;
// Firefox 1.5+
var isFF = 'InstallTrigger' in window;
var isFF = typeof InstallTrigger !== 'undefined';</code></pre><p>※「Gecko」は、Firefox と派生ブラウザのレタリングエンジンの名称です。<br>※<code>window.InstallTrigger</code>は、 Firefox の旧拡張機能をウェブページからインストールするための機能です。WebExtension(新拡張機能)には対応していません。現在は、初期設定で<code>null</code>初期化されており、使用することはできません。いつなくなってもおかしくない機能です。<br> <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1754441">1754441 - Deprecate InstallTrigger</a></p><h4>window.globalStorage</h4><pre><code>// Firefox 2-13
var isFF = window.globalStorage;</code></pre><p>この方法は既に利用できません。 Firefox 13 で window.globalStorage が削除されました。</p></section><section id="toc-6"><h3>WebKit を判定する</h3><pre><code>// Safari, Chrome 27-
var isSafari = /constructor/i.test(window.HTMLElement) || (!window.chrome && !!window.webkitAudioContext);
// Safari 9-, Chrome 27-
var isSafari = /constructor/i.test(window.HTMLElement);
// Safari 3.1+
var isSafari = !window.chrome && !!window.webkitAudioContext;</code></pre><p>※ WebKit は、現在主に Safari / iOS Third Party Browser で使用されています。</p><h4>'WebkitAppearance' in document.documentElement.style</h4><pre><code>var isWebKit = !window.chrome && 'WebkitAppearance' in document.documentElement.style;</code></pre><p>この方法は既に利用できません。 Firefox 64+ が<code>-webkit-appearance</code>に対応したことにより WebKit の判定として機能していません。</p><ul><li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/appearance">appearance (-moz-appearance, -webkit-appearance) - CSS: カスケーディングスタイルシート | MDN</a></li></ul><h4>/constructor/i.test(window.HTMLElement)</h4><pre><code>// Safari 9-
var isSafari = /constructor/i.test(window.HTMLElement);
var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;</code></pre><p>この方法は、既に利用できません。 Safari10 から機能していません。</p><p><code>Object.prototype.toString.call(window.HTMLElement)</code>は、<code>[object HTMLElementConstructor]</code>を期待しています。</p><ul><li><a href="https://stackoverflow.com/questions/39120772/how-to-detect-safari-10-browser-in-javascript">How to detect Safari 10 browser in Javascript? - Stack Overflow</a></li></ul><h4>'webkitAudioContext' in window</h4><pre><code>// Safari 3.1+
var isSafari = !window.chrome && !!window.webkitAudioContext;</code></pre><p>webkit プレフィックスを使用した単純な判定です。 AudioContext に特別な意味はありません。</p><p>そのため、「webkit プレフィックスが廃止される」又は「webkit プレフィックスに別ブラウザが対応した」場合修正が必要になります。</p></section><section id="toc-7"><h3>Blink を判定する</h3><pre><code>// Chrome 1+ (Headless Chrome を除く), Edge 79+ (Edge Chronium), Opera 14+
var isBlink = !!window.chrome;
// Chrome 1-70
var isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime);</code></pre><p>Blink は、 WebKit のフォークです。そのため、古いバージョンの Chromium 系のブラウザは、 WebKit として判定されます。</p><p>※ Chrome 28 以降 Blink が使用されています。<br>※ Headless Chrome は、<code>window.chrome</code>が存在しないため、判定できません。</p><h4>window.chrome</h4><p>Chrome 1+ から存在する機能です。<code>window.chrome</code>は、インラインインストール(<code>chrome.webstore.install()</code>、Chrome Web Store を経由しなくても Chrome 拡張機能を追加できるインストールボタンをウェブサイトに配置できる機能)などを使用するための Chronium 固有の機能です。ただし、インラインインストールは Chrome 2 から非推奨となり、 Chrome 71 で廃止された。</p><p>ただし、 Chrome 1 から存在するため、正確には Blink を判定できていません。 Chrome 1-27 は、 WebKit であるため、正確な Blink 判定はではありません。ですが、困ることもないため、このままとします。</p></section><section id="toc-8"><h3>Presto を判定する</h3><pre><code>// Opera 12-
var isPresto = !!window.opera;</code></pre><p>※「Presto」は、Opera 12 までのレタリングエンジンの名称です。<br>※ Opera は、Opera 14 から Chromium ベースで開発されているため、 Blink で判定します。</p><h4>window.opera</h4><p><code>window.opera</code> は、 Chronium 対応により削除されました。</p><p><code>window.opera</code> がいつから存在するのか確認できていません、ですが最低でも Opera 8 の時点では存在していたようです。</p></section><section id="toc-9"><h3>備考:スマホ・タブレット・デスクトップを判定する</h3><pre><code class="language-js">var isMobile = 'orientation' in window;
var isPhone = Math.min(window.screen.width, window.screen.height) < 512;
var isTablet = isMobile && !isPhone;
var isDesktop = !isMobile && !isPhone;</code></pre><p>※画面サイズ(ピクセルサイズ)境界の <code>512</code> に根拠はありません。<br> 最近の画面サイズの動向を元に適時変更してください。</p></section><section id="toc-10"><h3>備考:機能毎に判定する</h3><p>特定の機能に対応しているか否かは、ブラウザ単位で判定すべきではありません。機能単位で実装有無を確認して個別に対応すべきです。</p><pre><code class="language-js">if (window.requestAnimationFrame) {
window.requestAnimationFrame(function() {
// ...
});
}</code></pre><pre><code class="language-css">@supports (display: grid) {
div {
display: grid;
}
}</code></pre><p>※<code>@supports</code>は、 Firefox22+, Safari9+, Chrome28+ で利用可能です。<br> <a href="https://developer.mozilla.org/ja/docs/Web/CSS/@supports">@supports - CSS: カスケーディングスタイルシート | MDN</a><br>※ CSS によるブラウザ判定は、本記事の主題ではないため、簡略します。</p></section><section id="toc-11"><h3>参考</h3><ul><li><a href="http://browserhacks.com/">Browserhacks</a></li><li><a href="https://w3g.jp/blog/js_browser_sniffing">JavaScript ユーザエージェント条件分岐便利スニペット</a></li><li><a href="https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browsers/9851769#9851769">javascript - How to detect Safari, Chrome, IE, Firefox and Opera browsers? - Stack Overflow</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-12186071745919981482019-06-29T12:28:00.003+09:002023-09-16T20:33:38.064+09:00GAS のウェブアプリでハマった CORS エラーの話<p>旧題「GASのWebアプリケーションでハマったこと」</p><section id="toc-1"><h3>問題の概要</h3><ol><li>ウェブページから XmlHttpRequest で GoogleAppsScript と通信する</li><li>GoogleAppsScript 応答を元にウェブページで処理をする<ul><li>GoogleAppsScript 応答時に CORS エラーでデータを取得できない ← ここが問題</li></ul></li></ol><p>※記事内は、 XmlHttpRequest ですが fetch でも同様の問題に直面するはずです。</p></section><section id="toc-2"><h3>応答</h3><p>下記のエラーをブラウザの開発者ツールに出力する。(エラーでスクリプトは停止する)</p><pre><code>Chrome
Access to XMLHttpRequest at 'https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec' from origin 'https://www.bugbugnow.net' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Firefox
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Origin’ が足りない)。</code></pre><h4>補足(JSONP の場合)</h4><p>JSONP としても下記の警告を表示する。</p><pre><code>Chrome
Cross-Origin Read Blocking (CORB) blocked cross-origin response https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec?callback=test&error=err with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details.
Firefox
MIME タイプ (“text/html”) の不一致により “https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec?callback=test&error=err” からのリソースがブロックされました (X-Content-Type-Options: nosniff)。</code></pre></section><section id="toc-3"><h3>調べて特に関係なかったこと</h3><h4>プリフライトリクエスト(OPTIONS メソッド)</h4><p>ブラウザは、単純なリクエストでない場合、 GET などのメソッドの送信前にOPTIONSメソッドを自動的に送信します。 GoogleAppsScript は、 OPTIONS メソッドに対応していない?ため、 CORS エラーとなる。</p><p>GET や POST メソッドで<code>Content-Type</code>に<code>application/x-www-form-urlencoded</code>を設定して、他のヘッダーを指定していないような単純なリクエストで通信していたため、本件とは特段関係ありませんでした。</p><ul><li><a href="https://developer.mozilla.org/ja/docs/Web/HTTP/CORS">オリジン間リソース共有 (CORS) - HTTP | MDN</a></li><li><a href="https://qiita.com/mochizukikotaro/items/6b72ad595db8a6b5514f">CORS を分かってないから動くコード書いて理解する - Qiita</a></li><li><a href="https://qiita.com/nnishimura/items/1f156f05b26a5bce3672">CORS: OPTIONSリクエスト(preflight request)を避ける - Qiita</a></li></ul></section><section id="toc-4"><h3>原因</h3><p>GoogleAppsScript のコード内でエラーにより異常終了していたことが主な原因でした。 CORS は、付随的な問題であり、主な原因ではありませんでした。 CORS 関連調べまくって特に何も出てこないで時間を無駄にしたので、この記事を書いています。</p><h4>なぜ、 CORS エラーが表示されるのか</h4><p>GoogleAppsScript は、処理の成功と失敗で以下のような結果の出力をします。</p><ul><li>成功の場合<ul><li>要求URL: <a href="https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec?a=abc">https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec?a=abc</a></li><li>出力URL: <a href="https://script.googleusercontent.com/macros/echo?user_content_key=9vzoBbQX9BetXsgkJfJ-uViUvMS5yhiHHyFw970dv9OREWMWE3XC-m5JHuBxIEKIsrrttITZsZe-JkIsmDf_4EekzUGndTFjm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnMNqeOndzXRsTzjiwx9wBqiidjItq8OWJEsK0R3u86c6UjhZQTuMhz7zicrqSB8Uz_pNNcEnoskCmjhihpkOzwI&lib=MTlhb09PVDl_eh2Dk0TGcNwfbNS0rMw0b">https://script.googleusercontent.com/macros/echo?user_content_key=9vzoBbQX9BetXsgkJfJ-uViUvMS5yhiHHyFw970dv9OREWMWE3XC-m5JHuBxIEKIsrrttITZsZe-JkIsmDf_4EekzUGndTFjm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnMNqeOndzXRsTzjiwx9wBqiidjItq8OWJEsK0R3u86c6UjhZQTuMhz7zicrqSB8Uz_pNNcEnoskCmjhihpkOzwI&lib=MTlhb09PVDl_eh2Dk0TGcNwfbNS0rMw0b</a></li><li>結果: {"v":1,"a":"abc"}</li></ul></li><li>失敗の場合<ul><li>要求URL: <a href="https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec?a=abc&error=xyz">https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec?a=abc&error=xyz</a></li><li>出力URL: <a href="https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec?a=abc&error=xyz">https://script.google.com/macros/s/AKfycbw1uH-oQ8FO4RmdXzRdykkFUdcBPMce4EJ1xcRTd1qG8T-S5kE/exec?a=abc&error=xyz</a></li><li>結果: Error: xyz(行 14、ファイル「コード」、プロジェクト「Webアプリケーションテスト」)</li></ul></li></ul><p>このとき、成功側の応答ヘッダーには、「<code>access-control-allow-origin: *</code>」が含まれますが、失敗側の応答ヘッダには含まれません。そのため、 GoogleAppsScript 内で異常終了した場合、 XmlHttpRequest で CORS エラーが発生します。</p><p>失敗した場合、結果がそもそも意図したものではありませんが、ステータスコードは 200(OK) となるため、問題に気づくのが遅れました。なお、成功した場合、ステータスコードは 302(Found) になっています。</p></section><section id="toc-5"><h3>上記の説明でよく分からなかった人へ</h3><p>問題の本質は、 CORS エラーではありません。</p><p>GoogleAppsScript のコード内でエラーが発生して、 GoogleAppsScript のコードが異常終了している可能性が極めて高いです。 GoogleAppsScript のコードを確認してエラー箇所を修正してください。また、最新コードをデプロイして公開コードを最新コードに更新してください。</p><p>それでも問題が解決しないのであれば、下記のサンプルコードを一時的に公開して、ウェブページ側のスクリプトでサンプルのデータを正常に受信できることを確認してください。サンプルのデータを正常に取得できるのであれば、最低でもウェブページ側のスクリプトが問題でないことは確認できるはずです。</p></section><section id="toc-6"><h3>サンプル</h3><p>上記の成功・失敗サンプルのコードです。</p><pre><code class="language-js">function doGet(e) {
return doAction(e);
}
function doPost(e) {
return doAction(e);
}
// Web呼び出しへの応答
function doAction(e, debug) {
// 応答データ作成
var json = {};
if (e.parameter.error) {
throw new Error(e.parameter.error);
} else {
json = {v:1};
var keys = Object.keys(e.parameter);
for (var i=0; i<keys.length; i++) {
json[keys[i]] = e.parameter[keys[i]];
}
}
// 戻り値作成
var out = null;
if (e.parameter.callback) {
var text = e.parameter.callback + '(' + (debug ? JSON.stringify(json, null, 2) : JSON.stringify(json)) + ')';
out = ContentService.createTextOutput(text);
out.setMimeType(ContentService.MimeType.JAVASCRIPT);
} else {
out = ContentService.createTextOutput((debug ? JSON.stringify(json, null, 2) : JSON.stringify(json)));
out.setMimeType(ContentService.MimeType.JSON);
}
return out;
}
// アクション呼び出し用テスト関数
function doTest() {
var e = {
parameter: {
a: 'abc',
//error: 'xyz' // コメントアウトで成功・失敗を分岐
}
};
var out = doAction(e, true);
Logger.log('MimeType: '+out.getMimeType());
Logger.log('Content: \n'+out.getContent());
}</code></pre></section><section id="toc-7"><h3>補足(GAS のウェブアプリをデバッグする)</h3><h4>doGet, doPost を別関数から呼び出す</h4><pre><span class="pre-code-title">doGetDebug.js</span><code class="language-js:doGetDebug.js">function doGet(e) {
// 応答データ作成
var json = {};
json.data = e.parameter.a;
// 戻り値作成
var out = ContentService.createTextOutput(JSON.stringify(json));
out.setMimeType(ContentService.MimeType.TEXT);
return out;
}
function doTest() {
var e = {
parameter: {
a: 'abc'
}
};
var out = doGet(e);
Logger.log('MimeType: '+out.getMimeType());
Logger.log('Content: \n'+out.getContent());
}
// 実行結果例
// [19-10-04 22:37:20:653 JST] MimeType: TEXT
// [19-10-04 22:37:20:654 JST] Content:
// {"data":"abc"}</code></pre><h4>try catch で全体を囲んで、ログ出力する</h4><pre><span class="pre-code-title">tryCatchDebug.js</span><code class="language-js:tryCatchDebug.js">function doGet(e) {
var json = {};
var logs = [];
try {
// メインの処理
// ...
logs.push('Hello World');
// ...
throw new Error('xyz');
} catch (e) {
logs.push(e);
json.logs = logs.join('\n');
}
// 戻り値作成
var out = ContentService.createTextOutput(JSON.stringify(json));
out.setMimeType(ContentService.MimeType.TEXT);
return out;
}
// 実行結果例
// {"logs":"Hello World\nError: xyz"}</code></pre></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com2tag:blogger.com,1999:blog-279447686030876252.post-73411247638354156422023-02-19T16:38:00.013+09:002023-09-13T14:26:22.268+09:00Chrome でカレントタブの右隣に新規タブを開く<section id="toc-1"><h3>はじめに</h3><p>Firefox を長年使い続けています。ですが、 Chrome / Edge を使うこともあります。 Chrome の長年の不満の1つは、タブを開く挙動が Firefox と異なる点です。</p><p>カレントタブから2つ以上タブを開く挙動が次のように異なります。<br>Chrome は、 [カレントタブ][1つめ][2つめ] の順にタブを開きます。<br>Firefox は、 [カレントタブ][2つめ][1つめ] の順にタブを開きます。</p><p>カレントタブの右隣には、最後に開いたタブがあってほしいのです。これを実現する拡張機能を長年待ち続けましたが、ついぞこの機能を実現する拡張機能を見つけることができませんでした。</p><p>そこで、今回重い腰を上げて拡張機能を自作することにしました。</p><hr><p>後で気付いたことですが、上記の動作は、 Firefox 標準の動作ではありませんでした。Firefox の標準動作は、 Chrome 同様の動作のようです。ですが、拡張機能(<a href="https://addons.mozilla.org/ja/firefox/addon/sidebery/">Sidebery</a>)の設定によって、上記の動作を実現していたようです。(Sidebery の Chrome 版は、存在しません)</p><pre><code>Sidebery
Position of new tab
Place new tab opened from another tab
[last child] -> [first child]</code></pre></section><section id="toc-2"><h3>既存の拡張機能</h3><p>類似の機能を調べると次の拡張機能が多くのユーザーを抱えていることがわかりました。</p><ul><li><a href="https://chrome.google.com/webstore/detail/tab-position-options/fjccjnfkdkdmjohojoggodkigkjkkjhl">Tab Position Options - Chrome Web Store</a></li><li><a href="https://chrome.google.com/webstore/detail/tabsplus/nikomkkhhpfoeamojhhgpfkpkdlfhfii">TabsPlus - Chrome Web Store</a></li><li><a href="https://chrome.google.com/webstore/detail/open-tabs-next-to-current/gmpnnmonpnnmnhpdldahlekfofigiffh">Open Tabs Next to Current - Chrome Web Store</a></li></ul><p>これらの拡張機能は、上記の目的の動作を実現できます。ただし、副作用としてカレントタブ以外からの新規タブ(ウィンドウ・ブックマーク・履歴など)がことごとくカレントタブの右隣で開きはじめます。これは、目的とする拡張機能ではありません。</p></section><section id="toc-3"><h3>作成した拡張機能</h3><p>次の拡張機能を作成しました。</p><ul><li><a href="https://chrome.google.com/webstore/detail/open-tab-next-right-to-cu/iablodmefdmnffdgencdahlppobbjkme">Open Tab Next Right to Current - Chrome Web Store</a></li><li><a href="https://addons.mozilla.org/en-US/firefox/addon/open-tab-next-right-to-current/">Open Tab Next Right to Current – 🦊 Firefox</a></li></ul><p>この拡張機能は、カレントタブから作成された新規タブをカレントタブの右隣に開きます。</p><p>この拡張機能は、次の動作を実現します。</p><ul><li>カレントタブからの新規タブは、カレントタブの右隣に開きます。<ul><li>2つめ以降の新規タブも、カレントタブの右隣に開きます。</li><li>ピン留めタブからの新規タブは、最後のピン留めタブの右隣に開きます。</li></ul></li></ul><p>そして、次の動作に影響を受与えないよう制御します。</p><ul><li>ウィンドウからの新規タブは、ウィンドウの右端に開きます。</li><li>ショートカットからの新規タブは、ウィンドウの右端に開きます。</li><li>ブックマークからの新規タブは、ウィンドウの右端に開きます。</li><li>タブコンテキストメニューからの新規タブは、カレントタブの右隣に開きます。</li><li>閉じたタブ・ウィンドウを元の状態で復元します。</li></ul><p>※他の拡張機能または設定と干渉する場合は、この限りではありません。</p><p>これで、使い慣れたタブ挙動を実現できます。</p><p>不思議なことは、この拡張機能があまりにも単純なことです。コード本体が100行に満たないほどです。誰かが既に作成していたとしても不思議ではありません。ちゃんと探せば同じ機能を持った拡張機能が見つかるかもしれません。ただ、これで個人的には満足です。あのわずらわしさから開放されます。</p></section><section id="toc-4"><h3>備考:ソースコード</h3><p>コードは、次の GitHub でも確認できます。</p><ul><li><a href="https://github.com/k08045kk/OpenTabNextRightToCurrent">k08045kk/OpenTabNextRightToCurrent | GitHub</a></li></ul></section><section id="toc-5"><h3>備考:動作について</h3><p>拡張機能の開発に伴って理解したことですが、「カレントタブの右隣に新規タブを開く」は、正確ではないようです。正確には、「親タブの右隣に新規タブを開く」が正しいようです。</p><p>「カレントタブの右隣に新規タブを開く」だと、「新規タブを開く動作」と「左右へのタブ移動」を同時に行うと問題が発生する可能性があります。カレントタブがタイミングの関係から変化してしまい、意図した動作とならないことがあります。「親タブ(過去のカレントタブ)の右隣に新規タブを開く」であれば、この問題は発生しません。ただし、この問題が発生する可能性は極めて低いため、実際のところどちらの動作でもたいした影響はありません。</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-39324585872031542262021-02-08T12:50:00.005+09:002023-09-04T11:56:18.948+09:00JavaScript のグローバル変数未定義エラーの回避方法<section id="toc-1"><h3>はじめに</h3><p>JavaScript では、未定義のグローバル変数へのアクセスでエラーを出力します。ここでは、エラーを回避して、未定義を判定する方法を考えます。</p></section><section id="toc-2"><h3>失敗例</h3><p>下記のコード例では、グローバル変数の未定義エラーで失敗します。</p><pre><code class="language-js">if (a) {
console.log('OK');
} else {
console.log('NG');
}
// Uncaught ReferenceError: a is not defined</code></pre><hr><pre><code class="language-js">//var a = undefined;
if (a !== undefined) {
console.log('OK');
} else {
console.log('NG');
}
// Uncaught ReferenceError: a is not defined</code></pre><p>※<code>a = undefined;</code>を事前に設定した場合、意図した動作となる。<br> その場合、根本的に未定義ではなくなる。</p></section><section id="toc-3"><h3>回避策</h3><h4>window.property</h4><pre><code class="language-js">if (window.a) {
console.log('OK');
} else {
console.log('NG');
}</code></pre><p>グローバル変数に直接アクセスせずに、グローバル変数に間接的にアクセスします。</p><p>※変数が「<code>undefined</code>」「<code>null</code>」「<code>false</code>」「<code>0</code>」「<code>""</code>」の場合、誤判定する。<br> 完全ではないがほとんどの場合、「<code>window.a</code>」「<code>!window.a</code>」でこと足りる。<br>※<code>window</code>が存在しない環境の場合は、「window 以外のグローバル変数」を参照</p><h4>in window</h4><pre><code class="language-js">if ('a' in window) {
console.log('OK');
} else {
console.log('NG');
}</code></pre><p><code>in</code>演算子で指定したプロパティがオブジェクトに含まれるか判定します。</p><p>※<a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/in">in 演算子 - JavaScript | MDN</a><br>※<code>window</code>が存在しない環境の場合は、「window 以外のグローバル変数」を参照</p><h4>typeof</h4><pre><code class="language-js">if (typeof(a) !== 'undefined') {
console.log('OK');
} else {
console.log('NG');
}</code></pre><p><code>typeof</code>演算子でオペランドの型名を判定します。</p><p>※<a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/typeof">typeof - JavaScript | MDN</a><br>※<code>typeof(a) === void 0</code>では動作しない。<br> <code>typeof(a)</code>は、<code>undefined</code>のプリミティブ値ではなく、文字列を出力する。</p><h4>try/catch</h4><pre><code class="language-js">try {
a;
console.log('OK');
} catch (e) {
console.log('NG');
}</code></pre><p>エラーが発生することを逆に利用して、 try/catch で捕獲します。</p></section><section id="toc-4"><h3>備考</h3><h4>window 以外のグローバル変数</h4><p>上記の例でグローバル変数に<code>window</code>を使用しています。ですが、グローバル変数が<code>window</code>でないことがあります。<code>window</code>を使用せずにグローバル変数を取得する方法は、次の通りです。</p><pre><code class="language-js">var global = globalThis;
var global = Function('return this')();</code></pre><p>※<a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/globalThis">globalThis - JavaScript | MDN</a><br>※ Function を利用した方法は、最新環境で CSP 絡み制約を受けることがあります。</p><h4>window 自身の有無を判定する</h4><pre><code class="language-js">if ('window' in globalThis) {
console.log('OK');
} else {
console.log('NG');
}</code></pre></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-42031064086431194022018-03-06T18:00:00.014+09:002023-09-01T20:31:27.868+09:00タブメニューでCSSの有効無効を切替える:NoStyle.uc.js<p>FirefoxのuserChrome.js用スクリプトです。<br>CSS無効化でメインメニューを表示するのが煩わしかったため、タブコンテキストメニューに切替え用のメニューを追加します。</p><section id="toc-1"><h3>NoStyle.uc.js</h3><pre><span class="pre-code-title">NoStyle.uc.js</span><code class="language-js:NoStyle.uc.js">// ==UserScript==
// @name NoStyle.uc.js
// @description スタイルシートの有効無効をタブコンテキストメニューから選択する。
// 次の機能をタブコンテキストメニューから呼び出す。
// + [Main Menu] > [View] > [Page Style]
// + [メインメニュー] > [表示] > [スタイルシート]
// @include main
// @charset UTF-8
// @author toshi (https://github.com/k08045kk)
// @license MIT License | https://opensource.org/licenses/MIT
// @compatibility 69+
// @version 7
// @since 1 - 20180306 - 初版
// @since 2 - 20190905 - Firefox69対応 createElement → createXULElement に置換
// @since 3 - 20200118 - Firefox72対応 messageManager → switchStyleSheet/disableStyle で切換え
// @since 4 - 20201122 - fix #1 カレントタブ以外のタブコンテキストメニューで正常動作しない
// @since 4 - 20201122 - リファクタリング
// @since 5 - 20210201 - コンテキストメニューにチェックボックスを表示する
// @since 6 - 20210910 - メタデータ修正
// @since 6 - 20211008 - メタデータ修正
// @since 7 - 20230901 - gPageStyleMenu の仕様変更に対応
// @see https://github.com/k08045kk/userChrome.js
// @see https://www.bugbugnow.net/2018/03/nostyleucjsuserchromejs.html
// ==/UserScript==
(function() {
// Firefoxの標準関数の迂回処理(カレントタブ以外のタブコンテキストメニュー対応)
// chrome://browser/content/browser.js: gPageStyleMenu._sendMessageToAll
function _sendMessageToAll(message, data, browser) {
let contextsToVisit = [browser.browsingContext];//[gBrowser.selectedBrowser.browsingContext];
while (contextsToVisit.length) {
let currentContext = contextsToVisit.pop();
let global = currentContext.currentWindowGlobal;
if (!global) {
continue;
}
let actor = global.getActor("PageStyle");
actor.sendAsyncMessage(message, data);
contextsToVisit.push(...currentContext.children);
}
};
// chrome://browser/content/browser-pagestyle.js: gPageStyleMenu.switchStyleSheet
function switchStyleSheet(title, browser) {
let sheetData = this._getStyleSheetInfo(browser);
if (sheetData && sheetData.filteredStyleSheets) {
sheetData.authorStyleDisabled = false;
for (let sheet of sheetData.filteredStyleSheets) {
sheet.disabled = sheet.title !== title;
}
}
//this._sendMessageToAll("PageStyle:Switch", { title });
_sendMessageToAll.call(this, "PageStyle:Switch", { title }, browser);
};
// chrome://browser/content/browser-pagestyle.js: gPageStyleMenu.disableStyle
function disableStyle(browser) {
let sheetData = this._getStyleSheetInfo(browser);
if (sheetData) {
sheetData.authorStyleDisabled = true;
}
//this._sendMessageToAll("PageStyle:Disable", {});
_sendMessageToAll.call(this, "PageStyle:Disable", {}, browser);
};
// スタイルシート切換え
const mi = document.createXULElement('menuitem');
mi.setAttribute('id', 'context-nostyle');
mi.setAttribute('label', 'スタイルシート切換え');
//mi.setAttribute('label', 'PageStyle: Switch');
mi.setAttribute('type', 'checkbox');
mi.addEventListener('command', () => {
const tab = TabContextMenu.contextTab;
const browser = tab != null
? tab.linkedBrowser // 選択したタブ(右クリックしたタブ)
: gBrowser.selectedBrowser; // カレントタブ
const style = gPageStyleMenu._getStyleSheetInfo(browser); // スタイルシート有効無効
if (style.authorStyleDisabled) {
//gPageStyleMenu.switchStyleSheet(null);
switchStyleSheet.call(gPageStyleMenu, null, browser);
} else {
//gPageStyleMenu.disableStyle();
disableStyle.call(gPageStyleMenu, browser);
}
// #1: gPageStyleMenuの関数を無理やり置き換えて対応する
// ただし、gPageStyleMenuの実装に強く依存するようになる
});
// メニュー呼び出しイベント(右クリック時)
document.addEventListener('popupshowing', () => {
// ラベルを変更する
const tab = TabContextMenu.contextTab;
const browser = tab != null
? tab.linkedBrowser // 選択したタブ(右クリックしたタブ)
: gBrowser.selectedBrowser; // カレントタブ
const style = gPageStyleMenu._getStyleSheetInfo(browser); // スタイルシート有効無効
mi.setAttribute('checked', !style.authorStyleDisabled)
//mi.setAttribute('label', style.authorStyleDisabled
// ? 'スタイルシートを有効化'
// : 'スタイルシートを無効化')
//mi.setAttribute('label', style.authorStyleDisabled
// ? 'Enable stylesheets'
// : 'Disable stylesheets')
});
// セパレータ
const ms = document.createXULElement('menuseparator');
ms.setAttribute('id', 'context-nostyle-sep');
// タブコンテキストメニューの最下部に要素を追加
const tabContextMenu = document.getElementById('tabContextMenu');
tabContextMenu.appendChild(ms);
tabContextMenu.appendChild(mi);
}());</code></pre><h4>変更履歴</h4><ul><li><a href="https://github.com/k08045kk/userChrome.js">k08045kk/userChrome.js - GitHub</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-54126297491420528252020-01-24T23:43:00.004+09:002023-08-27T08:31:11.892+09:00JavaScript から CSS のルールを管理する<p>忘却録。</p><section id="toc-1"><h3><code><style></code> を追加する</h3><pre><span class="pre-code-title">addLocalStyle.js</span><code class="language-js:addLocalStyle.js">// CSSを追加する
var addLocalStyle = function(text) {
var style = document.createElement('style');
style.type = 'text/css';
style.textContent = text;
document.head.appendChild(style);
};</code></pre><p><code><style></code> を追加することで CSS コードをページ(ドキュメント)に追加します。</p><p>これは、 JavaScript から CSS のインターフェイスを理解することなく、 CSS コードを追加する簡単な方法です。この方法は、 JavaScript による要素(Element)追加だけで、 CSS のルール追加を実現しています。</p><p>※要素を id 等で識別することで、削除することもできます。<br>※CSSの優先順位は、より後にあるものを優先します。<br> そのため、<code><head></code>内よりも<code><body></code>内にあるものの方が優先される。<br> ただし、基本的に<code><style></code>は、<code><head></code>内に配置する要素です。</p><h4>使用例</h4><pre><code class="language-js">addLocalStyle(`
.sample1 { background: red !important; }
.sample2 { background: blue !important; }
`);</code></pre></section><section id="toc-2"><h3>スタイルシートの管理(document.styleSheets)</h3><pre><code class="language-js">for (const sheet of document.styleSheets) {
console.log(sheet);
try {
let i = 0;
for (const rule of sheet.cssRules) {
console.log(rule);
if (++i >= 5) { console.log('...'); break; }
}
} catch (e) { /* Cannot access rules 対策 */ }
}</code></pre><p>JavaScript の CSS 用のインターフェイスです。シートの追加・削除、ルールの追加・削除・確認を行えます。ただし、癖が強いため、使用には難があります。</p><p>※<a href="https://developer.mozilla.org/ja/docs/Web/API/Document/styleSheets">Document: styleSheets プロパティ - Web API | MDN</a><br>※「Cannot access rules」への対策が必要です。<br> 上位権限(ブラウザ・拡張機能等)の追加したルールは、下位権限(ウェブページ)からはアクセス(追加・削除・参照)ができません。</p></section><section id="toc-3"><h3>要素 style 属性の取得と変更(Element.style)</h3><pre><code class="language-js">var element = document.getElementById('element-id');
//element.style.background = '#f00';
element.style.setProperty('background', '#f00');
//console.log('color', element.style.color);
console.log('color', element.style.getPropertyValue('color'));</code></pre><p>要素の style 属性に JavaScript からアクセスして取得・変更ができます。ただし、 CSS 継承を考慮した値を取得できるわけではありません、要素単体に適用されている style 属性値を取得・変更できます。そのため、継承元・シートのルールを取得・変更できるわけではありません。</p><p>※<a href="https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/style">HTMLElement: style プロパティ - Web API | MDN</a></p></section><section id="toc-4"><h3>要素に適用された CSS プロパティを取得(window.getComputedStyle())</h3><pre><code class="language-js">var element = document.getElementById('element-id');
var compStyles = window.getComputedStyle(element);
console.log('color', compStyles.getPropertyValue('color'));</code></pre><p>要素のすべての CSS プロパティ値を含むオブジェクトを返します。 CSS 継承を考慮した要素に適用されている値を取得できます。ただし、プロパティは読み取り専用です。</p><p>※<a href="https://developer.mozilla.org/ja/docs/Web/API/Window/getComputedStyle">Window.getComputedStyle() - Web API | MDN</a></p></section><section id="toc-5"><h3>メディアクエリーを解釈する(window.matchMedia())</h3><pre><code class="language-js">if (window.matchMedia("(min-width: 400px)").matches) {
/* ビューポートの幅が 400 ピクセル以上の場合のコードをここに */
} else {
/* ビューポートの幅は 400 ピクセル未満の場合のコードをここに */
}</code></pre><p>ビューポートの幅などの CSS で使用できるメディアクエリーを JavaScript で判定します。また、メディアクエリーの変更をリッスンすることもできます。</p><p>※<a href="https://developer.mozilla.org/ja/docs/Web/CSS/CSS_media_queries/Using_media_queries">メディアクエリーの使用 - CSS: カスケーディングスタイルシート | MDN</a><br>※<a href="https://developer.mozilla.org/ja/docs/Web/API/Window/matchMedia">window.matchMedia - Web API | MDN</a></p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-80516962137087147002021-12-15T20:31:00.006+09:002023-08-23T01:38:29.507+09:00HTMLを簡易に解析(tokenize / parse)する<div id="toc"><div id="toc-title">目次</div><div id="toc-content"><ol><li><a class="toc-link" href="#toc-1">はじめに</a></li><li><a class="toc-link" href="#toc-2">DOMParser を使用する</a></li><li><a class="toc-link" href="#toc-3">HTMLから文字列を抽出する</a></li><li><a class="toc-link" href="#toc-4">正規表現を使用して、簡易にタグとタグ以外を分解する</a></li><li><a class="toc-link" href="#toc-5">正規表現を使用して、もう少し考えて分解する</a></li><li><a class="toc-link" href="#toc-6">HTMLのドキュメントツリーを簡易に作成する</a></li></ol></div></div><section id="toc-1"><h3>はじめに</h3><p>JavaScript で HTML を簡易に解析(字句解析・構文解析)します。</p><p>巨大なライブラリを使用せずに解析する方法を考えます。主にウェブページのスクレイピングを前提としています。処理速度は、考慮しません。コード量を重視して機能を実現します。</p><p>※次のようなライブラリが使用できる場合、そちらを使用したほうが懸命です。<br> 「jsdoc」「cheerio」「libxmljs」<br> 「chromy」「puppeteer」「Nightmare」「PhantomJS」</p></section><section id="toc-2"><h3>DOMParser を使用する</h3><pre><span class="pre-code-title">サンプル</span><code class="language-js:サンプル">var html = '<html><body><h1 id="header">〇〇ページ</h1><p>〇〇は、<span class="red">△△</span>です。</p></body></html>';
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
console.log(doc.getElementById('header').textContent);
// 〇〇ページ</code></pre><p>※ブラウザ環境であれば、標準の DOMParser が使用できます。<br> <a href="https://developer.mozilla.org/ja/docs/Web/API/DOMParser">DOMParser - Web API | MDN</a></p></section><section id="toc-3"><h3>HTMLから文字列を抽出する</h3><pre><span class="pre-code-title">findText.js</span><code class="language-js:findText.js">/**
* 文字列を抽出する
* 開始文字列と終了文字列に囲まれた文字列を取得します。
* @see https://www.bugbugnow.net/2021/12/tokenize-parse-html.html
* @param {string} text - テキスト
* @param {string} stext - 開始文字列
* @param {string} etext - 終了文字列
* @return {string[]} - 抽出文字列
*/
function findText(text, stext, etext) {
const stack = [];
const slen = stext.length;
const elen = etext.length;
let si, sindex, ei, eindex = 0;
while (true) {
si = text.indexOf(stext, eindex);
if (si < 0) { break; }
sindex = si + slen;
ei = text.indexOf(etext, sindex);
if (ei < 0) { break; }
stack.push(text.substring(sindex, ei));
eindex = ei + elen;
}
return stack;
}</code></pre><pre><span class="pre-code-title">サンプル</span><code class="language-js:サンプル">var html = '<html><body><h1 id="header">〇〇ページ</h1><p>〇〇は、<span class="red">△△</span>です。</p></body></html>';
console.log(findText(html, '<', '>'));
// Array(10) [ "html", "body", "h1 id=\"header\"", "/h1", "p", "span class=\"red\"", "/span", "/p", "/body", "/html" ]
console.log(findText(html, '<h1', '</h1'));
// Array [ " id=\"header\">〇〇ページ" ]</code></pre><h4>解説</h4><p>開始文字列と終了文字列に囲まれた文字列を単純に取得しています。単純ではあるものの、HTMLの構造を理解して使用すれば必要十分ではあります。<br>(HTML以外でも使用できます)</p><p>ただし、入れ子構造に弱く、コメントや属性内の「<code>></code>」など不親切なページに完全に対応することは難しいです。</p></section><section id="toc-4"><h3>正規表現を使用して、簡易にタグとタグ以外を分解する</h3><pre><span class="pre-code-title">tokenizeHTMLEasy.js</span><code class="language-js:tokenizeHTMLEasy.js">/**
* HTMLを簡易に字句解析する
* 生テキストタグは、非対応です。
* @author toshi (https://github.com/k08045kk)
* @license MIT License | https://opensource.org/licenses/MIT
* @version 3
* @since 1 - 20211215 - 初版
* @since 2 - 20220328 - fix 「'」「"」をタグ名・属性名に使用できる
* @since 2 - 20220328 - fix 全角スペースをタグ名・属性名に使用できる
* @since 2 - 20220328 - fix 属性の途中に「"'」を含むことがある
* @since 3 - 20221013 - 正規表現を最適化
* @since 3 - 20221013 - fix 「?」をタグ名先頭に許可しない
* @see https://www.bugbugnow.net/2021/12/tokenize-parse-html.html
* @param {string} html - 生テキストのHTML
* @param {Object} [option={}] - オプション
* @param {boolean} option.trim - タグ間の空白を削除する
* @return {string[]} - 分解した文字列
*/
function tokenizeHTMLEasy(html, option={}) {
const stack = [];
let lastIndex = 0;
const findTag = /<[!/?A-Za-z][^\t\n\f\r />]*([\t\n\f\r /]+[^\t\n\f\r /][^\t\n\f\r /=]*([\t\n\f\r ]*=[\t\n\f\r ]*("[^"]*"|'[^']*'|[^\t\n\f\r >]*))?)*[\t\n\f\r /]*>/g;
for (let m; m=findTag.exec(html); ) {
if (lastIndex < m.index) {
let text = html.substring(lastIndex, m.index);
if (option.trim) { text = text.trim(); }
if (text.length > 0) { stack.push(text); }
}
lastIndex = findTag.lastIndex;
let tag = m[0];
if (option.trim) { tag = tag.trim(); }
stack.push(tag);
}
return stack;
}</code></pre><pre><span class="pre-code-title">サンプル</span><code class="language-js:サンプル">var html = '<html><body><h1 id="header">〇〇ページ</h1><p>〇〇は、<span class="red">△△</span>です。</p></body></html>';
console.log(tokenizeHTMLEasy(html));
// Array(14) [ "<html>", "<body>", "<h1 id=\"header\">", "〇〇ページ", "</h1>", "<p>", "〇〇は、", "<span class=\"red\">", "△△", "</span>", "です。", "</p>", "</body>", "</html>" ]</code></pre><h4>解説</h4><p>正規表現を利用した単純な方法です。(正規表現はお世辞にも単純ではありませんが…)</p><p>コメントや改行文字、属性内の「<code>></code>」などほとんどのパターンに対応できます。20行未満のコードであり、行数的にも簡易です。</p><p>正規表現の概略図を次に示します。</p><img alt="概略図" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjF9WmuU212lPHs8-bWUreVFDYxayK1ZbLaEP_SeRl-81toGI1x7hbs6fjHqc3F86Om6oEsg3F0kA32trx_DUkQOWlItm9avE1_s4NYYyYoAD1iQkiFxISVMPdNyePz6cWKKHfovKUMYQKb_RigZAdq8FDS9KvPVOj2YMb67pb1pBKMIlWEJeTKlgoj1g/s1600/20211215_tokenizeHTML.png" loading="lazy" width="1790" height="600" decoding="async"><p>※概略図は、次のサイトで生成したものです。<br> <a href="https://jex.im/regulex/#!flags=&re=%3C%5B!%2F%3FA-Za-z%5D%5B%5E%5Ct%5Cn%5Cf%5Cr%20%2F%3E%5D*(%5B%5Ct%5Cn%5Cf%5Cr%20%2F%5D%2B%5B%5E%5Ct%5Cn%5Cf%5Cr%20%2F%5D%5B%5E%5Ct%5Cn%5Cf%5Cr%20%2F%3D%5D*(%5B%5Ct%5Cn%5Cf%5Cr%20%5D*%3D%5B%5Ct%5Cn%5Cf%5Cr%20%5D*(%22%5B%5E%22%5D*%22%7C'%5B%5E'%5D*'%7C%5B%5E%5Ct%5Cn%5Cf%5Cr%20%3E%5D*))%3F)*%5B%5Ct%5Cn%5Cf%5Cr%20%2F%5D*%3E">https://jex.im/regulex/</a></p><p>※生テキストタグ(<code><templete><script><style></code>等)は、非対応です。<br>※タグの開始文字が<code>[!/?A-Za-z]</code>以外の場合、タグとして認識しないため、弾いています。<br> 例:<code><>, < >, <0>, <あ></code><br> ブラウザはタグとして認識しません。文字列として処理します。<br>※一部のタグを間違って解釈します。<br> 例:<code></></code><br> タグとして処理しますが、ブラウザでは不正なタグとして消滅します。<br>※ファイル末尾の不完全なタグは、非対応です。<br> 例:<code>abc<a</code>(<code>abc</code>のみを表示する。<code><a</code>は消滅する)<br> ファイル末尾の不完全なタグは、ブラウザでは消滅します。</p><p>※タグ名の「<code>!</code>」「<code>?</code>」は、特殊なタグを判定します。<br> 例:<code><?xml version="1.0" encoding="UTF-8"?></code><br> 例:<code><!DOCTYPE html></code><br> 例:<code><!-- comment --></code><br>※タグ名の「<code>/</code>」は、閉じタグを判定します。<br> 例:<code></a></code><br>※タグ名判定で <code>[!/?A-Za-z]</code> の部分を <code>[!/?]?[A-Za-z]</code> と括りだす方が正確に判定しているように感じますが、次のパターンで問題が発生するため、実施しない。<br> 例:<code></></code>(不正なタグとして消滅すべき)</p><p>※タグ名・属性名に全角スペースを許容します。<br> 例:<code><a b></code>(<code>a b</code>がタグ名となる)<br> HTMLの区切り文字は、「<code>\t\n\f\r </code>」の5文字です。<br> <code>\t</code>:U+0009(水平タブ)<br> <code>\n</code>:U+000A(改行)<br> <code>\f</code>:U+000C(書式送り)<br> <code>\r</code>:U+000D(復帰)<br> <code> </code>:U+0020(半角スペース)<br> <code> </code>:U+3000(全角スペース)などを含む <code>\s</code> とは異なる点に注意してください。<br> see <a href="https://www.w3.org/TR/2011/WD-html5-20110525/common-microsyntaxes.html#space-character">https://www.w3.org/TR/2011/WD-html5-20110525/common-microsyntaxes.html#space-character</a><br>※属性名に「<code>"</code>」「<code>'</code>」を許容します。<br> 例:<code><a "=b></code>(属性<code>"</code>が<code>b</code>の値を持ちます)<br>※属性値に「<code>"</code>」「<code>'</code>」を途中から含めることができます。<br> 例:<code><a b=c'd></code>(属性<code>b</code>が<code>c'd</code>の値を持ちます)<br>※タグ内の「<code>/</code>」を一部無視します。(「<code>/</code>」の一部を区切り文字として扱います)<br> 例(無視する):<code><a/b=c></code>(<code><a b=c></code>と同じ)<br> 例(無視する):<code><a//=c></code>(<code><a =c></code>と同じ、 <code></code> (空文字)の属性を持つ)<br> 例(無視する):<code><a/b/=c></code>(<code><a b =c></code>と同じ、<code>b</code>と <code></code> (空文字)の属性を持つ)<br> 例(無視する):<code><a b="c"/d=e></code>(<code><a b=c d=e></code>と同じ)<br> 例(無視しない):<code><a b=c/d=e></code>(<code><a b="c/d=e"></code>と同じ)</p><p>※正確ではありませんが、<code>findTag</code>を次の簡易実装に置き換えることもできます。<br> 例:<code>/<[!/?A-Za-z][^\s/>]*([\s/]+[^\s/][^\s/=]*([\s]*=([\s]*("[^"]*"|'[^']*'|[^\s>]*)))?)*[\s/]*>/g</code><br> 「<code>\t\n\f\r </code>」を「<code>\s</code>」に置き換えた簡易実装です。<br> タグ名・属性名に全角スペースを含む場合、問題になります。<br> 例:<code>/<[!/?A-Za-z]("[^"]*"|'[^']*'|[^"'>])*>/g</code><br> タグ名と属性の括りだけを考慮した簡易実装です。<br> タグ名・属性名に全角スペース・「<code>"'</code>」を含む場合、問題になります。<br> 属性値の途中に「<code>"'</code>」を含む場合、問題になります。</p></section><section id="toc-5"><h3>正規表現を使用して、もう少し考えて分解する</h3><pre><span class="pre-code-title">tokenizerHTML.js</span><code class="language-js:tokenizerHTML.js">/**
* HTMLを字句解析する
* 正規表現を利用して、HTMLのタグとタグ以外を分離します。
* 生テキストタグ、空タグに対応します。
* @author toshi (https://github.com/k08045kk)
* @license MIT License | https://opensource.org/licenses/MIT
* @version 3
* @since 1 - 20211215 - 初版
* @since 2 - 20220328 - fix 「'」「"」をタグ名・属性名に使用できる
* @since 2 - 20220328 - fix 全角スペースをタグ名・属性名に使用できる
* @since 2 - 20220328 - fix 属性の途中に「"'」を含むことがある
* @since 3 - 20221013 - 正規表現を最適化
* @since 3 - 20221013 - fix 「?」をタグ名先頭に許可しない
* @see https://www.bugbugnow.net/2021/12/tokenize-parse-html.html
* @param {string} html - 生テキストのHTML
* @param {Object} [option={}] - オプション
* @param {boolean} option.trim - タグ間の空白を削除する
* @return {Object} - 分解したノード
*/
function *tokenizerHTML(html, option={}) {
const rawTags = ['SCRIPT','STYLE','TEMPLATE','TEXTAREA','TITLE'];
const emptyTags = ['AREA','BASE','BASEFONT','BR','COL','EMBED','FRAME','HR','IMG','INPUT','KEYGEN','ISINDEX','LINK','META','PARAM','SOURCE','TRACK','WBR'];
// Note: 廃止・非推奨:<basefont><keygen><frame><isindex>
let lastIndex = 0;
let findClosingRawTag = null;
const findTag = /<[!/?A-Za-z][^\t\n\f\r />]*([\t\n\f\r /]+[^\t\n\f\r /][^\t\n\f\r /=]*([\t\n\f\r ]*=[\t\n\f\r ]*("[^"]*"|'[^']*'|[^\t\n\f\r >]*))?)*[\t\n\f\r /]*>/g;
for (let m; m=findTag.exec(html); ) {
const tag = m[0];
if (findClosingRawTag) {
if (findClosingRawTag.test(tag)) { findClosingRawTag = null; }
else { continue; }
}
let type = 'opening-tag';
const pre = tag.substring(0, 2);
if (pre === '</') { type = 'closeing-tag'; }
if (pre === '<!') { type = 'comment'; }
// Note: <!DOCTYPE html> をコメントとして処理する
// Note: <?xml version="1.0" encoding="UTF-8"?> を無効として処理する(要検討)
let name;
if (type !== 'comment') {
if (tag.substring(tag.length-1) === '>') {
const findTagName = /^<\/?([A-Za-z][^\t\n\f\r />]*)/;
const n = tag.match(findTagName);
if (n) { name = n[1].toUpperCase(); }
}
if (name == null) { type = 'invalid'; }
}
let attribute;
if (type === 'opening-tag') {
attribute = {};
const content = tag.replace(/^<[A-Za-z][^\t\n\f\r />]*|>$/g, '');
const findTagAttribute = /([^\t\n\f\r /][^\t\n\f\r /=]*)([\t\n\f\r ]*=([\t\n\f\r ]*("([^"]*)"|'([^']*)'|([^\t\n\f\r >]*))))?/g;
for (let a; a=findTagAttribute.exec(content); ) {
if (!attribute.hasOwnProperty(a[1])) {
attribute[a[1]] = a[7] || a[6] || a[5]
|| a[3]
|| '';;
}
}
if (rawTags.indexOf(name) != -1) { findClosingRawTag = new RegExp('^</'+name+'[\\t\\n\\f\\r />]', 'i'); }
if (emptyTags.indexOf(name) != -1) { type = 'empty-tag'; }
// Note: <tagName /> を考慮しない
}
if (lastIndex < m.index) {
let text = html.substring(lastIndex, m.index);
if (option.trim) { text = text.trim(); }
if (text.length > 0) { yield {type:'text', name:'#text', content:text}; }
}
lastIndex = findTag.lastIndex;
const token = {type:type, name:(name ? name : '#'+type), content:(option.trim ? tag.trim() : tag)};
if (attribute) { token.attribute = attribute; }
yield token;
}
if (lastIndex < html.length) {
let text = html.substring(lastIndex);
if (option.trim) { text = text.trim(); }
if (text.length > 0) {
const findTagStart = /<[!/?A-Za-z]/;
const s = findTagStart.exec(text);
if (findClosingRawTag) {
yield {type:'text', name:'#text', content:text};
} else if (s) {
let text1 = text.substring(0, s.index);
let text2 = text.substring(s.index);
if (option.trim) {
text1 = text1.trim();
text2 = text2.trim();
}
if (text1.length > 0) { yield {type:'text', name:'#text', content:text1}; }
if (text2.length > 0) { yield {type:'invalid', name:'#invalid', content:text2}; }
} else {
yield {type:'text', name:'#text', content:text};
}
}
}
// Note: {type, name, content, attribute}
// Note: type = opening-tag / closeing-tag / empty-tag / comment / text / invalid
// Note: name = tagName / #comment / #text / #invalid
}
function tokenizeHTML(html, option) {
const stack = [];
for (const token of tokenizerHTML(html, option)) {
stack.push(token);
}
return stack;
}</code></pre><pre><span class="pre-code-title">サンプル</span><code class="language-js:サンプル">var html = '<html><body><h1 id="header">〇〇ページ</h1><p>〇〇は、<span class="red">△△</span>です。</p></body></html>';
console.log(tokenizeHTML(html));
//Array(14) [ {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, … ]
// 0: Object { type: "opening-tag", content: "<html>", name: "HTML", attribute: Object {} }
// 1: Object { type: "opening-tag", content: "<body>", name: "BODY", attribute: Object {} }
// 2: Object { type: "opening-tag", content: "<h1 id=\"header\">", name: "H1", attribute: Object {id: "header"} }
// 3: Object { type: "text", content: "〇〇ページ" }
// 4: Object { type: "closeing-tag", content: "</h1>", name: "H1" }
// 5: Object { type: "opening-tag", name: "P", content: "<p>", Object {} }
// 6: Object { type: "text", name: "#text", content: "〇〇は、" }
// 7: Object { type: "opening-tag", name: "SPAN", content: "<span class=\"red\">", Object {class: "red"} }
// 8: Object { type: "text", name: "#text", content: "△△" }
// 9: Object { type: "closeing-tag", name: "SPAN", content: "</span>" }
// 10: Object { type: "text", name: "#text", content: "です。" }
// 11: Object { type: "closeing-tag", name: "P", content: "</p>" }
// 12: Object { type: "closeing-tag", content: "</body>", name: "BODY" }
// 13: Object { type: "closeing-tag", content: "</html>", name: "HTML" }</code></pre><h4>解説</h4><p>生テキストタグや空タグを含めて、HTMLを分解します。タグ名と属性の抽出もサポートします。</p><p>HTMLの字句解析としては、フルスペックの性能があります。この次の段階は、字句解析の結果を使用して、構文解析でドキュメントツリーに変換します。</p></section><section id="toc-6"><h3>HTMLのドキュメントツリーを簡易に作成する</h3><pre><span class="pre-code-title">parseHTMLEasy.js</span><code class="language-js:parseHTMLEasy.js">/**
* HTMLを構文解析する
* HTMLから簡易なドキュメントツリーを作成します。
* @author toshi (https://github.com/k08045kk)
* @license MIT License | https://opensource.org/licenses/MIT
* @version 1
* @since 1 - 20211215 - 初版
* @see https://www.bugbugnow.net/2021/12/tokenize-parse-html.html
* @param {string} html - 生テキストのHTML
* @param {Object} [option={}] - オプション
* @param {boolean} option.trim - タグ間の空白を削除する
* @return {Object} - ドキュメントツリー
*/
function parseHTMLEasy(html, option) {
const Node = function(name, option) {
option && Object.keys(option).forEach((key) => {
this[key] = option[key];
});
this.parentNode = null;
this.nodeName = name;
this.childNodes = [];
this.firstChild = null;
this.lastChild = null;
this.previousSibling = null;
this.nextSibling = null;
};
const appendChild = (parent, node) => {
node.parentNode = parent;
node.previousSibling = null;
node.nextSibling = null;
if (parent.lastChild) {
parent.lastChild.nextSibling = node;
node.previousSibling = parent.lastChild;
}
if (parent.firstChild == null) { parent.firstChild = node; }
parent.lastChild = node;
parent.childNodes.push(node);
};
const dom = new Node('#document');
let parent = dom;
let lastIndex = 0;
for (const token of tokenizerHTML(html, option)) {
switch (token.type) {
case 'opening-tag':
case 'empty-tag':
const node = new Node(token.name, {attribute:token.attribute});
appendChild(parent, node);
if (token.type !== 'empty-tag') { parent = node; }
break;
case 'closeing-tag':
let valid = false;
for (let node=parent; node; node=node.parentNode) {
if (node.nodeName === token.name) {
parent = node.parentNode;
valid = true;
break;
}
}
if (valid) { break; }
case 'invalid':
break;
case 'comment':
case 'text':
appendChild(parent, new Node(token.name, {content:token.content}));
break;
default:
break;
}
}
return dom;
}</code></pre><pre><span class="pre-code-title">サンプル</span><code class="language-js:サンプル">var html = '<html><body><h1 id="header">〇〇ページ</h1><p>〇〇は、<span class="red">△△</span>です。</p></body></html>';
function showNode(node, deep=0) {
const option = node.attribute ? node.attribute : node.content;
console.log(Array(deep+1).join(' ')+node.nodeName, option);
for (const child of node.childNodes) { showNode(child, deep+1); }
}
showNode(parseHTMLEasy(html));
// #document undefined
// HTML Object { }
// BODY Object { }
// H1 Object { id: "header" }
// #text 〇〇ページ
// P Object { }
// #text 〇〇は、
// SPAN Object { class: "red" }
// #text △△
// #text です。</code></pre><h4>解説</h4><p>HTMLから簡易なドキュメントツリーを作成しています。</p><p>簡易な Node を使用して、ドキュメントツリーを構築しています。簡易であるため、省略可能なタグなどには対応していません。XML形式などのがっちりとしたHTMLならば、意図した動作をします。ですが、省略などを含む場合、意図した動作にはなりません。HTML5的な省略やブラウザ基準のドキュメントツリーを作成する場合、複雑度が跳ね上がるため、ここまでとします。</p><p>※前述の <code>tokenizerHTML()</code> を字句解析に使用しています。</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-10698565572262113662020-12-28T17:38:00.008+09:002023-08-22T17:19:52.282+09:00タッチスクリーンであるかを JavaScript で判定する<section id="toc-1"><h3>タッチスクリーン有無判定</h3><pre><code class="language-js">// タッチスクリーン有無判定
const isTouchDevice = function() {
return 'ontouchstart' in window;
};
console.log('touch screen:', isTouchDevice());</code></pre></section><section id="toc-2"><h3>理屈</h3><p>タッチスクリーンがある場合、<code>window.ontouchstart</code>は<code>null</code>で初期化されます。<br>タッチスクリーンがない場合、<code>window.ontouchstart</code>は初期化されていません。</p></section><section id="toc-3"><h3>別解</h3><h4>navigator.maxTouchPoints</h4><p><code>navigator.maxTouchPoints</code>は、同時タッチ点数の最大数を取得します。</p><pre><code class="language-js">if (navigator.maxTouchPoints > 0) {
// タッチ対応
}
if (navigator.maxTouchPoints > 1) {
// マルチタッチ対応
}</code></pre><p>※<code>navigator.msMaxTouchPoints</code>が存在します。<br> ただし、 IE11 で <code>navigator.maxTouchPoints</code> に対応しているため、考慮不要です。</p><ul><li><a href="https://developer.mozilla.org/ja/docs/Web/API/Navigator/maxTouchPoints">Navigator.maxTouchPoints - Web API | MDN</a></li></ul><h4>pointer: coarse</h4><p>CSS のメディアクエリです。ポインティングデバイスの正確性を判定できます。</p><p>正確性が高い(<code>ponter: fine</code>)場合、ポインティングデバイスは、マウスである可能性が高くなります。<br>正確性が限定されている(<code>ponter: coarse</code>)場合、ポインティングデバイスは、タッチスクリーンである可能性が高くなります。</p><pre><span class="pre-code-title">CSS</span><code class="language-css:CSS">@media (pointer: coarse) {
/* 正確性が限定されている */
}</code></pre><pre><span class="pre-code-title">JavaScript</span><code class="language-js:JavaScript">if (window.matchMedia('(pointer: coarse)').matches) {
// 正確性が限定されている
}</code></pre><ul><li><a href="https://developer.mozilla.org/ja/docs/Web/CSS/@media/pointer">pointer - CSS: カスケーディングスタイルシート | MDN</a></li></ul><h4>'orientation' in window</h4><p>画面の向きを取得します。画面の向きがあるのは、モバイル端末といって差し支えないため、タッチスクリーンである可能性が高くなります。</p><pre><code class="language-js">if ('orientation' in window) {
// 画面の向きがある
}</code></pre><ul><li><a href="https://developer.mozilla.org/ja/docs/Web/API/Screen/orientation">Screen.orientation - Web API | MDN</a></li></ul></section><section id="toc-4"><h3>補足</h3><h4>マウス有無判定</h4><p>モバイル環境でもマウス系のリスナーが <code>null</code> で初期化されているため、<code>'onmousedown' in window</code>で判定しても、マウス有無の判定には使えません。</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-23866005695005932162023-08-22T11:54:00.003+09:002023-08-22T12:01:34.048+09:00Blogger の m=1 リダイレクト問題を回避する<section id="toc-1"><h3>はじめに</h3><p>本件は、力技で無理やりな方法です。意図しない問題が発生する可能性も多大に存在します。導入する際には、十分注意して実施してください。</p></section><section id="toc-2"><h3>問題</h3><p>Blogger のサイトにモバイル環境でアクセスすると「<code>m=1</code>」がついたページにリダイレクトします。</p><p>もともとは、デスクトップとモバイルのサイトを分離して別ページを表示することで環境にあったページを表示することを目的とした機能だったものと推測できます。ですが、現在はレスポンシブなページが一般的であり、デスクトップとモバイルを同一コードで表示することが珍しくありません。</p><p>既に過去の遺物となった仕様ですが、変更される兆しはありません。</p><p>ここまでの話で終われば、古臭い機能がついているだけで実害はありません。ですが、強制的にリダイレクトが発生することには実害があります。リダイレクトにより、ページの初回表示がリダイレクト分だけ遅延します。これは、わずかですが重要な問題です。</p></section><section id="toc-3"><h3>回避策</h3><p>CDN(Cloudflare 等)でリクエストを書き換えます。</p><p>モバイル環境で「<code>m=1</code>」ページにアクセスすれば、この問題は回避できます。そのため、アクセス元がモバイル環境である場合、 CDN で先んじて<code>m=1</code>ページにリクエストを書き換えます。これにより、リダイレクト発生を抑止できます。</p><pre><code>Cloudflare
変更ルール(Transform Rules)
[ユーザーエージェント][次を含む][Mobile]
AND
[URL クエリー文字列][等しい][]
クエリー
[Static][?m=1]</code></pre><p>※ CDN 関連であれば、 Cloudflare に限らず類似機能があるはずです。<br>※ URL クエリー文字列が未指定の場合に限定しています。<br> クエリー文字列が何らかの外部目的で使用されている可能性を考慮しています。</p></section><section id="toc-4"><h3>おわりに</h3><p>長い間 Blogger の <code>m=1</code> 問題について考えてきました。本件が、現実的な対応策になるのではと考えています。</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0