สคริปต์เนื้อหา

สคริปต์เนื้อหาคือไฟล์ที่ทำงานในบริบทของหน้าเว็บ โดยใช้ Document Object Model (DOM) มาตรฐาน สคริปต์เหล่านี้จะอ่านรายละเอียดของหน้าเว็บที่เบราว์เซอร์เข้าชม ทำ การเปลี่ยนแปลง และส่งข้อมูลไปยังส่วนขยายหลักได้

ทำความเข้าใจความสามารถของสคริปต์เนื้อหา

สคริปต์เนื้อหาสามารถเข้าถึง API ของส่วนขยายต่อไปนี้ได้โดยตรง

สคริปต์เนื้อหาไม่สามารถเข้าถึง API อื่นๆ ได้โดยตรง แต่สามารถเข้าถึงได้โดยอ้อมด้วยการแลกเปลี่ยนข้อความกับส่วนอื่นๆ ของส่วนขยาย

นอกจากนี้ คุณยังเข้าถึงไฟล์อื่นๆ ในส่วนขยายจากสคริปต์เนื้อหาได้โดยใช้ API เช่น fetch() โดยต้องประกาศไฟล์เหล่านั้นเป็น แหล่งข้อมูลที่เข้าถึงได้จากเว็บ โปรดทราบว่าการดำเนินการนี้ยังเปิดเผยแหล่งข้อมูลให้สคริปต์ของบุคคลที่หนึ่งหรือบุคคลที่สามที่ทำงานในเว็บไซต์เดียวกันด้วย

ทำงานในโลกที่แยกจากกัน

สคริปต์เนื้อหาจะอยู่ในโลกที่แยกจากกัน ซึ่งช่วยให้สคริปต์เนื้อหาทำการเปลี่ยนแปลงสภาพแวดล้อม JavaScript ได้โดยไม่ขัดแย้งกับหน้าเว็บหรือสคริปต์เนื้อหาของส่วนขยายอื่นๆ

ส่วนขยายอาจทำงานในหน้าเว็บที่มีโค้ดคล้ายกับตัวอย่างต่อไปนี้

webPage.html

<html>
  <button id="mybutton">click me</button>
  <script>
    var greeting = "hello, ";
    var button = document.getElementById("mybutton");
    button.person_name = "Bob";
    button.addEventListener(
        "click", () => alert(greeting + button.person_name + "."), false);
  </script>
</html>

ส่วนขยายดังกล่าวสามารถแทรกสคริปต์เนื้อหาต่อไปนี้ได้โดยใช้เทคนิคอย่างใดอย่างหนึ่งที่ระบุไว้ในส่วน แทรกสคริปต์

content-script.js

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
    "click", () => alert(greeting + button.person_name + "."), false);

เมื่อมีการเปลี่ยนแปลงนี้ การแจ้งเตือนทั้ง 2 รายการจะปรากฏขึ้นตามลำดับเมื่อมีการคลิกปุ่ม

แทรกสคริปต์

สคริปต์เนื้อหาสามารถประกาศแบบคงที่ ประกาศ แบบไดนามิก หรือแทรกแบบเป็นโปรแกรมได้

แทรกด้วยการประกาศแบบคงที่

ใช้การประกาศสคริปต์เนื้อหาแบบคงที่ใน manifest.json สำหรับสคริปต์ที่ควรทำงานโดยอัตโนมัติในชุดหน้าเว็บที่รู้จักกันดี

สคริปต์ที่ประกาศแบบคงที่จะลงทะเบียนในไฟล์ Manifest ภายใต้คีย์ "content_scripts" โดยอาจมีไฟล์ JavaScript, ไฟล์ CSS หรือทั้ง 2 อย่าง สคริปต์เนื้อหาที่ทำงานอัตโนมัติทั้งหมดต้องระบุ รูปแบบการจับคู่

manifest.json

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["https://*.nytimes.com/*"],
     "css": ["my-styles.css"],
     "js": ["content-script.js"]
   }
 ],
 ...
}

