chrome extension manifest v3

記錄 chrome exension manifest v3 踩過的坑,不要升級(jí)V3渗柿,不要升級(jí)V3饱亮,不要升級(jí)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);
    });
});

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市斗锭,隨后出現(xiàn)的幾起案子地淀,更是在濱河造成了極大的恐慌,老刑警劉巖岖是,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骚秦,死亡現(xiàn)場離奇詭異她倘,居然都是意外死亡璧微,警方通過查閱死者的電腦和手機(jī)作箍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來前硫,“玉大人胞得,你說我怎么就攤上這事∫俚纾” “怎么了阶剑?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長危号。 經(jīng)常有香客問我牧愁,道長,這世上最難降的妖魔是什么外莲? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任猪半,我火速辦了婚禮,結(jié)果婚禮上偷线,老公的妹妹穿的比我還像新娘磨确。我一直安慰自己,他們只是感情好声邦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布乏奥。 她就那樣靜靜地躺著,像睡著了一般亥曹。 火紅的嫁衣襯著肌膚如雪邓了。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天媳瞪,我揣著相機(jī)與錄音骗炉,去河邊找鬼。 笑死材失,一個(gè)胖子當(dāng)著我的面吹牛痕鳍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播龙巨,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼笼呆,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了旨别?” 一聲冷哼從身側(cè)響起诗赌,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秸弛,沒想到半個(gè)月后铭若,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洪碳,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年叼屠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞳腌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镜雨,死狀恐怖嫂侍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荚坞,我是刑警寧澤挑宠,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站颓影,受9級(jí)特大地震影響各淀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诡挂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一碎浇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咆畏,春花似錦南捂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钮蛛,卻和暖如春鞭缭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背魏颓。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工岭辣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甸饱。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓沦童,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叹话。 傳聞我的和親對(duì)象是個(gè)殘疾皇子偷遗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容