先日作成した Chrome拡張機能 が http
サイト では上手く動かないことに気付いたので修正しました。
経緯
早速作成した Chrome拡張機能 を運用し始めたのですが、一部のサイトでは次のエラーを出力して上手く動かないことが分かりました。
Unchecked runtime.lastError: The message port closed before a response was received.
原因
いくつかのサイトを試験した結果、 http
サイト ではこのエラーが出力されることが分かりました。
そこで調べてみると……
WebExtension の場合、clipboardRead や clipboardWrite パーミッションを要求することで clipboard.readText() や clipboard.writeText() を使うことができます。HTTPサイトに適用されたコンテンツスクリプトは、Clipboard オブジェクトにアクセスすることはできません。
ビンゴ。 http
サイト の Content Script では navigator.clipboard
へアクセスできないようです。
対処
navigator.clipboard
へアクセスできないとなると、手っ取り早いのは document.execCommand('copy')
によるクリップぼ度貼り付けです。
今回は http
サイト という基本レガシーと想定される環境への対処なので、 document.execCommand('copy')
で妥協しておきますかね……という感じです。
chain.js
- chrome.tabs.sendMessage(tab.id, clipText, function(response) {
- console.log(response.value);
+ chrome.tabs.sendMessage(
+ tab.id,
+ {
+ text: clipText,
+ url: tab.url,
+ },
+ function(response) {
+ console.log(response.value);
chrome.tabs.sendMessage()
の第二引数は元はシンプルにクリップボードに貼り付けたいテキストを投げていましたが、今回は http://
か https://
かの判定をしたかったのでURLのみのシンプルなテキストも送ることにしました。そこで、Stringだった引数をObjectに変更。
smith.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
// メッセージとして送信されたクリップボードに貼り付けたいテキストをそのままレスポンスに設定して返却
- navigator.clipboard.writeText(request);
+ if(request.url.toLowerCase().startsWith('https://')) {
+ navigator.clipboard.writeText(request.text);
+ }
+ else {
+ const inputID = 'chromeExtension-emeraldIsle-clipText';
+ console.log(document.querySelector(`#${inputID}`))
+ if(document.querySelector(`#${inputID}`) === null) {
+ const $input = document.createElement('input');
+ $input.setAttribute('type', 'text');
+ $input.setAttribute('id', inputID);
+ $input.setAttribute('style', 'position: absolute; left: -100vw; top: 0');
+ document.body.appendChild($input);
+ }
+ const $inputDom = document.querySelector(`#${inputID}`);
+ $inputDom.value = request.text;
+ $inputDom.select();
+ document.execCommand('copy');
+ }
sendResponse({
- value: request,
+ value: request.text,
});
+ return true;
});
content Scripts 側を大改造。元々は navigator.clipboard.writeText(request);
のみのシンプルな内容でしたが、次のように処理を変更。
- URLに
https://
(大文字小文字区別せず) で始まるかどうかを判定 - 始まるならば今まで通りの
navigator.clipboard.writeText(request.text);
で貼り付け - 始まらないならば次の処理を実行
- もし指定したIDの要素が存在しないならば
- ID付与、画面外領域に
input
要素 を追加
- ID付与、画面外領域に
- 再度指定したIDの
input
要素 を取得、値にクリップボードに貼り付けたいリンク文字列をセット document.execCommand('copy')
でクリップボードにコピー
- もし指定したIDの要素が存在しないならば
これで http
サイト でもリンク文字列をクリップボードに貼り付けられるようになりました。
参考
Unchecked runtime.lastError: The message port closed before a response was received.
- Chromeのデベロッパーツールで「Unchecked runtime.lastError:」 | 己で解決!泣かぬなら己で鳴こうホトトギス
- Unchecked runtime.lastError: The message port closed before a response was received. を回避した一例 – Qiita
navigator.clipboard へのアクセス
- Clipboard – Web API | MDN
- 【JavaScript】 クリップボードへのコピー
- [JS] http環境ではnavigator.clipboard.writeTextがエラーになる | プロプログラマ -Flex,Air,C#,Oracle,HTML5+JS-
document.execCommand
JavaScript
DOM要素の有無
テキストボックスに値をセット
要素に属性をセット
body の末尾に DOM を挿入
startWith()
toLowerCase()
chrome.tabs.sendMessage, 第二引数 message の型
any
だった。
(未使用) html-loader
- Webpackを使用してhtmlの共通部分をテンプレート化 | chocolat | Freelance Frontend Engineer
- 【小ネタ】webpackでhtmlも扱う – くらげになりたい。
一瞬 Background Page を作ってそのテキストボックスにいったん貼り付けて、その値を取得する方法を考えました。
今回 Webpack 使用だったので html-loader
案件か、と思いましたが、回避しました。