ชื่อ ประเภท คำอธิบาย
matches อาร์เรย์ของสตริง จำเป็น ระบุหน้าเว็บที่จะแทรกสคริปต์เนื้อหานี้ ดูรายละเอียดเกี่ยวกับไวยากรณ์ของสตริงเหล่านี้ได้ที่รูปแบบการจับคู่และดูข้อมูลเกี่ยวกับวิธียกเว้น URL ได้ที่รูปแบบการจับคู่และ Glob
css อาร์เรย์ของสตริง ไม่บังคับ รายการไฟล์ CSS ที่จะแทรกลงในหน้าเว็บที่ตรงกัน ระบบจะแทรกไฟล์เหล่านี้ตามลำดับที่ปรากฏในอาร์เรย์นี้ ก่อนที่จะสร้างหรือแสดง DOM ใดๆ สำหรับหน้าเว็บ
js อาร์เรย์ของสตริง ไม่บังคับ รายการไฟล์ JavaScript ที่จะแทรกลงในหน้าเว็บที่ตรงกัน ระบบจะแทรกไฟล์ ตามลำดับที่ปรากฏในอาร์เรย์นี้ สตริงแต่ละรายการในรายการนี้ต้องมี เส้นทางที่สัมพันธ์กับแหล่งข้อมูลในไดเรกทอรีรากของส่วนขยาย ระบบจะตัดเครื่องหมายทับ (`/`) ที่นำหน้าออกโดยอัตโนมัติ
run_at RunAt ไม่บังคับ ระบุเวลาที่ควรแทรกสคริปต์ลงในหน้าเว็บ ค่าเริ่มต้นคือ document_idle.
match_about_blank บูลีน ไม่บังคับ ระบุว่าควรแทรกสคริปต์ลงในเฟรม about:blank ที่เฟรมหลักหรือเฟรมที่เปิดอยู่ตรงกับรูปแบบใดรูปแบบหนึ่งที่ประกาศไว้ใน matches หรือไม่ ค่าเริ่มต้นคือ "เท็จ"
match_origin_as_fallback บูลีน ไม่บังคับ ระบุว่าควรแทรกสคริปต์ลงในเฟรมที่สร้างขึ้นโดยต้นทางที่ตรงกัน แต่ URL หรือต้นทางอาจไม่ตรงกับรูปแบบโดยตรงหรือไม่ ซึ่งรวมถึงเฟรมที่มีรูปแบบต่างๆ เช่น about:, data:, blob: และ filesystem: ดูเพิ่มเติมที่ การแทรกในเฟรมที่เกี่ยวข้อง
world ExecutionWorld ไม่บังคับ โลก JavaScript ที่สคริปต์จะดำเนินการภายใน ค่าเริ่มต้นคือ ISOLATED ดูเพิ่มเติมที่ ทำงานในโลกที่แยกจากกัน

ภายในระยะหนึ่งของ ขั้นตอนของวงจรชีวิตของเอกสาร สคริปต์เนื้อหา ที่ประกาศแบบคงที่ในไฟล์ Manifest จะเป็นสคริปต์แรกที่แทรก ก่อนสคริปต์เนื้อหา ที่ลงทะเบียนด้วยวิธีอื่นๆ ระบบจะแทรกสคริปต์ตามลำดับที่ระบุไว้ในไฟล์ Manifest

แทรกด้วยการประกาศแบบไดนามิก

สคริปต์เนื้อหาแบบไดนามิกมีประโยชน์เมื่อไม่ทราบรูปแบบการจับคู่สำหรับสคริปต์เนื้อหา หรือเมื่อไม่ควรแทรกสคริปต์เนื้อหาในโฮสต์ที่รู้จักเสมอไป

การประกาศแบบไดนามิกที่เปิดตัวใน Chrome 96 มีลักษณะคล้ายกับการประกาศแบบ คงที่ แต่ระบบจะลงทะเบียนออบเจ็กต์สคริปต์เนื้อหากับ Chrome โดยใช้ วิธีการในเนมสเปซ chrome.scripting แทนที่จะใช้ใน manifest.json นอกจากนี้ Scripting API ยังช่วยให้นักพัฒนาส่วนขยายทำสิ่งต่อไปนี้ได้

การประกาศแบบไดนามิกสามารถมีไฟล์ JavaScript, ไฟล์ CSS หรือทั้ง 2 อย่างได้เช่นเดียวกับการประกาศแบบคงที่

service-worker.js

chrome.scripting
  .registerContentScripts([{
    id: "session-script",
    js: ["content.js"],
    persistAcrossSessions: false,
    matches: ["*://example.com/*"],
    runAt: "document_start",
  }])
  .then(() => console.log("registration complete"))
  .catch((err) => console.warn("unexpected error", err))

service-worker.js

