本文针对已对浏览器插件有了解的对象,对浏览器插件的介绍不做赘述,本文不完全是开发教程,更多的为本人日常学习记录,用来记录项目中相对重要以及需要注意的点,以实操和实现功能为主,如有疑问可联系博主解答相关问题。

背景介绍:做一款可以在vx公众号后台编辑页插入文字/图片的插件,本文围绕以下几点来做说明:

  1. 因为插入文字/图片到vx公众号需要借助vx本身的编辑器,vx把UE编辑器对象挂载到了window中,因此需要借助挂载到window中的UE对象来进行插入操作
  2. 因为我们的项目中用到了Vite+Vue3,因此如果想获取vx的window对象只能通过注入js来完成,在.Vue文件中是获取不到vx的window对象的
  3. 借助options页来做登录
  4. 借助options传递给background消息做登录跳转到目标页

manifest.json

"name": "xxx",  
"version": "1.0",  
"description": "xxx",  
"manifest_version": 3,  
"background": {  
"service_worker": "background.js"  
},  
"content_scripts": [  
{  
"matches": ["*://mp.weixin.qq.com/cgi-bin/appmsg*"],  // 只针对此链接生效
"css": ["content.css"],  
"js": ["content.js", "js/jquery-1.12.4.min.js", "js/crypto-js.min.js"],  
"run_at": "document_end"  
}  
],  
"options_ui": {  
"page": "options.html",  
"open_in_tab": true  // 安装插件时,默认打开options.html
},  
"permissions": ["storage", "declarativeContent", "tabs"],  // 使用到的权限
"host_permissions":[],  
"web_accessible_resources": [  
{  
"resources": [ "/images/icon16.png" ],  
"matches": ["<all_urls>"]  
},  
{  
"resources": [ "insert.js" ],  // 注入的js,用来做ue编辑器的通信
"matches": ["<all_urls>"]  
}  
],  
"action": {  
"default_popup": "index.html",  
"default_icon": {  
"16": "images/icon16.png",  
"32": "images/icon32.png",  
"48": "images/icon48.png",  
"128": "images/icon128.png"  
},  
"default_title": "xxx"  
},  
"icons": {  
"16": "images/icon16.png",  
"32": "images/icon32.png",  
"48": "images/icon48.png",  
"128": "images/icon128.png"  
}  
}

首先从options登录开始

我选择了在options中做登录,是因为谷歌API提供了非常好的一个操作,那就是用户第一次安装插件的时候会自动打开options页,在options做登录是个不错的选择
我的options页长这样:

image.png
现在我们需要考虑的是,登录完成之后如何保存用户信息、跳转到vx后台编辑页、监听存储的用户信息

使用storage保存用户信息,这是谷歌提供的API方法:

chrome.storage.sync.set({'myKey': JSON.stringify(data.data)}, function() {  
console.log("The myKey.");  
});

拿到用户信息之后,我们需要跳转到目标页面,假设目标页面是a,那么我们需要在已打开的窗口用查到a这个标签页进行跳转,首先在options.js是中是不具备这个能力的,那么我们就需要和background.js进行通信,借助background.js来做中转消息,background不做介绍,我们可以把它理解成中转站即可。
在options.js发送消息

chrome.runtime.sendMessage({ action: 'returnToPageACloseOptions' });

background接收options消息

在background.js中进行接收,跳转、关闭options、刷新页面具体看代码注释

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    // 接收来自content options insert的消息,request里不允许传递function和file类型的参数
    chrome.tabs.query({currentWindow: true}, function (tabs) {
        const { contentRequest, action } = request
            // 接收到options登录页成功登录信息
            if (action ==='returnToPageACloseOptions') {
                let mpWeiXin = []
                for (var i = 0; i < tabs.length; i++) {
                console.log('Tab ' + i + ': ' + tabs[i].url);   // 获取到已打开的所有标签页
                if (tabs[i].title === 'a') {
                    mpWeiXin.push(tabs[i].id)
                    chrome.tabs.update(tabs[i].id, {active: true});   // 跳转到目标页a
                    chrome.tabs.remove(sender.tab.id);                // 关闭options登录页
                    // 跳转到目标页后刷新页面加载,在background中通知content_script加载
                    chrome.tabs.reload(tabs[i].id);                   // 跳转到a页面后进行刷新执行content.js逻辑
                }
            }
            // 判断浏览器是否打开了包含“a”的页面
            if (mpWeiXin.length === 0) {
                // 关闭options页
                chrome.tabs.remove(sender.tab.id);
                window.open('a')
                return false
            }
        }
    })
return true
})

content.js

跳转到a目标页刷新后,这时候我们在content.js写的逻辑才算真正开始执行,我们在content写了监听storage方法,在options登录成功后我们执行过storage方法,一旦storage有变化,那么我们都可以监测到执行相应的业务逻辑

content.js 两个作用:

  1. 用来监听storage,控制插入到页面的元素显示隐藏
  2. 注入insert.js,注入insert.js的目的是它相当于一个外部js,content是一个内部js,为什么这样说?是因为此项目是用vue来做的content.js可以和vue相结合来执行一些操作,可以理解为就像我们在main.js引入了这个js,而insert.js是独立开的,看到这有的同学就会明白了,在insert.js中我们可以获取vx的window对象!
chrome.storage.onChanged.addListener(function (result) {
    if (result.myKey) {
        // chrome.storage.onChanged获取到有newValue和oldValue包裹
        const getMyKey = result.myKey && result.myKey.newValue
        if (getMyKey) {
            const myKey = (JSON.parse(getMyKey))
            localStorage.setItem('myKey', JSON.stringify(getMyKey))  // 本地存储一份
            $('.tab').css('display', 'block')   // 注入到a页面的html展示
            window.postMessage({"login": true, userInfo: myKey}, '*');  // 发送消息给a.vue
        }
    } else {
        $('.tab-box').css('display', ' none')
        localStorage.removeItem('myKey')
    }
})

在a.vue中我们全局监听message根据传递的消息执行对应的业务逻辑

onMounted(()=>{
    window.addEventListener('message', (e)=>{
        if (e.data.login) {
            ......
        }
    })
})

当我们在a.vue点击文字或者图片时,我们需要传递消息给insert.js

const setContent = (v) => {
    console.log('setContent')
    window.postMessage({type: 'insert', content: v}, '*')
}

insert.js

  1. 在insert.js中获取ue对象
  2. 在insert.js中执行和ue相关的操作
window.onload = function () {
    var ue = window.parent.UE && window.parent.UE.getEditor('js_editor');
    window.addEventListener('message',(e)=>{
        let { type, content, highlightedText } = event.data;
        if (type === 'insert') {
            ue.execCommand('insertHtml', `<img src="${content}">`)
        }
    })
}

像options和background中消息的传递我们可以使用官方API进行,但往往有些时候我们需要自定义文件,而自定义的文件并不能使用官方提供的API,我们就需要借助window.postMessage来进行消息的传递

以上是我个人的学习记录,希望能帮到大家,如有疑问可向我咨询,共同学习。
AIGC爱好者 专注前端开发AI提效 欢迎大家进行交流 共同学习

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