Xử lý sự kiện bằng trình chạy dịch vụ

Hướng dẫn bao gồm các khái niệm về trình chạy dịch vụ tiện ích

Tổng quan

Hướng dẫn này giới thiệu về worker dịch vụ của tiện ích Chrome. Trong hướng dẫn này, bạn sẽ xây dựng một tiện ích cho phép người dùng nhanh chóng chuyển đến các trang tham khảo Chrome API bằng cách sử dụng hộp tìm kiếm đa năng. Bạn sẽ tìm hiểu cách:

  • Đăng ký trình chạy dịch vụ và nhập các mô-đun.
  • Gỡ lỗi trình chạy dịch vụ tiện ích.
  • Quản lý trạng thái và xử lý sự kiện.
  • Kích hoạt các sự kiện định kỳ.
  • Giao tiếp với tập lệnh nội dung.

Trước khi bắt đầu

Hướng dẫn này giả định rằng bạn có kinh nghiệm phát triển web cơ bản. Bạn nên xem lại Tiện ích 101Hello World để giới thiệu về việc phát triển tiện ích.

Tạo tiện ích

Bắt đầu bằng cách tạo một thư mục mới có tên quick-api-reference để lưu trữ các tệp tiện ích hoặc tải mã nguồn xuống từ kho lưu trữ mẫu GitHub của chúng tôi.

Bước 1: Đăng ký worker dịch vụ

Tạo tệp manifest trong thư mục gốc của dự án và thêm mã sau:

manifest.json:

{
  "manifest_version": 3,
  "name": "Open extension API reference",
  "version": "1.0.0",
  "icons": {
    "16": "images/icon-16.png",
    "128": "images/icon-128.png"
  },
  "background": {
    "service_worker": "service-worker.js"
  }
}

Tiện ích đăng ký worker dịch vụ trong tệp kê khai, chỉ cần một tệp JavaScript. Bạn không cần gọi navigator.serviceWorker.register() như trong trang web.

Tạo thư mục images rồi tải biểu tượng xuống vào thư mục đó.

Hãy xem các bước đầu tiên của hướng dẫn về Thời gian đọc để tìm hiểu thêm về siêu dữ liệubiểu tượng của tiện ích trong tệp kê khai.

Bước 2: Nhập nhiều mô-đun trình chạy dịch vụ

Worker của dịch vụ triển khai hai tính năng. Để dễ bảo trì hơn, chúng ta sẽ triển khai từng tính năng trong một mô-đun riêng. Trước tiên, chúng ta cần khai báo worker dịch vụ dưới dạng Mô-đun ES trong tệp kê khai. Điều này cho phép chúng ta nhập các mô-đun trong worker dịch vụ:

manifest.json:

{
 "background": {
    "service_worker": "service-worker.js",
    "type": "module"
  },
}

Tạo tệp service-worker.js và nhập 2 mô-đun:

import './sw-omnibox.js';
import './sw-tips.js';

Tạo các tệp này và thêm nhật ký bảng điều khiển vào từng tệp.

sw-omnibox.js:

console.log("sw-omnibox.js");

sw-tips.js:

console.log("sw-tips.js");

Hãy xem phần Nhập tập lệnh để tìm hiểu các cách khác để nhập nhiều tệp trong một worker dịch vụ.

Không bắt buộc: Gỡ lỗi trình chạy dịch vụ

Tôi sẽ giải thích cách tìm nhật ký của worker dịch vụ và biết thời điểm worker dịch vụ chấm dứt. Trước tiên, hãy làm theo hướng dẫn để Tải tiện ích đã giải nén.

Sau 30 giây, bạn sẽ thấy thông báo "service worker (không hoạt động)" có nghĩa là worker dịch vụ đã bị chấm dứt. Nhấp vào đường liên kết "service worker (không hoạt động)" để kiểm tra. Ảnh động sau đây cho thấy điều này.

Bạn có nhận thấy việc kiểm tra trình chạy dịch vụ đã đánh thức trình chạy dịch vụ không? Việc mở trình chạy dịch vụ trong công cụ phát triển sẽ giúp trình chạy dịch vụ luôn hoạt động. Để đảm bảo tiện ích của bạn hoạt động chính xác khi worker dịch vụ bị chấm dứt, hãy nhớ đóng DevTools.

