From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../webextensions/content_scripts/index.html | 534 +++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 files/ja/mozilla/add-ons/webextensions/content_scripts/index.html (limited to 'files/ja/mozilla/add-ons/webextensions/content_scripts') diff --git a/files/ja/mozilla/add-ons/webextensions/content_scripts/index.html b/files/ja/mozilla/add-ons/webextensions/content_scripts/index.html new file mode 100644 index 0000000000..456ce69ddb --- /dev/null +++ b/files/ja/mozilla/add-ons/webextensions/content_scripts/index.html @@ -0,0 +1,534 @@ +--- +title: コンテンツスクリプト +slug: Mozilla/Add-ons/WebExtensions/Content_scripts +tags: + - WebExtensions +translation_of: Mozilla/Add-ons/WebExtensions/Content_scripts +--- +
{{AddonSidebar}}
+ +

コンテンツスクリプトは、特定のウェブページのコンテキストで実行される拡張機能の一部です(拡張機能の一部であるバックグラウンドスクリプトや、ウェブサイト自体の一部であるスクリプト、例えば {{HTMLElement("script")}} 要素みたいなものと対をなすような)。

+ +

バックグラウンドスクリプトはすべての WebExtension JavaScript API にアクセスできますが、ウェブページのコンテンツに直接アクセスすることはできません。だからあなたの拡張機能がそれを行う必要がある場合は、コンテンツスクリプトが必要です。

+ +

通常のウェブページで読み込まれたスクリプトと同様に、コンテンツスクリプトは、標準の DOM API を使用してページのコンテンツを読み取り、変更することができます。

+ +

コンテンツスクリプトは、WebExtension API の小さなサブセット にしかアクセスできませんが、メッセージングシステムを使用して バックグラウンドスクリプトと通信し、WebExtension API に間接的にアクセスすることができます。

+ +
+

コンテンツスクリプトは次のドメインでブロックされるのに注意してください:

+ + + +

このドメインのページにコンテンツスクリプトを挿入しようとすると、そのスクリプトは失敗し、ページは CSP エラーをログに記録します。

+ +

addons.mozilla.org を含む制限のために、ユーザーはインストール後すぐに拡張機能を試して、動かないのに気付くだけでしょう! 適切な警告を追加するか、addons.mozilla.org から動かす onboarding page を追加したくなるでしょう。

+
+ +
+

letfoowindow.foo = "bar" にて、コンテンツスクリプトのグローバルスコープで追加された値は、1408996 のバグによって消えることがあります。

+
+ +

コンテンツスクリプトの読み込み

+ +

次の 3 つの方法のいずれかを使用して、ウェブページにコンテンツスクリプトを読み込むことができます。

+ +
    +
  1. +
    +
    インストール時に、URL パターンにマッチするページ内へ
    +
    manifest.jsoncontent_scripts キーを使用して、URL が指定されたパターンにマッチするページをロードするたびにコンテンツスクリプトを読み込むようブラウザーに依頼できます。
    +
    +
  2. +
  3. +
    +
    実行時に、URL パターンにマッチするページ内へ
    +
    {{WebExtAPIRef("contentScripts")}} API を使って、URL が指定されたパターンにマッチするページをロードするたびにコンテンツスクリプトを読み込むようブラウザーに依頼できます。これは method (1) のようなもので、違いは実行時にコンテンツスクリプトを追加/削除できることです。
    +
    +
  4. +
  5. +
    +
    実行時に、特定のタブへ
    +
    tabs.executeScript() API を使用すると、ユーザーがブラウザーアクションをクリックした場合など、必要なときにコンテンツスクリプトを特定のタブに読み込むことができます。
    +
    +
  6. +
+ +

フレームごと、拡張機能ごとのグローバルスコープしかありません。これは 1 つのコンテンツスクリプトの変数は、読み込み方にかかわらず、他のコンテンツスクリプトからアクセスできることになります。

+ +

方法 (1) と (2) ではマッチパターンを使って表現された URL のスクリプトだけを読み込みできます。

+ +

方法 (3) では、拡張機能と一緒にパッケージされたページのスクリプトも読み込みできますが、"about:debugging" や "about:addons"のような権限つきページにはスクリプトを読み込めません。

+ +

コンテンツスクリプト環境

+ +

DOM アクセス

+ +

