零基础学会如何创建第一个属于自己的chrome插件【定时刷新页面、防止WebRTC泄漏】

相信各位佬友经常会用到 Chrome浏览器 或者 类Chrome浏览器,这类浏览器有个非常好用的功能就是支持插件扩展。这个插件能让浏览器实现更多的玩法,而且让定制各类功能需求也变的得心应手。

恰巧今日得闲,花了一个小时手搓了一个简单的基础类扩展,也正好用这个实例为佬友们说说这个扩展的基本实现原理。实例的主要功能是 **防止WebRTC泄露真实IP** 和实现 **各标签页的自动刷新**

[https://www.browserscan.net/zh/webrtc](https://www.browserscan.net/zh/webrtc) 来测试是否存在WebRTC泄露
[![](https://s.rmimg.com/2025-03-14/1741918114-209444-image.png)](https://s.rmimg.com/2025-03-14/1741918114-209444-image.png)

**教程的所有文件尽量保存为utf-8格式**

**主配置:**
manifest.json

</s><i> </i>{ "manifest_version": 3, "name": "My extension library", "version": "1.0", "description": "Welcome to my extension library", "permissions": ["tabs", "activeTab", "storage", "alarms", "privacy"], "background": { "service_worker": "background.js" }, "action": { "default_popup": "popup.html" }, "content_scripts": [{ "matches": ["&lt;all_urls&gt;"], "js": ["content.js"], "run_at": "document_start", "all_frames": true }] }<i> </i><e>

主配置中的**name****version****description**可以随意写,**permissions** 声明插件所需的权限,**content_scripts** 功能非常强大(暂定匹配所有URL)。至于插件icon不是必须的,也不是今天的重点,如果有这方面需求的可以自己在主配置中添加即可。

**主脚本:**
background.js

```
//定义一个变量(是否启用WebRTC)、一个常量(判断是不是firefox)。
let pubwebrtc=false;
const isFirefox = /Firefox/.test(navigator.userAgent) || typeof InstallTrigger !== ‘undefined’;

//这个是关闭标签页触发的回调,主要功能是删除计时器
chrome.tabs.onRemoved.addListener((tabId) => {
chrome.storage.local.get([“refreshTabs”], (data) => {
let refreshTabs = data.refreshTabs || {};
if (refreshTabs[tabId]){
if (refreshTabs[tabId][“timer”])chrome.alarms.clear(refreshTabs[tabId][“timer”]);
delete refreshTabs[tabId];
chrome.storage.local.set({ refreshTabs });
}
});
});

//这个是设置并开启定时刷新的功能,由popup.html(交互式窗口)触发
function setRefreshTimer(tabId, interval) {
chrome.storage.local.get([“refreshTabs”], (data) => {
let refreshTabs = data.refreshTabs || {};
if (!refreshTabs[tabId]) refreshTabs[tabId]={};
if (refreshTabs[tabId][“timer”])chrome.alarms.clear(refreshTabs[tabId][“timer”]);
refreshTabs[tabId][“timer”]=“auto”+tabId;
chrome.alarms.create(refreshTabs[tabId][“timer”], {delayInMinutes: 0, periodInMinutes: interval/60});
refreshTabs[tabId][“interval”] = interval;
chrome.storage.local.set({ refreshTabs });
});
}

//这个是接收popup.html传入的消息并做相应的处理,主要功能是开/关自动刷新、启用/禁用WebRTC
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const { action, tabId, interval } = request;
if (action === “start_refresh”) {
setRefreshTimer(tabId, interval);
} else if (action === “stop_refresh”) {
chrome.storage.local.get([“refreshTabs”], (data) => {
let refreshTabs = data.refreshTabs || {};
if(refreshTabs[tabId]){
if(refreshTabs[tabId][“timer”]){
chrome.alarms.clear(refreshTabs[tabId][“timer”]);
delete refreshTabs[tabId][“timer”];
chrome.storage.local.set({ refreshTabs });
}
}
})
} else if (action === “start_webrtc”) {
webrtcfunc(true,true);
} else if (action === “stop_webrtc”) {
webrtcfunc(false,true);
}
sendResponse({ success: true });
});

//计时器主函数,主要功能是重载页面(不受其他外在因素影响)
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name.substr(0,4) == “auto”) {
let tabId=parseInt(alarm.name.substr(4));
try{chrome.tabs.reload(tabId)}catch(e){}
}
});

//启用或禁用WebRTC后调用的功能函数
function webrtcaction() {
chrome.storage.local.get({
enabled: !pubwebrtc,
eMode: isFirefox ? ‘proxy_only’ : ‘disable_non_proxied_udp’,
dMode: ‘default_public_interface_only’
}, prefs => {
const value = prefs.enabled ? prefs.eMode : prefs.dMode;
chrome.privacy.network.webRTCIPHandlingPolicy.clear({}, () => {chrome.privacy.network.webRTCIPHandlingPolicy.set({value}, () => {chrome.privacy.network.webRTCIPHandlingPolicy.get({}, s => {})})});
});
}

//开启、禁用WebRTC
function webrtcfunc(v,s){
let webrtc = v; pubwebrtc = webrtc;
if (s) chrome.storage.local.set({ webrtc });
webrtcaction();
}

//在插件加载、安装、更新时触发,主要功能是初始化WebRTC
function webrtcinit(){
chrome.storage.onChanged.addListener(() => {webrtcaction()});
chrome.storage.local.get([“webrtc”], (data) => {
let webrtc = data.webrtc || false;
webrtcfunc(webrtc,false);
});
}

//在插件加载、安装、更新时触发,主要功能是初始化计时器本地存储
function clearStoredTabs() {
let refreshTabs={};
chrome.storage.local.set({ refreshTabs });
}

chrome.runtime.onStartup.addListener(() => {webrtcinit(); clearStoredTabs()});
chrome.runtime.onInstalled.addListener((details) => {webrtcinit(); if (details.reason === “install”) clearStoredTabs()});
```

**交互式页面:**
popup.html

</s><i> </i>&lt;!DOCTYPE html&gt; &lt;html lang="zh"&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt; &lt;title&gt;Auto refresh&lt;/title&gt; &lt;style&gt; body { width: 200px; font-family: Arial, sans-serif; text-align: center; } input { width: 30%; margin: 5px 0; } button { width: 90%; margin: 5px 0; padding: 5px; } &lt;/style&gt; &lt;/head&gt; &lt;body&gt; 间隔 &lt;input type="number" id="interval" value=60 min="1"&gt; 秒,自动刷新 &lt;button id="onebutton"&gt;&lt;/button&gt; &lt;script src="popup.js"&gt;&lt;/script&gt; &lt;button id="webrtc" title="作用于全局"&gt;&lt;/button&gt; &lt;/body&gt; &lt;/html&gt;<i> </i><e>

**交互式页面配套的脚本文件:**
popup.js

```
//定义提示文字常量
const strs=[“开始刷新”, “停止刷新”, “<font color=green>当前无WebRTC泄漏</font>”, “<font color=red>存在WebRTC泄漏风险</font>”]

//popup.html加载完成后执行,主要功能是和用户交互,并传递相应参数给background.js
document.addEventListener(“DOMContentLoaded”, () => {
const btn = document.getElementById(“onebutton”);
const wtn = document.getElementById(“webrtc”);
const intervalInput = document.getElementById(“interval”);
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs.length === 0) return;
const tabId = tabs[0].id;
btn.innerHTML=strs[0];
intervalInput.focus();intervalInput.select();
chrome.storage.local.get([“refreshTabs”], (data) => {
let refreshTabs = data.refreshTabs || {};
if (refreshTabs[tabId]){
if (refreshTabs[tabId][“interval”]){
intervalInput.value = refreshTabs[tabId][“interval”];
intervalInput.select();
}
if (refreshTabs[tabId][“timer”]) {
btn.innerHTML=strs[1];btn.setAttribute(“func”,“stop”);
}
}
});
btn.addEventListener(“click”, () => {
if (btn.getAttribute(“func”)){
chrome.runtime.sendMessage({ action: “stop_refresh”, tabId }, (response) => {
btn.innerHTML=strs[0];btn.removeAttribute(“func”);
});
}else{
if (isNaN(intervalInput.value) || intervalInput.value < 1) intervalInput.value=60;
let interval = parseFloat(intervalInput.value);
chrome.runtime.sendMessage({ action: “start_refresh”, tabId, interval }, (response) => {
btn.innerHTML=strs[1];btn.setAttribute(“func”,“stop”);
});
}
});
chrome.storage.local.get([“webrtc”], (data) => {
let webrtc = data.webrtc || false;
if (webrtc){
wtn.innerHTML=strs[3];wtn.setAttribute(“func”,“true”);
}else wtn.innerHTML=strs[2];
});
wtn.addEventListener(“click”, () => {
if (wtn.getAttribute(“func”)){
chrome.runtime.sendMessage({ action: “stop_webrtc”, tabId }, (response) => {
wtn.innerHTML=strs[2];wtn.removeAttribute(“func”);
});
}else{
chrome.runtime.sendMessage({ action: “start_webrtc”, tabId }, (response) => {
wtn.innerHTML=strs[3];wtn.setAttribute(“func”,“true”);
});
}
});
});
});
```

强大的功能脚本(由于过于强大,所以不方便展示)
content.js

```
//其实这个脚本的强大与否,主要还是取决于自己想要做什么。
//理论上只要看得见的就能进行个性化操作。
(function() {

document.addEventListener(‘DOMContentLoaded’, function() {
document.title = "Welcome " + document.title;
});

})();
```

**加载插件:**
1.将上述几个文件全部根据提示的文件名保存到本地(格式选UTF-8)
2.启动 **chrome** 并打开扩展页 **chrome://extensions/**
3.在扩展页面 **启用开发者模式**
4.点击 **加载已解压的扩展程序** ,选择上述文件保存的目录即可

[https://www.browserscan.net/zh/webrtc](https://www.browserscan.net/zh/webrtc) 再次来测试是否存在WebRTC泄露
[![](https://s.rmimg.com/2025-03-14/1741927639-530398-image.png)](https://s.rmimg.com/2025-03-14/1741927639-530398-image.png)

右上角的 **扩展程序** 按钮可以打开插件 **交互式页面**
[![](https://s.rmimg.com/2025-03-14/1741918127-945918-image.png)](https://s.rmimg.com/2025-03-14/1741918127-945918-image.png)

因为考虑到实用性和教程易于理解,所以代码基本上都采用较为基础的结构书写。

扩展插件的功能很多,在于你想干什么能干什么,在什么人手里用,所以剩下的就看佬友们的个人发挥了。

相信佬友们会写出更好的扩展,还请多多交流与分享。

Good luck to all

感谢大佬喂饭。 现在有很多根据fq的小鸡ip地址,能够查到真实ip,就是通过WebRTC泄露的。

Wow 牛B大佬,以前总以为上了节点就安全了,原来还有webRTC泄漏的风险,用教程里的链接查了一下还真是泄漏了,实际IP显示的一清二楚,原来还有这么一个坑 。

感谢大佬,照毛画虎搞定了,真实IP完全隐藏了。发现edge也能用。

Image description![Image description](https://s.rmimg.com/2025-03-14/1741929900-161437-image.png)

@“James”#p259832 感谢站长,继续努力,为社区做贡献。

@clofan 感谢支持。

我能说 [size=40]我靠[/size] 吗?

发现大佬的每个教程我都能用上。我太小白了吧

谢谢佬,基本上算是喂饭了,一次成功:ac26:

谢谢超级大佬,已经用上了

感谢大佬的喂饭教程,非常适合我。

谢谢大佬,今天就去抢hax试试

大佬牛逼666

十分感谢。

牛B plus!赞

收藏一下,有空测试一下:xhj02:

好东西 感谢分享

好东西,好操作,最关键是好楼主

喜欢用现成的

@“fat3264”#p259818 可以了,neng bImage description</s>Image description<e>

能不能大概给解读一下,这是做了什么


@“fat3264”#p259818 大佬在提个需求,我想在safari上也适用,就找了个帖子,想问下参照这个种方式我需要提取那个文件名

Image description</s>Image description<e>


好帖,感谢op分享。

感谢op分享