Chrome 浏览器插件获取网页 iframe 中的 window 对象
Chrome 浏览器插件获取网页 iframe 中的 window 对象
前言
之前写了篇《Chrome 浏览器插件获取网页 window 对象》文章,是获取当前页面的 window 对象,但是有些页面是嵌入 iframe 的,特别是系统项目主域一样,那就也需要获取 iframe 内部的 window 对象数据,而且还不能重复加载 content html 页面。这个时候就需要对 content_script 的 js 文件进行特殊处理了。
一、需求整理
1. 没有 iframe 内嵌
可以参考 《Chrome 浏览器插件获取网页 window 对象》文章
2. 嵌套一个 iframe
2.1. 页面如下
[外链图片转存中…(img-EUQWEaju-1734921003172)]
2.2. Parent 页面对象数据
<script>
window.coverageData = {
name: 'parent',
data: {
a: 'parent'
}
}
</script>
2.3. Child 页面对象数据
<script>
window.coverageData = {
name: 'child1',
data: {
a: 'child'
}
}
</script>
3. 嵌套多个 Iframe
3.1. 页面如下
[外链图片转存中…(img-DEdTomCY-1734921003172)]
3.2. Child2 页面对象数据
<script>
window.coverageData = {
name: 'child2',
data: {
a: 'child2'
}
}
</script>
二、需求实现
我们就用 《Chrome 浏览器插件获取网页 window 对象》文章中的第一种方案,使用两个 JS,通过 postMessage 进行消息通讯获取 window 对象数据。
1. 新建项目&文件
.
├── assets
│ └── icon.png
├── index.js
├── lucky.js
├── manifest.json
└── service-worker.js
- index.js:通过 content_scripts 引入
- lucky.js:通过 index.js 文件,插入当前页面的 head 标签中
2. manifest.json 文件
{
"manifest_version": 3,
"name": "Get Window Data",
"version": "0.0.1",
"description": "get window data",
"action": {
"default_title": "Get Window Data",
"default_icon": "assets/icon.png"
},
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
"content_scripts": [
{
"js": [
"index.js"
],
"matches": [
"http://127.0.0.1:*/*",
"http://localhost:*/*"
],
"all_frames": true,
"run_at": "document_end"
}
],
"host_permissions": [
"http://127.0.0.1:*/*",
"http://localhost:*/*"
],
"permissions": [
"tabs",
"scripting",
"activeTab"
],
"web_accessible_resources": [
{
"resources": ["lucky.js"],
"matches": ["http://127.0.0.1:*/*", "http://localhost:*/*"],
"use_dynamic_url": false
}
]
}
- content_scripts:
- 一定要设置 all_frames 为 true,这样才可以透传 iframe
- matches 匹配的是本地域名,根据项目需要自行修改
- host_permissions:匹配的是本地域名,根据项目需要自行修改
- permissions:需要用到的权限,根据项目需要自行修改
- web_accessible_resources:所有需要在插件代码里面用到的文件,都需要加在 resources 中,不加这个 lucky.js 不能嵌入页面。
3. lucky.js 文件
/**
* 发送 coverage data 数据
* @param {boolean} init 是否是初始化数据
*/
const sendCoverageData = (init = false) => {
window.postMessage({
type: 'coverage-data',
data: window.coverageData,
location: {
href: window.location.href,
hostname: window.location.hostname,
host: window.location.host,
pathname: window.location.pathname,
protocol: window.location.protocol,
port: window.location.port,
search: window.location.search,
hash: window.location.hash,
origin: window.location.origin,
domain: document.domain,
title: document.title
},
init
})
}
console.log('luckyjs, window.self === window.top', window.self === window.top)
// JS coverage data
sendCoverageData(true)
/**
* 监听 message 并进行处理
*/
window.addEventListener('message', (event) => {
if (event.data?.type === 'get-coverage') {
sendCoverageData()
}
})
- sendCoverageData:通过 postMessage 发送消息
- init 是值是否第一次发送消息
- 包含的数据为 window 下的 coverageData、location 对象,type 类型,init 字段
- 不可以直接把 location 传过来,会报错的
- addEventListener 进行消息监听,再次发送数据
3.1. 直传 location 报错
Uncaught DataCloneError: Failed to execute ‘postMessage’ on ‘Window’: Location object could not be cloned.
3.1.1. 页面报错消息
[外链图片转存中…(img-0UKCS9xx-1734921003172)]
3.1.2. 插件报错消息
[外链图片转存中…(img-UgOegQTJ-1734921003172)]
3.2. 为什么要传 location 数据?
因为可能存在多个 iframe,可以通过 location 进行区分
4. index.js 文件
/**
* 添加 script 标签
* @param {string} url 路径
* @param {string} id script ID
*/
const addScript = (url, id) => {
const script = document.createElement('script')
id && (script.id = id)
script.src = chrome.runtime.getURL(url)
document.head.appendChild(script)
}
/**
* 添加 JS 和事件监听
*/
const addJSAndEventListener = async () => {
// 监听从页面上下文发回的消息
window.addEventListener('message', (event) => {
console.log('event.data', event.data)
if (event.data?.type === 'coverage-data') {
}
})
addScript('lucky.js', 'coverage-script')
}
addJSAndEventListener()
- addScript:添加 JS 文件,把 lucky.js 添加到 head 标签中
- addEventListener:消息监听
5. 安装插件
[外链图片转存中…(img-BKvrYSvE-1734921003172)]
6. window 数据
6.1. 当前页面 console 日志
[外链图片转存中…(img-u1h1mu7y-1734921003172)]
6.2. 当前页面插件 console 日志
6.2.1. 点击下面选项可以插件插件 index.js 的 console 日志
[外链图片转存中…(img-xzqEK568-1734921003172)]
6.2.2. console 日志
[外链图片转存中…(img-IU7N0A26-1734921003172)]
6.3. iframe 页面 console 日志
[外链图片转存中…(img-RufeHsSV-1734921003173)]
6.4. iframe 页面插件 console 日志
6.4.1. 选择当前 iframe 下的插件
[外链图片转存中…(img-7Nyz7P4e-1734921003173)]
6.4.2. console 日志
[外链图片转存中…(img-z4TNdIqI-1734921003173)]
7. 把数据传给 service-worker
7.1. service-worker.js 文件
/**
* service worker 监听 接收消息
*/
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('message', message)
switch (message.action) {
// 从 content 发送消息到 SW,发送数据
case 'fromContent2SW-sendCoverage': {
// todo
break
}
default: {
break
}
}
sendResponse()
return false
})
7.2. service-worker 背景日志
[外链图片转存中…(img-DxqqjTnj-1734921003173)]
8. 嵌套多个 iframe
8.1. 查看页面
[外链图片转存中…(img-m5ot8wmH-1734921003173)]
8.2. service-worker 日志
[外链图片转存中…(img-PcuxPrOs-1734921003173)]
三、收集 coverage 数据并展示在 popup 中
[外链图片转存中…(img-qyZTcu1f-1734921003173)]
1. 增加 popup.html 和 popup.js 文件
.
├── assets
│ └── icon.png
├── index.js
├── lucky.js
├── manifest.json
├── popup.html
├── popup.js
└── service-worker.js
1.1. popup.html 文件内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Popup</title>
<style>
*{
padding: 0;
margin: 0;
}
body{
width: 400px;
max-height: 300px;
min-height: 200px;
}
header{
text-align: center;
margin-bottom: 10px;
}
main{
padding: 0 20px;
#get_window_but{
margin-bottom: 10px;
}
li{
margin-bottom: 10px;
word-break: break-all;
list-style: none;
}
}
</style>
</head>
<body>
<header>
<h1>Popup</h1>
</header>
<main>
<button id="get_window_but">获取 window 数据</button>
<ul id="window_coverage"></ul>
</main>
</body>
<script src="popup.js"></script>
</html>
1.2. popup.js 文件内容
/**
* 事件监听
*/
chrome.runtime.onMessage.addListener((e, _, sendResponse) => {
switch (e.action) {
case 'fromSW2Popup-sendCoverage': {
const coverageUl = document.getElementById('window_coverage')
const li = document.createElement('li')
li.innerHTML = `
<div>url: ${e.message.location.href}</div>
<div>coverage: ${e.message.data ? JSON.stringify(e.message.data) : ''}</div>
`
coverageUl.appendChild(li)
break
}
default:
break
}
sendResponse()
return false
})
const get_window_but = document.getElementById('get_window_but')
/**
* but 按钮事件
*/
get_window_but.onclick = async () => {
const tabId = await getCurrentTabId()
// 从 popup 发送消息到 SW
tabId && chrome.runtime.sendMessage({
action: 'fromPopup2SW-getWindowData',
message: {
tabId
}
})
}
/**
* 获取当前活动页面的 id
* @returns {string} tabId
*/
const getCurrentTabId = async () => {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
return tabs && tabs.length > 0 ? tabs[0].id : ''
}
- addListener:事件监听,监听从 SW 发送到 popup 的消息,把消息生成 li 在 append 到 ul 中
- onclick:按钮点击事件,发送消息到 SW
- getCurrentTabId:获取当前活动的页面的 tabId
2. manifest.json 文件
"action": {
"default_title": "Get Window Data",
"default_icon": "assets/icon.png",
"default_popup": "popup.html"
},
- 增加 default_popup 字段
3. index.js 文件
/**
* 添加 script 标签
* @param {string} url 路径
* @param {string} id script ID
*/
const addScript = (url, id) => {
const script = document.createElement('script')
id && (script.id = id)
script.src = chrome.runtime.getURL(url)
document.head.appendChild(script)
}
/**
* 添加 JS 和事件监听
*/
const addJSAndEventListener = async () => {
// 监听从页面上下文发回的消息
window.addEventListener('message', (event) => {
if (event.data?.type === 'coverage-data') {
// 从 content 脚本获取到 coverage 数据后,发送给 SW
chrome.runtime.sendMessage({
action: 'fromContent2SW-sendCoverage',
message: {
...event.data
}
})
}
})
addScript('lucky.js', 'coverage-script')
}
addJSAndEventListener()
// 消息监听
chrome.runtime.onMessage.addListener((e, _, sendResponse) => {
switch (e.action) {
// 从 SW 发送消息到 content 脚本,获取 coverage 数据
case 'fromSW2Content-getWindowData': {
window.postMessage({
type: 'get-coverage'
})
break
}
default:
break
}
sendResponse()
return false
})
- addListener:消息监听,并进行 postmessage 消息传递给 lucky.js
4. lucky.js 文件
// sendCoverageData(true)
- 因为我们是按钮点击在获取,所以可以把初始化就获取数据注释掉
5. service-worker.js
/**
* service worker 监听 接收消息
*/
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
switch (message.action) {
// 从 content 发送消息到 SW,发送数据
case 'fromContent2SW-sendCoverage': {
// 从 SW 发送消息到 popup,发送数据
chrome.runtime.sendMessage({
action: 'fromSW2Popup-sendCoverage',
message: {
...message.message
}
})
break
}
// 从 popup 发送消息到 SW,获取数据
case 'fromPopup2SW-getWindowData': {
// 从 SW 发送消息到 content,获取数据
chrome.tabs.sendMessage(
message.message.tabId,
{
action: 'fromSW2Content-getWindowData',
message: {}
},
{},
() => {}
)
break;
}
default: {
break
}
}
sendResponse()
return false
})
- 接收 popup 的消息,并发送到 content 文件
- 需要 tabId 字段
- 从 content 接收消息,并发送到 popup 页面
- service-worker 对两者发送消息的方式不一样
- content 因为是在页面中,所以需要 tabId
- popup 则不需要 tabId
6. 效果展示
[外链图片转存中…(img-JdmxovqK-1734921003173)]
6.1. 小问题
多次点击会重复渲染
[外链图片转存中…(img-itJlga8y-1734921003173)]
我这里就不做处理,需要的话自己判断处理即可
四、总结
- 获取逻辑和 《Chrome 浏览器插件获取网页 window 对象》中的方案一一样,有兴趣的可以试下其他方案
- 我这是本地 iframe URL,如果你的 URL 是网页链接,可以在 index.js 中嵌入 lucky.js 做延迟处理
- 如果你的 iframe URL 是动态的,比如,点击 tab,切换 URL,则可以在 index.js 中进行 MutationObserver 监听
- 如果你需要部分内容嵌入 iframe 中,则可以使用 window.top === window.self 判断是否是顶层
- 源码:【Gitee】
- 🎉🎉🎉🎉🎉🎉