コンテンツスクリプトは、普通のページスクリプトと同様に、ページの DOM にアクセスして修正できます。ページスクリプトにてなされた DOM の変更を見ることもできます。

+ +

しかし、コンテンツスクリプトは "DOM のきれいな見た目" を取得します。これはつまり:

+ + + +

Firefox では、この挙動は Xray vision と呼ばれます。

+ +

例えば、次のウェブページを考えます:

+ +
<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+  </head>
+
+  <body>
+    <script src="page-scripts/page-script.js"></script>
+  </body>
+</html>
+ +

"page-script.js" スクリプトは次を実行します:

+ +
// page-script.js
+
+// add a new element to the DOM
+let p = document.createElement("p");
+p.textContent = "This paragraph was added by a page script.";
+p.setAttribute("id", "page-script-para");
+document.body.appendChild(p);
+
+// define a new property on the window
+window.foo = "This global variable was added by a page script";
+
+// redefine the built-in window.confirm() function
+window.confirm = function() {
+  alert("The page script has also redefined 'confirm'");
+}
+ +

今度は拡張機能がページにコンテンツスクリプトを挿入します:

+ +
// content-script.js
+
+// can access and modify the DOM
+let pageScriptPara = document.getElementById("page-script-para");
+pageScriptPara.style.backgroundColor = "blue";
+
+// can't see page-script-added properties
+console.log(window.foo);  // undefined
+
+// sees the original form of redefined properties
+window.confirm("Are you sure?"); // calls the original window.confirm()
+ +

その反対もまた真です; ページスクリプトはコンテンツスクリプトが追加した JavaScript プロパティを見られません。

+ +

このことからすると、コンテンツスクリプトは予言できる振る舞いをする DOM プロパティに依存していて、ページスクリプトにて定義された変数とコンテンツスクリプトで定義する変数の衝突は心配しなくていいです。

+ +

この振る舞いの実用的な結論として、コンテンツスクリプトはページに読み込まれたいかなる JavaScript ライブラリーにもアクセスしません。なので例えば、ページに jQuery が含まれても、コンテンツスクリプトは見ることができません。

+ +

コンテンツスクリプトから JavaScript ライブラリを使いたい場合、ライブラリ自身を、使う方のコンテンツスクリプトと並べて挿入するべきです:

+ +
"content_scripts": [
+  {
+    "matches": ["*://*.mozilla.org/*"],
+    "js": ["jquery.js", "content-script.js"]
+  }
+]
+ +
+

記: Firefox ではコンテンツスクリプトからページスクリプトによって生成された JavaScript オブジェクトにアクセスしたり、ページスクリプトにコンテンツスクリプトの JavaScript オブジェクトを公開できるようにする API が提供されます。

+ +

詳しくはページスクリプトとオブジェクトを共有するのページを見てください。

+
+ +

WebExtension API

+ +

標準 DOM API に加え、コンテンツスクリプトは次の WebExtension API を使用できます:

+ +

extension から:

+ + + +

runtime から:

+ + + +

i18n から:

+ + + +

すべてから:

+ + + +

XHR と Fetch

+ +

コンテンツスクリプトは通常の window.XMLHttpRequestwindow.fetch() API を使ってリクエストを作成できます。

+ +
+

Firefox では、コンテンツスクリプトの (例えば、fetch() を使った) リクエストは、拡張機能のコンテキストで起こるので、ページコンテンツを参照する URL を絶対URL で提供せねばなりません。

+ +

Chrome では、リクエストはページのコンテ キストで起こるので、相対 URL で行われます。例えば、/apihttps://[現在のペー ジの URL]/api に送られます。

+
+ +

コンテンツスクリプトは拡張機能の他の部分と同一のクロスドメイン権限を取得します: よって拡張機能が manifest.jsonpermissions キーを使ってあるドメインのクロスドメインアクセスを要求している場合、コンテンツスクリプトも同様にそのドメインのアクセスを取得します。

+ +

これはより多く権限付けられた XHR に晒して、コンテンツスクリプトでインスタンスを取得することで達成し、その副作用としてページ自体からのリクエストがそうであるように OriginReferer ヘッダーがセットされず、リクエストからクロスオリジンな性質を隠すことが好ましいことがよくあります。

+ +
+

バージョン 58 以降、コンテンツ自体から送られたかのようなリクエストを必要とする拡張機能は  content.XMLHttpRequestcontent.fetch() を代わりに使うことができます。

