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-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-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-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-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.com0tag:blogger.com,1999:blog-279447686030876252.post-31386825516900387902023-03-18T10:13:00.002+09:002023-03-18T10:13:52.070+09:00Firefox111 のネイティブ通知を無効化する<section id="toc-1"><h3>はじめに</h3><p>通知機能は、 Firefox44 以降で実装された機能です。画面上に通知を配信できる機能です。 Push API を使用することで通知を表示できます。主にウェブページの通知(Web Push)と拡張機能の通知で利用されています。</p><p>これまでは、ブラウザの一部として通知を表示していました。ですが、 Firefox111 から OS 標準のネイティブ通知を利用するように標準設定が見直されました。(以前も <code>about:config</code> を変更することでネイティブ通知を利用できました)</p></section><section id="toc-2"><h3>ネイティブ通知の問題</h3><p>端的に邪魔です。</p><p>ブラウザ通知であれば、数秒程度表示されて消えます。その後、何らかのアクションを必要としません。ですが、(Windows の)ネイティブ通知では異なります。通知は、保存され通知欄に表示されたままとなります。意図的に通知を削除するまで残り続けます。これは、良い点もありますが、邪魔でもあります。</p></section><section id="toc-3"><h3>通知をブロックする</h3><p>ウェブページからの通知(Web Push)は、ブロックすることができます。 URL バーの設定ボタンから対象ページの通知をブロックできます。</p><p>[設定] > [セキュリティとプライバシー] > [許可設定] > [通知] から許可・ブロックの一覧を確認できます。</p><p>ただし、拡張機能からの通知をブロックすることはできません。(拡張機能にオプションがある場合を除く)</p></section><section id="toc-4"><h3>ネイティブ通知を無効化する</h3><pre><span class="pre-code-title">user.js</span><code class="language-js:user.js">// ネイティブ通知を使用する (default:true)
user_pref('alerts.useSystemBackend', false);</code></pre><p>上記の記述を user.js に追加する。<br>又は、 <code>about:config</code> の <code>alerts.useSystemBackend</code> に <code>false</code> を設定する。</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-42950621400045402022023-03-16T22:06:00.001+09:002023-03-16T22:07:05.069+09:00おすすめの userChrome.js スクリプト<p>「おすすめ」とは銘打ってありますが、 userChrome.js 自体が一般的な機能ではありません。導入に関しては、自己責任で実施してください。本記事は、 userChrome.js を自力で導入できる方が対象です。</p><section id="toc-1"><h3>tabLock.uc.js</h3><ul><li><a href="https://github.com/alice0775/userChrome.js">alice0775/userChrome.js - GitHub</a></li><li><a href="https://github.com/alice0775/userChrome.js/blob/master/95/tabLock_mod2.uc.js">alice0775/userChrome.js/95/tabLock_mod2.uc.js - GitHub</a></li></ul><p>タブコンテキストメニューからタブのロック(又はロック解除)を実施できます。</p><p>「ロック」とは、リンク等によるページ移動を防ぎます。もしも、ロックしているページでリンクをクリックした場合、リンクを新しいタブとして開きます。</p><p>次のオプションを設定できます。(コードを書き換える)</p><ul><li>「次ページ」「前ページ」を無視する</li><li>「href ="#xxx"」を無視する</li><li>「戻る」「進む」を無視する</li></ul></section><section id="toc-2"><h3>tabProtect.uc.js</h3><ul><li><a href="https://github.com/alice0775/userChrome.js">alice0775/userChrome.js - GitHub</a></li><li><a href="https://github.com/alice0775/userChrome.js/blob/4ac00ad51c337951735994eeb51783eae65404b1/72/tabProtect_mod2.uc.js">alice0775/userChrome.js/72/tabProtect_mod2.uc.js - GitHub</a></li></ul><p>タブコンテキストメニューからタブのプロテクト(又はプロテクト解除)を実施できます。</p><p>「プロテクト」とは、タブが閉じることを防ぎます。もしも、タブを閉じる操作をしてもタブがそのまま維持されます。</p></section><section id="toc-3"><h3>タブをダブルクリックでリロード.uc.js</h3><ul><li><a href="https://u6.getuploader.com/script/download/1945">タブをダブルクリックでリロードv2.uc.js | uploader.jp</a></li></ul><p>タブをダブルクリックでリロードする。</p></section><section id="toc-4"><h3>URLBarAutoCopy.uc.js</h3><ul><li><a href="https://github.com/k08045kk/userChrome.js/blob/master/URLBarAutoCopy.uc.js">k08045kk/userChrome.js/URLBarAutoCopy.uc.js - GitHub</a></li></ul><p>URLバーを選択すると自動的にクリップボードにコピーします。</p></section><section id="toc-5"><h3>ClearSearchWord.uc.js</h3><ul><li><a href="https://u6.getuploader.com/script/download/1778">ClearSearchWord.uc.js | uploader.jp</a></li></ul><p>ツールバー上の検索エリアで検索したとき、検索ワードを削除する。</p><p>前の検索が残っていてもあまりいいことがないため、検索時に削除します。</p></section><section id="toc-6"><h3>DisplayNumberOfDownloadsOnButton.uc.js</h3><ul><li><a href="https://github.com/k08045kk/userChrome.js/blob/master/DisplayNumberOfDownloadsOnButton.uc.js">k08045kk/userChrome.js/DisplayNumberOfDownloadsOnButton.uc.js - GitHub</a></li></ul><p>ツールバー上のダウンロードボタンにダウンロード中のダウンロード数を表示する。</p></section><section id="toc-7"><h3>BookmarkbarNotEditable.uc.js</h3><ul><li><a href="https://www.bugbugnow.net/2021/05/bookmarkbar-not-editable-uc-js.html">Firefoxのブックマークバーを編集不可にする</a></li></ul><p>ブックマークツールバーのドラッグによる編集を編集不可にする。ただし、Shift+ドラッグで編集可能にする。</p><p>ブックマークツールバーでの誤操作を防止します。(ただし、導入したことを忘れると編集できなくて困ります)</p></section><section id="toc-8"><h3>MouseGestures.uc.js</h3><ul><li><a href="https://github.com/alice0775/userChrome.js">alice0775/userChrome.js - GitHub</a></li><li><a href="https://github.com/alice0775/userChrome.js/blob/master/112/MouseGestures2_e10s.uc.js">alice0775/userChrome.js/112/MouseGestures2_e10s.uc.js - GitHub</a></li></ul><p>マウスジェスチャ機能です。</p><p>拡張機能では、制限される動作を実現することができます。ホイールジェスチャー。ロッカージェスチャーにも対応しています。</p><p>※ MouseGestures.uc.js は、上記以外にも複数の作者のバージョンが存在します。<br> 個別に独自進化しているため、注意が必要です。</p></section><section id="toc-9"><h3>IME-Colors.uc.js</h3><ul><li><del><a href="https://u6.getuploader.com/script/download/1696">IME-Colors-e10s.uc.js | uploader.jp</a></del> (削除済み)</li><li><a href="https://u6.getuploader.com/script/search?q=IME-Colors">IME-Colors | 検索 | uploader.jp</a></li><li><a href="https://gist.github.com/Griever/410093">IME の状態に応じて色を変える</a></li></ul><p>テキストフィールド、テキストエリアにカーソルがあるとき、 IME の状態に応じて寒色・暖色のバックグランドを設定する。</p><p>手元の最新版(v0.0.13)は、正常に動作しています。ですが、インターネット上で発見できる最新版(v0.0.11)では、ページ上の IME 切り替えに対応できていません。作者(Griever)様の最新版(v0.0.5)は既に動作していません。</p></section><section id="toc-10"><h3>TabMixPlus</h3><ul><li><a href="https://github.com/onemen/TabMixPlus">onemen/TabMixPlus - GitHub</a></li></ul><p>TabMixPlus は、タブに関する多岐にわたる機能を設定できます。(既存環境に既になれている場合は、あまりおすすめできません)</p><p>TabMixPlus を最新の Firefox 環境で使用することができます。(旧アドオンは、 Firefox57 から廃止となり、使用できなくなっていました)</p><p>TabMixPlus は、 一般的な userChrome.js (userChromeJS)とは異なり、 bootstorap 方式を採用しています。 bootstorap 方式に対応した userChrome.js (ローダー)でないと実行することはできません。</p></section><section id="toc-11"><h3>備考(e10s 版)</h3><p>e10s は、 Electrolysis の略称です。</p><p>Electrolysis は、 Firefox のマルチプロセス対応のことを指しています。マルチプロセスは、安定性向上、パフォーマンス向上、セキュリティ強化などのメリットがあります。(メモリ消費量増加などのデメリットもあります)</p><p>Firefox は、元々シングルプロセスを前提に制作されていました。ですが、上記のメリットがあるため、段階的にマルチプロセス対応が実施されてきました。 Firefox がマルチプロセス対応した結果、内部処理が変更され既存のスクリプトが動作しなくなり、個別の userChrome.js スクリプトも対応を迫られました。その結果が e10s 対応版のスクリプトです。 e10s 版は、分離されたプロセスを横断できる I/F への書き換えなどが行われています。より、複雑な処理が必要となりました。</p><p>段階的に対応が実施されたため、 Firefox のバージョンアップ毎にスクリプトが動作しなくなる現象が多発しました。その結果、開発を終了するスクリプト(旧アドオンも)が相次ぎました。</p></section><section id="toc-12"><h3>備考(userChromeJS 版・bootstorap 版)</h3><p>「userChromeJS」版は、ユーザースクリプトを源流とするスクリプトです。 Greasemonkey 用のスクリプトを chrome 環境で実行するためのスクリプトです。そのため、「実行場所」を指定する以外は、実行時に呼び出しを受ける程度でほとんど外部的な操作を必要としません。</p><p>「bootstorap」版は、旧アドオン(レガシー拡張機能)を源流とするスクリプト(拡張機能?)です。旧アドオンの実行用 I/F を模倣することで userChrome.js 方式で旧アドオンの実行をサポートします。ただし、前述の e10s 対応などがあり、単純に旧アドオンのコードがそのまま動作することはありません。ですが、 Firefox 本体を直接いじれるため、拡張機能とは異なり旧アドオンに類する動作を実現できます。</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-50765422437931383572023-03-06T11:05:00.007+09:002023-03-22T12:10:27.177+09:00IE11 デスクトップアプリを強制的に起動する<section id="toc-1"><h3>はじめに</h3><p>2023年02月14日のアップデート(<a href="https://support.microsoft.com/help/5022834">KB5022834</a>)で IE11 から Edge へ強制リダイレクトが有効になりました。 IE11 デスクトップアプリのサポート終了は、既に(2022年06月15日)実施済みですが、実際 IE11 はいままで通り使用することができました。ですが、今回のアップデートで一般的な利用が制限される形となりました。</p><p>基本的に IE11 デスクトップアプリを使用することはありません。ですが、一部の人(自分)のため、使用方法をまとめます。</p><h4>タイムライン</h4><div class="responsive-table"><table><thead><tr><th>日時</th><th>内容</th></tr></thead><tbody><tr><td>2022年06月15日</td><td>IE11 デスクトップアプリのサポート終了</td></tr><tr><td>2023年02月14日</td><td>IE11 から Edge へ強制リダイレクト</td></tr><tr><td>2023年05月23日</td><td>IE11 のアイコン等を削除(プレビュー)</td></tr><tr><td>2023年06月13日</td><td>IE11 のアイコン等を削除(一般公開)</td></tr><tr><td>~2029年</td><td>IE モードのサポート期限<br>(1年前までに廃止日を通知)</td></tr></tbody></table></div><ul><li><a href="https://techcommunity.microsoft.com/t5/windows-it-pro-blog/internet-explorer-11-desktop-app-retirement-faq/ba-p/2366549">Internet Explorer 11 desktop app retirement FAQ - Microsoft Community Hub</a></li><li><a href="https://blogs.windows.com/japan/2021/05/19/the-future-of-internet-explorer-on-windows-10-is-in-microsoft-edge/">Internet Explorer は Microsoft Edge へ - Windows Blog for Japan</a></li><li><a href="https://forest.watch.impress.co.jp/docs/news/1464654.html">「IE 11」の完全無効化は2023年2月14日、「Edge」の更新で実施 - 窓の杜</a></li></ul></section><section id="toc-2"><h3>アドオンを無効にして実行する</h3><h4>ファイル名を指定して実行([Win+R])</h4><pre><code class="language-txt">iexplore.exe -extoff</code></pre><h4>ショートカット(リンク先指定)</h4><pre><code class="language-txt">"C:\Program Files\Internet Explorer\iexplore.exe" -extoff</code></pre><h4>備考</h4><p>今回のアップデートは、「IEToEdge BHO」というアドオンを IE11 に導入することで実施されています。</p><p>「IEToEdge BHO」は、アドオンの機能として IE11 での表示を Edge へ強制リダイレクトしています。そのため、すべてのアドオンを無効化して IE11 を実行することで問題を回避できます。(アドオンは、通常個別に無効化できますが、「IEToEdge BHO」は個別の無効化には対応していません)</p></section><section id="toc-3"><h3>スクリプト経由で実行する</h3><h4>VBScript</h4><pre><span class="pre-code-title">ie11.vbs</span><code class="language-vbs:ie11.vbs">CreateObject("InternetExplorer.Application").Visible=true</code></pre><h4>JScript</h4><pre><span class="pre-code-title">ie11.jse</span><code class="language-js:ie11.jse">WScript.CreateObject('InternetExplorer.Application').Visible=true</code></pre><h4>PowerShell</h4><pre><span class="pre-code-title">ie11.ps1</span><code class="language-ps1:ie11.ps1">(New-Object -ComObject InternetExplorer.Application).Visible = $true</code></pre><p>※<code>powershell -NoProfile -ExecutionPolicy Unrestricted .\ie11.ps1</code> 等で実行する</p><h4>バッチファイル(PowerShell)</h4><pre><span class="pre-code-title">ie11.bat</span><code class="language-bat:ie11.bat">@powershell -NoProfile -ExecutionPolicy Unrestricted "&([ScriptBlock]::Create((cat \"%~f0\" | ?{$_.ReadCount -gt 1}) -join \"`n\"))" %* & goto:eof
(New-Object -ComObject InternetExplorer.Application).Visible = $true</code></pre><h4>備考(初期表示 URL の指定例)</h4><pre><span class="pre-code-title">ie11.vbs</span><code class="language-vbs:ie11.vbs">Dim ie
Set ie = CreateObject("InternetExplorer.Application")
ie.Navigate "https://www.google.co.jp/"
ie.Visible = true
Set ie = Nothing</code></pre></section><section id="toc-4"><h3>Edge の IE モードを使用する</h3><p>次の設定を有効にする。</p><pre><code>edge://settings/defaultBrowser
[設定] > [既定のブラウザー] > [Internet Explorer モード (IE モード) でサイトの再読み込みを許可]
[既定] → [許可]</code></pre><p>次の操作で IE モードを使用する。</p><pre><code>[…] > [Internet Explorer モードで再読み込みする]
[Internet Explorer モードのリロードタブ]
をクリックする
[設定] > [既定のブラウザー] > [Internet Explorer モードページ] > [追加]
で URL を追加する</code></pre><p>※ブラウザ右上のメニューボタン([…])<br>※[Internet Explorer モードのリロードタブ] は、ブラウザアクションの位置にあります。</p></section><section id="toc-5"><h3>インターネットオプションから実行する</h3><ol><li>[インターネットオプション] を開く<ul><li>ファイル名を指定して実行([Win+R])<ul><li><code>INETCPL.CPL</code></li></ul></li></ul></li><li>[インターネットオプション] > [プログラム] > [アドオンの管理(M)]<ul><li>[アドオン管理] が開く</li></ul></li><li>[アドオン管理] > [ツールバーと拡張機能の詳細(N)]<ul><li>IE が起動する</li></ul></li></ol><hr><ol><li>[インターネットオプション] を開く</li><li>[インターネットオプション] > [?]<ul><li>ウィンドウ右上の [?] をクリックする</li><li>IE が起動する</li></ul></li></ol><p>※なぜか、システム標準ブラウザではなく IE が起動する。</p></section><section id="toc-6"><h3>【非推奨】レジストリを変更する</h3><pre><code>HKCU\SOFTWARE\Microsoft\Edge\IEToEdge¥RedirectionMode
2 → 0
HKCU\SOFTWARE\AppDataLow\Software\Microsoft\Edge\IEToEdge¥DisableUpsellEdge
0 → 1</code></pre></section><section id="toc-7"><h3>参考</h3><ul><li><a href="https://forest.watch.impress.co.jp/docs/serial/yajiuma/1480018.html">廃止された「IE 11」を蘇らせる禁断の技、まだ使える? ~全部試してみました</a></li><li><a href="https://webllica.com/ie-to-edge-program/">IE から自動的に Edge が起動する仕組み。レジストリで制御できる?!</a></li><li><a href="https://nukunukusas.com/ie-to-edge-invalidation">終焉したIE復活の儀!無効化されたIEを起動する5つの方法</a></li><li><a href="https://nj-clucker.com/stop-open-ie-to-edge/">Internet Explorer 11 から勝手に Edge が開かないようにする方法</a></li><li><a href="https://forest.watch.impress.co.jp/docs/serial/yajiuma/1487240.html">「IE 11」をよみがえらせる最速(?)の技がまた報告される - 窓の杜</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag: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-35277691110536957362023-02-13T15:54:00.006+09:002023-02-13T15:55:37.064+09:00合計/平均/分散/中央値/etc を計算する【JavaScript】<script type="application/json" id="post-data-json">{
"title": "【JavaScript】合計/平均/分散/中央値/etc を計算する"
,"labels": ["JavaScript", "", ""]
,"url": "https://www.bugbugnow.net/2023/02/calculate-sum-avg-var-median-etc.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">k番目の値</a></li><li><a class="toc-link" href="#toc-8">中央値</a></li><li><a class="toc-link" href="#toc-9">参考</a></li></ol></div></div><section id="toc-1"><h3>合計</h3><h4>1行実装</h4><pre><code class="language-js">const sumValue = array.reduce((pre, cr) => pre+cr);</code></pre><h4>簡易実装</h4><pre><code class="language-js">const sum = (array) => {
const len = array.length;
let sum = 0
for (let i=0; i<len; i++) {
sum = sum + array[i];
}
return sum;
};</code></pre><h4>サンプル</h4><pre><code class="language-js">var array = [1, 3, 5, 7, 9, 8, 6, 4, 2];
var sumValue = array.reduce((pre, cr) => pre+cr);
console.log(sumValue); // 45
console.log(sum(array)); // 45
console.log(sum([2])); // 2
console.log(sum([2, 3])); // 5
console.log(sum([2, 3, 5])); // 10</code></pre></section><section id="toc-2"><h3>平均</h3><h4>1行実装</h4><pre><code class="language-js">const avgValue = array.reduce((pre, cr) => pre+cr) / array.length;</code></pre><h4>簡易実装</h4><pre><code class="language-js">const average = (array) => {
const len = array.length;
if (len === 0) { return 0; }
let sum = 0
for (let i=0; i<len; i++) {
sum = sum + array[i];
}
return sum / len;
};</code></pre><h4>サンプル</h4><pre><code>var array = [1, 3, 5, 7, 9, 8, 6, 4, 2];
var avgValue = array.reduce((pre, cr) => pre+cr) / array.length;
console.log(avgValue); // 5
console.log(average(array)); // 5
console.log(average([2])); // 2
console.log(average([2, 3])); // 2.5
console.log(average([2, 3, 5]));// 3.3333333333333335</code></pre></section><section id="toc-3"><h3>分散</h3><h4>簡易実装</h4><pre><code class="language-js">const variance = (array) => {
const len = array.length;
if (len === 0) { throw new Error('Cannot calculate variance of an empty array.'); }
let sum = 0
for (let i=0; i<len; i++) {
sum = sum + array[i];
}
const avg = sum / len;
let vsum = 0;
for (let i=0; i<len; i++) {
vsum = vsum + (array[i] - avg)**2
}
return vsum / len; // 標本分散
//return vsum / (len-1); // 不偏分散
};</code></pre><h4>サンプル</h4><pre><code class="language-js">console.log(variance([1, 2, 3])); // 0.6666666666666666
console.log(variance([1, 5, 9])); // 10.666666666666666
console.log(variance([9, 7, 5, 3, 1, 2, 4, 6, 8])); // 6.666666666666667
console.log(variance([9, 7, 5, 3, 1, 0, 2, 4, 6, 8])); // 8.25</code></pre></section><section id="toc-4"><h3>標準偏差</h3><h4>簡易実装</h4><pre><code class="language-js">const stdev = (array, normalize) => {
const len = array.length;
if (len === 0) { throw new Error('Cannot calculate standard deviation of an empty array.'); }
let sum = 0
for (let i=0; i<len; i++) {
sum = sum + array[i];
}
const avg = sum / len;
let vsum = 0;
for (let i=0; i<len; i++) {
vsum = vsum + (array[i] - avg)**2
}
return Math.sqrt(vsum / len); // 母集団標準偏差
//return Math.sqrt(vsum / (len-1)); // 標本標準偏差
};</code></pre><h4>サンプル</h4><pre><code class="language-js">console.log(stdev([1, 2, 3])); // 0.816496580927726
console.log(stdev([1, 5, 9])); // 3.265986323710904
console.log(stdev([9, 7, 5, 3, 1, 2, 4, 6, 8])); // 2.581988897471611
console.log(stdev([9, 7, 5, 3, 1, 0, 2, 4, 6, 8])); // 2.8722813232690143</code></pre></section><section id="toc-5"><h3>最大値</h3><h4>1行実装</h4><pre><code class="language-js">const maxValue = array.reduce((pre, cr) => pre > cr ? pre : cr);</code></pre><hr><pre><code class="language-js">const maxValue = Math.max(...array);</code></pre><h4>簡易実装</h4><pre><code class="language-js">const max = function(array) {
const len = array.length;
if (len === 0) { throw new Error('Cannot calculate min of an empty array.'); }
let max = array[0];
for (let i=1; i<len; i++) {
if (max < array[i]) {
max = array[i];
}
}
return max;
};</code></pre><h4>サンプル</h4><pre><code class="language-js">var array = [1, 3, 5, 7, 9, 8, 6, 4, 2];
var maxValue = array.reduce((pre, cr) => pre > cr ? pre : cr);
console.log(maxValue); // 9
console.log(max(array)); // 9</code></pre></section><section id="toc-6"><h3>最小値</h3><h4>1行実装</h4><pre><code class="language-js">const minValue = array.reduce((pre, cr) => pre < cr ? pre : cr);</code></pre><hr><pre><code class="language-js">const minValue = Math.min(...array);</code></pre><h4>簡易実装</h4><pre><code class="language-js">const min = function(array) {
const len = array.length;
if (len === 0) { throw new Error('Cannot calculate max of an empty array.'); }
let min = array[0];
for (let i=1; i<len; i++) {
if (array[i] < min) {
min = array[i];
}
}
return min;
};</code></pre><h4>サンプル</h4><pre><code class="language-js">var array = [9, 7, 5, 3, 1, 2, 4, 6, 8];
var minValue = array.reduce((pre, cr) => pre - cr < 0 ? pre : cr);
console.log(minValue); // 1
console.log(min(array)); // 1</code></pre></section><section id="toc-7"><h3><a id="toc-selectKth"></a>k番目の値</h3><h4>簡易実装</h4><pre><code class="language-js">// see https://blog.teamleadnet.com/2012/07/quick-select-algorithm-find-kth-element.html
const selectKth = function(array, k) {
const len = array.length;
if (k < 0 || len <= k) { throw new Error('k is out of bounds.'); }
let count = 0;
let from = 0;
let to = len - 1;
while (from < to) {
let r = from;
let w = to;
let mid = array[(r+w)/2|0];
while (r < w) {
if (array[r] >= mid) {
const tmp = array[w];
array[w] = array[r];
array[r] = tmp;
w--;
} else {
r++;
}
}
if (array[r] > mid) {
r--;
}
if (k <= r) {
to = r;
} else {
from = r + 1;
}
}
return array[k];
};
const small = function(array, k) { return selectKth(array, k); };
const large = function(array, k) { return selectKth(array, array.length-k-1); };</code></pre><p>※<code>selectKth()</code>は、<code>array</code> を不完全に並び替えます。</p><h4>サンプル</h4><pre><code class="language-js">console.log(small([1, 3, 5, 7, 9, 8, 6, 4, 2], 0)); // 1
console.log(small([1, 3, 5, 7, 9, 8, 6, 4, 2], 1)); // 2
console.log(small([9, 8, 7, 6, 5, 4, 3, 2, 1], 2)); // 3
console.log(small([9, 8, 7, 6, 5, 4, 3, 2, 1], 3)); // 4
console.log(small([9, 7, 5, 3, 1, 2, 4, 6, 8], 4)); // 5
console.log(small([1, 2, 3, 4, 5, 6, 7, 8, 9], 5)); // 6
console.log(small([1, 2, 3, 4, 5, 6, 7, 8, 9], 6)); // 7
console.log(small([9, 7, 5, 3, 1, 2, 4, 6, 8], 7)); // 8
console.log(small([9, 7, 5, 3, 1, 2, 4, 6, 8], 8)); // 9
console.log(large([1, 2, 3, 4, 5, 6, 7, 8, 9], 0)); // 9
console.log(large([1, 2, 3, 4, 5, 6, 7, 8, 9], 1)); // 8
console.log(large([9, 7, 5, 3, 1, 2, 4, 6, 8], 2)); // 7
console.log(large([9, 7, 5, 3, 1, 2, 4, 6, 8], 3)); // 6
console.log(large([9, 7, 5, 3, 1, 2, 4, 6, 8], 4)); // 5
console.log(large([1, 3, 5, 7, 9, 8, 6, 4, 2], 5)); // 4
console.log(large([1, 3, 5, 7, 9, 8, 6, 4, 2], 6)); // 3
console.log(large([9, 8, 7, 6, 5, 4, 3, 2, 1], 7)); // 2
console.log(large([9, 8, 7, 6, 5, 4, 3, 2, 1], 8)); // 1</code></pre></section><section id="toc-8"><h3>中央値</h3><h4>簡易実装(<code>sort()</code>版)</h4><pre><code class="language-js">const median = (array) => {
const len = array.length;
if (len === 0) { throw new Error('Cannot calculate median of an empty array.'); }
array.sort((a, b) => a-b);
if (len % 2) {
return array[(len-1) / 2)];
} else {
const mid = len / 2;
return (array[mid-1] + array[mid]) / 2;
}
};</code></pre><h4>簡易実装(<code>selectKth()</code>版)</h4><pre><code class="language-js">const median = (array) => {
const len = array.length;
if (len === 0) { throw new Error('Cannot calculate median of an empty array.'); }
if (len % 2) {
return selectKth(array, (len-1)/2);
} else {
const mid = len/2 - 1;
const large = selectKth(array, mid+1);
let small = array[mid];
for (let i=0; i<mid; i++) {
if (small < array[i]) {
small = array[i]
}
}
return (small + large) / 2;
}
};</code></pre><p>※完全には並び替えないため、<code>sort()</code>より<code>selectKth()</code>の方が高速です。<br>※<code>selectKth()</code>は、「<a href="#toc-selectKth">k番目の値</a>」参照</p><h4>サンプル</h4><pre><code class="language-js">console.log(median([2, 3])); // 2.5
console.log(median([5, 2, 3])); // 3
console.log(median([9, 7, 5, 3, 1, 2, 4, 6, 8])); // 5
console.log(median([9, 7, 5, 3, 1, 0, 2, 4, 6, 8])); // 4.5</code></pre></section><section id="toc-9"><h3>参考</h3><ul><li><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce">Array.prototype.reduce() - JavaScript | MDN</a></li><li><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/max">Math.max() - JavaScript | MDN</a></li><li><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/min">Math.min() - JavaScript | MDN</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-30684989858994378572023-02-11T21:20:00.007+09:002023-02-16T13:39:14.970+09:00Element から CSS Selector を取得する<script type="application/json" id="post-data-json">{
"title": "Element から CSS Selector を取得する"
,"labels": ["JavaScript", "CSS", ""]
,"url": "https://www.bugbugnow.net/2023/02/get-css-selector.html"
}</script><section id="toc-1"><h3>コード</h3><pre><span class="pre-code-title">getCSSSelector.js</span><code class="language-js:getCSSSelector.js">/**
* Element から CSS Selector を取得する
* @author toshi (https://github.com/k08045kk)
* @license MIT License | https://opensource.org/licenses/MIT
* @version 1
* @since 1 - 20230211 - 初版
* @see https://www.bugbugnow.net/2023/02/get-css-selector.html
* @see https://gist.github.com/k08045kk/d239bec86ae9b06c438e7e2bf67575fb
* @param {Element} element - 要素
* @return {string} - CSS Selector
*/
var getCSSSelector = function(element) {
if (!(element && element instanceof Node)
|| !(element.nodeType === Node.ELEMENT_NODE || (element=element.parentElement))) {
return 'unknown';
}
var array = [];
for (; element; element=element.parentElement) {
if (element.id) {
// #id
array.unshift('#'+element.id)
break;
} else {
// tagName.className:nth-of-type(n)
var tagName = element.tagName;
var text = tagName.toLowerCase();
var list = element.classList;
var len = list.length;
for (var i=0; i<len; i++) { text += '.'+list[i]; }
var n = 0;
for (var pre=element; (pre=pre.previousElementSibling) && (pre.tagName != tagName || ++n); ) {}
var nth = n+1;
if (!n) {
for (var next=element; (next=next.nextElementSibling) && (next.tagName != tagName || !++n); ) {}
}
if (n) {
text += ':nth-of-type('+nth+')';
}
array.unshift(text);
}
}
return array.join(' > ');
};</code></pre><hr><pre><span class="pre-code-title">getCSSSelector.min.js</span><code class="language-js:getCSSSelector.min.js">/*! getCSSSelector.js | MIT License | https://gist.github.com/k08045kk */var getCSSSelector=function(a){if(!(a&&a instanceof Node)||a.nodeType!==Node.ELEMENT_NODE&&!(a=a.parentElement))return"unknown";for(var e=[];a;a=a.parentElement)if(a.id){e.unshift("#"+a.id);break}else{for(var f=a.tagName,g=f.toLowerCase(),b=a.classList,c=b.length,d=0;d<c;d++)g+="."+b[d];b=0;for(c=a;(c=c.previousElementSibling)&&(c.tagName!=f||++b););c=b+1;if(!b)for(d=a;(d=d.nextElementSibling)&&(d.tagName!=f||!++b););b&&(g+=":nth-of-type("+c+")");e.unshift(g)}return e.join(" > ")};</code></pre></section><section id="toc-2"><h3>更新履歴</h3><ul><li>参照:<a href="https://gist.github.com/k08045kk/d239bec86ae9b06c438e7e2bf67575fb">getCSSSelector.js | GitHub Gist</a></li></ul></section><section id="toc-3"><h3>備考</h3><p><code>document.querySelector()</code> で <code>Element</code> を取得可能な <code>CSS Selector</code> を取得します。</p><p>ただ、上記コードが完全なコードではなく突き詰めれば多彩なバリエーションを作成できます。例えば次のような関数が考えられます。</p><ul><li>行数最小の関数</li><li>オプションによる高機能関数</li><li><code>.className</code> を含まない</li><li><code>:nth-of-type(n)</code> を含まない</li><li><code>:nth-child(n)</code> で実装する</li><li>一部の属性を追記する(<code><a></code>の <code>href</code>属性等)</li><li>Element 以外の対策除去(Node or null 対策)</li><li>XML モード対応(tagName の大文字小文字対策)</li></ul><p>上記コードは、「結果の読みやすさ」と「処理の継続性」を考慮して作成しています。「結果の読みやすさ」を無視できるのであれば、<code>.className</code> 部分を削除できます。同様に「処理の継続性」を無視できるのであれば、<code>Node</code>や<code>null</code>などへの対応部分を削除できます。<code>tagName</code>の大文字小文字を考慮すれば、XMLモードに対応することもできます。</p></section><section id="toc-4"><h3>使用例(Bookmarklet)</h3><p>ページ上の要素をクリックすると、 コンソールへ CSS Selector を出力します。</p><pre><span class="pre-code-title">getCSSSelector.bookmarklet.js</span><code class="language-js:getCSSSelector.bookmarklet.js">javascript:/*! included (getCSSSelector.js | MIT License | gist.github.com/k08045kk) */(function(){window.addEventListener("click",function(h){var k=console,l=k.log,a;if((a=h.target)&&a instanceof Node&&(a.nodeType===Node.ELEMENT_NODE||(a=a.parentElement))){for(var e=[];a;a=a.parentElement)if(a.id){e.unshift("#"+a.id);break}else{for(var f=a.tagName,g=f.toLowerCase(),b=a.classList,c=b.length,d=0;d<c;d++)g+="."+b[d];b=0;for(c=a;(c=c.previousElementSibling)&&(c.tagName!=f||++b););c=b+1;if(!b)for(d=a;(d=d.nextElementSibling)&&(d.tagName!=f||!++b););b&&(g+=":nth-of-type("+c+")");e.unshift(g)}a=e.join(" > ")}else a="unknown";l.call(k,a,h.target)})})();</code></pre><details><summary>無圧縮コード</summary><pre><span class="pre-code-title">getCSSSelector.bookmarklet.js</span><code class="language-js:getCSSSelector.bookmarklet.js">/*! included (getCSSSelector.js | MIT License | gist.github.com/k08045kk) */
(function () {
var getCSSSelector = function(element) {
if (!(element && element instanceof Node)
|| !(element.nodeType === Node.ELEMENT_NODE || (element=element.parentElement))) {
return 'unknown';
}
var array = [];
for (; element; element=element.parentElement) {
if (element.id) {
// #id
array.unshift('#'+element.id)
break;
} else {
// tagName.className:nth-of-type(n)
var tagName = element.tagName;
var text = tagName.toLowerCase();
var list = element.classList;
var len = list.length;
for (var i=0; i<len; i++) { text += '.'+list[i]; }
var n = 0;
for (var pre=element; (pre=pre.previousElementSibling) && (pre.tagName != tagName || ++n); ) {}
var nth = n+1;
if (!n) {
for (var next=element; (next=next.nextElementSibling) && (next.tagName != tagName || !++n); ) {}
}
if (n) {
text += ':nth-of-type('+nth+')';
}
array.unshift(text);
}
}
return array.join(' > ');
};
window.addEventListener('click', function(event) {
console.log(getCSSSelector(event.target), event.target);
});
})();</code></pre></details></section><section id="toc-5"><h3>参考</h3><ul><li><a href="https://stackoverflow.com/questions/3620116/get-css-path-from-dom-element">javascript - Get CSS path from Dom element - Stack Overflow</a></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-73841777661358875292022-10-10T16:38:00.005+09:002022-10-10T23:32:39.824+09:00canvas を画面サイズに合わせる方法<script type="application/json" id="post-data-json">{
"title": "canvas を画面サイズに合わせる方法"
,"labels": ["JavaScript", "HTML", "CSS"]
,"url": "https://www.bugbugnow.net/2022/10/fit-canvas-screen-size.html"
}</script><p>キャンバス要素を画面全体に表示します。</p><section id="toc-1"><h3>結論</h3><pre><span class="pre-code-title">sample-fit-canvas-screen-size.html</span><code class="language-html:sample-fit-canvas-screen-size.html"><!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<style>
html, body, #canvas {
display: block;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const onRepaint = function() {
console.log('repaint');
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.fillStyle = 'red';
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'blue';
context.fillRect(10, 10, canvas.width-20, canvas.height-20);
};
const onResize = function() {
console.log('resize');
const canvas = document.getElementById('canvas');
console.log(canvas.height, canvas.clientHeight, window.innerHeight);
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
onRepaint();
};
window.addEventListener('DOMContentLoaded', onResize);
window.addEventListener('resize', onResize);
</script>
</body>
</html></code></pre><p><a href="https://cdn.bugbugnow.net/blog/post/2022/sample-fit-canvas-screen-size.html">サンプルページ</a></p></section><section id="toc-2"><h3>スマホ用にビューポートを設定する</h3><pre><code class="language-html"><meta name="viewport" content="width=device-width,initial-scale=1"/></code></pre><p>スマホ環境でビューポートを端末サイズに合わせます。</p></section><section id="toc-3"><h3>要素を画面サイズに合わせる</h3><pre><code class="language-css">html, body, #canvas {
display: block;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}</code></pre><p>要素(<code><canvas id="canvas"></code>)を画面サイズに合わせます。<br>具体的には、次の5点を変更します。</p><ul><li><code><html></code>を画面サイズまで拡張する<ul><li><code>height: 100%;</code>で高さを拡張する</li></ul></li><li><code><body></code>の余白を削除する<ul><li><code>margin: 0;</code>で不要な余白を削除する</li></ul></li><li><code><body></code>を<code><html></code>まで拡張する<ul><li><code>height: 100%;</code>で高さを拡張する</li></ul></li><li><code><canvas></code>を<code><body></code>まで拡張する<ul><li><code>width: 100%;</code>で横幅を拡張する</li><li><code>height: 100%;</code>で高さを拡張する</li></ul></li><li><code><canvas></code>をブロック要素として表示する<ul><li><code>display: block;</code>でブロック要素として表示する<ul><li><code><canvas></code> の初期設定はインライン要素です</li></ul></li></ul></li></ul><h4>備考(<code>100vh</code> を使う)</h4><pre><code class="language-css">body {
height: 100vh;
margin: 0;
}
#canvas {
display: block;
width: 100%;
height: 100%;
}</code></pre><p><code>body { height: 100vh }</code> を設定する方法もあります。ただし、<code>100%</code>と<code>100vh</code>には、違いがあります。</p><p><code>100vh</code> の最大の問題は、スマホ環境のアドレスバーがある場合、アドレスバー分の領域がはみ出すことです。<code>100vh</code>は画面の高さであるため、アドレスバーの領域も含めて計算されるからです。<code>100vh</code>は画面のコンテンツ表示領域ではありません。画面のビューポートの高さなのです。他にもスマホ環境のキーボード周りの問題もあります。</p><h4>別解(キャンバスを浮かせる)</h4><pre><code class="language-css">#canvas {
display: block;
position: fixed;
top: 0;
left: 0;
z-index: 999;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
/*opacity: 0.5;*/
}</code></pre><p><code><canvas id="canvas"></code> を他要素の前面に配置して、画面サイズまで拡張します。</p><p>「ページに別要素を表示している状態で、一時的にキャンバスでページを覆い尽くす」などの用途に利用できます。</p></section><section id="toc-4"><h3><code><canvas></code>の表示サイズとキャンバスサイズを一致させる</h3><p><code><canvas></code>には、2つのサイズがあります。「HTML要素として表示サイズ」と「キャンバスのサイズ」です。具体的には、要素の <code>clientWidth / clientHeight</code> (表示サイズ)と <code>width / height</code> (キャンバスサイズ)です。</p><p>表示サイズとキャンバスサイズは、常に一致しているわけではありません。表示サイズは、CSS的な要素サイズの変更によって常に変化します。キャンバスサイズは、明示的なサイズ指定を行うことで変更することができます。表示サイズとキャンバスサイズが異なる場合、ボヤケたような不自然な表示になります。</p><pre><code class="language-js">const canvas = document.getElementById('canvas');
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;</code></pre><h4>備考(<code>window.innerHeight</code> を使う)</h4><pre><code class="language-js">const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;</code></pre><p><code>window.innerHeight</code> (ウィンドウの高さ)を使用します。ただし、次の問題があります。</p><p><code><canvas></code> のキャンバスサイズに<code>window.innerHeight</code> (ウィンドウの高さ)を指定しても、<code><canvas></code>の表示サイズがウィンドウの高さと異なる場合、表示サイズとキャンバスサイズの不一致により表示の歪みが発生します。キャンバスサイズをウィンドウの高さに設定しても、<code><canvas></code>の表示サイズが連動して変更されるわけではない点を考慮してください。</p></section><section id="toc-5"><h3>ブラウザのサイズ変更を監視する</h3><pre><code class="language-js">window.addEventListener('resize', onResize);</code></pre><p>ブラウザのサイズ変更を考慮して、<code>resize</code> イベントを監視し、表示サイズの変更をキャンバスサイズに随時再設定します。</p></section><section id="toc-6"><h3>まとめ</h3><ol><li>ビューポートを適切に設定する</li><li>要素の表示サイズを画面サイズに合わせる</li><li>キャンバスサイズを表示サイズに設定する</li><li>表示サイズの変更をキャンバスサイズに適時再設定する</li></ol></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-48922239869412995842022-03-10T22:38:00.008+09:002022-08-06T14:17:16.809+09:00Firefox98 のダウンロード関連の設定に関する忘却録<script type="application/json" id="post-data-json">{
"title": "Firefox98 のダウンロード関連の設定に関する忘却録"
,"labels": ["Firefox", "", ""]
,"url": "https://www.bugbugnow.net/2022/03/firefox98-download.html"
}</script><section id="toc-1"><h3>Firefox98 の変更点</h3><ul><li><a href="https://www.mozilla.org/en-US/firefox/98.0/releasenotes/">Firefox 98.0, See All New Features, Updates and Fixes</a></li><li><a href="https://support.mozilla.org/en-US/kb/manage-downloads-preferences-using-downloads-menu">Changes to how file downloads are handled in Firefox version 98 | Firefox Help</a></li></ul></section><section id="toc-2"><h3>ダウンロード関連の変更点</h3><ul><li>標準で確認ダイアログを表示しなくなりました。<ul><li>標準でファイルの確認ダイアログを表示しなくなりました。</li><li>Firefox98 に更新すると、既存の [毎回確認する] 設定が初期化されます。</li><li>再設定:次の設定から確認ダイアログを再度表示することができます。<ul><li>[設定] > [一般] > [ファイルとプログラム] > [プログラム]</li><li>指定のファイルの種類に [毎回確認する] を設定する。</li></ul></li></ul></li><li>ダウンロードパネルが自動的に開きます。<ul><li>ダウンロードが開始するたびに、ダウンロードパネルを自動的に表示する。</li><li>元に戻す方法(ダウンロードパネルを自動的に表示しない)<ul><li><code>about:config</code> で <code>browser.download.alwaysOpenPanel</code> に <code>false</code> を指定する</li></ul></li></ul></li><li>ダウンロードしたファイルはディスクに保存されます<ul><li>過去の動作<ul><li>Temp フォルダに一時保存する。</li><li>一時保存後ファイル保存の場合、ダウンロードフォルダ移動する。</li><li>一時保存後ダウンロードプレビュー場合、 Temp フォルダから使用する。</li><li>Temp フォルダ内のファイルは、一定期間後に OS で自動的に削除されます。</li></ul></li><li>Firefox98 から Temp フォルダを経由せずに、ダウンロードフォルダに直接保存されます。</li><li>ダウンロードプレビューの場合、ダウンロードフォルダにファイルが残る点に注意してください。</li></ul></li><li>ファイルタイプを開くようにデフォルトのアプリを設定する<ul><li>特定の種類のファイルを開くアプリケーションを選択できます。</li><li>[設定] > [一般] > [ファイルとプログラム] > [プログラム]</li></ul></li><li>ダウンロードパネルの「常に規定のプログラムで開く」オプション<ul><li>[常に規定のプログラムで開く] コンテキストメニューからも設定できます。</li></ul></li></ul><p>※「ダウンロードプレビュー」とは、指定のプログラムでファイルを開くことを指します。</p></section><section id="toc-3"><h3>ダウンロード関連の設定を元に戻す(Firefox97 以前に戻す)</h3><p><code>about:config</code> で <code>browser.download.improvements_to_download_panel</code> に <code>false</code> を指定することで上記のダウンロード関連の設定を元に戻すことができます。</p><p>ただし、これは古い設定をそのまま使用するための設定ではありません。新しい設定を使用しないための設定であり、今後この設定がなくなる可能性があります。</p></section><section id="toc-4"><h3>ダウンロードプレビューの問題</h3><p>他の問題は、上記の通り。ある程度回避する方法が準備されています。ただし、ダウンロードプレビューの仕様変更を回避する方法は、 <code>browser.download.improvements_to_download_panel = false</code> 以外準備されていません。</p><p>PDFや画像ファイルなどを Firefox 以外のプログラムでダウンロードプレビューしている場合、問題となります。([Firefox で開く] を設定している場合は問題となりません)</p><p>次の場所で議論されていますが、現状の動作が仕様になるようです。</p><ul><li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1738574">https://bugzilla.mozilla.org/show_bug.cgi?id=1738574</a></li></ul><h4>追記</h4><p>議論の結果、<code>browser.download.start_downloads_in_tmp_dir</code> (規定:<code>false</code>)が新設されました。 Firefox は、Firefox で構成されたダウンロードフォルダーではなく、 OS 一時フォルダー(のサブフォルダー)に最初にダウンロードを配置します。「Firefox がこのファイルをどのように処理するか」ダイアログから開いたファイル、またはヘルパーアプリケーションで自動的に開くように設定されたファイルは、このフォルダーに残ります。保存されたファイル(前述のように開かれていません)は、 Firefox のダウンロードフォルダーに残ります。</p><p>詳細については、<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1738574#c133">bug1738574#c133</a> を参照してください。</p><p>これにより、 <code>browser.download.start_downloads_in_tmp_dir = true</code> でダウンロードプレビューの問題を回避できるようになりました。</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com4tag: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-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-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-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-73455772014782428162022-01-19T18:21:00.004+09:002023-02-23T07:51:10.300+09:00JavaScript でルートドメイン(root domain)を取得する<script type="application/json" id="post-data-json">{
"title": "JavaScript でルートドメイン(root domain)を取得する"
,"labels": ["JavaScript", "DNS", ""]
,"url": "https://www.bugbugnow.net/2022/01/get-root-domain.html"
}</script><div><style> #app-form {
margin: 1em 0;
padding: 1em 0;
border: 1px solid #aaa;
background: #F8F8F0;
text-align: center;
}
#app-input,
#app-output {
width: 400px;
} </style><form id="app-form"><p><input type="text" id="app-input" placeholder="入力(例:https://www.bugbugnow.net/)"></p><p><button id="app-encode" type="button">変換</button></p><p><input type="text" id="app-output" placeholder="出力(例:bugbugnow.net)" readonly=""></p></form><script defer="" src="https://cdn.bugbugnow.net/lib/psl.min.js"></script><script type="none" id="post-body-script">const GetRootDomain = function(url) {
if (!psl) { return 'Error: Failed to load library.'; }
let domain = url;
try {
domain = new URL(url).hostname;
// Note: `ttp://example.com/` で空文字を返す
} catch (e) {}
try {
if (domain.length) {
return psl.parse(domain).domain || 'Error';
}
} catch (e) {}
return 'Error';
};
document.getElementById('app-encode').addEventListener('click', function() {
const input = document.getElementById('app-input');
const output = document.getElementById('app-output');
output.value = GetRootDomain(input.value);
});</script></div><section id="toc-1"><h3>はじめに</h3><p>URLからルートドメインを取得する処理について考えます。</p><p>結果から言えば、正確な結果が必要ならば、素直にライブラリを使いましょう。</p></section><section id="toc-2"><h3>ルートドメインとは</h3><p>ここで言う「ルートドメイン」とは、サブドメイン名やホスト名を含まない一般的に個人や組織が取得できるドメイン名のことを指します。</p><p>例として、「<code>www.example.co.jp</code>」であれば、「<code>example.co.jp</code>」のことを指します。</p><p>※「独自ドメイン名」「カスタムドメイン名」「ドメイン名」と呼ばれることもあります。<br> 「ルートドメイン」は、ドメイン名の一番右側のドットです。一般的に省略されます。<br> そのため、ルートドメインは正確には誤用です。</p></section><section id="toc-3"><h3>サブドメインを含むドメイン名の取得処理</h3><p>サブドメインを含むドメイン名は、次のように簡単に取得することができます。</p><pre><code class="language-js">console.log(document.domain);
console.log(window.location.hostname);
console.log(new URL('https://www.bugbugnow.net/').hostname);
// www.bugbugnow.net</code></pre></section><section id="toc-4"><h3>簡易なルートドメインの取得処理</h3><p>安易な考えとして、「<code>.com</code>」「<code>.jp</code>」「<code>.co.jp</code>」などのトップレベルドメイン(TLD)を考慮すれば、容易にルートドメインを取得できるように思えます。</p><p>簡易な実装として次のようなものが考えられます。</p><pre><code class="language-js">function GetDomain(url) {
const hostname = new URL(url).hostname;
const level = hostname.split('.');
if (level[level.length-1] == '') { level.pop(); }
let domain;
const len = level.length;
if (len > 2 && level[len-2].length == 2 && level[len-1].length == 2) {
domain = level[len-3] + '.' + level[len-2] + '.' + level[len-1];
} else if (len > 1) {
domain = level[len-2] + '.' + level[len-1];
} else {
domain = level.join('.');
}
return domain;
}</code></pre><p>ただし、この処理では多くの問題を含みます。</p><p>※「<code>.co.jp</code>」は、正確にはセカンドレベルドメインです。</p></section><section id="toc-5"><h3>簡易な処理の問題点</h3><p>上記の処理の問題点は、次のパターンが漏れている点です。(他にも多く漏れています)</p><pre><code>https://www.pref.aichi.jp/
https://www.city.iwakura.aichi.jp/</code></pre><p>上記の <code>GetDomain()</code> では、共に「<code>aichi.jp</code>」を取得しますが、正しくは、「<code>pref.aichi.jp</code>」「<code>city.iwakura.aichi.jp</code>」を取得しなければなりません。これは、「都道府県型JPドメイン名」と「地域型JPドメイン名」と呼ばれるドメイン名です。</p><p>このURLは、「愛知県」「愛知県岩倉市」のホームページです。日本の公共的なURLを正確に判定できないのでは、簡易実装と言えど問題があります。</p></section><section id="toc-6"><h3>正確に取得する</h3><p>正確に取得する方法として、「<code>.com</code>」「<code>.jp</code>」「<code>.co.jp</code>」などのサフィックスをすべて保持しておく方法があります。</p><p>すべてのサフィックスリストは、 <a href="https://publicsuffix.org/">Public Suffix List</a> にあります。</p><p>すべてのサフィックスリストを保持することで正確なドメイン名を解釈できます。このため、正確なルートドメインを取得したい場合、ライブラリ等の利用を推奨します。</p></section><section id="toc-7"><h3>備考</h3><p>上記変換機には、次のライブラリを使用しています。</p><ul><li><a href="https://github.com/lupomontero/psl">lupomontero/psl - GitHub</a></li></ul><h4>試験用</h4><pre><code>入力
https://www.bugbugnow.net/
jprs.jp.
ttp://jprs.jp./
ttps://jprs.jp/
ttps://jprs.jp.
www.google.com
http://www.google.co.jp/
https://www.pref.aichi.jp/
https://www.city.iwakura.aichi.jp/
https://日本語.jp/
https://xn--wgv71a119e.jp/
https://はじめよう.みんな/</code></pre><hr><pre><code>出力
bugbugnow.net
jprs.jp
google.com
google.co.jp
pref.aichi.jp
city.iwakura.aichi.jp
xn--wgv71a119e.jp
xn--wgv71a119e.jp
xn--p8j9a0d9c9a.xn--q9jyb4c</code></pre><h4>備考(JPドメイン名)</h4><p>JPドメイン名には、次のようなドメインが存在します。</p><ul><li>「汎用JPドメイン名」(例:<code>example.jp</code>)</li><li>「属性型JPドメイン名」(例:<code>example.co.jp</code>)</li><li>「都道府県型JPドメイン名」(例:<code>example.aichi.jp</code>)</li><li>「地域型JPドメイン名」(例:<code>example.iwakura.aichi.jp</code>)</li></ul><p>※地域型JPドメイン名の新規登録は、2012年3月末で停止しています。<br>※参考:<a href="https://jprs.jp/about/jp-dom/prefecture.html">都道府県型JPドメイン名について | JPドメイン名について | JPRS</a></p><h4>備考(Punycode)</h4><p>Punycode とは、国際化ドメイン名で使用される文字符号化方式です。</p><p>ピリオドで区切られたドメイン名の各階層毎に、プレフィックスとして「<code>xn--</code>」を使用してエンコードされます。</p><ul><li><a href="https://ja.wikipedia.org/wiki/Punycode">Punycode - Wikipedia</a></li></ul></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com3tag:blogger.com,1999:blog-279447686030876252.post-81256832133817290392022-01-18T11:11:00.003+09:002022-01-18T11:20:54.699+09:00Web SQL Database に関する覚書<script type="application/json" id="post-data-json">{
"title": "Web SQL Database に関する覚書"
,"labels": ["JavaScript", "UserScript", ""]
,"url": "https://www.bugbugnow.net/2022/01/web-sql-database.html"
}</script><section id="toc-1"><h3>Web SQL Database とは</h3><p>Web SQL Database とは、Cookie や Web Storage の用にデータをブラウザ側に保存する仕組みのひとつです。仕様書は、次の場所で公開されています。</p><ul><li><a href="https://www.w3.org/TR/webdatabase/">https://www.w3.org/TR/webdatabase/</a></li></ul><p>ただし、既に事実上の廃止されており、今後実装されることはまずありえません。ですが、次のブラウザで既に実装されており、今現在(2022年)動作させることができます。</p><ul><li>Chrome</li><li>Edge</li><li>Safari</li></ul><p>※他にもChrominum系のブラウザで使用できます。<br>※Web SQL Database の代わりに Indexed Database API の利用が推奨されています。</p><h4>備考</h4><ul><li><a href="https://www.tohoho-web.com/html5/web_sql_db.html">HTML5 - Web SQLデータベース - とほほのWWW入門</a></li><li><a href="https://docs.microsoft.com/ja-jp/microsoft-edge/devtools-guide-chromium/storage/websql">Web データの表示SQLする - Microsoft Edge Development | Microsoft Docs</a></li></ul></section><section id="toc-2"><h3>Web SQL Database の問題点</h3><p>Web SQL Database は、事実上廃止されており、既に殆ど利用されていません。ですが、一部ブラウザでは動作を継続しています。</p><p>Web SQL Database は、データを無期限に保存します。有効期限がありません。また、一度作成されてしまうとJavaScript経由での削除ができません。これの最大の問題は、拡張機能などで自動的に古いデータを削除できないことです。ユーザーにほとんど認知されないまま、データだけが残り続けることが考えられます。</p></section><section id="toc-3"><h3>Web SQL Database を無効化する(UserScrpt)</h3><pre><span class="pre-code-title">DisableWebSQLDatabase.user.js</span><code class="language-js:DisableWebSQLDatabase.user.js">// ==UserScript==
// @name Disable Web SQL Database
// @description Disable the Web SQL Database (Old Web Database).
// @include *
// @namespace https://www.bugbugnow.net/
// @author toshi (https://github.com/k08045kk)
// @license MIT License | https://opensource.org/licenses/MIT
// @version 1.0
// @since 1.0 - 20220117 - 初版
// @run-at document-start
// @grant none
// ==/UserScript==
window.openDatabase = null;
// Note: UserScrpt の実行速度では、完全に拒否することはできません。
// 完全に拒否するためには、拡張機能で無遅延に実行する必要があります。
// Note: 拡張機能などページ以外からの Web SQL Database 使用には対応していません。</code></pre></section><section id="toc-4"><h3>Web SQL Database を削除する</h3><p>Web SQL Database のデータベースを JavaScript 経由で削除することはできません。ですが、次の方法で削除することができます。</p><h4>設定から削除する</h4><pre><code>Chrome > 設定 > セキュリティとプライバシー > Cookieと他のサイトデータ > すべてのCookieとサイトデータを表示
chrome://settings/siteData
Edge > 設定 > Cookieとサイトのアクセス許可 > Cookie とサイト データの管理と削除 > すべてのCookieとサイトデータを表示する
edge://settings/siteData</code></pre><p>データベースストレージとして表示される。削除ボタンから削除できます。</p><p>※データベースストレージは、Web SQL Database だけでなく、IndexedDB も同名で表示します。</p><h4>開発ツールから削除する</h4><pre><code>Chrome > 開発ツール > Application > Storage
Edge > 開発ツール > アプリケーション > Storage</code></pre><p>サイトデータのクリアから削除できます。</p><h4>プロファイルフォルダから削除する</h4><p>ブラウザのプロファイルフォルダから削除することで削除できます。</p><pre><code>Chrome (Windows10)
C:\Users\(ユーザー名)\AppData\Local\Google\Chrome\User Data\(プロファイル名)\databases
Edge (Windows10)
C:\Users\(ユーザー名)\AppData\Local\Microsoft\Edge\User Data\(プロファイル名)\databases</code></pre><p>※プロファイル名は、<code>Default</code>, <code>Profile n</code>が標準で使用されます。</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-59238036342587763532022-01-13T21:10:00.012+09:002022-02-03T15:52:52.772+09:00Firefox が突然ネットに繋がらなくなる<script type="application/json" id="post-data-json">{
"title": "Firefox が突然ネットに繋がらなくなる"
,"labels": ["Firefox", "", ""]
,"url": "https://www.bugbugnow.net/2022/01/firefox96-cannot-online.html"
}</script><p><strong>2022年01月13日22時現在、問題は暫定的に解決済みです。</strong><br><strong>Firefoxを再起動することで通常の状態に復帰できます。</strong></p><section id="toc-1"><h3>問題</h3><p>2022年01月13日、 Firefox<del>96</del> で突然ネットに繋がらなくなる問題が発生しています。</p><p>普通に使用していても、突然ネットに接続できなくなります。Firefox がネットに接続できなくなっても Chrome などの他のブラウザはネットに接続できます。もちろん、ブラウザ以外の通信も接続できます。 Firefox のみがネットと接続できなくなります。</p><p>また、同時に CPU 使用率が跳ね上がり、CPU使用率が 100% に張り付くほどに上昇します。最終的にハングアップします。</p><p>※Windows だけでなく、 macOS / Ubuntu でも問題が発生します。<br>※Fireofox96 固有の問題ではなく、ESR を含むすべての Firefox に影響します。<br>※問題は、日本だけでなく世界的に発生しています。</p><h4>再現手順 - 既に再現できません</h4><ol><li>以前のFirefoxをインストールする</li><li>Firefoxを更新する</li><li>Firefoxを再起動する</li></ol><p>※再起動後、「更新のチェック...」でスタックし、最終的にクラッシュする。</p></section><section id="toc-2"><h3>回避策</h3><h4>回避策1(テレメトリーの無効化) - 非推奨</h4><pre><code>設定 > プライバシーとセキュリティ > Firefox のデータ収集と利用について
のチェックをすべて外す</code></pre><p>※テレメトリーの送信を無効化しています。</p><h4>回避策2(HTTP3の無効化) - 推奨</h4><p><code>about:config</code> で <code>network.http.http3.enabled</code> を <code>false</code> に設定する。</p></section><section id="toc-3"><h3>問題の修正</h3><h4>bugzilla</h4><p>Bugzillaで問題が解決済みであることが公表されました。Firefoxを再起動することで通常の動作に戻るとのことです。近日中に詳細情報の提供があるとのことです。</p><blockquote><p><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1749910#c52">https://bugzilla.mozilla.org/show_bug.cgi?id=1749910#c52</a><br>Firefox has witnessed outages and we are sorry for that. We believe it's fixed and a restart of Firefox should restore normal behaviour. We will provide more information shortly</p></blockquote><p>※本修正は、Firefox本体の修正ではありません。<br> テレメトリーサーバー側の修正により、問題の発生原因を解消する対応です。</p><h4>リリース</h4><blockquote><p><a href="https://www.mozilla.org/en-US/firefox/96.0.1/releasenotes/">https://www.mozilla.org/en-US/firefox/96.0.1/releasenotes/</a><br>Improvements to make the parsing of content-length headers more robust (bug 1749957)</p></blockquote><p>2022年1月14日の Firefox 96.0.1 のリリースにより、問題が修正されました。</p><p>問題の原因は、HTTP3における Content-Length の取得ミスと、コンテンツ長を取得できなかったことによりハングアップを引き起こす問題です。</p><h4>問題の詳細なレポート</h4><ul><li><a href="https://hacks.mozilla.org/2022/02/retrospective-and-technical-details-on-the-recent-firefox-outage/">Retrospective and Technical Details on the recent Firefox Outage - Mozilla Hacks - the Web developer blog</a></li></ul><p>bugzilla で言及されていた詳細な情報が2022年2月2日付けで公開されました。</p><h4>古いFirefoxへの対応</h4><p>古いFirefoxへの対応は、ありません。</p><p>HTTP3対応済みの Firefox88~96.0.0 を使用する場合、回避策2(HTTP3の無効化)を実施することが懸命です。</p></section><section id="toc-4"><h3>問題の追跡</h3><p>次のスレッドで問題について議論されています。</p><ul><li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1749908">https://bugzilla.mozilla.org/show_bug.cgi?id=1749908</a></li><li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1749957">https://bugzilla.mozilla.org/show_bug.cgi?id=1749957</a></li></ul></section><section id="toc-5"><h3>各社の報道</h3><ul><li><a href="https://forest.watch.impress.co.jp/docs/news/1380334.html">「Firefox」で突如Webサイトが開けなくなる現象が発生中【19:30更新】 - 窓の杜</a></li><li><a href="https://nlab.itmedia.co.jp/nl/articles/2201/13/news178.html">Firefoxだけ突然インターネットにつながらなくなる障害発生中か - ねとらぼ</a></li><li><a href="https://gigazine.net/news/20220113-firefox-problem/">Firefoxでネットに接続できなくなる不具合が発生 - GIGAZINE</a></li></ul></section><section id="toc-6"><h3>関連(過去の事例)</h3><ul><li><a href="https://www.bugbugnow.net/2019/05/firefox-extension-certified-out.html">Firefoxで突然、ほぼすべてのアドオンが使えなくなった</a></li></ul></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-65564637940779137292021-12-03T12:30:00.001+09:002021-12-03T12:30:41.939+09:00ウェブページにアウトラインを表示するブックマークレット<script type="application/json" id="post-data-json">{
"title": "ウェブページにアウトラインを表示するブックマークレット"
,"labels": ["Bookmarklet", "JavaScript", "CSS"]
,"url": "https://www.bugbugnow.net/2021/12/displaying-outlines.html"
}</script><section id="toc-1"><h3>はじめに</h3><p>文章のアウトラインではないです。DOM要素のアウトラインのレイアウトを表示します。</p><p>次の画像のような表示を確認するブックマークレットです。</p><img alt="アウトライン表示" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPapcyuOSKkxVNQNYyRxAyjWA9jiHvfjbSyLQ9nI6recqsOsgFldi3rrXjkSTrtlLxP9y6DpfnEm9eZkNFhQnnjXqli0i4MfzOxyX4vNmCy22jscl5sFBaRrg8jc6f_HQbog0ZJkfT9gE6/s0/20211203_outline.png" loading="lazy" decoding="async" width="1117" height="639"><p>この表示の何が良いのかといえば、要素の配置が一目瞭然になるところです。不要な要素の発見、意図しない位置に配置された要素の発見などに役立ちます。</p></section><section id="toc-2"><h3>コード</h3><pre><code class="language-js">(function() {
var style = document.createElement('style');
style.textContent = '* { outline: magenta solid 1px; }';
document.head.appendChild(style);
})();</code></pre><h4>コード解説</h4><p><code>* { outline: magenta solid 1px; }</code> のスタイルシートをヘッダー末尾に追加する。</p><h4>既知の問題</h4><ul><li>iframe 内部には適用できない</li><li>ShadowDOM 内部には適用できない</li></ul></section><section id="toc-3"><h3>ブックマークレット</h3><pre><code class="language-js">javascript:(function(){var a=document.createElement('style');a.textContent='* { outline: magenta solid 1px; }';document.head.appendChild(a)})();</code></pre><p>ブックマークレット:「<a href="javascript:(function(){var a=document.createElement('style');a.textContent='* { outline: magenta solid 1px; }';document.head.appendChild(a)})();">* { outline: magenta; }</a>」</p></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0tag:blogger.com,1999:blog-279447686030876252.post-88672932028194536062021-11-20T10:41:00.001+09:002021-11-20T10:41:45.767+09:00macOS版 Firefox のコンテキストメニューを編集する<script type="application/json" id="post-data-json">{
"title": "macOS版 Firefox のコンテキストメニューを編集する"
,"labels": ["macOS", "Firefox", "aboutconfig", "userChrome.js"]
,"url": "https://www.bugbugnow.net/2021/11/edit-contenxtmenu-macos-firefox.html"
}</script><p>本記事は、「<a href="https://www.bugbugnow.net/2017/12/firefox-quantum-57.html">Firefox のコンテキストメニューを編集する</a>」の記事を前提としています。</p><section id="toc-1"><h3>macOS におけるコンテキストメニュー編集の問題</h3><p>Firefox90 から macOS 版の Firefox は、ネイティブコンテキストメニューに対応しました。それにより、コンテキストメニューがネイティブ機能で動作するように変更されました。</p><p>ですが、ネイティブコンテキストメニュー対応により、コンテキストメニュー関連へCSSが適用されない仕様となりました。これにより、 <a href="https://github.com/k08045kk/userChrome.menus.css">userChrome.menus.css</a> によるコンテキストメニュー編集が動作しなくなっていました。</p><ul><li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=34572">34572 - Use native context menus on Mac OS</a></li><li><a href="https://github.com/k08045kk/userChrome.menus.css/issues/7">macOS のネイティブコンテキストメニューでメニューを削除できていない · Issue #7 · k08045kk/userChrome.menus.css</a></li></ul></section><section id="toc-2"><h3>簡易の回避策(about:config)</h3><p>ネイティブコンテキストメニューを使用しない、元々のコンテキストメニューを使用することで問題を回避できます。ただし、表示の美しさはある程度損なわれます。</p><p><code>about:config</code> で <code>widget.macos.native-context-menus</code> を <code>false</code> に設定する。<br>もしくは、<code>user.js</code>に次のコードを追加する。</p><pre><span class="pre-code-title">user.js</span><code class="language-js:user.js">// macOS のネイティブコンテキストメニューを使用する (default:true)
user_pref('widget.macos.native-context-menus', false);</code></pre></section><section id="toc-3"><h3>userChrome.js で対応する</h3><p>次のユーザースクリプトを使用する。</p><pre><span class="pre-code-title">macOSNativeContextMenuHidden.uc.js</span><code class="language-js:macOSNativeContextMenuHidden.uc.js">// ==UserScript==
// @name macOSNativeContextMenuHidden.uc.js
// @description Apply CSS hiding to native context menus in macOS.
// It assumes that the user is using userChrome.menus.css.
// Native context menu in macOS (widget.macos.native-context-menus).
// Native context menus have been supported by default since Firefox90.
// @include main
// @charset UTF-8
// @author toshi (https://github.com/k08045kk)
// @license MIT License | https://opensource.org/licenses/MIT
// @version 1
// @since 1 - 20211120 - 初版
// @see https://github.com/k08045kk/userChrome.js
// @see https://github.com/k08045kk/userChrome.menus.css
// ==/UserScript==
(function() {
document.addEventListener('popupshowing', (event) => {
const menupopup = event.target;
const query = 'menupopup, menugroup, menuitem, menu, menuseparator, toolbarbutton';
menupopup.querySelectorAll(query).forEach((item) => {
const cssStyleDeclaration = window.getComputedStyle(item, null);
const display = cssStyleDeclaration.getPropertyValue('display');
item.hidden = display == 'none';
// Note: macOS のネイティブコンテキストメニューでは、CSSが適用されないため、
// hidden 属性で非表示にする。
// Note: 対象コンテキストメニューを事前に削除(display: none !import;)する必要があります。
// (userChrome.menus.css の使用を前提としています)
});
});
// Note: 問題が発生した場合、問題を指摘してください。
// 作者は、MacOS版 Firefox を愛用していないため、問題に気づくことはありません。
}());</code></pre></section>toshihttp://www.blogger.com/profile/02673765704159584610noreply@blogger.com0