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 cùng một API Nền tảng web navigator.geolocation như mọi trang web thường làm. 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 theo cách khác với 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 biết đầy đủ và kiểm soát được thời điểm cũng như vị trí mà vị trí chính xác của họ được chia sẻ.

Sử dụng vị trí địa lý trong tiện ích MV3

Trên web, các trình duyệt bảo vệ dữ liệu định vị vị trí bằng cách hiển thị lời nhắc yêu cầu họ cấp quyền truy cập nguồn gốc cụ thể đó vào vị trí của họ. Cùng một mô hình cấp 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 định vị vị trí

Quyền không phải là sự khác biệt duy nhất. Như đã đề cập ở trên, navigator.geolocation là một API DOM, nghĩa 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 bên trong ngữ cảnh trình thực thi, chẳng hạn như trình chạy dịch vụ tiện ích là xương sống của 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à vị trí bạn sử dụng Gemini.

Sử dụng thông tin định vị vị trí trong trình chạy dịch vụ

Không có đối tượng navigator nào bên trong trình chạy dịch vụ. Mã 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 một trang. Để truy cập bên trong một trình chạy dịch vụ, hãy dùng Offscreen Document. Trình này cung cấp quyền truy cập vào một tệp HTML mà bạn có thể nhóm 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 một tệp HTML chứa tài liệu ngoài màn hình vào tiện ích 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. Đó 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 vào 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 cần phải nhóm nội dung này với tiện ích của mình. Đó sẽ là nguồn thông tin về 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 nó và trình chạy dịch vụ của mình.

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 mục đó, 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',
});

Lưu ý rằng khi truy cập vào một tài liệu ngoài màn hình, bạn cần thêm reason. Nguyên nhân geolocation ban đầu không có sẵn, vì vậy, hãy chỉ định khả năng dự phòng DOM_SCRAPING và giải thích trong phần justification mã thực sự đang 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 tài liệu ngoài màn hình đang được sử dụng cho mục đích hợp lệ.

Khi có thông tin 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ý mới.

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, bây giờ bất cứ khi nào bạn muốn lấy dữ liệu định vị vị trí từ trình chạy 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 trên web nên 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 thông thường 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, việc sử dụng đoạn mã này sẽ gây ra lỗi ngay lập tức, giống như việc người dùng từ chối yêu cầu. Bạn có thể xem nội dung 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

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ẽ chuyển sang quy trình cấp quyền thông thường của người dùng. Điều đó có nghĩa là việc thêm "geolocation" vào "permissions" của bạn sẽ không tự động cấp cho bạn quyền truy cập vào thông tin về vị trí địa lý. Bạn có thể xem điều này trong mẫu tập lệnh nội dung.