+ +

クロスブラウザー拡張機能にとってこれらの存在は機能検出となります。

+
+ +

バックグラウンドスクリプトとの通信

+ +

コンテンツスクリプトは WebExtension API の大半を直接には使用できませんが、メッセージ API を用いて拡張機能のバックグラウンドスクリプトと通信できて、それゆえにバックグラウンドスクリプトがアクセスできるすべての API に間接的にアクセスできます。

+ +

バックグラウンドスクリプトとコンテンツスクリプトが通信する 2 つのパターンがあります: (オプションなレスポンスつきの)ワンオフメッセージを送るのと、お互いに息の長いコネクションを確立して、そこでメッセージを交換するのとです。

+ +

ワンオフメッセージ

+ +

レスポンスが必須でないワンオフメッセージを送るには、次の API を使います:

+ + + + + + + + + + + + + + + + + + + + + +
コンテンツスクリプト内バックグラウンドスクリプト内
送信メッセージbrowser.runtime.sendMessage()browser.tabs.sendMessage()
受信メッセージbrowser.runtime.onMessagebrowser.runtime.onMessage
+ +

例えば、ウェブページでのクリックイベントをリッスンするコンテンツスクリプトがここにあります。クリックがリンク上である場合、ターゲット URL をバックグラウンドページにメッセージします。

+ +
// content-script.js
+
+window.addEventListener("click", notifyExtension);
+
+function notifyExtension(e) {
+  if (e.target.tagName != "A") {
+    return;
+  }
+  browser.runtime.sendMessage({"url": e.target.href});
+}
+ +

バックグラウンドスクリプトはこのメッセージをリッスンして、notifications API を使って通知を表示します:

+ +
// background-script.js
+
+browser.runtime.onMessage.addListener(notify);
+
+function notify(message) {
+  browser.notifications.create({
+    "type": "basic",
+    "iconUrl": browser.extension.getURL("link.png"),
+    "title": "You clicked a link!",
+    "message": message.url
+  });
+}
+
+ +

この例のコードは Github の notify-link-clicks-i18n のサンプルから簡単に適用できます。

+ +

コネクションベースのメッセージ

+ +

ワンオフメッセージの送信は、バックグラウンドスクリプトとコンテンツスクリプトとで多くのメッセージを交換する場合はめんどくさいです。なので代替パターンは、この 2 つのコンテキスト間で寿命の長いコネクションを確立して、メッセージ交換にこのコネクションを使うことです。

+ +

いずれの側にも runtime.Port オブジェクトがあり、メッセージ交換に使えます。

+ +

コネクションを作成するには:

+ + + +

これは runtime.Port オブジェクトを返します。

+ + + +

それぞれがポートを持ったら、両方が:

+ + + +

例えば、ロードしたらすぐに、このコンテンツスクリプトは:

+ + + +
// content-script.js
+
+var myPort = browser.runtime.connect({name:"port-from-cs"});
+myPort.postMessage({greeting: "hello from content script"});
+
+myPort.onMessage.addListener(function(m) {
+  console.log("In content script, received message from background script: ");
+  console.log(m.greeting);
+});
+
+document.body.addEventListener("click", function() {
+  myPort.postMessage({greeting: "they clicked the page!"});
+});
+ +

対応するバックグラウンドスクリプトは:

+ + + +
// background-script.js
+
+var portFromCS;
+
+function connected(p) {
+  portFromCS = p;
+  portFromCS.postMessage({greeting: "hi there content script!"});
+  portFromCS.onMessage.addListener(function(m) {
+    console.log("In background script, received message from content script")
+    console.log(m.greeting);
+  });
+}
+
+browser.runtime.onConnect.addListener(connected);
+
+browser.browserAction.onClicked.addListener(function() {
+  portFromCS.postMessage({greeting: "they clicked the button!"});
+});
+
+ +

複数のコンテンツスクリプト

+ +

同時に複数のコンテンツスクリプトが通信する場合、各接続を配列に格納するのが良いかもしれません。

+ + + +
// background-script.js
+
+var ports = []
+
+function connected(p) {
+  ports[p.sender.tab.id]    = p
+  //...
+}
+
+browser.runtime.onConnect.addListener(connected)
+
+browser.browserAction.onClicked.addListener(function() {
+  ports.forEach(p => {
+        p.postMessage({greeting: "they clicked the button!"})
+    })
+});
+ + + +

