记录 chrome exension manifest v3 踩过的坑,不要升级V3,不要升级V3,不要升级V3。
1、manifest 配置
{
"name": "demo",
"version": "1.0.1",
"manifest_version": 3,
"description": "d",emo
"background": {
"service_worker": "background.js"
},
"omnibox": { "keyword" : "auto" },
"action": {
"default_title": demo,
"default_popup": "popup.html"
},
"devtools_page": "devtools.html",
"options_page": "options.html",
"content_scripts": [
{
"matches": ["https://*/*"],
"js": [
"content.js"
],
"run_at": "document_start"
}
],
"permissions": [
"background",
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"declarativeNetRequestFeedback",
"contextMenus",
"activeTab",
"tabs",
"storage",
"unlimitedStorage"
],
"host_permissions": ["<all_urls>"]
}
2、background.js
var ports = [];
var globalId = 0;
chrome.runtime.onConnect.addListener(function(port) {
if (port.name !== "devtools") return;
ports.push(port);
port.onDisconnect.addListener(function() {
var i = ports.indexOf(port);
if (i !== -1) ports.splice(i, 1);
});
port.onMessage.addListener(async function(msg) {
console.log('[bg]', msg);
if(msg.action === 'sync') {
syncLocal();
}
});
});
chrome.runtime.onMessage.addListener(async (msg, e, resp) => {
if(msg.action === 'sync') {
syncLocal();
}
console.log(msg);
});
function isEmpty(obj) {
if(!obj) {
return true;
}
for(const o in obj) {
if(obj.hasOwnProperty(o)) {
return false;
}
}
return true;
}
async function getLocal(key) {
const local = await chrome.storage.sync.get(key);
if(isEmpty(local)) {
return null;
}
return local;
}
async function setLocal(key, value) {
await chrome.storage.sync.set({[key]: value});
}
chrome.runtime.onInstalled.addListener(async function (details) {
if (details.reason == "install") {
chrome.contextMenus.create({
type: 'normal',
title: 'demo',
contexts: ['all'],
id: 'add-rule'
});
} else if (details.reason == "update") {
// perform some logic
}
});
async function syncLocal() {
const local = await getLocal('tmps');
if(local && local.tmps) {
for(const tmp in local.tmps) {
if(local.tmps.hasOwnProperty(tmp)) {
console.log(local.tmps[tmp]);
}
}
const rules = Object.values(local.tmps);
const addedRules = await getRules();
if(addedRules && addedRules.length) {
const lastRule = addedRules.at(-1);
const lastId = Number(lastRule.id);
globalId = lastId + 1;
} else {
globalId = 1;
}
const formatRules = rules.map(r => ruleFormat(r.type, r.url));
for(const rule of rules) {
await setLocal(rule.url, rule);
}
const addRules = {
addRules: formatRules
};
await updateDynamicRules(addRules);
await removeLocal("tmps");
await setLocal('ruleId', globalId);
}
}
function ruleFormat(type, url) {
const condition = {};
// domainType: firstParty, thirdParty
// resourceTypes: main_frame, xmlhttprequest,scripts
if (type === 'host') {
condition.domains = [url];
} else {
condition.urlFilter = url;
}
const rule = {
"id": globalId++,
"priority": 1,
"action": {
"type": "block"
},
"condition": condition
};
return rule;
}
async function updateDynamicRules(rules) {
await chrome.declarativeNetRequest.updateDynamicRules(
rules
);
}
async function removeLocal(key) {
await chrome.storage.sync.remove(key);
}
async function clean() {
await chrome.storage.sync.clear();
await cleanRules();
}
async function getRules() {
return chrome.declarativeNetRequest.getDynamicRules();
}
async function cleanRules(ids) {
let ruleIds = ids;
if(ruleIds == null) {
const rules = await getRules();
ruleIds = rules.map(r => r.id);
}
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: ruleIds
});
}
async function removeRule(domainOrId) {
let ids;
if(isNaN(domainOrId)) {
const rules = await getRules();
ids = rules.filter(r=>r.condition.domains.includes(domainOrId) || r.condition.urlFilter.includes(domainOrId)).map(r=>r.id);
}else {
ids = [domainOrId];
}
cleanRules(ids);
}
3、content.js
function sendMessage(msg) {
chrome.runtime.sendMessage(msg);
}
chrome.runtime.onMessage.addListener((msg, sender, resp) => {
if(msg.action === 'get_urls') {
resp({action: 'done', urls: window.performance.getEntries()});
} else {
resp({action: 'nc', msg});
}
});
function getAllUrls() {
const allUrls = window.performance.getEntries();
return allUrls.filter(r => r.type === 'resource').map(r => r.name);
}
4、share.js
const port = chrome.runtime.connect({ name: 'devtools' });
port.onMessage.addListener((msg, sender) => {
log('[share]', msg);
});
function log(...args) {
chrome.devtools.inspectedWindow.eval(`
console.log(...${JSON.stringify(args)});
`)
};
function createElem(tag, type) {
const elem = document.createElem(tag);
return type == null ? elem : (elem.type = type, elem);
}
function getElem(id) {
return document.getElementById(id);
}
function appendNode(dom, nodes) {
if (Array.isArray(nodes)) {
nodes.forEach(node => dom.appendChild(node));
} else {
dom.appendChild(nodes);
}
}
function sendMessage(msg) {
port.postMessage(msg);
}
async function startSyncReq() {
sendMessage({action: 'sync'});
}
function addElem(dom, url) {
const hostType = url.split('/')[2];
const template = `
<li>
<label><input type='radio' name="${url}" data-type="host" data-url="${hostType}"/> Host </label>
<label><input type='radio' name="${url}" data-type="path" data-url="${url}"/> Path </label>
<a href="${url}" target="_blank">${url}</>
</li>
`;
const root = new DOMParser().parseFromString(template, "text/html");
const node = root.body.firstElementChild;
appendNode(dom, node);
}
async function setLocalReq(type, url) {
const local = await getLocal('tmps');
if(local) {
local.tmps[url]={type, url};
await setLocal('tmps', local.tmps);
} else {
await setLocal('tmps', {[url]:{type, url}});
}
}
async function setLocal(key, value) {
await chrome.storage.sync.set({[key]: value});
}
async function getLocal(key) {
const local = await chrome.storage.sync.get(key);
if(isEmpty(local)) {
return null;
}
return local;
}
async function clean() {
await chrome.storage.sync.clear();
}
function isEmpty(obj) {
if (!obj) return true;
for (const o in obj) {
if (obj.hasOwnProperty(o))
return false;
}
return true;
}
5、panel.js
chrome.devtools.network.onRequestFinished.addListener(
function(req) {
const url = req.request.url;
const dom = getElem('network');
addElem(dom, url);
}
);
window.onload = function() {
const dom = getElem('network');
const btn = {
btnSync: {
elem: getElem('sync'),
action() {
startSyncReq();
}
},
btnClean: {
elem: getElem('clean'),
async action() {
await clean();
const title = this.elem.textContent;
this.elem.innerText = 'cleaned';
setTimeout(() => {
this.elem.innerText = title;
}, 3000);
}
},
btnCapture: {
elem: getElem('network'),
action(e) {
if(e.target.nodeName == 'LABEL') return;
if(e.target.nodeName != 'INPUT') {
return;
}
const {type, url} = e.target.dataset;
console.log('['+type + ']', url);
setLocalReq(type, url);
return false;
}
},
btnReload: {
elem: getElem('reload'),
action(e) {
while(dom.firstChild) {
dom.removeChild(dom.firstChild);
}
chrome.tabs.reload();
}
}
};
['btnSync', 'btnClean','btnReload', 'btnCapture'].forEach(type => {
btn[type].elem.addEventListener('click', e => {
return btn[type].action(e);
})
});
}
6、option.js
const dom = getElem('requstList');
chrome.declarativeNetRequest.getDynamicRules(rules => {
const data = [];
for (const rule of rules) {
const url = rule.condition.urlFilter || rule.condition.domains[0];
data.push(url);
const elem = createElem('li');
elem.textContent = url;
dom.appendChild(elem);
}
dom.rules = data;
});
function createElem(tag, type) {
const elem = document.createElement(tag);
if (type) {
elem.type = type;
}
return elem;
}
function getElem(id) {
return document.getElementById(id);
}
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
const [tab] = tabs;
const obj = await chrome.tabs.sendMessage(tab.id, { action: 'get_urls' });
if (obj && obj.urls.length) {
const urls = obj.urls.filter(r => r.entryType === 'resource').map(r => r.name);
for (const url of urls) {
const elem = createElem('li');
elem.textContent = url;
dom.appendChild(elem);
};
dom.urls = urls;
}
console.log(obj);
});
async function setLocalReq(type, url) {
const local = await getLocal('tmps');
if(local) {
local.tmps[url]={type, url};
await setLocal('tmps', local.tmps);
} else {
await setLocal('tmps', {[url]:{type, url}});
}
}
async function setLocal(key, value) {
await chrome.storage.sync.set({[key]: value});
}
async function getLocal(key) {
const local = await chrome.storage.sync.get(key);
if(isEmpty(local)) {
return null;
}
return local;
}
function isEmpty(obj) {
if(!obj) return true;
for(const o in obj) {
if(obj.hasOwnProperty(o)) {
return false;
}
}
return true;
}
const btn = {
btnReload: {
elem: getElem('reload'),
action(e) {
while(dom.firstChild) {
dom.removeChild(dom.firstChild);
}
chrome.tabs.reload();
}
},
btnAll: {
elem: getElem("all"),
action() {
const title = this.elem.textContent;
this.elem.textContent = '开始处理...';
const rules = dom.rules || [];
const urls = dom.urls || [];
const dataset = [...rules, ...urls];
const allUrls = dataset.map(url => ({type: 'path', url}));
allUrls.forEach(async ({type, url}) => {
await setLocalReq(type, url);
});
setTimeout(() => {
this.elem.textContent = title;
}, 2000);
}
},
btnSync: {
elem: getElem('sync'),
async action() {
sendMessage({action: 'sync'});
}
}
}
function sendMessage(msg) {
chrome.runtime.sendMessage(msg);
}
const options = ['btnReload', 'btnAll', 'btnSync'];
options.forEach(type => {
btn[type].elem.addEventListener('click', e => {
return btn[type].action(e);
});
});