Bây giờ, hãy chia nhỏ tiện ích để tìm hiểu vị trí phát hiện lỗi. Một cách để thực hiện việc này là xoá ".js" khỏi lệnh nhập './sw-omnibox.js' trong tệp service-worker.js. Chrome sẽ không thể đăng ký trình chạy dịch vụ.

Quay lại chrome://extensions và làm mới tiện ích. Bạn sẽ thấy hai lỗi:

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

Hãy xem phần Gỡ lỗi tiện ích để biết thêm các cách gỡ lỗi trình chạy dịch vụ tiện ích.

Bước 4: Khởi tạo trạng thái

Chrome sẽ tắt worker dịch vụ nếu không cần thiết. Chúng ta sử dụng API chrome.storage để duy trì trạng thái trên các phiên của worker dịch vụ. Để truy cập vào bộ nhớ, chúng ta cần yêu cầu quyền trong tệp kê khai:

manifest.json:

{
  ...
  "permissions": ["storage"],
}

Trước tiên, hãy lưu nội dung đề xuất mặc định vào bộ nhớ. Chúng ta có thể khởi chạy trạng thái khi cài đặt tiện ích lần đầu bằng cách theo dõi sự kiện runtime.onInstalled():

sw-omnibox.js:

...
// Save default API suggestions
chrome.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === 'install') {
    chrome.storage.local.set({
      apiSuggestions: ['tabs', 'storage', 'scripting']
    });
  }
});

Worker không có quyền truy cập trực tiếp vào đối tượng cửa sổ, do đó không thể sử dụng window.localStorage để lưu trữ giá trị. Ngoài ra, trình chạy dịch vụ là môi trường thực thi ngắn hạn; các trình chạy này bị chấm dứt liên tục trong phiên trình duyệt của người dùng, khiến chúng không tương thích với các biến toàn cục. Thay vào đó, hãy sử dụng chrome.storage.local để lưu trữ dữ liệu trên máy cục bộ.

Hãy xem phần Lưu trữ dữ liệu thay vì sử dụng biến toàn cục để tìm hiểu về các tuỳ chọn lưu trữ khác cho worker dịch vụ tiện ích.

Bước 5: Đăng ký sự kiện

Tất cả trình nghe sự kiện cần được đăng ký tĩnh trong phạm vi toàn cầu của trình chạy dịch vụ. Nói cách khác, trình nghe sự kiện không được lồng trong các hàm không đồng bộ. Bằng cách này, Chrome có thể đảm bảo rằng tất cả trình xử lý sự kiện đều được khôi phục trong trường hợp trình chạy dịch vụ khởi động lại.

Trong ví dụ này, chúng ta sẽ sử dụng API chrome.omnibox, nhưng trước tiên, chúng ta phải khai báo điều kiện kích hoạt từ khoá trên thanh địa chỉ trong tệp kê khai:

manifest.json:

{
  ...
  "minimum_chrome_version": "102",
  "omnibox": {
    "keyword": "api"
  },
}

Bây giờ, hãy đăng ký trình nghe sự kiện hộp tìm kiếm ở cấp cao nhất của tập lệnh. Khi người dùng nhập từ khoá hộp tìm kiếm đa năng (api) vào thanh địa chỉ, theo sau là phím tab hoặc dấu cách, Chrome sẽ hiển thị danh sách nội dung đề xuất dựa trên từ khoá trong bộ nhớ. Sự kiện onInputChanged(), lấy hoạt động đầu vào hiện tại của người dùng và đối tượng suggestResult, chịu trách nhiệm điền các đề xuất này.

sw-omnibox.js:

...
const URL_CHROME_EXTENSIONS_DOC =
  'https://developer.chrome.com/docs/extensions/reference/';
const NUMBER_OF_PREVIOUS_SEARCHES = 4;

// Display the suggestions after user starts typing
chrome.omnibox.onInputChanged.addListener(async (input, suggest) => {
  await chrome.omnibox.setDefaultSuggestion({
    description: 'Enter a Chrome API or choose from past searches'
  });
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  const suggestions = apiSuggestions.map((api) => {
    return { content: api, description: `Open chrome.${api} API` };
  });
  suggest(suggestions);
});

