よんどころなき事情により、新年早々に Chrome拡張機能を作成してみました。
経緯
(Chrome拡張機能) Create Link の挙動が不安定の記事に書いた通り、 Chrome拡張機能 の Create Link – Chromeウェブストア が挙動不安定になってしまいました。
ブログを書く際に参考記事をリストアップしたり、自分の活動にはこの機能はなくてはならないのでそれがままならないというのは不便極まりない。
そこで、代替となる Chrome拡張機能 を自作してみることにしました。
成果物
機能
Create Link の代替を想定したため、機能はこれに準じます。
ただし、貼り付けるコードのカスタマイズの必要はない (プレーン、HTML、Markdown の3種があれば充分) ので設定画面等の自前でカスタマイズできる機能は全てオミット。
ページを開いている状態で右クリックして、コンテキストメニューから上述3種の形式でリンクをクリップボードに貼り付けられれば良いです。
コード(要点)
manifest.json
{
"manifest_version": 2,
"name": "emerald-isle",
"description": "Make link text of open page.",
"version": "0.0.1",
"background": {
"scripts": [
"dist/chain.js"
],
"persistent": false
},
"content_scripts": [
{
"matches": [
"*://*/*"
],
"js": [
"dist/smith.js"
]
}
],
"permissions": [
"tabs",
"activeTab",
"clipboardWrite",
"contextMenus"
],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"icons": {
"48": "assets/icon_48.png",
"128": "assets/icon_128.png"
}
}
特筆すべき事項はあまりないですが
- Chrome拡張機能 から IE を開く等の実績から、幾分か触り慣れているバージョンの
2
を選択 permissions
はタブ、クリップボードへの書き込み、右クリックメニュー(コンテキストメニュー)等- 後述しますが、クリップボードへの貼り付けは Background Scripts(Event Scripts) からアクセスはできずに Content Scripts に持ち込む必要があったため、2つのスクリプトに分かれています (結果、
background
,content_scripts
の2つのフィールドが存在)content_scripts
を実行させるURLは全てのURLで良いので*://*/*
指定 (matches
フィールド)
といったところでしょうか。
chain.js (Background / Event Scripts)
import { mdLink } from "markdown-function"
/**
* grace_O_Malley : Grace O'Malley / titleタグ 文字列のエスケープ
*
* @param {string} text : 開いているタブの titleタグ の中身の文字列
* @param {string} url : 開いているタブの URL の文字列
*
* @returns {string} : Markdown 記法をエスケープした Markdown 形式のリンク
*/
const grace_O_Malley = (text, url) => {
return mdLink({
text: text,
url: url,
});
};
/**
* brianBoru_s_March : Brian Boru's March / メイン処理
*
* @returns {void}
*/
const brianBoru_s_March = () => {
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
type: 'normal',
id: 'CahirCastle',
title: 'Emerald Isle',
contexts: [
'all'
],
});
chrome.contextMenus.create({
type: 'normal',
id: 'plain',
parentId: 'CahirCastle',
title: 'Plain Text',
contexts: [
'all'
],
});
chrome.contextMenus.create({
type: 'normal',
id: 'html',
parentId: 'CahirCastle',
title: 'HTML',
contexts: [
'all'
],
});
chrome.contextMenus.create({
type: 'normal',
id: 'md',
parentId: 'CahirCastle',
title: 'Markdown',
contexts: [
'all'
],
});
});
chrome.contextMenus.onClicked.addListener(function(item){
chrome.tabs.getSelected((tab) => {
// 現在のタブ
let clipText = '';
// クリックされたメニューに応じてテキストを組み立て
switch (item.menuItemId) {
case 'plain':
clipText = `${tab.title} - ${tab.url}`;
break;
case 'html':
clipText = `<a href="${tab.url}" target="_blank" rel="noopener noreferrer">${tab.title}</a>`;
break;
case 'md':
clipText = grace_O_Malley(tab.title, tab.url);
break;
default:
break;
}
chrome.tabs.sendMessage(tab.id, clipText, function(response) {
console.log(response.value);
});
});
});
};
document.addEventListener('DOMContentLoaded', brianBoru_s_March());
chrome.contextMenus.create()
でコンテキストメニューの中身を作成chrome.contextMenus.onClicked.addListener()
にコンテキストメニューがクリックされた場合の処理を記述- コンテキストメニューの各項目に記述した
id
の値で条件分岐してプレーン,HTML,Markdownの3種のリンクのテキストを生成 - Markdown 形式に関してはブラケットや括弧をエスケープ処理したかったので、markdown-functionを利用させていただきました
- コンテキストメニューの各項目に記述した
- 軽く先述した通り、 Background Scripts (Event Scripts) からはクリップボードへのアクセス権限がないため、このスクリプトの中で
navigator.clipboard.writeText()
を発動させようとしても失敗してしまいます (Uncaught (in promise) DOMException: Document is not focused.
というエラー文が出力される)- かといってクリップボードへ貼り付ける系の記事で散見された
Document.execCommand
はDOMの指定が必要でこれも Content Scripts 側の動作と思われる上、Document.execCommand() – Web API | MDNに非推奨: この機能は非推奨になりました。
と記載されているため、今からこの方法を採用するのは得策ではないと判断しました- しかも開いているタブにフォーカスが当たっている必要がありますが、
document.querySelector('document').focus();
等としても Background Scripts からDOM指定をすると Background Scripts に紐付けられた Background Page を拾ってしまうので実際に開いているタブのDOMは拾えずにエラーとなる
- しかも開いているタブにフォーカスが当たっている必要がありますが、
- Clipboard.js もインスタンス化の際にDOM指定が必要なのでこれも没
- 色々調べた結果、 Content Scripts へ経由させる必要がありそうなことが分かったため
chrome.tabs.sendMessage()
でクリップボードへ貼り付けたいテキストをrequest
としてメッセージ送信で投げることにしました
- かといってクリップボードへ貼り付ける系の記事で散見された
ちなみに markdown-function 使用のため Webpack を使用しました。
smith.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
// メッセージとして送信されたクリップボードに貼り付けたいテキストをそのままレスポンスに設定して返却
navigator.clipboard.writeText(request);
sendResponse({
value: request,
});
});
ということで、上述した通り Background Scripts (Event Scripts) から投げられたメッセージを受け取って navigator.clipboard.writeText()
でクリップボードに貼り付ける側のコード。こちらは述べた通りの役割のみなのでシンプルです。
お作法として元の Background Scripts (Event Scripts) にレスポンスを返す形になるので、 request
で投げられたものをそのまま返しています (返却後も console.log()
しているだけですが)。
これで望む動作の Chrome拡張機能 を作ることができました。
一度 Background Scripts (Event Scripts) と Content Scripts の違いや権限周りを履修していたのでそこそこスムーズに進めることができたと思います。
(余談) 名前の由来
毎度恒例の名前について。
今回はリンクを生成するということで鎖を連想しました。
で、色々調べたところ
鎖を楽器として使用する吹奏楽の楽曲があることを知りました。冒頭部で本当に鎖を落として音を出していますね……。
この楽曲に着想を得てアイルランドの俗称である「エメラルドの島 (Emerald Isle)」としました。
各種関数やファイル名もこれに関連したものからチョイス。
参考
Content Scirpts, Background Script (Event Scripts)
- 過去記事: Chrome拡張機能 から IE を開く
- Chrome拡張機能!コンテキストメニューでクリックされた要素を取得する方法-スケ郎のお話
- Chrome Extension の作り方 (最終話: メッセージパッシング) – Qiita
- キー割り当てをカスタマイズする拡張機能を自作してみる – やらねば.com
Content Scripts
chrome.tabs
clipboard API
- Clipboard – Web APIs | MDN
- Clipboard.writeText() – Web APIs | MDN
- 【Javascript】Markdown形式のウェブページのリンクを、ブックマークレットで取得する – Qiita
- firefox – How can I use navigator.clipboard.readText() in a Chrome extension? – Stack Overflow
権限
右クリックメニュー (Context Menu)
- chrome.contextMenus – Chrome Developers
- Chrome拡張機能で右クリックメニューを作る方法 – Qiita
- chrome-extensions-samples\/apps\/samples\/context-menu at main · GoogleChrome\/chrome-extensions-samples
- chrome-extensions-samples\/main.js at main · GoogleChrome\/chrome-extensions-samples
Markdown のリンク生成・エスケープ
(未使用) Clipboard.js
- 【Javascript】Markdown形式のウェブページのリンクを、ブックマークレットで取得する – Qiita
- clipboard – npm
- Clipboard.jsの使い方 – Qiita
- Google Chrome 拡張機能の開発 – 2 – 早速自作したものに機能追加していく – Qiita