隔壁李老太说自己孙子总是用电脑玩网页游戏,希望我帮忙做个浏览器插件,禁止他孙子玩电脑网页游戏。经过一番思想斗争,还是帮忙做了出来!
贴上源码,禁止商业用途,仅供学习参考交流使用,贴上源码如下:
background.js
// 插件状态
let isEnabled = true;
// 初始化
chrome.runtime.onInstalled.addListener(() => {
// 设置默认状态为启用
chrome.storage.local.set({ isEnabled: true });
// 初始化空的拦截列表
chrome.storage.local.set({ blockedSites: [] });
});
// 监听网页导航事件
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
// 只处理主框架的导航
if (details.frameId !== 0) return;
// 检查插件是否启用
chrome.storage.local.get(['isEnabled', 'blockedSites'], (result) => {
if (!result.isEnabled) return;
const url = new URL(details.url);
const hostname = url.hostname;
// 检查当前网址是否在拦截列表中
const blockedSites = result.blockedSites || [];
const isBlocked = blockedSites.some(site => {
// 移除 http:// 或 https:// 前缀进行比较
const siteHostname = site.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
return hostname.includes(siteHostname) || siteHostname.includes(hostname);
});
if (isBlocked) {
// 拦截访问,重定向到拦截页面
chrome.tabs.update(details.tabId, {
url: chrome.runtime.getURL('blocked.html')
});
}
});
});
// 监听来自弹出窗口的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'toggleEnabled') {
isEnabled = message.isEnabled;
chrome.storage.local.set({ isEnabled });
sendResponse({ success: true });
}
if (message.action === 'getState') {
chrome.storage.local.get(['isEnabled', 'blockedSites'], (result) => {
sendResponse({
isEnabled: result.isEnabled,
blockedSites: result.blockedSites || []
});
});
return true; // 异步响应
}
if (message.action === 'addSite') {
chrome.storage.local.get(['blockedSites'], (result) => {
const blockedSites = result.blockedSites || [];
// 检查是否已存在
if (!blockedSites.includes(message.site)) {
blockedSites.push(message.site);
chrome.storage.local.set({ blockedSites });
}
sendResponse({ success: true, blockedSites });
});
return true; // 异步响应
}
if (message.action === 'removeSite') {
chrome.storage.local.get(['blockedSites'], (result) => {
let blockedSites = result.blockedSites || [];
blockedSites = blockedSites.filter(site => site !== message.site);
chrome.storage.local.set({ blockedSites });
sendResponse({ success: true, blockedSites });
});
return true; // 异步响应
}
});
blocked.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>网站已被拦截</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 50px;
background-color: #f8f9fa;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
color: #e74c3c;
}
p {
font-size: 18px;
line-height: 1.6;
color: #333;
}
.icon {
font-size: 80px;
color: #e74c3c;
margin-bottom: 20px;
}
.button {
display: inline-block;
background-color: #3498db;
color: white;
padding: 10px 20px;
border-radius: 4px;
text-decoration: none;
margin-top: 20px;
transition: background-color 0.3s;
}
.button:hover {
background-color: #2980b9;
}
</style>
</head>
<body>
<div class="container">
<div class="icon">⛔</div>
<h1>网站已被拦截</h1>
<p>您尝试访问的网站已被您设置为拦截。</p>
<p>如果您想访问此网站,请在插件设置中移除该网站。</p>
<a href="javascript:history.back()" class="button">返回上一页</a>
</div>
</body>
</html>
manifest.json
{
"manifest_version": 3,
"name": "网站拦截工具",
"version": "1.0",
"description": "拦截指定的网站,防止访问",
"permissions": ["storage", "webNavigation", "tabs"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
}
},
"background": {
"service_worker": "background.js"
},
"icons": {
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
}
}
popup.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>网站拦截工具</title>
<style>
body {
width: 350px;
font-family: Arial, sans-serif;
padding: 10px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.toggle-container {
display: flex;
align-items: center;
}
.toggle {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
margin-left: 10px;
}
.toggle input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #2196F3;
}
input:checked + .slider:before {
transform: translateX(26px);
}
.input-container {
display: flex;
margin-bottom: 15px;
}
#newSite {
flex-grow: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 5px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 8px 12px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
.search-container {
margin-bottom: 15px;
}
#searchInput {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.site-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 15px;
}
.site-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-bottom: 1px solid #eee;
}
.site-item:last-child {
border-bottom: none;
}
.delete-btn {
background-color: #f44336;
padding: 5px 8px;
font-size: 12px;
}
.delete-btn:hover {
background-color: #d32f2f;
}
.pagination {
display: flex;
justify-content: center;
}
.pagination button {
margin: 0 5px;
background-color: #2196F3;
}
.pagination button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.empty-message {
text-align: center;
padding: 20px;
color: #666;
}
</style>
</head>
<body>
<div class="header">
<h2>网站拦截工具</h2>
<div class="toggle-container">
<span>启用</span>
<label class="toggle">
<input type="checkbox" id="enableToggle" checked>
<span class="slider"></span>
</label>
</div>
</div>
<div class="input-container">
<input type="text" id="newSite" placeholder="输入要拦截的网址 (例如: example.com)">
<button id="addSite">添加</button>
</div>
<div class="search-container">
<input type="text" id="searchInput" placeholder="搜索拦截的网站...">
</div>
<div class="site-list" id="siteList">
<!-- 网站列表将在这里动态生成 -->
</div>
<div class="pagination" id="pagination">
<!-- 分页按钮将在这里动态生成 -->
</div>
<script src="popup.js"></script>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
// 获取DOM元素
const enableToggle = document.getElementById('enableToggle');
const newSiteInput = document.getElementById('newSite');
const addSiteButton = document.getElementById('addSite');
const siteList = document.getElementById('siteList');
const searchInput = document.getElementById('searchInput');
const paginationDiv = document.getElementById('pagination');
// 分页设置
const itemsPerPage = 10;
let currentPage = 1;
let allSites = [];
let filteredSites = [];
// 初始化
function init() {
chrome.runtime.sendMessage({ action: 'getState' }, function(response) {
enableToggle.checked = response.isEnabled;
allSites = response.blockedSites || [];
filteredSites = [...allSites];
renderSiteList();
});
}
// 切换插件启用状态
enableToggle.addEventListener('change', function() {
chrome.runtime.sendMessage({
action: 'toggleEnabled',
isEnabled: enableToggle.checked
});
});
// 添加新网站
addSiteButton.addEventListener('click', function() {
const site = newSiteInput.value.trim();
if (site) {
chrome.runtime.sendMessage({
action: 'addSite',
site: site
}, function(response) {
if (response.success) {
newSiteInput.value = '';
allSites = response.blockedSites;
filteredSites = [...allSites];
searchInput.value = '';
renderSiteList();
}
});
}
});
// 回车键添加网站
newSiteInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
addSiteButton.click();
}
});
// 搜索功能
searchInput.addEventListener('input', function() {
const searchTerm = searchInput.value.toLowerCase();
filteredSites = allSites.filter(site =>
site.toLowerCase().includes(searchTerm)
);
currentPage = 1;
renderSiteList();
});
// 渲染网站列表
function renderSiteList() {
siteList.innerHTML = '';
if (filteredSites.length === 0) {
siteList.innerHTML = '<div class="empty-message">没有拦截的网站</div>';
paginationDiv.innerHTML = '';
return;
}
// 计算分页
const totalPages = Math.ceil(filteredSites.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = Math.min(startIndex + itemsPerPage, filteredSites.length);
// 显示当前页的网站
for (let i = startIndex; i < endIndex; i++) {
const site = filteredSites[i];
const siteItem = document.createElement('div');
siteItem.className = 'site-item';
const siteText = document.createElement('span');
siteText.textContent = site;
const deleteButton = document.createElement('button');
deleteButton.className = 'delete-btn';
deleteButton.textContent = '删除';
deleteButton.addEventListener('click', function() {
removeSite(site);
});
siteItem.appendChild(siteText);
siteItem.appendChild(deleteButton);
siteList.appendChild(siteItem);
}
// 渲染分页按钮
renderPagination(totalPages);
}
// 渲染分页按钮
function renderPagination(totalPages) {
paginationDiv.innerHTML = '';
if (totalPages <= 1) return;
const prevButton = document.createElement('button');
prevButton.textContent = '上一页';
prevButton.disabled = currentPage === 1;
prevButton.addEventListener('click', function() {
if (currentPage > 1) {
currentPage--;
renderSiteList();
}
});
const nextButton = document.createElement('button');
nextButton.textContent = '下一页';
nextButton.disabled = currentPage === totalPages;
nextButton.addEventListener('click', function() {
if (currentPage < totalPages) {
currentPage++;
renderSiteList();
}
});
const pageInfo = document.createElement('span');
pageInfo.textContent = `${currentPage}/${totalPages}`;
pageInfo.style.margin = '0 10px';
paginationDiv.appendChild(prevButton);
paginationDiv.appendChild(pageInfo);
paginationDiv.appendChild(nextButton);
}
// 删除网站
function removeSite(site) {
chrome.runtime.sendMessage({
action: 'removeSite',
site: site
}, function(response) {
if (response.success) {
allSites = response.blockedSites;
// 重新应用搜索过滤
const searchTerm = searchInput.value.toLowerCase();
filteredSites = allSites.filter(site =>
site.toLowerCase().includes(searchTerm)
);
// 如果当前页没有内容了,回到上一页
const totalPages = Math.ceil(filteredSites.length / itemsPerPage);
if (currentPage > totalPages && currentPage > 1) {
currentPage = totalPages;
}
renderSiteList();
}
});
}
// 初始化
init();
});