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 xét Tiện ích 101Hello World để giới thiệu về 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 là quick-api-reference để lưu giữ 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 tệp kê khai trong 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. Không cần gọi navigator.serviceWorker.register(), như bạn thực hiện 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 trong 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. Để có khả năng bảo trì tốt 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 hai mô-đun:

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

Hãy tạo các tệp này rồi thêm nhật ký bảng điều khiển vào mỗi 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 trình chạy dịch vụ và biết thời điểm trình chạy 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 chưa giải nén.

Sau 30 giây, bạn sẽ thấy "worker dịch vụ (không hoạt động)", nghĩa là worker dịch vụ đã chấm dứt. Nhấp vào "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 ngắt tiện ích này để tìm hiểu nơi tìm lỗi. Một cách để thực hiện việc này là xoá ".js" qua 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á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 tạo trạng thái khi tiện ích được cài đặt 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']
    });
  }
});

Trình chạy dịch vụ không có quyền truy cập trực tiếp vào đối tượng cửa sổ nên không thể sử dụng window.localStorage để lưu trữ giá trị. Ngoài ra, trình chạy dịch vụ là các môi trường thực thi ngắn hạn; chúng bị chấm dứt nhiều lần trong suốt phiên duyệt web của người dùng, khiến chúng không tương thích với 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 bài viết Lưu trữ cố định dữ liệu thay vì sử dụng biến toàn cục để tìm hiểu về các lựa chọn lưu trữ khác dành cho trình chạy 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 trên thanh địa chỉ ở 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 dữ liệu đầ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, từ khoá 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 việc trễ hoặc định kỳ công việc. Tuy nhiên, các API này có thể không hoạt động được vì trình lập lịch biểu sẽ huỷ bộ tính giờ khi dịch vụ worker 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ả các mẹo, chọn một mẹo ngẫu nhiên và lưu vào bộ nhớ. Chúng tôi sẽ tạo một chuông báo được kích hoạt mỗi ngày một lần để cập nhật tiền boa. Báo thức 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 khảo API của 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. Ứng dụng này gửi một thông báo để yêu cầu lời khuyên của ngày hôm nay từ worker 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 và thêm mẫu khớp tương ứng với tài liệu tham khảo 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 API Popover của 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 một trình xử lý thông báo vào trình chạy dịch vụ. Trình xử lý này sẽ gửi phản hồi cho tập lệnh nội dung kèm theo 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 tra để đả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 máy

Để 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ừ khóa "api" trong thanh địa chỉ của trình duyệt.
  2. Nhấn phím "tab" hoặc "dấu cách".
  3. Nhập tên đầy đủ của API.
    • HOẶC chọn từ danh sách các 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ở Runtime API.

Mở mẹo hay trong 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ở đầu 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á cách khác để triển khai đề xuất trên thanh địa chỉ.
  • Tạo phương thức tuỳ chỉnh của riêng bạn để hiển thị tiền boa dành cho phần mở rộng.
  • 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 khác dành cho người mới bắt đầu:

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ế độ lấy nét Để chạy mã trên trang hiện tại sau khi nhấp vào thao tác với tiện ích.

Tiếp tục khám phá

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