Sau khi người dùng chọn một đề xuất, onInputEntered() sẽ mở trang tài liệu tham khảo API của Chrome tương ứng.

sw-omnibox.js:

...
// Open the reference page of the chosen API
chrome.omnibox.onInputEntered.addListener((input) => {
  chrome.tabs.create({ url: URL_CHROME_EXTENSIONS_DOC + input });
  // Save the latest keyword
  updateHistory(input);
});

Hàm updateHistory() lấy dữ liệu đầu vào của hộp tìm kiếm đa năng và lưu dữ liệu đó vào storage.local. Bằng cách này, cụm từ tìm kiếm gần đây nhất có thể được sử dụng sau này dưới dạng đề xuất trong hộp tìm kiếm đa năng.

sw-omnibox.js:

...
async function updateHistory(input) {
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  apiSuggestions.unshift(input);
  apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES);
  return chrome.storage.local.set({ apiSuggestions });
}

Bước 6: Thiết lập sự kiện định kỳ

Các phương thức setTimeout() hoặc setInterval() thường dùng để thực hiện các tác vụ bị trễ hoặc định kỳ. Tuy nhiên, các API này có thể không hoạt động vì trình lập lịch biểu sẽ huỷ các bộ hẹn giờ khi worker của dịch vụ bị chấm dứt. Thay vào đó, các tiện ích có thể sử dụng API chrome.alarms.

Bắt đầu bằng cách yêu cầu quyền "alarms" trong tệp kê khai. Ngoài ra, để tìm nạp các mẹo về tiện ích từ một vị trí lưu trữ từ xa, bạn cần yêu cầu quyền lưu trữ:

manifest.json:

{
  ...
  "permissions": ["storage"],
  "permissions": ["storage", "alarms"],
  "host_permissions": ["https://extension-tips.glitch.me/*"],
}

Tiện ích này sẽ tìm nạp tất cả mẹo, chọn ngẫu nhiên một mẹo và lưu vào bộ nhớ. Chúng ta sẽ tạo một chuông báo được kích hoạt một lần mỗi ngày để cập nhật số tiền boa. Chuông báo sẽ không được lưu khi bạn đóng Chrome. Vì vậy, chúng ta cần kiểm tra xem chuông báo có tồn tại hay không và tạo chuông báo nếu chưa có.

sw-tips.js:

// Fetch tip & save in storage
const updateTip = async () => {
  const response = await fetch('https://extension-tips.glitch.me/tips.json');
  const tips = await response.json();
  const randomIndex = Math.floor(Math.random() * tips.length);
  return chrome.storage.local.set({ tip: tips[randomIndex] });
};

const ALARM_NAME = 'tip';

// Check if alarm exists to avoid resetting the timer.
// The alarm might be removed when the browser session restarts.
async function createAlarm() {
  const alarm = await chrome.alarms.get(ALARM_NAME);
  if (typeof alarm === 'undefined') {
    chrome.alarms.create(ALARM_NAME, {
      delayInMinutes: 1,
      periodInMinutes: 1440
    });
    updateTip();
  }
}

createAlarm();

// Update tip once a day
chrome.alarms.onAlarm.addListener(updateTip);

Bước 7: Giao tiếp với các ngữ cảnh khác

Tiện ích sử dụng tập lệnh nội dung để đọc và sửa đổi nội dung của trang. Khi người dùng truy cập vào trang tham chiếu API Chrome, tập lệnh nội dung của tiện ích sẽ cập nhật trang với mẹo trong ngày. Dịch vụ này gửi tin nhắn để yêu cầu nhận tiền boa trong ngày từ trình chạy dịch vụ.

Bắt đầu bằng cách khai báo tập lệnh nội dung trong tệp kê khai rồi thêm mẫu so khớp tương ứng với tài liệu tham khảo về API Chrome.

manifest.json:

{
  ...
  "content_scripts": [
    {
      "matches": ["https://developer.chrome.com/docs/extensions/reference/*"],
      "js": ["content.js"]
    }
  ]
}

