Content Script คือไฟล์ที่ทำงานในบริบทของหน้าเว็บ การใช้Document Object Model (DOM) มาตรฐานทำให้ส่วนขยายสามารถอ่านรายละเอียดของหน้าเว็บที่เบราว์เซอร์เข้าชม ทำการเปลี่ยนแปลง และส่งข้อมูลไปยังส่วนขยายหลักได้
ทำความเข้าใจความสามารถของ Content Script
Content Script สามารถเข้าถึง 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;
นอกจากนี้ Content Script ยังเข้าถึง Chrome API ต่อไปนี้ได้โดยตรง
Content Script จะเข้าถึง 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 รายการจะปรากฏขึ้นหากกดปุ่ม
Isolated World จะไม่อนุญาตให้ Content Script, ส่วนขยาย และหน้าเว็บเข้าถึงตัวแปรหรือฟังก์ชันใดๆ ที่สร้างโดยองค์ประกอบอื่นๆ นอกจากนี้ ยังช่วยให้สคริปต์เนื้อหาสามารถเปิดใช้ฟังก์ชันที่ไม่ควรเข้าถึงได้ในหน้าเว็บ
แทรกสคริปต์
สคริปต์เนื้อหาสามารถแทรกแบบเป็นโปรแกรมหรือแบบประกาศได้
แทรกโดยใช้โปรแกรม
ใช้การแทรกแบบเป็นโปรแกรมสำหรับ Content Script ที่ต้องทำงานในโอกาสที่เฉพาะเจาะจง
หากต้องการแทรก Content Script แบบเป็นโปรแกรม ให้ระบุสิทธิ์ activeTab ในไฟล์ Manifest ซึ่งจะให้สิทธิ์เข้าถึงโฮสต์ของเว็บไซต์ที่ใช้งานอยู่ได้อย่างปลอดภัยและให้สิทธิ์เข้าถึงแท็บชั่วคราว ทำให้ Content Script ทำงานในแท็บที่ใช้งานอยู่ปัจจุบันได้โดยไม่ต้องระบุสิทธิ์แบบข้ามต้นทาง
{
"name": "My extension",
...
"permissions": [
"activeTab"
],
...
}
คุณแทรก Content Script เป็นโค้ดได้
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'
});
}
});
แทรกแบบประกาศ
ใช้การแทรกแบบประกาศสำหรับ Content Script ที่ควรเรียกใช้โดยอัตโนมัติในหน้าที่ระบุ
สคริปต์ที่แทรกแบบประกาศจะได้รับการลงทะเบียนในไฟล์ Manifest ภายใต้ช่อง "content_scripts"
ซึ่งอาจมีไฟล์ JavaScript, ไฟล์ CSS หรือทั้ง 2 อย่าง สคริปต์เนื้อหาที่เรียกใช้โดยอัตโนมัติทั้งหมดต้องระบุรูปแบบที่ตรงกัน
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"css": ["myStyles.css"],
"js": ["contentScript.js"]
}
],
...
}
| ชื่อ | ประเภท | คำอธิบาย |
|---|---|---|
matches {: #matches } |
ต้องระบุ ระบุหน้าเว็บที่จะแทรก Content Script นี้ ดูรายละเอียดเพิ่มเติมเกี่ยวกับไวยากรณ์ของสตริงเหล่านี้ได้ที่รูปแบบการจับคู่ และดูข้อมูลเกี่ยวกับวิธียกเว้น URL ได้ที่รูปแบบการจับคู่และ Glob | |
css {: #css } |
ไม่บังคับ รายการไฟล์ CSS ที่จะแทรกลงในหน้าเว็บที่ตรงกัน ระบบจะแทรกข้อมูลเหล่านี้ตามลำดับที่ปรากฏในอาร์เรย์นี้ ก่อนที่จะสร้างหรือแสดง DOM ใดๆ สำหรับหน้าเว็บ | |
js {: #js } |
ไม่บังคับ รายการไฟล์ JavaScript ที่จะแทรกลงในหน้าเว็บที่ตรงกัน ระบบจะแทรกตามลำดับที่ปรากฏในอาร์เรย์นี้ | |
match_about_blank {: #match_about_blank } |
บูลีน | ไม่บังคับ ไม่ว่าสคริปต์ควรแทรกลงในเฟรม about:blank ที่เฟรมหลักหรือเฟรมที่เปิดตรงกับรูปแบบใดรูปแบบหนึ่งที่ประกาศไว้ใน matches หรือไม่ ค่าเริ่มต้นคือ false |
ยกเว้นการจับคู่และ Glob
การจับคู่หน้าเว็บที่ระบุสามารถปรับแต่งได้โดยการรวมช่องต่อไปนี้ในการลงทะเบียนไฟล์ Manifest
| ชื่อ | ประเภท | คำอธิบาย |
|---|---|---|
exclude_matches {: #exclude_matches } |
ไม่บังคับ ยกเว้นหน้าเว็บที่สคริปต์เนื้อหานี้จะแทรกเข้าไป ดูรายละเอียดเพิ่มเติมเกี่ยวกับไวยากรณ์ของสตริงเหล่านี้ได้ที่รูปแบบการจับคู่ | |
include_globs {: #include_globs } |
ไม่บังคับ ใช้หลังจาก matches เพื่อรวมเฉพาะ URL ที่ตรงกับ Glob นี้ด้วย มีจุดประสงค์เพื่อเลียนแบบคีย์เวิร์ด @include Greasemonkey |
|
exclude_globs {: #exclude_globs } |
ไม่บังคับ มีผลหลังจากวันที่ matches เพื่อยกเว้น URL ที่ตรงกับ Glob นี้ มีจุดประสงค์เพื่อเลียนแบบคีย์เวิร์ด @excludeGreasemonkey |
ระบบจะแทรก Content Script ลงในหน้าเว็บหาก 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:// the .example.com/foo /
อย่างไรก็ตาม จะไม่ตรงกับรายการต่อไปนี้
- http:// my .example.com/foo/bar
- http:// example .com/foo/
- http://www.example.com/foo
ส่วนขยายนี้จะแทรกสคริปต์เนื้อหาลงใน http:/www.nytimes.com/ arts /index.html และ http://www.nytimes.com/ jobs /index.html แต่จะไม่แทรกใน http://www.nytimes.com/ sports /index.html
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"include_globs": ["*nytimes.com/???s/*"],
"js": ["contentScript.js"]
}
],
...
}
ส่วนขยายนี้จะแทรก Content Script ลงใน http:// history .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"]
}
],
...
}
คุณสามารถรวมรายการเหล่านี้ทั้งหมด บางรายการ หรือรายการเดียวเพื่อให้ได้ขอบเขตที่ถูกต้อง
{
"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"]
}
],
...
}
| ชื่อ | ประเภท | คำอธิบาย |
|---|---|---|
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"]
}
],
...
}
| ชื่อ | ประเภท | คำอธิบาย |
|---|---|---|
all_frames {: #all_frames } |
บูลีน | ไม่บังคับ ค่าเริ่มต้นคือ false ซึ่งหมายความว่าจะจับคู่เฉพาะเฟรมบนสุดหากระบุ true ระบบจะแทรกลงในทุกเฟรม แม้ว่าเฟรมนั้นจะไม่ใช่เฟรมบนสุดในแท็บก็ตาม ระบบจะตรวจสอบแต่ละเฟรมแยกกันตามข้อกำหนด URL และจะไม่แทรกลงในเฟรมย่อยหากไม่เป็นไปตามข้อกำหนด URL |
การสื่อสารกับหน้าเว็บที่ฝัง
แม้ว่าสภาพแวดล้อมการดำเนินการของ Content Script และหน้าเว็บที่โฮสต์ Content Script จะแยกออกจากกัน แต่ทั้ง 2 ส่วนก็แชร์สิทธิ์เข้าถึง DOM ของหน้าเว็บ หากหน้าเว็บต้องการสื่อสารกับ Content Script หรือกับส่วนขยายผ่าน Content Script หน้าเว็บจะต้องสื่อสารผ่าน 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 จะโพสต์ข้อความไปยังตัวมันเอง สคริปต์เนื้อหาจะดักจับและ ตรวจสอบข้อความนี้ แล้วโพสต์ไปยังกระบวนการของส่วนขยาย ด้วยวิธีนี้ หน้าเว็บจะ สร้างช่องทางการสื่อสารกับกระบวนการของส่วนขยาย คุณสามารถทำในทางกลับกันได้โดยใช้ วิธีการที่คล้ายกัน
ปลอดภัยอยู่เสมอ
แม้ว่าโลกที่แยกจากกันจะให้การปกป้องอีกชั้นหนึ่ง แต่การใช้สคริปต์เนื้อหาอาจสร้างช่องโหว่ในส่วนขยายและหน้าเว็บได้ หาก Content Script ได้รับเนื้อหาจากเว็บไซต์อื่น เช่น การสร้าง 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);