使用地理位置

如要透過 Chrome 擴充功能取得地理位置資訊,請使用任何網站平常使用的 navigator.geolocation Web Platform API。這篇文章之所以如此,是因為 Chrome 擴充功能處理機密資料的權限與網站不同。地理位置是非常機密資料,因此瀏覽器必須確保使用者完全瞭解並掌控自己的確切位置分享時間與位置。

在 MV3 擴充功能中使用地理位置

在網頁上,瀏覽器會顯示提示,要求使用者授予特定來源存取位置資訊,以保護使用者的地理位置資料。相同的權限模型不一定適用於擴充功能。

網站要求存取 Geolocation API 時看到的權限提示螢幕截圖
地理位置權限提示

權限並非唯一的區別。如前所述,navigator.geolocationDOM API,也就是構成網站之 API 的一部分。因此,這些元件無法在工作站環境中存取,例如是資訊清單 v3 擴充功能骨幹的擴充功能 Service Worker。不過您還是能使用 geolocation。使用方式和位置都不盡相同。

在服務 Worker 中使用地理位置功能

服務工作站內沒有 navigator 物件。這個 API 只能在可存取網頁 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 之間傳遞訊息。

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

完成以上步驟後,您就可以開始在服務工作處理程序中存取螢幕外文件了。

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"中,不會自動授予使用者的地理位置資訊存取權。內容指令碼範例請見下方說明。