2026年7月6日月曜日
2026年2月14日土曜日
スマホ版の Chrome ではちゃんと DOM を定義してから値を設定したりするほうがいい
概要
動的に appendChild とかするとリソースを消費しすぎと判断して描画を強制的に停止するようです
環境
- iOS 26.2.1
- Chrome 144.07559.95
ダメなコード
function addMessage(msg) {
// avoid duplicates; update indicator if already present
const existing = document.querySelector(`#log li[data-msg-id="${msg.id}"]`);
if (existing) {
const ind = existing.querySelector(".read-indicator");
if (ind) {
updateIndicator(ind, existing, msg.reads || []);
}
return;
}
// 新着メッセージ(相手からのもの)に対する通知・未読処理
if (msg.message && msg.client_id !== CLIENT_ID) {
// ブラウザ通知の呼び出し
showBrowserNotification(msg);
// タブが裏側にある場合、未読カウントを増やす
if (document.visibilityState !== "visible") {
unreadCount++;
updateTabTitle();
}
}
// new message
const li = document.createElement("li");
li.className = msg.client_id === CLIENT_ID ? "me" : "other";
li.dataset.msgId = msg.id;
const textSpan = document.createElement("span");
textSpan.className = "msg-text";
textSpan.textContent = msg.message;
const ind = document.createElement("span");
ind.className = "read-indicator";
if (msg.client_id === CLIENT_ID) {
// 自分のメッセージ: サーバーの reads 情報を使って表示
updateIndicator(ind, li, msg.reads || []);
li.appendChild(textSpan);
li.appendChild(ind);
} else {
// 相手のメッセージ
updateIndicator(ind, li, msg.reads || []);
li.addEventListener("click", () => {
if (isRead(msg.id)) return;
setRead(msg.id);
ind.classList.remove("unread");
ind.classList.add("read");
ind.title = "既読";
// notify server
markRead(msg.id);
});
li.appendChild(textSpan);
li.appendChild(ind);
}
document.getElementById("log").appendChild(li);
}
良いコード
function addMessage(msg) {
// 1. 基本チェック
if (!msg || !msg.id) return;
// read_event は applyReadEvent で処理されるため、ここではメッセージ本体がないものを除外
if (msg.type === "read_event" || !msg.message) return;
// 2. 重複チェック
const log = document.getElementById("log");
if (!log) return;
const existing = document.querySelector(`#log li[data-msg-id="${msg.id}"]`);
if (existing) return;
// 3. 要素の作成
const li = document.createElement("li");
// 安全な ID チェック
const currentId =
typeof CLIENT_ID !== "undefined" ? CLIENT_ID : getClientId();
li.className = msg.client_id === currentId ? "me" : "other";
li.setAttribute("data-msg-id", msg.id); // dataset ではなく setAttribute を使用(古いブラウザ対策)
const textSpan = document.createElement("span");
textSpan.className = "msg-text";
textSpan.textContent = String(msg.message); // 明示的に文字列変換
const ind = document.createElement("span");
ind.className = "read-indicator unread"; // デフォルトは unread
// 4. 要素の組み立て(先に DOM に追加してから細部を調整するのがスマホでは安定します)
li.appendChild(textSpan);
li.appendChild(ind);
log.appendChild(li);
// 5. 既読インジケーターの更新(エラーが起きても表示自体は維持する)
try {
updateIndicator(ind, li, msg.reads || []);
} catch (e) {
console.warn("Indicator error ignored for safety", e);
}
// 6. イベントリスナー(相手のメッセージのみ)
if (msg.client_id !== currentId) {
li.addEventListener("click", function () {
try {
if (isRead(msg.id)) return;
setRead(msg.id);
ind.classList.remove("unread");
ind.classList.add("read");
markRead(msg.id);
} catch (err) {
console.error(err);
}
});
// 通知処理(エラー回避のため try-catch)
try {
showBrowserNotification(msg);
} catch (e) {}
}
// 7. スクロール
log.scrollTop = log.scrollHeight;
}
比較
堅牢性とエラーハンドリング
「良いコード」は、外部要因でプログラムが止まらないよう工夫されています。
| 項目 | ダメなコード | 良いコード |
|---|---|---|
| ガード句 | データの存在チェックが甘く、msg.idが欠落しているとエラーになる可能性がある。 | 冒頭で `!msg |
| 例外処理 | updateIndicator 等でエラーが起きると、後続の処理(DOM追加等)が止まる。 | try-catch を活用し、一部の表示エラーが起きてもアプリ全体が止まらない。 |
| 変数の安全性 | CLIENT_ID がグローバルにある前提。 | typeof チェックやフォールバック(getClientId())を用意している。 |
責務の明確化と可読性
コードが整理されているため、後からの修正が容易です。
- 単一責任の原則:
- ダメ: addMessage 内で通知、未読カウント、DOM操作、重複チェックが混ざり、条件分岐(if (msg.client_id === CLIENT_ID) … else …)で同じような appendChild が重複している。
- 良い: メッセージの型判定(read_event かどうか)を fetchMessages 側で仕分け、addMessage は「表示」に専念している。
- DRY (Don’t Repeat Yourself):
- ダメ: 自分のメッセージと相手のメッセージで、インジケーターの追加処理を2回書いている。
- 良い: 共通の組み立て(li への追加)を先に行い、差分(イベントリスナー)だけを条件分岐で書いている。
ユーザー体験 (UX) と実用性
ブラウザやネットワークの特性を考慮した実装になっています。
- スクロール処理: 「良いコード」では追加後に log.scrollTop = log.scrollHeight を行い、常に最新メッセージが見えるように配慮されています。
- 通信の最適化: fetchMessages でキャッシュ回避のためのタイムスタンプを追加しており、古いデータを掴まされるリスクを減らしています。
- 暗黙の型変換への対処: String(msg.message) とすることで、数値や null が送られてきても textContent で安全に表示できます。
改善のポイント(まとめ)
- ❌ ダメな点
- 重複コード: li.appendChild(textSpan) などが if/else 両方にあり、修正漏れの原因になる。
- 不親切なエラー: ネットワークエラーやデータ不備で JS が停止し、画面が真っ白になるリスクがある。
- 状態管理の欠如: スクロール位置の調整がないため、ユーザーが手動でスクロールする必要がある。
- ✅ 良い点
- 徹底したバリデーション: 「データはあるか?」「型は正しいか?」を常に疑っている。
- 保守性の高い構造: コメントで工程(1〜7)が区切られており、どこで何をしているか一目でわかる。
- API設計への配慮: lastId の更新により、重複してメッセージを取得しない仕組みが整っている。
最後に
スマホ版の Chrome も意識して JavaScript を書かないとダメなようです
2026年2月4日水曜日
Windows 起動時に特定のページで Chrome を起動する方法
概要
ホームページにはしたくないが Windows 起動時に特定ページを開く方法を紹介します
環境
- Windows11
- Chrome 144.0.7559.110
shell:startup を開く
エクスプローラで「shell:startup」と入力し起動時に自動で開くフォルダを開きます
ショートカットの作成
- 右クリック
- 新規作成
- ショートカット
ショートカットの設定
- 項目の場所を入力してくださいに以下を入力
"C:\Program Files\Google\Chrome\Application\chrome.exe" "http://xxx.xxx.xxx.xxx"
- 次へ
- 適当なショートカットの名前を設定
最後に
ホームページを設定し Chrome のショートカットをスタートアップに追加してもいいですがホームページに設定すると毎回そのページからになるのでそれが嫌な場合は便利です
2025年9月2日火曜日
Youtubeのチャンネル登録者を一括で削除するブックマークレット
概要
過去にアクティビティから削除する方法を紹介しました
今回はブックマークレットで削除する方法を紹介します
環境
- Chrome 139.0.7258.155
ブックマークレット
javascript:(function(){
let openBtn=document.querySelector('a[href="/feed/channels"]');
if(openBtn) {
openBtn.click();
console.log("チャンネル一覧を開きました");
}
setTimeout(()=>{
let buttons=document.querySelectorAll('#notification-preference-button > ytd-subscription-notification-toggle-button-renderer-next > yt-button-shape > button');
let i=0;
function unsubscribe(){
if(i<buttons.length){
buttons[i].click();
console.log(`通知設定ボタンをクリック: ${i+1}/${buttons.length}`);
setTimeout(()=>{
let confirmBtn=document.querySelector('#items > ytd-menu-service-item-renderer:nth-child(3)');
if(confirmBtn) {
confirmBtn.click();
console.log(`登録解除メニューをクリック: ${i+1}/${buttons.length}`);
setTimeout(()=>{
let dialogBtn=document.querySelector('#confirm-button > yt-button-shape > button > yt-touch-feedback-shape > div');
if(dialogBtn) {
dialogBtn.click();
console.log(`確認ダイアログをクリック: ${i+1}/${buttons.length}`);
}
i++;
if(i<buttons.length){
setTimeout(unsubscribe,1500);
} else {
console.log("すべてのチャンネル登録解除が完了しました");
alert("すべてのチャンネル登録解除が完了しました");
}
},500);
} else {
console.log(`登録解除メニューが見つかりません: ${i+1}`);
i++;
setTimeout(unsubscribe,1500);
}
},1000);
}
}
unsubscribe();
},1500);
})();
最後に
スクロールに対応していないので大量にチャンネル登録している場合には一回だとダメかもしれません
また DOM が変わり CSS セレクタ情報が変わった場合には変更する必要があります
2025年5月15日木曜日
Ubuntu24.04にChromeの最新版をインストールする方法
概要
apt でインストールできます
環境
- Ubuntu24.04
- Chrome 136.0.7103.92-1
gpgキー取得
-
wget -qO- https://dl.google.com/linux/linux_signing_key.pub | sudo gpg --yes --dearmor -o /etc/apt/trusted.gpg.d/google-chrome.gpg
リポジトリ追加
-
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
インストール
- sudo apt -y update
- sudo apt install -y google-chrome-stable
最後に
headless でも使えるのでインストールしておくと便利かもです
参考サイト
2024年12月4日水曜日
Bookmarklet で jQuery を使う方法
概要
もしページ内ですでに読み込まれている場合はそのまま使えます
読み込んでいない場合はブックマークレット側で読み込めば使えるようになります
環境
- Chrome 131.0.6778.86
ブックマークレット
javascript:(function() {
if (!window.jQuery) {
let script = document.createElement('script');
script.src = 'https://code.jquery.com/jquery-3.6.0.min.js';
script.type = 'text/javascript';
script.onload = runBookmarklet;
document.getElementsByTagName('head')[0].appendChild(script);
} else {
runBookmarklet();
}
function runBookmarklet() {
alert('jQuery バージョン: ' + $.fn.jquery);
}
})();
最後に
他のライブラリも同じ仕組みで読み込めると思います
バージョンなどは面倒ですが直接書き込むしかないです
2024年12月3日火曜日
サイト内の画像だけを抽出し表示するブックマークレット
概要
上からずらっと表示します
環境
- Chrome 131.0.6778.86
ブックマークレット
javascript:(function() {
const images = Array.from(document.querySelectorAll('img'));
if (images.length === 0) {
alert('画像が見つかりませんでした');
return;
}
document.body.innerHTML = '';
document.body.style.margin = '0';
document.body.style.padding = '0';
document.body.style.backgroundColor = 'black';
images.forEach(img => {
const newImg = document.createElement('img');
newImg.src = img.src;
newImg.style.display = 'block';
newImg.style.margin = '10px auto';
newImg.style.maxWidth = '90%';
newImg.style.height = 'auto';
document.body.appendChild(newImg);
});
alert(`${images.length} 個の画像を表示しました`);
})();
最後に
画像だけシンプルに表示したい場合に使えます
スタイルも全部解除するのでどんなページにも使えるはずです
2024年12月2日月曜日
GIGOのオンクレでプレイ中のプライズのみ表示するブックマークレット
概要
ブックマークレットを紹介します
DOM が変わった場合は適宜クラス名などを変更してください
環境
- Chrome 131.0.6778.86
ブックマークレット
javascript:(function() {
const targetUlClass = 'prizes__list';
var allListItems = document.querySelectorAll(`ul.${targetUlClass} > li`);
allListItems.forEach(function(li) {
var spans = li.querySelectorAll('span');
var hasTargetBackground = false;
spans.forEach(function(span) {
var bgColor = window.getComputedStyle(span).backgroundColor;
if (bgColor === 'rgb(255, 226, 108)') {
hasTargetBackground = true;
}
});
if (hasTargetBackground) {
li.style.display = '';
} else {
li.style.display = 'none';
}
});
var highestIntervalId = setInterval(() => {}, 1000);
for (var i = 0; i <= highestIntervalId; i++) {
clearInterval(i);
}
})();
最後に
ページのリロードする API のコールも停止しているので適宜手動でリロードしてください
2024年11月27日水曜日
chrome のブックマークレットでボタンを押したあとにinputに値を入力しイベントを強制発火する方法
概要
テキスト入力用のフォームなどにイベントが設定されてある場合はブックマークレットでは強制的にイベントを発火させる必要があります
環境
- macOS 15.1.1
- Google Chrome 131.0.6778.86
ブックマークレット(js)
javascript:(function(){
var button = document.querySelector('input.pc-tab__hidden-input[type="checkbox"][name="pnav-group"]');
if (button) {
button.click();
} else {
alert('ボタンが見つかりません');
return;
}
setTimeout(function() {
var input = document.getElementById('search-text');
if (input) {
input.value = 'スイカゲーム';
var event = new Event('input', { bubbles: true });
input.dispatchEvent(event);
} else {
alert('入力フィールドが見つかりません');
}
}, 500);
})();
chrome への登録
あとは chrome のブックマークへ上記 Javascript を登録すれば OK です
最後に
ブックマークレットは対象のURLを開いてから実行する必要があるので注意してください
ブックマークレットだけで特定の URL を開いた上で JavaScript を実行することは難しいようです
ボタンを押してからテキストフィールドに入力するまでに0.5秒待っていますが画面がうまく動作しない場合は値を調整してください
2024年11月6日水曜日
2024年10月28日月曜日
Google Chrome をプロフィールを指定して open コマンドで開く方法
概要
プロフィールはなぜかインデックス番号付きのディレクトリで管理されているのでそのディレクトリ名を指定することでプロフィール指定して Chrome を開くことができます
環境
- macOS 15.0.1
- Google Chrome 129.0.6668.100
コマンド
-
open -a "Google Chrome" --args --profile-directory="Profile 1"
プロフィールがどの Google アカウントに紐づくか確認する方法
なぞにディレクトリにスペースが含まれているので find + while を使って Profile ディレクトリ配下にある Preferences ファイルを調査する感じです
-
cd ~/Library/Application\ Support/Google/Chrome/
-
find . -type d -name "Profile\ *" | while IFS= read -r i; do echo ${i}; cat ${i}/Preferences | jq '.account_info[].full_name'; done
最後に
このプロフィールディレクトリのインデックス番号は追加したプロフィール順になるのでもし環境をまたいで同じプロフィール番号で起動する場合には追加する順番に注意が必要です
2023年11月21日火曜日
Chromeでパスワードを一括削除する方法
概要
最近の chrome だと履歴から削除できなくなっています
とりあえずローカルだけでも削除したい場合には以下の方法が簡単です
環境
- Windows 10
- Chrome 119.0.6045.160
同期をオフにする
Chrome で以下にアクセスし同期設定でパスワードだけ無効にします
chrome://settings/syncSetup/advanced
Login Data ファイルの削除
%LocalAppData%\Google\Chrome\User Data\Default\ にある Login Data を削除します
動作確認
Chrome を再起動してパスワードが補完されないことを確認しましょう
最後に
根本的には Google のパスワードマネージャに保存されているデータを削除する必要がありますがなぜかパスワードマネージャはパスワードを一件ずつしか削除できないのでこの方法で無理やりローカルだけ削除しています
ちなみに同期を有効にしてしまうと再度ローカルにパスワードファイルが生成されてしまいます
また執筆時点での Chrome ではパスワードのオートフィルを無効にする機能がないのでパスワード自体を削除しないと勝手に補完してしまいます
参考サイト
2022年11月16日水曜日
Chromeのパスワード情報をkeychain側にコピーする方法
概要
Apple が公式で提供しているiCloudパスワードというエクステンションがあるのですがこれは keychain にある認証情報を Chrome 上で使用できるようにするエクステンションになります
今回やりたいのは Chrome 上にすでにある認証情報を keychain 側にまるまるコピーする方法になります
環境
- macOS 11.7.1
- chrome 107.0.5304.87
- csv2keychain 0.1.3
csv2keychain のインストール
- pip install cmdline-csv2keychain
chrome のパスワード情報をエクスポート
- chrome://settings/passwords を入力
- パスワードのエクスポートを選択
Macの認証情報を求められるので入力します
keychain 側にコピーする
先程のパスワードファイルは pass.csv というファイルで保存しました
- csv2keychain ~/Downloads/pass.csv
実行すると以下のようなログがつらつら流れるので完了するまで待ちましょう
Copying item #462...
#462 complete
The specified item already exists in the keychain.
というログはそのまんまの意味ですでにパスワードが登録されていると言われてエラーになっています
-u オプションを使うと上書きコピーできるので全部 Chrome 優先にしたい場合はオプションを使いましょう
- csv2keychain ~/Downloads/pass.csv -u
ただこの方法だと毎回認証ダイアログが表示されてしまいかなり面倒です
解決方法もないようです
https://github.com/nntarasov/csv2keychain/issues/7
なのでもし重複している場合には手動でパスワードを修正しましょう
Safari 側でパスワードが参照されない場合は
よくわからないのですが keychain にコピーしたパスワードは Safari ではうまく参照してくれませんでした
なので Safari で Chrome の情報を読み込みたい場合は
- ファイル -> 読み込む -> Google Chrome
でコピーする必要があります
最後に
上書きコピーになるのですでに keychain 側にある認証情報は残ります
参考サイト
2021年5月26日水曜日
Selenium でダイアログなしでファイルをダウンロードする方法
概要
重要なのは Firefox を使わずに Chrome を使う点です
参考記事
環境
- macOS 11.3.1
- Ruby 3.0.0
- selenium 4.0.0.beta3
- Chrome
サンプルコード
一部抜粋です ポイントはプリファレンスを使って Chrome にオプションを渡すところです
download_pref = {
'prompt_for_download' => false,
'default_directory' => '/tmp',
'directory_upgrade' => true
}
options = Selenium::WebDriver::Chrome::Options.new
options.add_preference(:download, download_pref)
@driver = Selenium::WebDriver.for :chrome, options: options
最後に
自分が試した限りこれならダイアログなしでファイルのダウンロードができました 念を押しますが Firefox はハマるので諦めて ChromeDriver を使いましょう
なお Selenium は localhost でも動作しましたし remote (docker) でも動作しました
2021年5月24日月曜日
Ruby + selenium + chrome のシンプルなサンプルコード
概要
動作に必要な最低限の設定のみ紹介します
環境
- macOS 11.3.1
- Ruby 3.0.0
- selenium-webdriver 4
gem インストール
- bundle init
- vim Gemfile
gem "selenium-webdriver", "4.0.0.beta3"
chromedriver のインストール
- brew install chromedriver
サンプル
- vim test.rb
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
driver.get('https://www.yahoo.co.jp/')
実行
- bundle exec ruby test.rb
2020年8月28日金曜日
Chrome でテストなどで自己証明書を設定しているサイトアクセスする方法
概要
https では証明書が必須ですが証明書が正しく発行されていない場合には Chrome では NET::ERR_CERT_INVALID となりアクセスできません
環境
- macOS 10.15.6
- Chrome 84.0.4147.135
thisisunsafe とタイプする
警告画面が表示された状態でブラウザにフォーカスを合わせて「thiisunsafe」とキーボードでタイプしましょう
URL バーなどにフォーカスがいかないので文字は表示されていませんがちゃんとタイプできてればサイトにサクセスできるようになります
2017年2月26日日曜日
Chrome に Google Cast エクステンションをインストールしてみた
概要
Chrome のブラウザ情報を ChromeCast に連携できるエクステンション「Google Cast」をインストールしてみました
インストール後実際にテレビにキャストして動作確認まで行いました
環境
- Mac OS X 10.11.5
- Chrome 50.0.2661.102
Google Cast をインストール
Chrome に Google Cast をインストールします
Chrome を開いてダウンロードサイトにアクセスします
「CHROME に追加」を選択します
確認画面が表示されるので「拡張機能を追加」を選択します
ツールバーに Google Cast のアイコンが追加されるのを確認します
利用状況の送信に協力する場合はチェックして OK してください
どちらでも OK です
これでインストールは完了です
Google Cast の設定
Chrome Cast の電源がすでに ON になっており、かつ Chrome Cast と同じ LAN 上にいる場合は特に設定しなくてもすでに Chrome Cast が登録されていると思います

追加されていない場合は以下を試してみてください
1 - Chrome Cast の電源が ON になっていることを確認する

ランプがちゃんと白になっていることを確認してください
テレビの電源が入っていないと白にならないことがあります
2 - キャストを探す
オプション -> 新しいキャスト デバイスをセットアップ
でキャストを探すことができます

これで新規のキャストを探して追加してみてください
見つからない大抵の場合は違うネットワークにいるか Chrome Cast の電源が ON になっていません
動作確認
キャストが追加できたら動作確認してみましょう
ツールバーから「このタブのキャスト先…」で追加したキャストを選択しましょう
Chrome Cast にこんな感じでキャストされれば OK です

Chrome にも同じ情報が表示されているのでそっちで操作すると Chrome Cast 側にも情報が反映されます
もちろん動画ではなく Google の検索結果やサイトの閲覧もキャストすることができます
その場合は現在キャストしているタブのキャストを停止して別のタブに移動してそのタブをキャストしてください
最後に
おそらくブラウザの情報をキャストできるのは Chrome だけだと思います
Firefox や IE もアドオンを作ればできると思いますが探した限りではなかったような気がします
スマホのアプリでは結構対応しているアプリがあるので、そっちを使うのも手だと思います
ブラウザのタグをそのままキャストできるので普段ブラウザで作業する機会が多い人は HDMI 等のケーブルがなくてもモニタが使えるのは嬉しい気がします
ただ、1 タブ分しかキャストできないのでそこが辛い部分かもしれません
P.S 20160604
実際に使っていて気づいたのですが、使っていると CPU 使用率が跳ね上がって PC が唸りを上げ始めました
長時間の利用は避けたほうがいいかもしれません