chrome.scripting
  .updateContentScripts([{
    id: "session-script",
    excludeMatches: ["*://admin.example.com/*"],
  }])
  .then(() => console.log("registration updated"));

service-worker.js

chrome.scripting
  .getRegisteredContentScripts()
  .then(scripts => console.log("registered content scripts", scripts));

service-worker.js

chrome.scripting
  .unregisterContentScripts({ ids: ["session-script"] })
  .then(() => console.log("un-registration complete"));

แทรกแบบเป็นโปรแกรม

ใช้การแทรกแบบเป็นโปรแกรมสำหรับสคริปต์เนื้อหาที่ต้องทำงานเพื่อตอบสนองต่อเหตุการณ์หรือในโอกาสที่เฉพาะเจาะจง

หากต้องการแทรกสคริปต์เนื้อหาแบบเป็นโปรแกรม ส่วนขยายต้องมีสิทธิ์โฮสต์สำหรับ หน้าเว็บที่จะแทรกสคริปต์ คุณสามารถให้สิทธิ์โฮสต์ได้โดย ขอสิทธิ์ดังกล่าวเป็นส่วนหนึ่งของไฟล์ Manifest ของส่วนขยาย หรือใช้ "activeTab"ชั่วคราว

ต่อไปนี้เป็นส่วนขยายเวอร์ชันต่างๆ ที่อิงตาม activeTab

manifest.json:

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_title": "Action Button"
  }
}

สคริปต์เนื้อหาสามารถแทรกเป็นไฟล์ได้

content-script.js


document.body.style.backgroundColor = "orange";

service-worker.js:

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ["content-script.js"]
  });
});

หรือจะแทรกและเรียกใช้เนื้อหาของฟังก์ชันเป็นสคริปต์เนื้อหาก็ได้

service-worker.js:

function injectedFunction() {
  document.body.style.backgroundColor = "orange";
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
  });
});

โปรดทราบว่าฟังก์ชันที่แทรกเป็นสำเนาของฟังก์ชันที่อ้างอิงในการเรียก chrome.scripting.executeScript() ไม่ใช่ฟังก์ชันเดิม ดังนั้น เนื้อหาของฟังก์ชันจึงต้องมีข้อมูลครบถ้วนในตัวเอง การอ้างอิงตัวแปรนอกฟังก์ชันจะทำให้สคริปต์เนื้อหาแสดง ReferenceError

เมื่อแทรกเป็นฟังก์ชัน คุณยังส่งอาร์กิวเมนต์ไปยังฟังก์ชันได้ด้วย

service-worker.js

function injectedFunction(color) {
  document.body.style.backgroundColor = color;
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
    args : [ "orange" ],
  });
});

ยกเว้นการจับคู่และ Glob

หากต้องการปรับแต่งการจับคู่หน้าเว็บที่ระบุ ให้ใส่ช่องต่อไปนี้ในการลงทะเบียนแบบประกาศ

ชื่อ ประเภท คำอธิบาย
exclude_matches อาร์เรย์ของสตริง ไม่บังคับ ยกเว้นหน้าเว็บที่จะแทรกสคริปต์เนื้อหานี้ ดูรายละเอียดเกี่ยวกับไวยากรณ์ของสตริงเหล่านี้ได้ที่ รูปแบบการจับคู่
include_globs อาร์เรย์ของสตริง ไม่บังคับ ใช้หลังจาก matches เพื่อรวมเฉพาะ URL ที่ตรงกับ Glob นี้ด้วย มีวัตถุประสงค์เพื่อจำลองคีย์เวิร์ด @include ของ Greasemonkey
exclude_globs อาร์เรย์ของสตริง ไม่บังคับ ใช้หลังจาก matches เพื่อยกเว้น URL ที่ตรงกับ Glob นี้ มีวัตถุประสงค์เพื่อจำลอง@exclude คีย์เวิร์ดของ Greasemonkey

ระบบจะแทรกสคริปต์เนื้อหาลงในหน้าเว็บหากเงื่อนไขทั้ง 2 ข้อต่อไปนี้เป็นจริง

  • URL ของสคริปต์ตรงกับรูปแบบ matches และรูปแบบ include_globs
  • URL ไม่ตรงกับรูปแบบ exclude_matches หรือ exclude_globs เนื่องจากต้องระบุพร็อพเพอร์ตี้ matches คุณจึงใช้ exclude_matches, include_globs, และ exclude_globs ได้เพื่อจำกัดหน้าเว็บที่จะได้รับผลกระทบเท่านั้น

