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

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

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

สคริปต์เนื้อหาสามารถเข้าถึง Chrome API ที่ส่วนขยายหลักใช้โดยการแลกเปลี่ยนข้อความกับส่วนขยาย นอกจากนี้ ยังเข้าถึง URL ของไฟล์ส่วนขยายด้วย chrome.runtime.getURL() และใช้ผลการค้นหาเดียวกับ URL อื่นๆ ได้อีกด้วย

// Code for displaying EXTENSION_DIR/images/myimage.png:
var imgURL = chrome.runtime.getURL("images/myimage.png");
document.getElementById("someImage").src = imgURL;

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

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

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

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

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

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

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

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

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

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

แทรกสคริปต์

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

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

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

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

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab"
  ],
  ...
}

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

chrome.runtime.onMessage.addListener(
  function(message, callback) {
    if (message == "changeColor"){
      chrome.tabs.executeScript({
        code: 'document.body.style.backgroundColor="orange"'
      });
    }
  });

หรือแทรกทั้งไฟล์ก็ได้

chrome.runtime.onMessage.addListener(
  function(message, callback) {
    if (message == "runContentScript"){
      chrome.tabs.executeScript({
        file: 'contentScript.js'
      });
    }
  });

แทรกอย่างประกาศ

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

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

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["http://*.nytimes.com/*"],
     "css": ["myStyles.css"],
     "js": ["contentScript.js"]
   }
 ],
 ...
}
ชื่อ Type คำอธิบาย
matches {: #matches } อาร์เรย์ของสตริง ต้องระบุ ระบุหน้าที่จะแทรกสคริปต์เนื้อหานี้ ดูข้อมูลเกี่ยวกับวิธียกเว้น URL ได้ที่จับคู่รูปแบบสำหรับรายละเอียดเพิ่มเติมเกี่ยวกับไวยากรณ์ของสตริงเหล่านี้ และจับคู่รูปแบบและกลืน
css {: #css } อาร์เรย์ของสตริง ไม่บังคับ รายการไฟล์ CSS ที่จะแทรกลงในหน้าที่ตรงกัน ระบบจะแทรกคำสั่งเหล่านี้ตามลำดับที่ปรากฏในอาร์เรย์นี้ ก่อนที่จะมีการสร้างหรือแสดง DOM ใดๆ สำหรับหน้าเว็บ
js {: #js } อาร์เรย์ของสตริง ไม่บังคับ รายการไฟล์ JavaScript ที่จะแทรกในหน้าที่ตรงกัน โดยการแทรกข้อมูลเหล่านี้ตามลำดับที่ปรากฏในอาร์เรย์นี้
match_about_blank {: #match_about_blank } boolean ไม่บังคับ ระบุว่าสคริปต์ควรแทรกลงในเฟรม about:blank ที่เฟรมระดับบนสุดหรือเฟรมเปิดตรงกับรูปแบบที่ประกาศหนึ่งใน matches หรือไม่ ค่าเริ่มต้นคือ false

ยกเว้นการจับคู่ที่ตรงกันและกลุ่ม

การจับคู่หน้าที่ระบุปรับแต่งได้โดยใส่ช่องต่อไปนี้ในการลงทะเบียนไฟล์ Manifest

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

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

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

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

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

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

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

  • http:// www .example.com/foo /bar
  • http:// ไฟล์ .example.com/foo /

แต่ไม่ตรงกับเงื่อนไขต่อไปนี้

  • http:// .example.com/foo/bar ของฉัน
  • http:// ตัวอย่าง .com/foo/
  • http://www.example.com/foo

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

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

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

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

โดยจะเลือกรวมทั้งหมด 1 รายการ หรือเพียงบางส่วนก็ได้เพื่อให้บรรลุขอบเขตที่ถูกต้อง

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

เวลาทำงาน

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

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}
ชื่อ Type คำอธิบาย
document_idle {: #document_idle } สตริง แนะนำ ใช้ "document_idle" เมื่อเป็นไปได้

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

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

ระบุเฟรม

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

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}
ชื่อ Type คำอธิบาย
all_frames {: #all_frames } boolean ไม่บังคับ ค่าเริ่มต้นคือ false หมายความว่าจับคู่เฉพาะเฟรมบนสุดเท่านั้น

หากระบุ true ระบบจะแทรกลงในเฟรมทั้งหมด แม้ว่าเฟรมจะไม่ใช่เฟรมบนสุดในแท็บก็ตาม ระบบจะตรวจสอบแต่ละเฟรมเพื่อดูข้อกำหนดของ URL แยกกัน โดยจะไม่แทรกเฟรมลงในเฟรมย่อยหากไม่เป็นไปตามข้อกำหนด URL

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

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

ดูตัวอย่างได้โดยใช้ window.postMessage

var port = chrome.runtime.connect();

window.addEventListener("message", function(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);
document.getElementById("theButton").addEventListener("click",
    function() {
  window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
}, false);

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

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

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

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

var data = document.getElementById("json-data")
// WARNING! Might be evaluating an evil script!
var parsed = eval("(" + data + ")")
var elmt_id = ...
// WARNING! elmt_id might be "); ... evil script ... //"!
window.setTimeout("animate(" + elmt_id + ")", 200);

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

var data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
var parsed = JSON.parse(data);
var elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(function() {
  animate(elmt_id);
}, 200);