Thay thế nền hoặc các trang sự kiện bằng một trình chạy dịch vụ
Trình chạy dịch vụ sẽ thay thế trang sự kiện hoặc trang nền của tiện ích để đảm bảo mã nền không nằm trên luồng chính. Điều này cho phép các tiện ích chỉ chạy khi cần, giúp tiết kiệm tài nguyên.
Các trang nền là một thành phần cơ bản của tiện ích kể từ khi được giới thiệu. Nói một cách đơn giản, các trang nền cung cấp một môi trường hoạt động độc lập với mọi cửa sổ hoặc thẻ khác. Điều này cho phép các tiện ích theo dõi và hành động để phản hồi các sự kiện.
Trang này mô tả các tác vụ để chuyển đổi trang nền thành worker dịch vụ của tiện ích. Để biết thêm thông tin về trình chạy dịch vụ của tiện ích nói chung, hãy xem hướng dẫn Xử lý sự kiện bằng trình chạy dịch vụ và phần Giới thiệu về trình chạy dịch vụ của tiện ích.
Sự khác biệt giữa tập lệnh nền và worker dịch vụ của tiện ích
Trong một số ngữ cảnh, bạn sẽ thấy các trình chạy dịch vụ tiện ích được gọi là "tập lệnh nền". Mặc dù trình chạy dịch vụ của tiện ích chạy ở chế độ nền, nhưng việc gọi chúng là tập lệnh nền có phần gây hiểu lầm khi ngụ ý các chức năng giống nhau. Những điểm khác biệt này được mô tả dưới đây.
Thay đổi từ các trang nền
Service worker có một số điểm khác biệt so với các trang nền.
- Các tiện ích này hoạt động ngoài luồng chính, nghĩa là chúng không ảnh hưởng đến nội dung của tiện ích.
- Chúng có các chức năng đặc biệt như chặn các sự kiện tìm nạp trên nguồn gốc của tiện ích, chẳng hạn như các sự kiện từ cửa sổ bật lên của thanh công cụ.
- Chúng có thể giao tiếp và tương tác với các bối cảnh khác thông qua giao diện Clients.
Những thay đổi bạn cần thực hiện
Bạn cần điều chỉnh một số mã để tính đến sự khác biệt giữa cách hoạt động của tập lệnh nền và worker dịch vụ. Trước tiên, cách chỉ định một trình chạy dịch vụ trong tệp kê khai sẽ khác với cách chỉ định các tập lệnh nền. Ngoài ra:
- Vì không thể truy cập vào DOM hoặc giao diện
window, nên bạn sẽ cần di chuyển các lệnh gọi như vậy sang một API khác hoặc vào một tài liệu ngoài màn hình. - Bạn không nên đăng ký trình nghe sự kiện để phản hồi các lời hứa đã trả về hoặc bên trong lệnh gọi lại sự kiện.
- Vì không tương thích ngược với
XMLHttpRequest(), bạn sẽ cần thay thế các lệnh gọi đến giao diện này bằng các lệnh gọi đếnfetch(). - Vì các hoạt động này sẽ kết thúc khi không được sử dụng, nên bạn cần duy trì trạng thái ứng dụng thay vì dựa vào các biến chung. Việc chấm dứt service worker cũng có thể kết thúc các bộ hẹn giờ trước khi chúng hoàn tất. Bạn cần thay thế các chế độ này bằng chuông báo.
Trang này mô tả chi tiết những việc cần làm này.
Cập nhật trường "background" trong tệp kê khai
Trong Manifest V3, các trang nền được thay thế bằng một trình chạy dịch vụ. Các thay đổi về tệp kê khai được liệt kê bên dưới.
- Thay thế
"background.scripts"bằng"background.service_worker"trongmanifest.json. Xin lưu ý rằng trường"service_worker"nhận một chuỗi chứ không phải một mảng chuỗi. - Xoá
"background.persistent"khỏimanifest.json.
{ ... "background": { "scripts": [ "backgroundContextMenus.js", "backgroundOauth.js" ], "persistent": false }, ... }
{ ... "background": { "service_worker": "service_worker.js", "type": "module" } ... }
Trường "service_worker" nhận một chuỗi duy nhất. Bạn chỉ cần trường "type" nếu sử dụng các mô-đun ES (dùng từ khoá import). Giá trị của tham số này sẽ luôn là "module". Để biết thêm thông tin, hãy xem bài viết Kiến thức cơ bản về trình chạy dịch vụ của tiện ích
Di chuyển các lệnh gọi DOM và cửa sổ sang một tài liệu ngoài màn hình
Một số tiện ích cần có quyền truy cập vào các đối tượng DOM và cửa sổ mà không cần mở một cửa sổ hoặc thẻ mới. Offscreen API hỗ trợ những trường hợp sử dụng này bằng cách mở và đóng các tài liệu không hiển thị được đóng gói cùng với tiện ích mà không làm gián đoạn trải nghiệm người dùng. Ngoại trừ việc truyền thông báo, các tài liệu ngoài màn hình không dùng chung API với các ngữ cảnh tiện ích khác, nhưng hoạt động như các trang web đầy đủ để tiện ích tương tác.
Để sử dụng Offscreen API, hãy tạo một tài liệu ngoài màn hình từ trình chạy dịch vụ.
browser.offscreen.createDocument({
url: browser.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
Trong tài liệu ngoài màn hình, hãy thực hiện mọi thao tác mà trước đây bạn đã chạy trong một tập lệnh nền. Ví dụ: bạn có thể sao chép văn bản đã chọn trên trang chủ.
let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');
Giao tiếp giữa các tài liệu ngoài màn hình và các worker dịch vụ của tiện ích bằng cách sử dụng truyền thông báo.
Chuyển đổi localStorage sang một loại khác
Không thể sử dụng giao diện Storage của nền tảng web (có thể truy cập từ window.localStorage) trong một trình chạy dịch vụ. Để giải quyết vấn đề này, hãy làm một trong hai việc sau. Trước tiên, bạn có thể thay thế bằng các lệnh gọi đến một cơ chế lưu trữ khác. Không gian tên browser.storage.local sẽ phục vụ hầu hết các trường hợp sử dụng, nhưng bạn có thể dùng các lựa chọn khác.
Bạn cũng có thể di chuyển các lệnh gọi của nó đến một tài liệu ngoài màn hình. Ví dụ: để di chuyển dữ liệu đã lưu trữ trước đó trong localStorage sang một cơ chế khác:
- Tạo một tài liệu ngoài màn hình bằng một quy trình chuyển đổi và trình xử lý
runtime.onMessage. - Thêm một quy trình chuyển đổi vào tài liệu ngoài màn hình.
- Trong trình chạy dịch vụ của tiện ích, hãy kiểm tra
browser.storageđể biết dữ liệu của bạn. - Nếu không tìm thấy dữ liệu, hãy tạo một tài liệu ngoài màn hình và gọi
runtime.sendMessage()để bắt đầu quy trình chuyển đổi. - Trong trình xử lý
runtime.onMessagemà bạn đã thêm vào tài liệu ngoài màn hình, hãy gọi quy trình chuyển đổi.
Ngoài ra, có một số điểm khác biệt về cách các API lưu trữ trên web hoạt động trong các tiện ích. Tìm hiểu thêm trong phần Bộ nhớ và cookie.
Đăng ký trình nghe đồng bộ
Việc đăng ký một trình nghe không đồng bộ (ví dụ: bên trong một lời hứa hoặc lệnh gọi lại) không đảm bảo hoạt động trong Manifest V3. Hãy xem xét đoạn mã sau.
browser.storage.local.get(["badgeText"], ({ badgeText }) => {
browser.browserAction.setBadgeText({ text: badgeText });
browser.browserAction.onClicked.addListener(handleActionClick);
});
Cách này hoạt động với trang nền liên tục vì trang này liên tục chạy và không bao giờ được khởi động lại. Trong Manifest V3, trình chạy dịch vụ sẽ được khởi động lại khi sự kiện được gửi đi. Điều này có nghĩa là khi sự kiện kích hoạt, các trình nghe sẽ không được đăng ký (vì chúng được thêm không đồng bộ) và sự kiện sẽ bị bỏ lỡ.
Thay vào đó, hãy di chuyển quá trình đăng ký trình nghe sự kiện lên cấp cao nhất của tập lệnh. Điều này đảm bảo rằng Chrome có thể tìm và gọi ngay trình xử lý lượt nhấp của thao tác, ngay cả khi tiện ích của bạn chưa hoàn tất việc thực thi logic khởi động.
browser.action.onClicked.addListener(handleActionClick);
browser.storage.local.get(["badgeText"], ({ badgeText }) => {
browser.action.setBadgeText({ text: badgeText });
});
Thay thế XMLHttpRequest() bằng global fetch()
Bạn không thể gọi XMLHttpRequest() từ một trình chạy dịch vụ, tiện ích hoặc bất kỳ thành phần nào khác. Thay thế các lệnh gọi từ tập lệnh nền của bạn đến XMLHttpRequest() bằng các lệnh gọi đến 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);
Trạng thái duy trì
Các worker dịch vụ có tính chất tạm thời, tức là chúng có thể bắt đầu, chạy và kết thúc nhiều lần trong phiên hoạt động trên trình duyệt của người dùng. Điều này cũng có nghĩa là dữ liệu không có sẵn ngay lập tức trong các biến toàn cục vì bối cảnh trước đó đã bị xoá. Để khắc phục vấn đề này, hãy sử dụng các API lưu trữ làm nguồn đáng tin cậy. Ví dụ sẽ cho thấy cách thực hiện việc này.
Ví dụ sau đây sử dụng một biến chung để lưu trữ tên. Trong một trình chạy dịch vụ, biến này có thể được đặt lại nhiều lần trong suốt phiên hoạt động của trình duyệt người dùng.
let savedName = undefined; browser.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { savedName = name; } }); browser.browserAction.onClicked.addListener((tab) => { browser.tabs.sendMessage(tab.id, { name: savedName }); });
Đối với Manifest V3, hãy thay thế biến chung bằng một lệnh gọi đến Storage API.
browser.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { browser.storage.local.set({ name }); } }); browser.action.onClicked.addListener(async (tab) => { const { name } = await browser.storage.local.get(["name"]); browser.tabs.sendMessage(tab.id, { name }); });
Chuyển đổi bộ hẹn giờ thành chuông báo
Bạn thường dùng các thao tác định kỳ hoặc có độ trễ bằng cách sử dụng phương thức setTimeout() hoặc setInterval(). Tuy nhiên, các API này có thể không hoạt động trong trình chạy dịch vụ vì bộ hẹn giờ sẽ bị huỷ bất cứ khi nào trình chạy dịch vụ bị chấm dứt.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { browser.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Thay vào đó, hãy sử dụng Alarms API. Giống như các trình nghe khác, trình nghe báo thức phải được đăng ký ở cấp cao nhất của tập lệnh.
async function startAlarm(name, duration) { await browser.alarms.create(name, { delayInMinutes: 3 }); } browser.alarms.onAlarm.addListener(() => { browser.action.setIcon({ path: getRandomIconPath(), }); });
Duy trì hoạt động của trình chạy dịch vụ
Theo định nghĩa, các worker dịch vụ là hướng sự kiện và sẽ chấm dứt khi không hoạt động. Bằng cách này, Chrome có thể tối ưu hoá hiệu suất và mức tiêu thụ bộ nhớ của tiện ích. Tìm hiểu thêm trong tài liệu về vòng đời của trình chạy dịch vụ. Các trường hợp đặc biệt có thể yêu cầu các biện pháp bổ sung để đảm bảo trình chạy dịch vụ hoạt động trong thời gian dài hơn.
Duy trì hoạt động của một trình chạy dịch vụ cho đến khi một thao tác diễn ra trong thời gian dài hoàn tất
Trong các thao tác của trình chạy dịch vụ diễn ra trong thời gian dài mà không gọi API tiện ích, trình chạy dịch vụ có thể tắt giữa chừng. Ví dụ:
- Yêu cầu
fetch()có thể mất hơn 5 phút (ví dụ: một tệp tải xuống lớn trên một kết nối có thể kém). - Một phép tính không đồng bộ phức tạp mất hơn 30 giây.
Để kéo dài vòng đời của trình chạy dịch vụ trong những trường hợp này, bạn có thể định kỳ gọi một API tiện ích không quan trọng để đặt lại bộ đếm thời gian chờ. Xin lưu ý rằng điều này chỉ dành cho các trường hợp đặc biệt và trong hầu hết các tình huống, thường có một cách tốt hơn, theo cách thức riêng của nền tảng để đạt được kết quả tương tự.
Ví dụ sau đây cho thấy một hàm trợ giúp waitUntil() giúp duy trì hoạt động của trình chạy dịch vụ cho đến khi một promise nhất định được thực hiện:
async function waitUntil(promise) {
const keepAlive = setInterval(browser.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Luôn duy trì trạng thái hoạt động của service worker
Trong một số ít trường hợp, bạn cần phải kéo dài thời gian tồn tại vô thời hạn. Chúng tôi xác định doanh nghiệp và giáo dục là những trường hợp sử dụng lớn nhất và chúng tôi cho phép sử dụng tính năng này trong những trường hợp đó, nhưng chúng tôi không hỗ trợ tính năng này nói chung. Trong những trường hợp đặc biệt này, bạn có thể giữ cho trình chạy dịch vụ hoạt động bằng cách định kỳ gọi một API tiện ích không quan trọng. Xin lưu ý rằng đề xuất này chỉ áp dụng cho các tiện ích chạy trên thiết bị do doanh nghiệp hoặc tổ chức giáo dục quản lý. Trong các trường hợp khác, bạn không được phép sử dụng và nhóm tiện ích Chrome có quyền xử lý những tiện ích đó trong tương lai.
Hãy dùng đoạn mã sau để duy trì hoạt động của trình chạy dịch vụ:
/**
* 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 browser.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 browser.storage.local.get('last-heartbeat'))['last-heartbeat'];
}