ส่วนขยายต่อไปนี้จะแทรกสคริปต์เนื้อหาลงใน https://www.nytimes.com/health แต่ไม่แทรกใน https://www.nytimes.com/business

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  excludeMatches : [ "*://*/*business*" ],
  js : [ "contentScript.js" ],
}]);

พร็อพเพอร์ตี้ Glob ใช้ไวยากรณ์ที่แตกต่างและยืดหยุ่นกว่า รูปแบบการจับคู่ สตริง Glob ที่ยอมรับคือ URL ที่อาจมีเครื่องหมายดอกจันและเครื่องหมายคำถามที่เป็น "ไวลด์การ์ด" เครื่องหมายดอกจัน (*) จะจับคู่กับสตริงที่มีความยาวใดก็ได้ รวมถึงสตริงว่าง ในขณะที่เครื่องหมายคำถาม (?) จะจับคู่กับอักขระเดี่ยวตัวใดก็ได้

ตัวอย่างเช่น Glob https://???.example.com/foo/\* จะจับคู่กับรายการต่อไปนี้

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

แต่จะไม่จับคู่กับรายการต่อไปนี้

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

ส่วนขยายนี้จะแทรกสคริปต์เนื้อหาลงใน https://www.nytimes.com/arts/index.html และ https://www.nytimes.com/jobs/index.htm* แต่ไม่แทรกใน https://www.nytimes.com/sports/index.html

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

ส่วนขยายนี้จะแทรกสคริปต์เนื้อหาลงใน https://history.nytimes.com และ https://.nytimes.com/history แต่ไม่แทรกใน https://science.nytimes.com หรือ https://www.nytimes.com/science

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

คุณสามารถรวมองค์ประกอบเหล่านี้ทั้งหมด บางส่วน หรือทั้งหมดเพื่อให้ได้ขอบเขตที่ถูกต้อง

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

เวลาทำงาน

ช่อง run_at จะควบคุมเวลาที่แทรกไฟล์ JavaScript ลงในหน้าเว็บ ค่าที่แนะนำและ ค่าเริ่มต้นคือ "document_idle" ดูค่าอื่นๆ ที่เป็นไปได้ ได้ที่ประเภท RunAt

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  runAt : "document_idle",
  js : [ "contentScript.js" ],
}]);
ชื่อ ประเภท คำอธิบาย
document_idle สตริง แนะนำ ใช้ "document_idle" ทุกครั้งที่ทำได้

เบราว์เซอร์ จะเลือกเวลาที่จะแทรกสคริปต์ระหว่าง "document_end" กับทันทีหลังจาก เหตุการณ์ window.onload เริ่มทำงาน ช่วงเวลาที่แท้จริงของการแทรกจะขึ้นอยู่กับความซับซ้อนของเอกสารและระยะเวลาที่ใช้ในการโหลด และได้รับการปรับให้เหมาะสมกับความเร็วในการโหลดหน้าเว็บสคริปต์เนื้อหาที่ทำงานที่ "document_idle" ไม่จำเป็นต้องรอเหตุการณ์ window.onload เนื่องจากระบบรับประกันว่าจะทำงานหลังจาก DOM เสร็จสมบูรณ์

หากสคริปต์จำเป็นต้องทำงานหลังจาก window.onload ส่วนขยายสามารถตรวจสอบว่า onload เริ่มทำงานแล้วหรือไม่โดยใช้document.readyState พร็อพเพอร์ตี้
document_start สตริง ระบบจะแทรกสคริปต์หลังจากไฟล์จาก css แต่ก่อนที่จะสร้าง DOM อื่นๆ หรือเรียกใช้สคริปต์อื่นๆ
document_end สตริง ระบบจะแทรกสคริปต์ทันทีหลังจาก DOM เสร็จสมบูรณ์ แต่ก่อนที่จะโหลดแหล่งข้อมูลย่อย เช่น รูปภาพและเฟรม

ระบุเฟรม

สำหรับสคริปต์เนื้อหาแบบประกาศที่ระบุไว้ในไฟล์ Manifest ช่อง "all_frames" ช่วยให้ส่วนขยายระบุได้ว่าจะแทรกไฟล์ JavaScript และ CSS ลงในเฟรมทั้งหมดที่ตรงกับข้อกำหนด URL ที่ระบุ หรือเฉพาะเฟรมบนสุดในแท็บ

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