Tạo tệp nội dung mới. Mã sau đây sẽ gửi thông báo đến worker dịch vụ yêu cầu tiền boa. Sau đó, hãy thêm một nút để mở một cửa sổ bật lên chứa mẹo về tiện ích. Mã này sử dụng Popover API nền tảng web mới.

content.js:

(async () => {
  // Sends a message to the service worker and receives a tip in response
  const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' });

  const nav = document.querySelector('.upper-tabs > nav');
  
  const tipWidget = createDomElement(`
    <button type="button" popovertarget="tip-popover" popovertargetaction="show" style="padding: 0 12px; height: 36px;">
      <span style="display: block; font: var(--devsite-link-font,500 14px/20px var(--devsite-primary-font-family));">Tip</span>
    </button>
  `);

  const popover = createDomElement(
    `<div id='tip-popover' popover style="margin: auto;">${tip}</div>`
  );

  document.body.append(popover);
  nav.append(tipWidget);
})();

function createDomElement(html) {
  const dom = new DOMParser().parseFromString(html, 'text/html');
  return dom.body.firstElementChild;
}

Bước cuối cùng là thêm trình xử lý thông báo vào worker dịch vụ để gửi thư trả lời cho tập lệnh nội dung kèm theo số tiền boa hằng ngày.

sw-tips.js:

...
// Send tip to content script via messaging
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.greeting === 'tip') {
    chrome.storage.local.get('tip').then(sendResponse);
    return true;
  }
});

Kiểm thử để đảm bảo tính năng này hoạt động

Xác minh rằng cấu trúc tệp của dự án có dạng như sau:

Nội dung của thư mục tiện ích: thư mục images, manifest.json, service-worker.js, sw-omnibox.js, sw-tips.js, và content.js

Tải tiện ích trên thiết bị

Để tải một tiện ích chưa giải nén ở chế độ nhà phát triển, hãy làm theo các bước trong phần Xin chào thế giới.

Mở trang tham khảo

  1. Nhập từ khoá "api" vào thanh địa chỉ của trình duyệt.
  2. Nhấn phím "tab" hoặc "space".
  3. Nhập tên đầy đủ của API.
    • HOẶC chọn trong danh sách nội dung tìm kiếm trước đây
  4. Một trang mới sẽ mở ra trang tham khảo API Chrome.

Ứng dụng sẽ hiển thị như sau:

Tài liệu tham khảo API nhanh mở tài liệu tham khảo API thời gian chạy
Tiện ích API nhanh mở API Thời gian chạy.

Mở mẹo của ngày

Nhấp vào nút Mẹo trên thanh điều hướng để mở mẹo về tiện ích.

Mở mẹo hằng ngày trong
Tiện ích API nhanh mở mẹo của ngày.

🎯 Các điểm cải tiến tiềm năng

Dựa trên những nội dung bạn đã tìm hiểu hôm nay, hãy cố gắng hoàn thành bất kỳ việc nào sau đây:

  • Khám phá một cách khác để triển khai nội dung đề xuất trong hộp tìm kiếm đa năng.
  • Tạo một cửa sổ bật lên tuỳ chỉnh của riêng bạn để hiển thị mẹo về tiện ích.
  • Mở một trang bổ sung cho các trang API tham chiếu Tiện ích web của MDN.

Hãy tiếp tục xây dựng!

Chúc mừng bạn đã hoàn thành hướng dẫn này 🎉. Hãy tiếp tục nâng cao kỹ năng của bạn bằng cách hoàn thành các hướng dẫn dành cho người mới bắt đầu khác:

Phần mở rộng Kiến thức bạn sẽ học được
Thời gian đọc Tự động chèn một phần tử trên một tập hợp trang cụ thể.
Trình quản lý thẻ Để tạo một cửa sổ bật lên quản lý các thẻ trình duyệt.
Chế độ tập trung Để chạy mã trên trang hiện tại sau khi nhấp vào hành động của tiện ích.

Tiếp tục khám phá

Để tiếp tục lộ trình học tập về worker dịch vụ tiện ích, bạn nên khám phá các bài viết sau: