上班摸鱼看掘金,老板突然出现在身后...
那是一个平静的下午。我正在掘金上摸鱼写文章,代码编辑器在另一个屏幕上装模作样地躺着。 突然,一个熟悉的声音从身后传来:
"小明,在干嘛呢?"
心跳瞬间飙到 180,肾上腺素狂飙。我的手指以肌肉记忆般的速度按下了 Alt+Q。
0.1 秒后——
屏幕上的掘金文章瞬间蒸发,取而代之的是一个朴实无华的百度首页。
我淡定转身,面不改色: "老板,正准备查个技术问题!"
老板满意地点点头,拍了拍我的肩膀:"不错,要多钻研。" 然后转身离开。
我长舒一口气,心率逐渐平复。再次按下 Alt+Q——掘金文章完美归位。
这不是科幻片,也不是魔术。
这是我上周刚撸出来的 「浏览器伪装插件」 —— 一个真正的 "职场求生神器"。
今天,我就把这个插件制作过程,毫无保留地分享给你。
先看效果
按下 Alt+Q显示百度界面, 再次按下 Alt+Q回到原来页面
需求分析
核心功能
「一键切换」:按快捷键(Alt+Q)0.1 秒瞬间切换 「完美伪装」:标题、图标、页面内容三位一体 「无缝恢复」:再按一次,一切如初 「兼容性强」:支持 99% 的网站(包括掘金、知乎、B 站...)
立即使用
「5 分钟搞定安装」:复制代码 → 创建文件 → 加载扩展 → 开始使用!
📦 获取完整代码 👉GitHub - Teernage/disguise-extension如果对你有帮助,欢迎 Star ⭐ 支持!
实现过程
第一步:创建插件结构
disguise-extension/├── manifest.json # 插件配置文件├── background.js # 后台脚本├── disguise.js # 核心逻辑└── rules.json # 网络请求规则
第二步:配置 manifest.json
{"manifest_version": 3,"name": "网站伪装器","version": "1.0","permissions": ["activeTab", "scripting", "declarativeNetRequest"],"host_permissions": ["<all_urls>"],"background": {"service_worker": "background.js"},"content_scripts": [{"matches": ["<all_urls>"],"js": ["disguise.js"]}],"declarative_net_request": {"rule_resources": [{"id": "remove_frame_headers","enabled": true,"path": "rules.json"}]},"commands": {"toggle": {"suggested_key": { "default": "Alt+Q" },"description": "切换伪装"}}}
「关键配置说明:」
permissions:申请必要权限content_scripts:在所有网页注入脚本declarativeNetRequest:修改 HTTP 响应头commands:注册快捷键
第三步:处理快捷键(background.js)
// 监听快捷键chrome.commands.onCommand.addListener(async (command) => {if(command === 'toggle') {const [tab] = await chrome.tabs.query({active: true,currentWindow: true,});chrome.tabs.sendMessage(tab.id, { action: 'toggle' });}});
「工作流程:」
用户按 Alt+Q↓background.js 监听到快捷键↓发送消息到当前标签页↓disguise.js 接收消息并执行切换
💡 深入理解脚本通信机制
看到这里,你可能好奇:「background.js 和 disguise.js 之间是如何通信的?为什么能跨页面传递消息?」 如果你想深入了解 它们之间真实的运行上下文与消息链路,强烈推荐阅读:
👉 大部分人都错了!这才是 Chrome 插件多脚本通信的正确姿势[2] 这篇文章用原理 + 示例拆解了常见误区与正确做法,能帮你更透彻地理解本插件的通信机制。
第四步:核心伪装逻辑(disguise.js)
const config = {url: 'https://www.baidu.com/',title: '百度一下',};let isDisguised = false;let originalTitle = document.title;let originalIcon = null;// 接收快捷键消息chrome.runtime.onMessage.addListener((msg) => {if (msg.action === 'toggle') {toggle();}});function toggle() {isDisguised = !isDisguised;let iframe = document.getElementById('work-iframe');if (isDisguised) {// 保存原始图标const iconLink = document.querySelector('link[rel*="icon"]');if (iconLink) originalIcon = iconLink.href;// 修改标题和图标document.title = config.title;changeFavicon(`${config.url}/favicon.ico`);// 创建或显示 iframeif (!iframe) {iframe = document.createElement('iframe');iframe.id = 'work-iframe';iframe.src = config.url;// 设置iframe样式Object.assign(iframe.style, {position: 'fixed',top: '0',left: '0',width: '100vw',height: '100vh',zIndex: '2147483647',border: 'none',background: 'white',});// 添加加载错误处理iframe.onerror = () => {console.warn('iframe 加载失败,可能被跨域阻止');showFallbackMessage(iframe);};document.body.appendChild(iframe);}iframe.style.display = 'block';} else if (iframe) {// 恢复原始标题和图标document.title = originalTitle;if (originalIcon) {changeFavicon(originalIcon);} else {// 如果没有原始图标,移除当前图标const currentIcon = document.querySelector('link[rel*="icon"]');if (currentIcon) currentIcon.remove();}iframe.style.display = 'none';}}function changeFavicon(iconUrl) {// 移除所有现有的图标链接const existingIcons = document.querySelectorAll('link[rel*="icon"]');existingIcons.forEach((icon) => icon.remove());// 创建新的图标链接const link = document.createElement('link');link.rel = 'icon';link.type = 'image/x-icon';link.href = iconUrl;document.head.appendChild(link);}// 当 iframe 被阻止时显示 404 页面function showFallbackMessage(iframe) {const fallback = document.createElement('div');fallback.id = 'fallback-message';fallback.innerHTML = `<div style="display: flex;flex-direction: column;align-items: center;justify-content: center;height: 100vh;background: #f5f5f5;font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;margin: 0;color: #333;"><div style="text-align: center; max-width: 600px; padding: 40px;"><h1 style="font-size: 120px;margin: 0;color: #ddd;font-weight: 700;">404</h1><h2 style="font-size: 24px;margin: 20px 0;color: #666;font-weight: 400;">页面未找到</h2><p style="font-size: 16px;color: #999;line-height: 1.6;margin: 20px 0;">抱歉,您访问的页面不存在或已被删除。</p><div style="margin-top: 30px;"><a href="${config.url}" style="display: inline-block;padding: 12px 30px;background: #1a73e8;color: white;text-decoration: none;border-radius: 4px;font-size: 14px;transition: background 0.3s;" onmouseover="this.style.background='#1557b0'"onmouseout="this.style.background='#1a73e8'">返回首页</a></div></div></div>`;iframe.style.display = 'none';document.body.appendChild(fallback);}
第五步 rules.json
很多网站(如百度)设置了 X-Frame-Options 响应头,禁止被 iframe 嵌入。
我们需要通过 declarativeNetRequest 移除这些限制:
[{"id": 1,"priority": 1,"action": {"type": "modifyHeaders","responseHeaders": [{"header": "X-Frame-Options","operation": "remove"},{"header": "Content-Security-Policy","operation": "remove"}]},"condition": {"urlFilter": "*","resourceTypes": ["sub_frame"]}}]
「作用:」
移除 X-Frame-Options响应头移除 Content-Security-Policy响应头允许任何网站被 iframe 嵌入
技术细节
1. 为什么用 iframe?
「方案对比:」
| 「修改 DOM」 | ||
| 「截图替换」 | ||
| 「iframe 嵌入」 |
「最终选择:」 iframe + 跨域绕过
2. 如何处理跨域限制?
问题
很多网站设置了 X-Frame-Options: DENY,禁止被 iframe 嵌入。
解决方案
使用 Chrome Extension 的 declarativeNetRequest API,在响应到达浏览器前移除这些响应头。
{"action": {"type": "modifyHeaders","responseHeaders": [ { "header": "X-Frame-Options", "operation": "remove" }]}}
「原理:」
服务器返回响应↓X-Frame-Options: DENY↓插件拦截并移除响应头↓浏览器接收到无限制的响应↓iframe 成功加载 ✅
安装使用
安装:开发者模式加载
打开 Chrome 浏览器 访问 chrome://extensions/开启「开发者模式」 点击「加载已解压的扩展程序」 选择 disguise-extension文件夹
用法
「访问任意网站」(如掘金) 「按 Alt+Q」 「页面瞬间变成百度首页」 「再按 Alt+Q 恢复」
自定义伪装网站
修改 disguise.js 中的配置:
const config = {url: 'https://www.baidu.com/', // 伪装的网站title: '百度一下', // 标签页标题};
❝「重要提醒:」 此插件仅供学习交流,请勿在工作时间摸鱼!🤣
❞
如果觉得对您有帮助,欢迎点交流哦!