เมื่อลงทะเบียนสคริปต์เนื้อหาแบบเป็นโปรแกรมโดยใช้ chrome.scripting.registerContentScripts(...) คุณจะใช้พารามิเตอร์ allFrames เพื่อ ระบุว่าควรแทรกสคริปต์เนื้อหาลงในเฟรมทั้งหมดที่ตรงกับ ข้อกำหนด URL ที่ระบุ หรือเฉพาะเฟรมบนสุดในแท็บ โดยจะใช้ได้กับ tabId เท่านั้น และจะใช้ไม่ได้หากมีการระบุ frameIds หรือ documentIds

service-worker.js

chrome.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);

ส่วนขยายอาจต้องการเรียกใช้สคริปต์ในเฟรมที่เกี่ยวข้องกับเฟรมที่ตรงกัน แต่ไม่ตรงกันเอง สถานการณ์ทั่วไปที่เกิดขึ้นคือเฟรมที่มี URL ที่สร้างขึ้นโดยเฟรมที่ตรงกัน แต่ URL ไม่ตรงกับรูปแบบที่ระบุของสคริปต์

กรณีนี้จะเกิดขึ้นเมื่อส่วนขยายต้องการแทรกในเฟรมที่มี URL ที่มีรูปแบบ about:, data:, blob: และ filesystem: ในกรณีเหล่านี้ URL จะไม่ตรงกับรูปแบบของสคริปต์เนื้อหา (และในกรณีของ about: และ data: URL จะไม่มี URL หลักหรือต้นทางใน URL เลย เช่น about:blank หรือ data:text/html,<html>Hello, World!</html>) อย่างไรก็ตาม เฟรมเหล่านี้ยังคงเชื่อมโยงกับเฟรมที่สร้างได้

หากต้องการแทรกในเฟรมเหล่านี้ ส่วนขยายสามารถระบุพร็อพเพอร์ตี้ "match_origin_as_fallback" ในข้อกำหนดสคริปต์เนื้อหาในไฟล์ Manifest ได้

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

เมื่อระบุและตั้งค่าเป็น true Chrome จะดูต้นทางของผู้เริ่มเฟรมเพื่อพิจารณาว่าเฟรมตรงกันหรือไม่ แทนที่จะดู URL ของเฟรมเอง โปรดทราบว่าต้นทางนี้อาจแตกต่างจาก ต้นทาง ของเฟรมเป้าหมายด้วย (เช่น URL data: มีต้นทางเป็น null)

ผู้เริ่มเฟรมคือเฟรมที่สร้างหรือนำทางเฟรมเป้าหมาย แม้ว่าโดยทั่วไปแล้วจะเป็นเฟรมหลักหรือเฟรมที่เปิดอยู่โดยตรง แต่ก็อาจไม่ใช่เช่นนั้น (เช่น กรณีที่เฟรมนำทาง iframe ภายใน iframe)

เนื่องจากเป็นการเปรียบเทียบ ต้นทาง ของเฟรมผู้เริ่ม เฟรมผู้เริ่มจึงอาจอยู่ในเส้นทางใดก็ได้จากต้นทางนั้น เพื่อให้การดำเนินการนี้ชัดเจน Chrome กำหนดให้สคริปต์เนื้อหาใดก็ตามที่ระบุด้วย "match_origin_as_fallback" ที่ตั้งค่าเป็น true ต้องระบุเส้นทางเป็น * ด้วย

เมื่อระบุทั้ง "match_origin_as_fallback" และ "match_about_blank" แล้ว "match_origin_as_fallback" จะมีความสำคัญมากกว่า

การสื่อสารกับหน้าเว็บที่ฝัง

แม้ว่าสภาพแวดล้อมการดำเนินการของสคริปต์เนื้อหาและหน้าเว็บที่โฮสต์สคริปต์เนื้อหาจะแยกจากกัน แต่ทั้ง 2 อย่างก็แชร์การเข้าถึง DOM ของหน้าเว็บ หากหน้าเว็บต้องการสื่อสารกับสคริปต์เนื้อหา หรือกับส่วนขยายผ่านสคริปต์เนื้อหา หน้าเว็บจะต้องดำเนินการผ่าน DOM ที่แชร์

ตัวอย่างการดำเนินการนี้ทำได้โดยใช้ window.postMessage()

content-script.js

var port = chrome.runtime.connect();