ワンオフメッセージとコネクションベースのメッセージとの選択

+ +

ワンオフとコネクションベースのメッセージの選択は、拡張機能がどうメッセージを利用すると期待されるかに依存します。

+ +

推奨のベストプラクティスは次の通りです:

+ +

次のときにワンオフメッセージを使用…

+ + + +

次のときにコネクションベースメッセージを使用…

+ + + +

ウェブページとの通信

+ +

既定では、コンテンツスクリプトはページスクリプトが作成したオブジェクトにアクセスできませんが、DOM window.postMessagewindow.addEventListener API を使ってページスクリプトと通信できます。

+ +

例えば:

+ +
// page-script.js
+
+var messenger = document.getElementById("from-page-script");
+
+messenger.addEventListener("click", messageContentScript);
+
+function messageContentScript() {
+  window.postMessage({
+    direction: "from-page-script",
+    message: "Message from the page"
+  }, "*");
+ +
// content-script.js
+
+window.addEventListener("message", function(event) {
+  if (event.source == window &&
+      event.data &&
+      event.data.direction == "from-page-script") {
+    alert("Content script received message: \"" + event.data.message + "\"");
+  }
+});
+ +

これの完全な動作サンプルは、GitHub のデモページを訪れて指示に従ってください。

+ +
+

この方法で信頼できないウェブコンテンツと相互作用するには細心の注意が必要です
+ 拡張機能は強力な力を持つコードの権限があり、敵意のあるウェブページは簡単にこの力にアクセスします。

+ +

細かい例を作るには、メッセージを受け取ったコンテンツスクリプトがこのようなことを行うと仮定してください:

+ +
// content-script.js
+
+window.addEventListener("message", function(event) {
+  if (event.source == window &&
+      event.data.direction &&
+      event.data.direction == "from-page-script") {
+    eval(event.data.message);
+  }
+});
+ +

今やページスクリプトはコンテンツスクリプトのすべての権限でコードを実行できます。

+
+ +

コンテンツスクリプト内で eval() を使う

+ +
+
Chrome では
+
{{jsxref("eval")}} は常にページコンテキストではなくてコンテンツスクリプトのコンテキストで動作します。
+
Firefox では
+
+

eval() を呼ぶ場合、コンテンツスクリプトのコンテキストで動作します。 window.eval() を呼ぶ場合、ページのコンテキストで動作します。, it runs code in the context of the content script.

+
+
+ +

例えば、こんなコンテンツスクリプトを考えてみます:

+ +
// content-script.js
+
+window.eval('window.x = 1;');
+eval('window.y = 2');
+
+console.log(`In content script, window.x: ${window.x}`);
+console.log(`In content script, window.y: ${window.y}`);
+
+window.postMessage({
+  message: "check"
+}, "*");
+ +

このコードは単に変数 x と y を、window.eval()eval() を用いて作成し、値をログに出し、ページにメッセージします。

+ +

メッセージの受信に際し、ページスクリプトは同じ変数をログに出します:

+ +
window.addEventListener("message", function(event) {
+  if (event.source === window && event.data && event.data.message === "check") {
+    console.log(`In page script, window.x: ${window.x}`);
+    console.log(`In page script, window.y: ${window.y}`);
+  }
+});
+ +

Chrome では、こんな出力が生成されます:

+ +
In content script, window.x: 1
+In content script, window.y: 2
+In page script, window.x: undefined
+In page script, window.y: undefined
+ +

Firefox では、こんな出力が生成されます:

+ +
In content script, window.x: undefined
+In content script, window.y: 2
+In page script, window.x: 1
+In page script, window.y: undefined
+ +

同じことは setTimeout()setInterval()Function() にも言えます。

+ +
+

ページのコンテキストでコードを実行するときは特に注意してください!

+ +

ページの環境が悪意をはらんだウェブページにコントロールされ、期待しない方法であなたが操作するオブジェクトを再定義するかもしれません。

+ +
// page.js redefines console.log
+
+var original = console.log;
+
+console.log = function() {
+  original(true);
+}
+
+ +
// content-script.js calls the redefined version
+
+window.eval('console.log(false)');
+
+
-- cgit v1.2.3-54-g00ecf