Thay thế nền hoặ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. Nhờ đó, các tiện ích có thể quan sát 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 các 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 worker dịch vụ của 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 hệt nhau. Những điểm khác biệt này được mô tả dưới đây.
Các 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, tức là chúng không can thiệp vào 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 chút mã để tính đến những điểm 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 trình chạy dịch vụ trong tệp kê khai khác với cách chỉ định 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, 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ì chúng không tương thích ngược với
XMLHttpRequest(), nên bạn 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 các worker dịch vụ cũng có thể kết thú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 mô-đun ES (sử dụng từ khoá import). Giá trị của trường 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ụ.
chrome.offscreen.createDocument({
url: chrome.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 điệp.
Chuyển đổi localStorage sang một loại khác
Không thể 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 chrome.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
chrome.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 được đảm bảo hoạt động trong Manifest V3. Hãy xem xét đoạn mã sau.
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.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.
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.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ì
Worker dịch vụ là tạm thời, tức là chúng có khả năng 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ụ này sẽ cho thấy cách thực hiện.
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 của người dùng.
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 }); });
Đối với Manifest V3, hãy thay thế biến chung bằng một lệnh gọi đến 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 }); });
Chuyển 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(() => { chrome.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 chuông báo phải được đăng ký ở cấp cao nhất của tập lệnh.
async function startAlarm(name, duration) { await chrome.alarms.create(name, { delayInMinutes: 3 }); } chrome.alarms.onAlarm.addListener(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); });
Duy trì hoạt động của trình chạy dịch vụ
Theo định nghĩa, trình chạy dịch vụ là trình chạy dựa trên sự kiện và sẽ kết thúc 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ụ chạy 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ụ:
- Một 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 ngoại lệ 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 lời hứa nhất định được thực hiện:
async function waitUntil(promise) {
const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Luôn duy trì hoạt động của một trình chạy dịch vụ
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 điều này một cách cụ thể ở đó, nhưng chúng tôi không hỗ trợ điều này nói chung. Trong những trường hợp đặc biệt này, bạn có thể giữ cho một 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. Điều quan trọng cần lưu ý là đề xuất này chỉ áp dụng cho các tiện ích chạy trên thiết bị được quản lý cho các trường hợp sử dụng trong doanh nghiệp hoặc giáo dục. 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 của Chrome có quyền xử lý những tiện ích đó trong tương lai.
Sử 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 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'];
}