Chrome 瀏覽器外掛獲取網頁 window 物件(方案三)

日升_rs發表於2024-09-05

前言

最近有個需求,是在瀏覽器外掛中獲取 window 物件下的某個資料,當時覺得很簡單,和 document 一樣,直接透過嵌入 content_scripts 直接獲取,然後使用 sendMessage 傳送資料到外掛就行了,結果發現不是這樣滴...

在這裡不推薦使用 runtime.executeScript 進行注入,很可能會報錯:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost😗 http://127.0.0.1😗". Either the 'unsafe-inline' keyword, a hash ('sha256-P5exJBBLYN1KVh+CK9MkXvRal4ZQQu9VaKPvx4JuVLE='), or a nonce ('nonce-...') is required to enable inline execution.

Chrome 瀏覽器外掛獲取網頁 window 物件(方案一)

Chrome 瀏覽器外掛獲取網頁 window 物件(方案二)

一、兩個檔案,透過 CustomEvent 傳遞訊息

1. 方案思路

  1. 新建兩個 js 檔案,index.jslucky.js
  2. content_scripts 中嵌入 lucky.js 檔案和 index.js 檔案
  3. index.js 中透過 window.dispatchEvent 派發自定義 custom event 訊息
  4. index.js 中透過 addEventListener 監聽訊息
  5. lucky.js 中透過 addEventListener 監聽訊息,再透過 dispatchEvent 派發訊息

1.1. content_scripts 嵌入 JS 檔案

一定要把 lucky.js 檔案放在 index.js 檔案前面

content_scripts 中新增 lucky.js 的時候需要加上 "world": "MAIN" 欄位

world 為列舉型別

  • ISOLATED 預設值
    • 此擴充套件程式所獨有的執行環境
  • MAIN
    • 指定 DOM 的主域,也就是與託管頁面的 JavaScript 共享的執行環境

1.2. 方案流程

流程圖如下:

image

2. 獲取內容

獲取 window 下的 MyBlog 欄位

window.MyBlog = {
  juejin: 'https://juejin.cn/user/2409752520033768/posts',
  csdn: 'https://guoqiankun.blog.csdn.net/',
  'chrome-blog': {
    netlify: 'https://gqk-extension.netlify.app/',
    github: 'https://18055975947.github.io/extension/'
  }
}

3. 實現程式碼

3.1. index.js

/**
 * index 檔案傳送訊息到 lucky.js 檔案
 * @param {string} type custom 型別
 * @param {any} data 資料
 */
const indexSendMessageToLucky = async (type, data) => {
  window.dispatchEvent(new CustomEvent('custom-index-type', { detail: { type, data } }))
  return new Promise((res) => {
    function handleResponse(e) {
      const detail = e.detail
      if (detail.type == type) {
        window.removeEventListener('custom-lucky-type', handleResponse)
        return res(detail.data)
      }
    }
    window.addEventListener('custom-lucky-type', handleResponse)
  })
}

/**
 * 傳送訊息
 */
const sendMessage = () => {
  function getMyBolg() {
    return window.MyBlog
  }
  indexSendMessageToLucky('run-index-fun', {
    function: getMyBolg.toString()
  }).then((res) => {
    console.log('res-->', res)
  }).catch((e) => {
    console.log('e', e)
  })
}
/**
 * 初始化
 */
const init = () => {

  // 插入 button 按鈕
  const button = document.createElement('button')
  button.innerText = '獲取資料'
  button.id = 'chrome-ext-but'
  document.body.appendChild(button)
  button.onclick = () => {
    sendMessage()
  }
  // 初始化獲取資料
  sendMessage()
}

// 判斷 window.top 和 self 是否相等,如果不相等,則不注入
if (window.top == window.self) {
  init()
}

3.2. lucky.js

/**
 * 事件監聽
 */
window.addEventListener('custom-index-type', async (e) => {
  const { type, data } = e.detail
  switch (type) {
    case 'run-index-fun': {
      const fn = new Function(`return (${data.function})(...arguments)`)
      const rs = await fn(...(data.args ?? []))
      luckySendMessageToIndex(type, rs)
      break
    }
  }
})

/**
 * lucky 檔案傳送訊息到 index.js 檔案
 * @param {string} type custom 型別
 * @param {any} data 資料
 */
const luckySendMessageToIndex = (type, data) => {
  window.dispatchEvent(
    new CustomEvent('custom-lucky-type', {
      detail: { type, data, file: 'lucky' }
    })
  )
}

3.3. manifest.json

{
  "manifest_version": 3,
  "name": "Get Winddow Object Field",
  "version": "1.0",
  "description": "Gets the field under window",
  "content_scripts": [
    {
      "js": [
        "lucky.js"
      ],
      "matches": ["http://localhost:*/*"],
      "run_at": "document_end",
      "world": "MAIN"
    },
    {
      "js": [
        "index.js"
      ],
      "matches": ["http://localhost:*/*"],
      "all_frames": true,
      "run_at": "document_end"
    }
  ],
  "background": {
    "service_worker": "service-worker.js"
  },
  "host_permissions": [
    "http://localhost:*/*"
  ],
  "permissions": [
  ],
  "web_accessible_resources": []
}

3.4. 專案檔案結構

.
├── index.html
├── index.js
├── lucky.js
├── manifest.json
└── service-worker.js

3.5. 方案效果

在控制檯中選擇當前外掛,即可檢視獲取的 window 下的 MyBlog 物件

image

4. 動態獲取資料

4.1. 點選按鈕

image

4.2. 資料返回

image

5. 程式碼地址

  • Gitee
  • Github

四、總結

1. 文章總結

  1. 獲取當前頁面下的 window 物件和 document 物件不一樣,需要另外的處理方式
  2. 此次提供了三種方案,核心原理都是嵌入當前頁面,透過訊息派發和接收來獲取資料
  3. 第一種透過 postMessage 的方式更為大家熟悉,自定義 Event 相對偏一點
  4. 三種方案的程式碼我都上傳到 gitee/github 上了

2. 程式碼地址

  • Gitee:https://gitee.com/gqk-chrome-extension/get-window-fields
  • Github:https://github.com/gqk-chrome-extension/get-window-fields/tree/main

引用

  • chrome extensions mv3透過content scripts注入/獲取原網站的window資料
  • CustomEvent:CustomEvent() 建構函式
  • Content_script world

相關文章