chrome extension manifest v3

2023-08-12  本文已影响0人  niccky

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

上一篇下一篇

猜你喜欢

热点阅读