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

Hướng dẫn trình bày 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ề trình chạy dịch vụ của Tiện ích của Chrome. Là một phần của quá trình này hướng dẫn, bạn sẽ tạo một tiện ích cho phép người dùng nhanh chóng điều hướng đến tài liệu tham khảo về API Chrome trang bằng thanh địa chỉ. 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ý các 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ý trình chạy 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"
  }
}

Các tiện ích sẽ đăng ký trình chạy dịch vụ trong tệp kê khai mà chỉ lấy một tệp JavaScript duy nhất. Không cần gọi navigator.serviceWorker.register(), như bạn thực hiện trong trang web.

Tạo một thư mục images rồi tải các 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ụ

Trình chạy dịch vụ của chúng tôi triển khai 2 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 trình chạy dịch vụ dưới dạng Mô-đun ES trong tệp kê khai. Việc này cho phép chúng ta nhập các mô-đun trong trình chạy dịch vụ của mình:

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';

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");

Xem phần Nhập tập lệnh để tìm hiểu về các cách khác để nhập nhiều tệp trong một trình chạy 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 đã giải nén.

Sau 30 giây, bạn sẽ thấy thông báo "nhân viên dịch vụ (không hoạt động)" tức là trình chạy 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 quy trình 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ụ cho nhà phát triển sẽ giúp trình chạy dịch vụ đó hoạt động. Để đảm bảo tiện ích của bạn hoạt động chính xác khi trình chạy dịch vụ của bạn bị chấm dứt, hãy nhớ đóng Công cụ cho nhà phát triển.

Bây giờ, hãy ngắt tiện ích này để tìm hiểu nơi xác định 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 rồi 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.

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 trình chạy dịch vụ nếu không cần thiết. Chúng ta dùng API chrome.storage để duy trì trạng thái trên các phiên của trình chạy 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 nên đượ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á (api) vào thanh địa chỉ, theo sau là thẻ hoặc dấu cách, Chrome sẽ cho thấy danh sách đề 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 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() nhận thông tin nhập vào thanh địa chỉ và lưu vào storage.local. Bằng cách này, bạn có thể sử dụng cụm từ tìm kiếm gần đây nhất dưới dạng đề xuất trên thanh địa chỉ.

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 tiện ích từ một vị trí được lưu trữ từ xa, bạn cần yêu cầu quyền của máy chủ:

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 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 nếu 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 bối 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 một thông báo đến trình chạy dịch vụ để yêu cầu tiền boa. Sau đó, hãy thêm một nút sẽ mở ra cửa sổ bật lên có chứa mẹo 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 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 tra xem ứng dụng có hoạt động khô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 đã giải nén ở chế độ nhà phát triển, hãy làm theo các bước trong thẻ Hello world (Xin chào mọi ngườ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ở API Runtime API.

Mở mẹo của ngày

Nhấp vào nút Mẹo nằm trên thanh điều hướng để mở mẹo 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 gì bạn đã học 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 🎉. Tiếp tục nâng cấp kỹ năng bằng cách hoàn thành các hướng dẫn 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ử vào một nhóm trang cụ thể.
Trình quản lý thẻ Tạo 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 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: