Sử dụng vị trí địa lý

Nếu bạn muốn nhận thông tin về vị trí địa lý trong tiện ích của Chrome, hãy sử dụng API Nền tảng web navigator.geolocation giống như mọi trang web thường dùng. Bài viết này tồn tại vì các tiện ích của Chrome xử lý quyền truy cập vào dữ liệu nhạy cảm khác với quyền trên trang web. Vị trí địa lý là dữ liệu rất nhạy cảm, vì vậy, các trình duyệt đảm bảo rằng người dùng nhận biết được đầy đủ và kiểm soát được thời điểm cũng như vị trí chính xác của họ được chia sẻ.

Sử dụng vị trí địa lý trong phần mở rộng MV3

Trên web, trình duyệt bảo vệ dữ liệu vị trí địa lý của người dùng bằng cách hiển thị lời nhắc yêu cầu họ cấp quyền truy cập vị trí theo nguồn gốc cụ thể đó vào vị trí của họ. Cùng một mô hình quyền không phải lúc nào cũng phù hợp cho các tiện ích.

Ảnh chụp màn hình lời nhắc cấp quyền mà bạn thấy khi một trang web yêu cầu quyền truy cập vào API vị trí địa lý
Lời nhắc cấp quyền truy cập vào thông tin định vị vị trí

Quyền không phải là khác biệt duy nhất. Như đã đề cập ở trên, navigator.geolocation là một API DOM, tức là một phần của các API tạo nên trang web. Do đó, bạn không thể truy cập vào trình thực thi này trong ngữ cảnh trình chạy, chẳng hạn như trình chạy dịch vụ tiện ích là xương sống của các tiện ích Manifest v3. Tuy nhiên, bạn hoàn toàn vẫn có thể sử dụng geolocation. Có một số điểm khác biệt nhỏ về cách thức và nơi sử dụng.

Sử dụng tính năng định vị vị trí trong trình chạy dịch vụ

Không có đối tượng navigator bên trong trình chạy dịch vụ. Tính năng này chỉ có sẵn trong các ngữ cảnh có quyền truy cập vào đối tượng document của trang. Để có quyền truy cập bên trong một trình chạy dịch vụ, hãy sử dụng Offscreen Document. Lớp này cung cấp quyền truy cập vào tệp HTML mà bạn có thể gói cùng với tiện ích của mình.

Để bắt đầu, hãy thêm "offscreen" vào phần "permissions" của tệp kê khai.

manifest.json:

{
  "name": "My extension",
    ...
  "permissions": [
    ...
   "offscreen"
  ],
  ...
}

Sau khi thêm quyền "offscreen", hãy thêm tệp HTML vào tiện ích. Tệp này chứa tài liệu ngoài màn hình của bạn. Trường hợp này không sử dụng bất kỳ nội dung nào của trang, vì vậy đây có thể là một tệp gần như trống. Nó chỉ cần là một tệp HTML nhỏ tải trong tập lệnh của bạn.

offscreen.html:

<!doctype html>
<title>offscreenDocument</title>
<script src="offscreen.js"></script>

Lưu tệp này trong thư mục gốc của dự án dưới dạng offscreen.html.

Như đã đề cập, bạn cần một tập lệnh có tên là offscreen.js. Bạn cũng sẽ cần gói kèm tiện ích này với tiện ích của mình. Đó sẽ là nguồn thông tin vị trí địa lý của nhân viên dịch vụ. Bạn có thể chuyển thông báo giữa thiết bị này và trình chạy dịch vụ.

offscreen.js:

chrome.runtime.onMessage.addListener(handleMessages);
function handleMessages(message, sender, sendResponse) {
  // Return early if this message isn't meant for the offscreen document.
  if (message.target !== 'offscreen') {
    return;
  }

  if (message.type !== 'get-geolocation') {
    console.warn(`Unexpected message type received: '${message.type}'.`);
    return;
  }

  // You can directly respond to the message from the service worker with the
  // provided `sendResponse()` callback. But in order to be able to send an async
  // response, you need to explicitly return `true` in the onMessage handler
  // As a result, you can't use async/await here. You'd implicitly return a Promise.
  getLocation().then((loc) => sendResponse(loc));

  return true;
}

// getCurrentPosition() returns a prototype-based object, so the properties
// end up being stripped off when sent to the service worker. To get
// around this, create a deep clone.
function clone(obj) {
  const copy = {};
  // Return the value of any non true object (typeof(null) is "object") directly.
  // null will throw an error if you try to for/in it. Just return
  // the value early.
  if (obj === null || !(obj instanceof Object)) {
    return obj;
  } else {
    for (const p in obj) {
      copy[p] = clone(obj[p]);
    }
  }
  return copy;
}

async function getLocation() {
  // Use a raw Promise here so you can pass `resolve` and `reject` into the
  // callbacks for getCurrentPosition().
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(
      (loc) => resolve(clone(loc)),
      // in case the user doesnt have/is blocking `geolocation`
      (err) => reject(err)
    );
  });
}

Với tính năng đó, giờ đây bạn đã sẵn sàng để truy cập vào Tài liệu ngoài màn hình trong trình chạy dịch vụ.

chrome.offscreen.createDocument({
  url: 'offscreen.html',
  reasons: [chrome.offscreen.Reason.GEOLOCATION || chrome.offscreen.Reason.DOM_SCRAPING],
  justification: 'geolocation access',
});

Xin lưu ý rằng khi truy cập vào tài liệu ngoài màn hình, bạn cần thêm reason. Lý do geolocation ban đầu không có sẵn, vì vậy, hãy chỉ định phương án dự phòng của DOM_SCRAPING và giải thích trong phần justification xem mã đang thực sự làm gì. Thông tin này được quy trình xem xét của Cửa hàng Chrome trực tuyến sử dụng để đảm bảo các tài liệu ngoài màn hình đang được sử dụng cho mục đích hợp lệ.

Sau khi tham chiếu đến Tài liệu ngoài màn hình, bạn có thể gửi thông báo cho Tài liệu đó để yêu cầu tài liệu đó cung cấp cho bạn thông tin vị trí địa lý cập nhật.

service_worker.js:

const OFFSCREEN_DOCUMENT_PATH = '/offscreen.html';
let creating; // A global promise to avoid concurrency issues

chrome.runtime.onMessage.addListener(handleMessages);

async function getGeolocation() {
  await setupOffscreenDocument(OFFSCREEN_DOCUMENT_PATH);
  const geolocation = await chrome.runtime.sendMessage({
    type: 'get-geolocation',
    target: 'offscreen'
  });
  await closeOffscreenDocument();
  return geolocation;
}

async function hasDocument() {
  // Check all windows controlled by the service worker to see if one
  // of them is the offscreen document with the given path
  const offscreenUrl = chrome.runtime.getURL(OFFSCREEN_DOCUMENT_PATH);
  const matchedClients = await clients.matchAll();

  return matchedClients.some(c => c.url === offscreenUrl)
}

async function setupOffscreenDocument(path) {
  //if we do not have a document, we are already setup and can skip
  if (!(await hasDocument())) {
    // create offscreen document
    if (creating) {
      await creating;
    } else {
      creating = chrome.offscreen.createDocument({
        url: path,
        reasons: [chrome.offscreen.Reason.GEOLOCATION || chrome.offscreen.Reason.DOM_SCRAPING],
        justification: 'add justification for geolocation use here',
      });

      await creating;
      creating = null;
    }
  }
}

async function closeOffscreenDocument() {
  if (!(await hasDocument())) {
    return;
  }
  await chrome.offscreen.closeDocument();
}

Vì vậy, giờ đây, bất cứ khi nào bạn muốn nhận vị trí địa lý từ dịch vụ của mình, bạn chỉ cần gọi:

const location = await getGeolocation()

Sử dụng thông tin định vị vị trí trong cửa sổ bật lên hoặc bảng điều khiển bên

Việc sử dụng vị trí địa lý trong cửa sổ bật lên hoặc bảng điều khiển bên rất đơn giản. Cửa sổ bật lên và bảng điều khiển bên chỉ là tài liệu web và do đó có quyền truy cập vào các API DOM thông thường. Bạn có thể truy cập trực tiếp vào navigator.geolocation. Điểm khác biệt duy nhất so với các trang web tiêu chuẩn là bạn cần sử dụng trường manifest.json "permission" để yêu cầu quyền "geolocation". Nếu không cấp quyền này, bạn sẽ vẫn có quyền truy cập vào navigator.geolocation. Tuy nhiên, mọi nỗ lực sử dụng yêu cầu đó đều sẽ gây ra lỗi ngay lập tức, giống như khi người dùng từ chối yêu cầu. Bạn có thể xem thông tin này trong mẫu cửa sổ bật lên.

Sử dụng vị trí địa lý trong tập lệnh nội dung

Cũng giống như cửa sổ bật lên, tập lệnh nội dung có toàn quyền truy cập vào API DOM; tuy nhiên, người dùng sẽ trải qua quy trình cấp quyền của người dùng thông thường. Điều đó có nghĩa là việc thêm "geolocation" vào "permissions" sẽ không tự động cấp cho bạn quyền truy cập vào thông tin vị trí địa lý của người dùng. Bạn có thể xem thông tin này trong mẫu tập lệnh nội dung.