การแทนที่หน้าเว็บพื้นหลังหรือหน้ากิจกรรมด้วย Service Worker
Service Worker จะแทนที่พื้นหลังหรือหน้ากิจกรรมของส่วนขยายเพื่อดูแลให้โค้ดพื้นหลังอยู่นอกชุดข้อความหลัก วิธีนี้ช่วยให้ส่วนขยายทำงานได้เมื่อจำเป็นเท่านั้นและเป็นการประหยัดทรัพยากร
หน้าเว็บพื้นหลังเป็นองค์ประกอบพื้นฐานของส่วนขยายมานับตั้งแต่ที่มีการเปิดตัว กล่าวให้ชัดเจนก็คือ หน้าเว็บพื้นหลังมีสภาพแวดล้อมการทำงานที่ไม่เกี่ยวข้องกับหน้าต่างหรือแท็บอื่นๆ วิธีนี้ช่วยให้ส่วนขยายสังเกตการณ์และตอบสนองต่อเหตุการณ์ได้
หน้านี้อธิบายเกี่ยวกับงานในการแปลงหน้าเว็บพื้นหลังเป็นโปรแกรมทำงานของบริการส่วนขยาย สำหรับข้อมูลเพิ่มเติมเกี่ยวกับโปรแกรมทำงานของบริการส่วนขยายโดยทั่วไป โปรดดูที่บทแนะนำจัดการเหตุการณ์ด้วยโปรแกรมทำงานของบริการ และส่วนเกี่ยวกับโปรแกรมทำงานของบริการส่วนขยาย
ความแตกต่างระหว่างสคริปต์ที่ทำงานอยู่เบื้องหลังและโปรแกรมทำงานของบริการส่วนขยาย
ในบางบริบท คุณจะเห็นโปรแกรมทำงานของบริการส่วนขยายที่เรียกว่า "สคริปต์พื้นหลัง" แม้ว่าโปรแกรมทำงานของบริการส่วนขยายจะทำงานในพื้นหลัง แต่การเรียกสคริปต์ที่ทำงานอยู่เบื้องหลังจะค่อนข้างทำให้เข้าใจผิดจากการบอกเป็นนัยว่ามีความสามารถเหมือนกัน ความแตกต่างจะอธิบายไว้ด้านล่าง
การเปลี่ยนแปลงจากหน้าพื้นหลัง
โปรแกรมทำงานของบริการมีความแตกต่างกับหน้าเว็บพื้นหลังอยู่หลายจุด
- ส่วนขยายจะทำงานนอกเทรดหลัก ซึ่งหมายความว่าจะไม่รบกวนเนื้อหาของส่วนขยาย
- เนื่องจากส่วนขยายประเภทนี้มีความสามารถพิเศษ เช่น การสกัดกั้นเหตุการณ์การดึงข้อมูลจากต้นทางของส่วนขยาย เช่น เหตุการณ์จากป๊อปอัปแถบเครื่องมือ
- โดยสามารถสื่อสารและโต้ตอบกับบริบทอื่นๆ ผ่านอินเทอร์เฟซไคลเอ็นต์
การเปลี่ยนแปลงที่คุณต้องดำเนินการ
คุณต้องทำการปรับเปลี่ยนโค้ด 2-3 อย่างเพื่อรองรับความแตกต่างระหว่างการทำงานของสคริปต์ที่ทำงานอยู่เบื้องหลังและโปรแกรมทำงานของบริการ ในการเริ่มต้น วิธีการระบุ Service Worker ในไฟล์ Manifest จะต่างจากการระบุสคริปต์พื้นหลัง คำอธิบายเพิ่มเติม
- เนื่องจากผู้ใช้เข้าถึง DOM หรืออินเทอร์เฟซ
window
ไม่ได้ คุณจึงต้องย้ายการเรียกใช้ดังกล่าวไปยัง API อื่นหรือไปยังเอกสารนอกหน้าจอ - ไม่ควรลงทะเบียน Listener เหตุการณ์เพื่อตอบสนองต่อสัญญาที่ส่งกลับหรือโค้ดเรียกกลับภายในเหตุการณ์
- เนื่องจากเข้ากันไม่ได้แบบย้อนหลังกับ
XMLHttpRequest()
คุณจึงต้องแทนที่การเรียกไปยังอินเทอร์เฟซนี้ด้วยการเรียกfetch()
- เนื่องจากระบบจะยุติลงเมื่อไม่ได้ใช้งาน คุณจะต้องคงสถานะของแอปพลิเคชันไว้แทนที่จะใช้ตัวแปรร่วม การหยุดการทำงานของโปรแกรมทำงานของบริการยังสามารถหยุดตัวจับเวลาก่อนที่จะทำงานเสร็จได้ คุณจะต้องแทนที่ด้วยการปลุก
หน้านี้จะอธิบายงานเหล่านี้โดยละเอียด
อัปเดตช่อง "พื้นหลัง" ในไฟล์ Manifest
ในไฟล์ Manifest V3 หน้าพื้นหลังจะถูกแทนที่โดยโปรแกรมทำงานของบริการ การเปลี่ยนแปลงในไฟล์ Manifest แสดงอยู่ด้านล่าง
- ให้แทนที่
"background.scripts"
ด้วย"background.service_worker"
ในmanifest.json
โปรดทราบว่าช่อง"service_worker"
จะใช้สตริง ไม่ใช่อาร์เรย์ของสตริง - นำ
"background.persistent"
ออกจากmanifest.json
{ ... "background": { "scripts": [ "backgroundContextMenus.js", "backgroundOauth.js" ], "persistent": false }, ... }
{ ... "background": { "service_worker": "service_worker.js", "type": "module" } ... }
ช่อง "service_worker"
จะมีสตริงเดียว คุณจะต้องใช้ช่อง "type"
เฉพาะในกรณีที่ใช้โมดูล ES (โดยใช้คีย์เวิร์ด import
) ค่าจะเป็น "module"
เสมอ โปรดดูข้อมูลเพิ่มเติมที่ข้อมูลพื้นฐานเกี่ยวกับ Extension Service Worker
ย้ายการเรียกใช้ DOM และการเรียกใช้จากหน้าต่างไปยังเอกสารนอกหน้าจอ
ส่วนขยายบางรายการต้องมีสิทธิ์เข้าถึงออบเจ็กต์ DOM และหน้าต่างโดยไม่ต้องเปิดหน้าต่างหรือแท็บใหม่ ซึ่ง Offscreen API รองรับกรณีการใช้งานเหล่านี้โดยการเปิดและปิดเอกสารที่ไม่แสดงในรูปแบบแพ็กเกจที่มีส่วนขยาย โดยไม่รบกวนประสบการณ์ของผู้ใช้ เอกสารนอกหน้าจอจะไม่แชร์ API กับบริบทของส่วนขยายอื่นๆ แต่ทำหน้าที่เป็นหน้าเว็บแบบเต็มเพื่อให้ส่วนขยายโต้ตอบด้วย ยกเว้นการส่งข้อความ
หากต้องการใช้ API นอกหน้าจอ ให้สร้างเอกสารนอกจอภาพจาก Service Worker
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
ในเอกสารนอกหน้าจอ ให้ดําเนินการต่างๆ ที่ก่อนหน้านี้คุณได้เรียกใช้ในสคริปต์พื้นหลัง เช่น คัดลอกข้อความที่เลือกไว้ในหน้าโฮสต์
let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');
สื่อสารกันระหว่างเอกสารนอกจอภาพกับโปรแกรมทำงานของบริการส่วนขยายโดยใช้การส่งข้อความ
แปลง localStorage เป็นประเภทอื่น
อินเทอร์เฟซ Storage
ของแพลตฟอร์มเว็บ (เข้าถึงได้จาก window.localStorage
) จะใช้ในโปรแกรมทำงานของบริการไม่ได้ วิธีแก้ไขปัญหานี้คือหนึ่งใน 2 สิ่งต่อไปนี้ ประการแรก คุณสามารถแทนที่ด้วยการเรียกไปยังกลไกของพื้นที่เก็บข้อมูลอื่น เนมสเปซของ chrome.storage.local
จะให้บริการ Use Case ส่วนใหญ่ แต่ก็มีตัวเลือกอื่นๆ ให้ใช้งาน
นอกจากนี้คุณยังย้ายการโทรไปยังเอกสารที่ไม่อยู่ในหน้าจอได้ด้วย เช่น หากต้องการย้ายข้อมูลที่เก็บไว้ก่อนหน้านี้ใน localStorage
ไปยังกลไกอื่น ให้ทำดังนี้
- สร้างเอกสารนอกหน้าจอด้วยกิจวัตร Conversion และเครื่องจัดการ
runtime.onMessage
- เพิ่มกิจวัตร Conversion ลงในเอกสารนอกหน้าจอ
- ในโปรแกรมทำงานของบริการส่วนขยาย ให้ตรวจสอบ
chrome.storage
เพื่อดูข้อมูลของคุณ - หากไม่พบข้อมูล ให้สร้างเอกสารนอกหน้าจอแล้วเรียกใช้
runtime.sendMessage()
เพื่อเริ่มกิจวัตร Conversion - ในเครื่องจัดการ
runtime.onMessage
ที่คุณเพิ่มลงในเอกสารนอกหน้าจอ ให้เรียกใช้กิจวัตร Conversion
นอกจากนี้ยังมีความแตกต่างเล็กน้อยเกี่ยวกับวิธีการทํางานของ API พื้นที่เก็บข้อมูลบนเว็บในส่วนขยาย ดูข้อมูลเพิ่มเติมในพื้นที่เก็บข้อมูลและคุกกี้
ลงทะเบียน Listener พร้อมกัน
เราไม่รับประกันการลงทะเบียน Listener แบบไม่พร้อมกัน (เช่น ภายในคำสัญญาหรือโค้ดเรียกกลับ) ว่าจะใช้งานได้ในไฟล์ Manifest V3 ลองพิจารณาโค้ดต่อไปนี้
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.browserAction.onClicked.addListener(handleActionClick);
});
ฟีเจอร์นี้ใช้งานได้กับหน้าเว็บพื้นหลังที่คงอยู่ถาวร เนื่องจากหน้าเว็บทำงานอย่างต่อเนื่องและไม่มีการกำหนดค่าเริ่มต้นอีกครั้ง ในไฟล์ Manifest V3 ระบบจะเริ่มต้น Service Worker อีกครั้งเมื่อมีการส่งเหตุการณ์ ซึ่งหมายความว่าเมื่อเหตุการณ์เริ่มทำงาน ระบบจะไม่ลงทะเบียน Listener (เนื่องจากมีการเพิ่มแบบไม่พร้อมกัน) และเหตุการณ์จะหายไป
แต่ให้ย้ายการลงทะเบียน Listener เหตุการณ์ไปที่ระดับบนสุดของสคริปต์แทน วิธีนี้ช่วยให้ Chrome สามารถค้นหาและเรียกใช้เครื่องจัดการคลิกของการทำงานของคุณได้ทันที แม้ว่าส่วนขยายจะยังไม่ดำเนินการตามตรรกะการเริ่มต้นใช้งานจนเสร็จสมบูรณ์ก็ตาม
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
แทนที่ XMLHttpRequest() ด้วยการเรียกข้อมูลส่วนกลาง()
คุณจะเรียกใช้ XMLHttpRequest()
จาก Service Worker ส่วนขยาย หรืออื่นๆ ไม่ได้ แทนที่การเรียกจากสคริปต์พื้นหลังเป็น XMLHttpRequest()
ด้วยการเรียกไปยัง fetch()
ส่วนกลาง
const xhr = new XMLHttpRequest(); console.log('UNSENT', xhr.readyState); xhr.open('GET', '/api', true); console.log('OPENED', xhr.readyState); xhr.onload = () => { console.log('DONE', xhr.readyState); }; xhr.send(null);
const response = await fetch('https://www.example.com/greeting.json'') console.log(response.statusText);
คงสถานะ
โปรแกรมทำงานของบริการเป็นแบบชั่วคราว ซึ่งหมายความว่าผู้ปฏิบัติงานมีแนวโน้มที่จะเริ่มต้น เรียกใช้ และสิ้นสุดซ้ำๆ ระหว่างเซสชันเบราว์เซอร์ของผู้ใช้ นอกจากนี้ยังหมายความว่าข้อมูลจะไม่พร้อมใช้งานทันทีในตัวแปรร่วมเนื่องจากบริบทก่อนหน้านี้ได้ถูกลบออกไปแล้ว หากต้องการแก้ปัญหานี้ ให้ใช้ API พื้นที่เก็บข้อมูลเป็นแหล่งข้อมูลที่เชื่อถือได้ ตัวอย่างแสดงวิธีดําเนินการ
ตัวอย่างต่อไปนี้ใช้ตัวแปรร่วมในการจัดเก็บชื่อ ในโปรแกรมทำงานของบริการ คุณจะรีเซ็ตตัวแปรนี้ได้หลายครั้งในเซสชันของเบราว์เซอร์ของผู้ใช้
let savedName = undefined; chrome.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { savedName = name; } }); chrome.browserAction.onClicked.addListener((tab) => { chrome.tabs.sendMessage(tab.id, { name: savedName }); });
สำหรับไฟล์ Manifest V3 ให้แทนที่ตัวแปรร่วมด้วยการเรียกใช้ Storage API
chrome.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { chrome.storage.local.set({ name }); } }); chrome.action.onClicked.addListener(async (tab) => { const { name } = await chrome.storage.local.get(["name"]); chrome.tabs.sendMessage(tab.id, { name }); });
แปลงตัวจับเวลาเป็นการปลุก
การดำเนินการที่ล่าช้าหรือเป็นระยะโดยใช้เมธอด setTimeout()
หรือ setInterval()
นั้นเป็นเรื่องปกติ แต่ API เหล่านี้อาจล้มเหลวในโปรแกรมทำงานของบริการ เนื่องจากตัวจับเวลาจะถูกยกเลิกเมื่อมีการหยุดการทำงานของ Service Worker
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
แต่ให้ใช้ Alarms API แทน คุณควรลงทะเบียน Listener การปลุกไว้ในระดับบนสุดของสคริปต์เช่นเดียวกับโปรแกรมฟังอื่นๆ
async function startAlarm(name, duration) { await chrome.alarms.create(name, { delayInMinutes: 3 }); } chrome.alarms.onAlarm.addListener(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); });
คงการทำงานของ Service Worker ไว้
โปรแกรมทำงานของบริการจะขับเคลื่อนด้วยเหตุการณ์ตามคำจำกัดความและจะยุติลงเมื่อไม่มีการใช้งาน วิธีนี้จะช่วยให้ Chrome เพิ่มประสิทธิภาพการทำงานและการใช้หน่วยความจำของส่วนขยายได้ ดูข้อมูลเพิ่มเติมในเอกสารประกอบเกี่ยวกับวงจรการทำงานของโปรแกรมทำงานของบริการ ในบางกรณีอาจต้องใช้มาตรการเพิ่มเติมเพื่อให้แน่ใจว่าโปรแกรมทำงานของบริการจะทำงานได้นานขึ้น
คงการทำงานของ Service Worker ไว้จนกว่าการดำเนินการที่ใช้เวลานานจะเสร็จสิ้น
ในระหว่างดำเนินการ Service Worker เป็นเวลานานซึ่งไม่ได้เรียกใช้ API ของส่วนขยายการโทร โปรแกรมทำงานของบริการอาจปิดลงระหว่างดำเนินการ ตัวอย่างเช่น
- คำขอ
fetch()
อาจใช้เวลานานกว่า 5 นาที (เช่น การดาวน์โหลดขนาดใหญ่เมื่อการเชื่อมต่ออาจไม่ดี) - การคำนวณแบบซิงโครนัสที่ซับซ้อนซึ่งใช้เวลามากกว่า 30 วินาที
หากต้องการยืดอายุการใช้งานของ Service Worker ในกรณีเหล่านี้ คุณสามารถเรียกใช้ API ส่วนขยายที่ไม่สำคัญได้เป็นระยะๆ เพื่อรีเซ็ตตัวนับการหมดเวลา โปรดทราบว่าวิธีนี้สงวนไว้สำหรับกรณีพิเศษเท่านั้น และในกรณีส่วนใหญ่ มักจะมีวิธีการที่เป็นสำนวนของแพลตฟอร์มที่ดีกว่าและจะทำให้ได้รับผลลัพธ์เดียวกัน
ตัวอย่างต่อไปนี้แสดงฟังก์ชันตัวช่วยของ waitUntil()
ที่ทำให้ Service Worker ทำงานต่อไปจนกว่าสัญญาที่กำหนดจะได้รับการแก้ไข
async function waitUntil(promise) = {
const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
ดูแลการทำงานของ Service Worker อย่างต่อเนื่อง
ในบางกรณีที่เกิดขึ้นไม่บ่อยนัก ก็จำเป็นต้องยืดอายุการใช้งานออกไปอย่างไม่มีที่สิ้นสุด เราได้ระบุว่าองค์กรและการศึกษาเป็นกรณีการใช้งานที่ใหญ่ที่สุด และเราได้อนุญาตให้ดำเนินการดังกล่าวโดยเฉพาะ แต่เราไม่ได้ให้การสนับสนุนในเรื่องนี้โดยทั่วไป ในกรณีพิเศษนี้ คุณสามารถคงการทำงานของ Service Worker ได้ด้วยการเรียก API ส่วนขยายที่ไม่สำคัญเป็นระยะๆ โปรดทราบว่าคําแนะนํานี้ใช้กับส่วนขยายที่ทำงานในอุปกรณ์ที่มีการจัดการสำหรับกรณีการใช้งานขององค์กรหรือการศึกษาเท่านั้น ไม่อนุญาตในกรณีอื่นๆ และทีมส่วนขยาย Chrome ขอสงวนสิทธิ์ในการดำเนินการกับส่วนขยายเหล่านี้ในอนาคต
ใช้ข้อมูลโค้ดต่อไปนี้เพื่อให้ Service Worker ไม่หยุดทำงาน
/**
* Tracks when a service worker was last alive and extends the service worker
* lifetime by writing the current time to extension storage every 20 seconds.
* You should still prepare for unexpected termination - for example, if the
* extension process crashes or your extension is manually stopped at
* chrome://serviceworker-internals.
*/
let heartbeatInterval;
async function runHeartbeat() {
await chrome.storage.local.set({ 'last-heartbeat': new Date().getTime() });
}
/**
* Starts the heartbeat interval which keeps the service worker alive. Call
* this sparingly when you are doing work which requires persistence, and call
* stopHeartbeat once that work is complete.
*/
async function startHeartbeat() {
// Run the heartbeat once at service worker startup.
runHeartbeat().then(() => {
// Then again every 20 seconds.
heartbeatInterval = setInterval(runHeartbeat, 20 * 1000);
});
}
async function stopHeartbeat() {
clearInterval(heartbeatInterval);
}
/**
* Returns the last heartbeat stored in extension storage, or undefined if
* the heartbeat has never run before.
*/
async function getLastHeartbeat() {
return (await chrome.storage.local.get('last-heartbeat'))['last-heartbeat'];
}