Thay thế trang nền hoặc trang sự kiện bằng trình chạy dịch vụ
Trình chạy dịch vụ thay thế trang nền hoặc trang sự kiện của tiện ích để đảm bảo rằng mã nền nằm ngoài luồng chính. Điều này cho phép tiện ích chỉ chạy khi cần, giúp tiết kiệm tài nguyên.
Trang nền là một thành phần cơ bản của tiện ích kể từ khi ra mắt. 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 quan sát và hành động để phản hồi các sự kiện.
Trang này mô tả các nhiệm vụ để chuyển đổi trang nền thành trình chạy dịch vụ tiện ích. Để biết thêm thông tin chung về trình chạy dịch vụ của tiện ích, 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à trình chạy dịch vụ tiện ích
Trong một số bối cảnh, bạn sẽ thấy trình chạy 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ó thể gây hiểu lầm vì ngụ ý rằng chúng có các chức năng giống hệt nhau. Sự khác biệt được mô tả dưới đây.
Thay đổi từ trang nền
Trình chạy dịch vụ có một số điểm khác biệt với trang nền.
- Các trình chạy dịch vụ hoạt động ngoài luồng chính, nghĩa là chúng không can thiệp vào nội dung của tiện ích.
- Các trình chạy này 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 trên thanh công cụ.
- Các ngữ cảnh này có thể giao tiếp và tương tác với các ngữ cảnh khác thông qua giao diện ứng dụng.
Những thay đổi bạn cần thực hiện
Bạn cần điều chỉnh một vài đoạn 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à trình chạy 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ì các lệnh gọi này không thể truy cập vào DOM hoặc giao diện
window, nên bạn cần chuyển các lệnh gọi đó 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 lời hứa được trả về hoặc bên trong lệnh gọi lại sự kiện.
- Vì các phương thức này 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 trình chạy dịch vụ sẽ chấm dứt khi không 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 toàn cục. Việc chấm dứt trình chạy dịch vụ cũng có thể kết thúc bộ tính giờ trước khi hoàn tất. Bạn cần thay thế các lời nhắc đó bằng chuông báo.
Trang này mô tả chi tiết các nhiệm vụ 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ê dưới đây.
- 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 sẽ chỉ cần trường "type" nếu sử dụng mô-đun ES (bằng cách 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 Thông tin 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 quyền truy cập vào DOM và các đối tượng cửa sổ mà không cần mở cửa sổ hoặc thẻ mới một cách trực quan. Offscreen API hỗ trợ các 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, tài liệu ngoài màn hình không chia sẻ API với các bối 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 hành động mà trước đây bạn đã chạy trong tập lệnh nền. Ví dụ: bạn có thể sao chép văn bản đã chọn trên trang lưu trữ.
let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');
Giao tiếp giữa tài liệu ngoài màn hình và trình chạy dịch vụ của tiện ích bằng cách sử dụng tính nă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 trình chạy dịch vụ. Để giải quyết vấn đề này, hãy làm theo một trong hai cách sau. Trước tiên, bạn có thể thay thế bằng 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ũng 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 tiện ích sang 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, hãy làm như sau:
- Tạo một tài liệu ngoài màn hình bằng quy trình chuyển đổi và trình xử lý
runtime.onMessage. - Thêm 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để tìm 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òn có một số điểm khác biệt về cách hoạt động của API lưu trữ web trong 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ý 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 cố định vì trang này liên tục chạy và không bao giờ được khởi chạy lại. Trong Manifest V3, trình chạy dịch vụ sẽ được khởi chạy 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, 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 việc đă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ể ngay lập tức tìm và gọi trình xử lý lượt nhấp của hành động, 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 fetch() chung
Không thể gọi XMLHttpRequest() từ trình chạy dịch vụ, tiện ích hoặc các thành phần khác. Thay thế các lệnh gọi từ tập lệnh nền đến XMLHttpRequest() bằng các lệnh gọi đến fetch() toàn cục.
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 cố định
Trình chạy dịch vụ chỉ tồn tại trong thời gian ngắn, tức là chúng có thể sẽ bắt đầu, chạy và chấm dứt 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ị huỷ. Để giải quyết vấn đề này, hãy sử dụng API lưu trữ làm nguồn đáng tin cậy. Ví dụ sau đây sẽ minh hoạ cách thực hiện.
Ví dụ sau đây sử dụng một biến toàn cục để 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 trên trình duyệt của 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 toàn cục bằng 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 đồng hồ hẹn giờ thành chuông báo
Thông thường, bạn nên sử dụng các thao tác bị trì hoãn hoặc định kỳ bằ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ộ tính 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, bạn nên đăng ký trình nghe chuông báo ở 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, trình chạy dịch vụ được điều khiển theo 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 sử dụng 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 ngoại lệ có thể yêu cầu các biện pháp bổ sung để đảm bảo rằng trình chạy dịch vụ duy trì hoạt động trong thời gian dài hơn.
Duy trì hoạt động của trình chạy dịch vụ cho đến khi hoàn tất một thao tác chạy trong thời gian dài
Trong các hoạt động chạy trong thời gian dài của trình chạy dịch vụ 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ụ: tải tệp lớn xuống khi 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 thời gian hoạt động 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 đơn giản để đặ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 trường hợp, thường có cách tốt hơn, mang tính thành ngữ của nền tảng để đạt được cùng một kết quả.
Ví dụ sau đây cho thấy một hàm trợ giúp waitUntil() giúp trình chạy dịch vụ của bạn hoạt động cho đến khi một lời hứa 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());
Duy trì hoạt động liên tục của 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 hoạt động vô thời hạn. Chúng tôi nhận thấy 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 trong những trường hợp đó, nhưng chúng tôi không hỗ trợ điều này nói chung. Trong những trường hợp ngoại lệ này, bạn có thể duy trì hoạt động của trình chạy dịch vụ bằng cách gọi định kỳ một API tiện ích đơn giản. 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ị đượ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, việc này không được cho phép và nhóm tiện ích của Chrome giữ 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 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'];
}