window.addEventListener("message", (event) => {
  // We only accept messages from ourselves
  if (event.source !== window) {
    return;
  }

  if (event.data.type && (event.data.type === "FROM_PAGE")) {
    console.log("Content script received: " + event.data.text);
    port.postMessage(event.data.text);
  }
}, false);

example.js

document.getElementById("theButton").addEventListener("click", () => {
  window.postMessage(
      {type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

หน้าเว็บที่ไม่ใช่ส่วนขยาย (example.html) จะโพสต์ข้อความไปยังตัวเอง สคริปต์เนื้อหาจะสกัดกั้นและตรวจสอบข้อความนี้ แล้วโพสต์ไปยังกระบวนการส่วนขยาย ด้วยวิธีนี้ หน้าเว็บจะสร้างช่องทางการสื่อสารไปยังกระบวนการส่วนขยาย และสามารถดำเนินการย้อนกลับได้ด้วยวิธีที่คล้ายกัน

เข้าถึงไฟล์ส่วนขยาย

หากต้องการเข้าถึงไฟล์ส่วนขยายจากสคริปต์เนื้อหา คุณสามารถเรียก chrome.runtime.getURL() เพื่อรับ Absolute URL ของชิ้นงานส่วนขยายได้ตามที่แสดงในตัวอย่างต่อไปนี้ (content.js)

content-script.js

let image = chrome.runtime.getURL("images/my_image.png")

หากต้องการใช้แบบอักษรหรือรูปภาพในไฟล์ CSS คุณสามารถใช้ @@extension_id เพื่อสร้าง URL ได้ตามที่แสดงในตัวอย่างต่อไปนี้ (content.css)

content.css

body {
 background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}

@font-face {
 font-family: 'Stint Ultra Expanded';
 font-style: normal;
 font-weight: 400;
 src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}

ต้องประกาศชิ้นงานทั้งหมดเป็น แหล่งข้อมูลที่เข้าถึงได้จากเว็บ ในไฟล์ manifest.json

manifest.json

{
 ...
 "web_accessible_resources": [
   {
     "resources": [ "images/*.png" ],
     "matches": [ "https://example.com/*" ]
   },
   {
     "resources": [ "fonts/*.woff" ],
     "matches": [ "https://example.com/*" ]
   }
 ],
 ...
}

นโยบายรักษาความปลอดภัยเนื้อหา

สคริปต์เนื้อหาที่ทำงานในโลกที่แยกจากกันจะมี นโยบายรักษาความปลอดภัยเนื้อหา (CSP) ดังนี้

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

ข้อจำกัดนี้คล้ายกับข้อจำกัดที่ใช้กับบริบทส่วนขยายอื่นๆ ซึ่งจะป้องกันการใช้ eval() รวมถึงการโหลดสคริปต์ภายนอก

สำหรับส่วนขยายที่ไม่ได้แพ็ก CSP จะรวม localhost ไว้ด้วย

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

เมื่อแทรกสคริปต์เนื้อหาลงในโลกหลัก ระบบจะใช้นโยบายรักษาความปลอดภัยเนื้อหาของหน้าเว็บ

ปลอดภัยอยู่เสมอ

แม้ว่าโลกที่แยกจากกันจะให้การป้องกันในระดับหนึ่ง แต่การใช้สคริปต์เนื้อหาอาจทำให้เกิดช่องโหว่ในส่วนขยายและหน้าเว็บได้ หากสคริปต์เนื้อหารับเนื้อหาจากเว็บไซต์อื่น เช่น โดยการเรียก fetch() โปรดกรองเนื้อหาเพื่อป้องกันการโจมตีแบบ Cross-Site Scripting ก่อนที่จะแทรก และสื่อสารผ่าน HTTPS เท่านั้นเพื่อ หลีกเลี่ยงการโจมตีแบบ "man-in-the-middle"

อย่าลืมกรองหน้าเว็บที่เป็นอันตราย ตัวอย่างเช่น รูปแบบต่อไปนี้เป็นอันตรายและไม่อนุญาตใน Manifest V3

ไม่ควรทำ

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
ไม่ควรทำ

content-script.js

const elmt_id = ...
// WARNING! elmt_id might be '); ... evil script ... //'!
window.setTimeout("animate(" + elmt_id + ")", 200);

แต่ควรใช้ API ที่ปลอดภัยกว่าซึ่งไม่เรียกใช้สคริปต์

ควรทำ

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
ควรทำ

content-script.js

const elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(() => animate(elmt_id), 200);