chrome extension manifest v3

记录 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);
    });
});

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容