การแทนที่หน้าพื้นหลังหรือหน้ากิจกรรมด้วย Service Worker
Service Worker จะแทนที่พื้นหลังหรือหน้ากิจกรรมของส่วนขยายเพื่อให้แน่ใจว่าโค้ดพื้นหลังอยู่นอกเทรดหลัก ซึ่งจะช่วยให้ส่วนขยายทำงานเฉพาะเมื่อจําเป็นเท่านั้น จึงช่วยประหยัดทรัพยากร
หน้าเว็บพื้นหลังเป็นองค์ประกอบพื้นฐานของส่วนขยายมาตั้งแต่เปิดตัว พูดง่ายๆ ก็คือ หน้าเว็บพื้นหลังจะให้สภาพแวดล้อมที่ทำงานแยกเป็นอิสระจากหน้าต่างหรือแท็บอื่นๆ ซึ่งช่วยให้ส่วนขยายสังเกตและดำเนินการเพื่อตอบสนองต่อเหตุการณ์ได้
หน้านี้จะอธิบายงานสำหรับการแปลงหน้าเบื้องหลังเป็นผู้ให้บริการของส่วนขยาย ดูข้อมูลเพิ่มเติมเกี่ยวกับ Service Worker ของส่วนขยายโดยทั่วไปได้ที่บทแนะนำจัดการเหตุการณ์ด้วย Service Worker และส่วนเกี่ยวกับ Service Worker ของส่วนขยาย
ความแตกต่างระหว่างสคริปต์พื้นหลังและโปรแกรมทำงานของบริการส่วนขยาย
ในบางบริบท คุณจะเห็นหน่วยงานบริการของส่วนขยายที่เรียกว่า "สคริปต์เบื้องหลัง" แม้ว่า Service Worker ของส่วนขยายจะทำงานอยู่เบื้องหลัง แต่การเรียก Service Worker ว่าสคริปต์เบื้องหลังก็อาจทำให้เข้าใจผิดได้เนื่องจากเป็นการบอกเป็นนัยว่า Service Worker มีความสามารถเหมือนกับสคริปต์เบื้องหลัง ความแตกต่างมีดังนี้
การเปลี่ยนแปลงจากหน้าพื้นหลัง
เซอร์วิสเวิร์กมีความแตกต่างจากหน้าเบื้องหลังหลายประการ
- โดยทำงานนอกเธรดหลัก ซึ่งหมายความว่าจะไม่รบกวนเนื้อหาส่วนขยาย
- โดยจะมีความสามารถพิเศษ เช่น การขัดจังหวะเหตุการณ์การดึงข้อมูลในต้นทางของส่วนขยาย เช่น เหตุการณ์จากป๊อปอัปแถบเครื่องมือ
- โดยสามารถสื่อสารและโต้ตอบกับบริบทอื่นๆ ผ่านอินเทอร์เฟซไคลเอ็นต์
การเปลี่ยนแปลงที่คุณต้องทำ
คุณจะต้องทำการปรับเปลี่ยนโค้ดเล็กน้อยเพื่อพิจารณาความแตกต่างระหว่างวิธีที่สคริปต์เบื้องหลังและ Service Worker ทำงาน ในการเริ่มต้น วิธีระบุ Service Worker ในไฟล์ Manifest จะแตกต่างจากการระบุสคริปต์พื้นหลัง คำอธิบายเพิ่มเติม
- เนื่องจากเข้าถึง DOM หรืออินเทอร์เฟซ
window
ไม่ได้ คุณจึงต้องย้ายการเรียกดังกล่าวไปยัง API อื่นหรือไปยังเอกสารนอกหน้าจอ - ไม่ควรลงทะเบียน Listener เหตุการณ์เพื่อตอบสนองต่อ Promise ที่แสดงผลหรือใน Callback เหตุการณ์
- เนื่องจากไม่เข้ากันได้แบบย้อนหลังกับ
XMLHttpRequest()
คุณจะต้องแทนที่การเรียกใช้อินเทอร์เฟซนี้ด้วยการเรียกใช้fetch()
- เนื่องจากตัวแปรเหล่านี้จะสิ้นสุดลงเมื่อไม่ได้ใช้งาน คุณจึงต้องเก็บสถานะแอปพลิเคชันไว้แทนที่จะใช้ตัวแปรส่วนกลาง การสิ้นสุด Service Worker ยังสิ้นสุดตัวจับเวลาได้ก่อนที่ตัวจับเวลาจะทำงานเสร็จ คุณจะต้องแทนที่ด้วยสัญญาณเตือน
หน้านี้จะอธิบายถึงงานเหล่านี้โดยละเอียด
อัปเดตช่อง "background" ในไฟล์ 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"
เสมอ โปรดดูข้อมูลเพิ่มเติมที่ข้อมูลเบื้องต้นเกี่ยวกับ Service Worker ของส่วนขยาย
ย้ายการเรียก DOM และการเรียกใช้หน้าต่างไปยังเอกสารนอกหน้าจอ
ส่วนขยายบางรายการจำเป็นต้องเข้าถึง DOM และออบเจ็กต์หน้าต่างโดยไม่ต้องเปิดหน้าต่างหรือแท็บใหม่ Offscreen API รองรับกรณีการใช้งานเหล่านี้โดยการเปิดและปิดเอกสารที่ไม่แสดงแพ็กเกจซึ่งมีส่วนขยายโดยไม่รบกวนประสบการณ์ของผู้ใช้ เอกสารนอกหน้าจอจะไม่แชร์ API กับบริบทส่วนขยายอื่นๆ ยกเว้นการส่งข้อความ แต่ทํางานเป็นหน้าเว็บแบบเต็มเพื่อให้ส่วนขยายโต้ตอบด้วย
หากต้องการใช้ Offscreen API ให้สร้างเอกสาร Offscreen จาก 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
) ใน Service Worker ไม่ได้ ในการแก้ไขปัญหานี้ ให้ทําอย่างใดอย่างหนึ่งต่อไปนี้ ก่อนอื่น คุณสามารถแทนที่การเรียกใช้ด้วยเมชานิซึมการจัดเก็บข้อมูลอื่น เนมสเปซ chrome.storage.local
จะใช้กับกรณีการใช้งานส่วนใหญ่ได้ แต่ก็มีตัวเลือกอื่นๆ ให้เลือกด้วย
นอกจากนี้ คุณยังย้ายการเรียกไปยังเอกสารนอกหน้าจอได้ด้วย ตัวอย่างเช่น หากต้องการย้ายข้อมูลที่จัดเก็บไว้ก่อนหน้าใน localStorage
ไปยังกลไกอื่น ให้ทำดังนี้
- สร้างเอกสารนอกหน้าจอที่มีกิจวัตร Conversion และตัวแฮนเดิล
runtime.onMessage
- เพิ่มกิจวัตร Conversion ลงในเอกสารนอกหน้าจอ
- ใน Service Worker ของส่วนขยาย ให้ตรวจสอบ
chrome.storage
เพื่อดูข้อมูลของคุณ - หากไม่พบข้อมูล ให้createเอกสารนอกหน้าจอแล้วเรียกใช้
runtime.sendMessage()
เพื่อเริ่มกิจวัตรการเปลี่ยน - ในตัวแฮนเดิล
runtime.onMessage
ที่คุณเพิ่มลงในเอกสารที่อยู่นอกหน้าจอ ให้เรียกใช้กิจวัตร Conversion
นอกจากนี้ ยังมีความแตกต่างเล็กน้อยเกี่ยวกับวิธีการทำงานของ Web Storage API ในส่วนขยาย ดูข้อมูลเพิ่มเติมได้ที่หัวข้อพื้นที่เก็บข้อมูลและคุกกี้
ลงทะเบียน Listener แบบซิงค์
การลงทะเบียน Listener แบบไม่พร้อมกัน (เช่น ในสัญญาหรือ Callback) ไม่มีการรับประกันว่าจะทำงานในไฟล์ Manifest V3 ลองดูโค้ดต่อไปนี้
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.browserAction.onClicked.addListener(handleActionClick);
});
ซึ่งใช้ได้กับหน้าเว็บที่ทำงานอยู่เบื้องหลังอย่างต่อเนื่องเนื่องจากหน้าเว็บทำงานอยู่ตลอดเวลาและไม่เคยรีนิไทซ์ ใน Manifest V3 ระบบจะเริ่มต้น Service Worker อีกครั้งเมื่อมีการเรียกเหตุการณ์ ซึ่งหมายความว่าเมื่อเหตุการณ์เริ่มทำงาน ผู้ฟังจะไม่ได้รับการลงทะเบียน (เนื่องจากมีการเพิ่มแบบไม่พร้อมกัน) และกิจกรรมจะพลาดไป
แต่ให้ย้ายการลงทะเบียนโปรแกรมรับฟังเหตุการณ์ไปยังระดับบนสุดของสคริปต์แทน วิธีนี้ช่วยให้ Chrome ค้นหาและเรียกใช้ตัวแฮนเดิลการคลิกของการดำเนินการได้ทันที แม้ว่าส่วนขยายจะยังไม่เรียกใช้ตรรกะการเริ่มต้นทำงานให้เสร็จสิ้นก็ตาม
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
แทนที่ XMLHttpRequest() ด้วย fetch() ระดับส่วนกลาง
XMLHttpRequest()
เรียกจาก Service Worker, ส่วนขยาย หรืออื่นๆ ไม่ได้ แทนที่การเรียกใช้จากสคริปต์เบื้องหลังไปยัง XMLHttpRequest()
ด้วยการเรียกใช้ global 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);
เก็บสถานะ
โปรแกรมทำงานของบริการเป็นการทำงานชั่วคราว ซึ่งหมายความว่าผู้ใช้มีแนวโน้มที่จะเริ่ม เรียกใช้ และยุติการทำงานซ้ำๆ ระหว่างเซสชันเบราว์เซอร์ของผู้ใช้ นอกจากนี้ ยังหมายความว่าข้อมูลจะไม่พร้อมใช้งานทันทีในตัวแปรร่วม เนื่องจากระบบได้แยกบริบทก่อนหน้านี้ออกแล้ว วิธีแก้ปัญหานี้คือใช้ Storage API เป็นแหล่งข้อมูลที่ถูกต้อง ตัวอย่างจะแสดงวิธีดำเนินการนี้
ตัวอย่างต่อไปนี้ใช้ตัวแปรส่วนกลางเพื่อจัดเก็บชื่อ ใน Service Worker ตัวแปรนี้อาจรีเซ็ตหลายครั้งตลอดเซสชันเบราว์เซอร์ของผู้ใช้
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 เนื่องจากตัวจับเวลาจะถูกยกเลิกทุกครั้งที่ Service Worker สิ้นสุดการทำงาน
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
แต่ให้ใช้ Alarms API แทน เช่นเดียวกับตัวรับฟังอื่นๆ คุณควรลงทะเบียนตัวรับฟังการแจ้งเตือนที่ระดับบนสุดของสคริปต์
async function startAlarm(name, duration) { await chrome.alarms.create(name, { delayInMinutes: 3 }); } chrome.alarms.onAlarm.addListener(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); });
คง Service Worker ไว้
Service Worker ทำงานโดยอิงตามเหตุการณ์ตามคำจำกัดความ และจะสิ้นสุดลงเมื่อไม่มีการใช้งาน วิธีนี้จะช่วยให้ Chrome เพิ่มประสิทธิภาพและลดการใช้หน่วยความจำของเทมเพลตได้ ดูข้อมูลเพิ่มเติมได้ในเอกสารประกอบเกี่ยวกับวงจรชีวิตของ Service Worker กรณีที่ยกเว้นอาจต้องใช้มาตรการเพิ่มเติมเพื่อให้มั่นใจว่า Service Worker จะยังคงทำงานต่อไปได้นานขึ้น
เก็บ Service Worker ไว้จนกว่าการดำเนินการที่ใช้เวลานานจะเสร็จสิ้น
ในระหว่างการดำเนินการของ Service Worker ที่ทำงานต่อเนื่องเป็นเวลานานซึ่งไม่ได้เรียก API ของส่วนขยาย Service Worker อาจปิดลงในระหว่างการดำเนินการ ตัวอย่างเช่น
- คำขอ
fetch()
อาจใช้เวลานานกว่า 5 นาที (เช่น การดาวน์โหลดขนาดใหญ่ในการเชื่อมต่อที่อาจไม่ดี) - การคํานวณแบบไม่พร้อมกันที่ซับซ้อนซึ่งใช้เวลานานกว่า 30 วินาที
หากต้องการยืดอายุการใช้งานของ Service Worker ในกรณีเหล่านี้ คุณสามารถเรียกใช้ API ส่วนขยายที่ไม่สำคัญเป็นระยะๆ เพื่อรีเซ็ตตัวนับระยะหมดเวลา โปรดทราบว่าวิธีนี้สงวนไว้สําหรับกรณีที่พิเศษเท่านั้น และในกรณีส่วนใหญ่มักจะมีวิธีอื่นที่ดีกว่าซึ่งเหมาะกับแพลตฟอร์มเพื่อให้ได้ผลลัพธ์เดียวกัน
ตัวอย่างต่อไปนี้แสดงฟังก์ชันตัวช่วย waitUntil()
ที่ช่วยให้บริการของ Firebase ทำงานต่อไปจนกว่าพรอมต์หนึ่งๆ จะดำเนินการเสร็จสมบูรณ์
async function waitUntil(promise) = {
const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
คงสถานะ Service Worker ไว้อย่างต่อเนื่อง
ในบางกรณีที่พบได้น้อย จำเป็นต้องขยายอายุการใช้งานแบบไม่มีกำหนด เราพบว่าองค์กรและการศึกษาเป็น Use Case ที่ใหญ่ที่สุด และเราอนุญาตกรณีการใช้งานนี้โดยเฉพาะ แต่เราไม่รองรับกรณีการใช้งานนี้โดยทั่วไป ในกรณีพิเศษเหล่านี้ คุณสามารถทำให้ 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'];
}