使用地理定位

如果您想在 Chrome 扩展程序中获取地理定位信息,请使用任何网站通常使用的 navigator.geolocation Web 平台 API。本文之所以存在,是因为 Chrome 扩展程序处理敏感数据访问权限的方式与处理网站权限的方式不同。地理定位是非常敏感的数据,因此浏览器需要确保用户完全了解并控制共享其确切位置的时间和位置。

在 MV3 扩展中使用地理定位

在 Web 上,浏览器可以保护用户地理定位数据。同一权限模型并不总是适合扩展程序。

<ph type="x-smartling-placeholder">
</ph> 当网站请求访问 Geolocation API 时您看到的权限提示的屏幕截图
地理定位权限提示

权限不是唯一的区别。如上所述,navigator.geolocation 是一个 DOM API,亦即构成网站的 API 的一部分。因此,它无法在 Worker 上下文中访问,例如作为 Manifest V3 扩展程序的支柱的“Extension Service Worker”。不过,您完全可以使用 geolocation。但只是使用方式和使用位置略有不同。

在 Service Worker 中使用地理定位

Service Worker 内部没有 navigator 对象。它只能在有权访问页面 document 对象的上下文中使用。如需在 Service Worker 内部获取访问权限,请使用 Offscreen Document,它可提供对可与扩展程序捆绑的 HTML 文件的访问权限。

首先,将 "offscreen" 添加到清单的 "permissions" 部分。

manifest.json:

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

添加 "offscreen" 权限后,请向包含屏幕外文档的扩展程序添加一个 HTML 文件。此案例没有使用网页上的任何内容,因此该文件可能是几乎空白的文件。它只需是一个在您的脚本中加载的 HTML 小文件。

offscreen.html:

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

将此文件在项目的根目录中保存为 offscreen.html

如前所述,您需要一个名为 offscreen.js 的脚本。您还需要将它与您的扩展程序绑定。它将是 Service Worker 的地理定位信息来源。您可以在它与 Service Worker 之间传递消息。

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

设置完成后,您就可以在 Service Worker 中访问 Offscreen Document 了。

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

请注意,当您访问屏幕外文档时,需要添加 reasongeolocation 原因最初不可用,因此请指定 DOM_SCRAPING 的回退,并在 justification 部分中说明代码的实际用途。Chrome 应用商店的审核流程会使用这些信息,以确保屏幕外的文档被用于有效用途。

一旦您引用了屏幕外文档,就可以向其发送消息,要求它为您提供更新后的地理定位信息。

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();
}

因此,现在每当您想要从 Service Worker 获取地理定位时,只需调用:

const location = await getGeolocation()

在弹出式窗口或侧边栏中使用地理定位

弹出式窗口侧边栏中使用地理定位非常简单。弹出式窗口和侧边栏只是网络文档,因此可以访问常规 DOM API。您可以直接访问 navigator.geolocation。与标准网站的唯一区别在于,您需要使用 manifest.json "permission" 字段来请求 "geolocation" 权限。如果您不添加此权限,您将仍然拥有“navigator.geolocation”的访问权限。但是,任何试图使用它将导致立即的错误,就像用户拒绝了请求一样。您可以在弹出式窗口示例中查看相关信息。

在内容脚本中使用地理定位

与弹出式窗口一样,内容脚本也具有 DOM API 的完整访问权限;不过,用户将需要执行常规的用户权限流程。也就是说,将 "geolocation" 添加到您的 "permissions"不会自动授予您对用户的访问权限地理定位信息。如需查看相关信息,请参阅内容脚本示例