本文整顿自:技术干货丨谷歌插件开发摸索及其利用
数栈是云原生—站式数据中台 PaaS,咱们在 github 和 gitee 上有一个乏味的开源我的项目:FlinkX,记得给咱们点个 star!star!star!
gitee 开源我的项目:https://gitee.com/dtstack_dev…
github 开源我的项目:https://github.com/DTStack/fl…
FlinkX 是一个基于 Flink 的批流对立的数据同步工具,既能够采集动态的数据,比方 MySQL,HDFS 等,也能够采集实时变动的数据,比方 MySQL binlog,Kafka 等,是全域、异构、批流一体的数据同步引擎,大家如果有趣味,欢送来 github 社区找咱们玩~
一、前言
笔者之前始终想理解一些对于谷歌插件的相干常识,心愿通过谷歌插件能够更好的意识到谷歌的调试工具,同时也想着能够应用谷歌插件去写一些小工具,既学习了新的货色,又有肯定的趣味性。恰逢近段时间须要分享,因而花了两周工夫学习和理解谷歌插件相干常识,本篇文章就将笔者在学习过程中的一些思考分享给大家。当然,因为工夫的起因,如果笔者对于这一块的意识有不对的中央,欢送批评指正~
二、什么是谷歌插件
上面先介绍一下谷歌插件的次要组成部分,因为目前谷歌插件应用比拟广泛的版本为 2.0 版本,因而本文都是基于 2.0 版本进行应用阐明,3.0 版本相较于 2.0 版本更为简便,感兴趣的同学能够点击文章开端处的链接理解更多相干常识。
(一)配置文件
谷歌插件的外围文件就是配置文件 –manifest.json(清单)文件。
其中,manifest.json 文件最根本的 Api 如下:
{
"name": "chrome extension",
"version": "1.0.0",
"manifest_version": 2,
"description": "A litlle chrome extension demo"
}
[点击并拖拽以挪动]
次要是蕴含所写谷歌插件的名称、版本以及相干形容,其中 manifest_version 示意清单文件版本。
manifest.json 作为谷歌插件的外围局部,笔者认为该文件对插件来说就相当于一个入口配置文件,开发人员只须要在这个文件通过配置相应的 js,调用谷歌浏览器提供的 Api,就能实现达到欠缺这个插件的目标。
1、根本应用 Api
在清单文件中有 许多的 Api,笔者就不一一列举了,上面为大家介绍几个笔者认为比拟重要的 Api,通过以下几个 Api 的介绍,心愿能够使读者对于谷歌插件的开发过程有一个大略的意识。
1)browser_action
{
...
"browser_action": {
"default_icon": {
"16": "images/get_started16.png",
"32": "images/get_started32.png"
},
"default_title": "谷歌划词翻译",
"default_popup": "popup.html"
},
...
}
[点击并拖拽以挪动]
browser_action 可设置浏览器右上角的图标及名称。
default_popup 可配置点击图标后会呈现的一个小窗口,这里能够做一些临时性的操作
2)permissions
{
...
"permissions": ["activeTab", "storage", "tabs", "contextMenus"],
...
}
[点击并拖拽以挪动]
permissions 可配置谷歌插件权限申请,如 contextMenus(右键菜单)、tabs(标签)和 storage(插件本地存储)。
3)content_scripts
{
...
"content_scripts": {"matches": ["<all_urls>"],
"css": ["content/content_script.css"],
"js": ["content/content_script.js"]
},
...
}
content-scripts 其实就是谷歌插件中向页面注入脚本的一种模式(尽管名为 script,其实还能够包含 CSS 的),借助 content-scripts 能够实现通过配置的形式轻松向指定页面注入 JS 和 CSS。
4)background
{
···
"background": {"scripts": ["background.js"],
"persistent": false
},
···
}
[点击并拖拽以挪动]
background 是一个常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的关上而关上,随着浏览器的敞开而敞开,所以通常把须要始终运行的、启动就运行的及全局的代码放在 background 外面。
笔者也画了一个下面波及到的脚本在浏览器中的散布,如下图:
以上就是笔者认为比拟重要的一些 Api,在介绍完之后,感兴趣的同学就能够开始着手写几个简略的工具,用来实现本人远大的抱负以及现实,升华本人高贵的灵魂。
一些同学可能不太晓得开发谷歌插件的前置条件,在这里为大家简略介绍下。
首先须要关上治理扩大程序,关上开发者模式。
点击加载已解压的程序按钮即可加载本地谷歌插件,开发的时候代码如果有更新的话,须要刷新已加载插件,点击敞开后再开启,不用刷新开发页面。
[点击并拖拽以挪动]
在理解完前置条件后,笔者将在下文中为大家分享谷歌划词翻译插件从 0 - 1 的实现过程,通过开发这个工具也能够加深对于大家谷歌插件的意识。
三、谷歌划词翻译插件
谷歌翻译算是笔者应用比拟频繁的插件,对于在网页上看到的不懂的英文单词或者句子,间接应用鼠标选中,轻松快捷的翻译出相应的中文。因而在学习的过程中,笔者就在想谷歌浏览器插件的翻译工具是如何实现的呢?
(一)思考
如何去做一个划词翻译插件,首先要思考的有以下几点:
如何实现翻译成果
如何选中咱们须要的元素
选中元素之后如何展现划词翻译面板
所有的浏览器 Tab 都须要反对翻译成果
思考完下面的这些点后,带着这几个纳闷,笔者将在下文一一解答,同时也列举一下遇到的一些点。
(二)划词翻译面板
首先不去思考该插件的性能,先写下划词翻译的面板的款式,所达到的成果如下:
HTML 代码如下:
<div class=”translate-panel show”>
<header> 谷歌划词翻译插件 <span class="close">X</span></header>
<main>
<div class="source">
<div class="title"> 英文 </div>
<div class="content">test</div>
</div>
<div class="result">
<div class="title"> 简体中文 </div>
<div class="content">...</div>
</div>
</main>
</div>
[点击并拖拽以挪动]
将下面的款式简略写好之后,开始思考如何将划词翻译的面板展现在浏览器以后页面。对于谷歌浏览器来说,在网页上进行的交互是属于 content_scripts 的,须要引入划词翻译面板所须要的 JS 或者 CSS 来生成以后面板。
其次,在配置文件中配置 content_scripts 引入 JS 文件,动静的生成 DOM 元素。大抵的思路就是通过监听到鼠标松开后,去生成翻译面板,在生成的元素下面增加 opacity 款式管制显隐,应用谷歌收费翻译 Api 进行翻译。
其中代码如下所示:
// manifest.json
{
...
"content_scripts": {"matches": ["<all_urls>"],
"css": ["content_script.css"],
"js": ["content_script.js"]
},
"permissions": ["activeTab"],
...
}
// content_script.js
class TranslatePanel {
createPanel = () => {let wrapper = document.createElement('div')
wrapper.innerHTML = `
<header> 谷歌划词翻译插件 <span class="close">X</span></header>
<main>
<div class="source">
<div class="title"> 英文 </div>
<div class="content">test</div>
</div>
<div class="result">
<div class="title"> 简体中文 </div>
<div class="content">...</div>
</div>
</main>
`
wrapper.classList.add('translate-panel')
wrapper.querySelector('.close').onclick = () => {this.wrapper.classList.remove('show')
}
document.body.appendChild(wrapper)
this.wrapper = wrapper
}
showPanel = () => {this.wrapper.classList.add('show')
}
translateSelect = (content) => {const source = this.wrapper.querySelector('.source .content')
const result = this.wrapper.querySelector('.result .content')
source.innerHTML = content
result.innerHTML = '翻译中...'
fetch(`https://translate.google.cn/translate_a/single?client=at&sl=en&tl=zh-CN&dt=t&q=${content}`)
.then(res => res.json())
.then(res => {result.innerHTML = res[0][0][0]
})
}
locationPanel = (target) => {
this.wrapper.style.top = target.y + 'px'
this.wrapper.style.left = target.x + 'px'
}
}
let panel = new TranslatePanel()
panel.createPanel()
window.onmouseup = (target) => {
// 获取选中内容
const content = window.getSelection().toString().trim()
if (!content) return
panel.locationPanel({x: target.pageX, y: target.pageY})
panel.translateSelect(content)
panel.showPanel()
}
[点击并拖拽以挪动]
在下面过程的中,笔者应用了谷歌收费的翻译接口,然而这个接口依照目前的设置还是有一点问题,咱们临时不表。当初划词翻译的面板就曾经根本写好了。
(三)脚本通信
划词翻译插件开发到这里,仔细的同学应该发现了,每次选中单词时都会触发划词翻译性能,此时急需一个管制翻译性能的开关,这个开关就能够放在 popup 脚本下面。
具体的款式的实现就不去介绍了,次要看一下 HTML 构造:
<div class=”switch-wrapper”>
<div class="switch-desc"> 是否启用划词翻译 </div>
<input type="checkbox" class="switch" />
</div>
根本成果如下:
此时面板和划词翻译的面板都曾经有了,再考虑一下如何实现 popup 脚本与 content_script 脚本之间的通信。首先,在
popup 脚本,咱们在关上窗口的时候须要去查问是否有存储开启划词翻译的状态,同时,
同时当状态产生变更的时候须要将其存储时,再在以后的 Tab 上面发送申请。
// popup.js
let switchWrapp = document.querySelector(‘.switch’)
chrome.storage.sync.get([‘checked’], (target) => {
if (target) {switchWrapp.checked = target.checked}
})
switchWrapp.onclick = (e) => {
chrome.storage.sync.set({checked: e.target.checked})
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {chrome.tabs.sendMessage(tabs[0].id, {checked: e.target.checked})
})
}
[点击并拖拽以挪动]
下面代码中的 chrome.storage 可用于存储数据,追踪数据。storage.sync 的作用是让谷歌浏览器的数据同步,这使得在不同 Tab 页下面切换的状态也是能够同步的,同时也不必将数据保留在 background 后盾页面中,storage 还有很多 Api 比方监听 storage 数据变动的 onChanged,这里就不一一介绍了。
将开启或敞开划词翻译的状态发送后,content_script.JS 须要增加监听事件,获取到该状态后,进行敞开或开启操作。
// content_script.js
let checked = false
window.onmouseup = (target) => {
···
if (!content || !checked) return
···
}
chrome.storage.sync.get([‘checked’], (target) => {
if (target) checked = target.checked
})
chrome.runtime.onMessage.addListener((target) => {
if (target) {checked = target.checked}
})
[点击并拖拽以挪动]
在开发过程中,发现在以后的 Tab 是能够去实现这个操作的,然而当开启了多个 Tab 的状况下就会呈现开启翻译却不能展现翻译面板的状况。
针对以上状况笔者思考了一下,此时应该将 checked 储存起来,不应该放在 content_script 脚本当中。
// content_script.js
let panel = new TranslatePanel()
panel.createPanel()
window.onmouseup = (target) => {
// 获取选中内容
const content = window.getSelection().toString().trim()
if (!content) return
window.chrome.storage.sync.get(['checked'], (result) => {if (result.checked) {panel.locationPanel({ x: target.pageX, y: target.pageY})
panel.translateSelect(content)
panel.showPanel()}
})
}
chrome.runtime.onMessage.addListener((target) => {
if (target.type == 'CHECKED') {chrome.storage.sync.set({ checked: target.checked})
}
})
[点击并拖拽以挪动]
以上,popup 脚本和 content_script 脚本之间就实现了通信,翻译插件也能够通过 popup 下面的按钮,进行开启或敞开翻译性能。
同理,也能够晓得其余模块也是能够通过这种形式去进行通信,不同的是其余脚本向 content_script 通信是须要应用 tabs,先查找到以后的 Tab 在发送申请。
[点击并拖拽以挪动]
(四)右键中转翻译页面
当敞开划词翻译的时候,间接无奈翻译选中内容也不是很敌对,这个时候能够设置为点击右键的时候呈现翻译菜单项。因为这部分内容须要始终存在就加在 background 中。
// backgrond.js
// 当扩大程序第一次装置、更新至新版本或 Chrome 浏览器更新至新版本时产生
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
"id": "SELECT_TRANSLATE",
"title": "翻译 %s",
"contexts": ["selection"]
})
})
chrome.contextMenus.onClicked.addListener((target) => {
if (target.menuItemId == 'SELECT_TRANSLATE') {chrome.tabs.create({url: `https://translate.google.cn/?sl=en&tl=zh-CN&text=${target.selectionText}&op=translate`})
}
})
[点击并拖拽以挪动]
(五)跨域问题
开发过程中,有的同学也看进去了一个问题,比如说谷歌的这个翻译的 Api 须要同源的状况下能力失常调用该接口,而后就只能在谷歌翻译的页面中应用划词翻译,局面一度非常难堪 …
那么,失常来说这个划词翻译应用起来也是非常不合理的,接下来就须要解决一下这个跨域的问题。
笔者过后想要尝试的是应用 JSONP,也就是去应用嵌入脚本去进行跨域,发现还是会有一些问题,次要是谷歌的翻译的接口不反对回调函数。
同时也去查阅了一些材料,发现是能够在 content_script 中告诉 background,background 后盾去调用谷歌翻译的 Api 是来防止这个状况的。
次要因为 background 的权限十分高,简直能够调用所有的 Chrome 扩大 Api,而且它能够无限度跨域,也就是能够跨域拜访任何网站而无需要求对方设置 CORS。
具体增加的代码如下:
// content_script.js
translateSelect = (content) => {
const source = this.wrapper.querySelector('.source .content')
const result = this.wrapper.querySelector('.result .content')
source.innerHTML = content
result.innerHTML = '翻译中...'
chrome.runtime.sendMessage({type: 'QUERY_TRANSLATE', queryContent: content}, (res) => {result.innerHTML = res[0][0][0]
})
}
// background.js
chrome.runtime.onMessage.addListener((request, sender, callBack) => {
if (request.type == 'QUERY_TRANSLATE') {fetch(`https://translate.google.cn/translate_a/single?client=at&sl=en&tl=zh-CN&dt=t&q=${request.queryContent}`)
.then(res => res.json())
.then(res => {callBack(res)
})
return true
}
})
[点击并拖拽以挪动]
background 中的发送音讯的监听事件返回 true 是为了与 content_script 的音讯通道放弃关上,通过异步的形式发送申请。
当初想想,如果应用插件的 background 就能够去跨域去进行申请一些借口,应用不得当的话感觉还是很危险的,能够去获取其余网站的一些信息,由此可见,还是要谨慎的进行此操作。
(六)待欠缺的点
反对其余语言的翻译,谷歌翻译的接口有两个 Api,sl(文本翻译之前的语言)和 tl(文本须要翻译成的语言)可通过扭转对应的值反对其余语言的翻译;款式欠缺,实现先选中图标在进行翻译。多增加一步感觉对交互更敌对,不必去进行开关的操作。
代码构造
[点击并拖拽以挪动]
介绍完了划词翻译插件,笔者本来是打算再分享一个对于谷歌 devtool 开发工具,开发一个相似于 React Developer Tools 的本地开发工具,然而因为工夫也是不太够,波及到的点比拟多,同时查阅了很多的材料对于这一块的介绍也是比拟的浅,所以还是决定着重于介绍划词翻译。
置信通过划词翻译的开发能使得读者比拟疾速的意识到谷歌插件,除了谷歌插件外,如果对于 devtool 工具插件开发有趣味的同学也能够去理解一下。
另外,有的同学可能会认为目前开发的效率是有一点低的,当初的话谷歌插件的开发也是能够基于 react + antd 去进行开发的,也是能够达到高效疾速的去开发一个插件的成果。
四、结尾
在学习谷歌插件的时候,笔者遇到了一些问题,比如说文档比拟少,官网文档又是英文的还常常 404,不过呢,好在谷歌浏览器有提供了很多 Api,笔者这边在写插件的时候也感觉到了很多趣味性。本篇文章还是写的比拟通俗易懂,如果有什么点写的不对或着不清晰的中央,欢送大家留言进行探讨。
相干资源及参考:
官网文档:https://developer.chrome.com/docs/extensions/mv3/
react + antd 脚手架:https://github.com/jhen0409/react-chrome-extension-boilerplate
谷歌翻译插件残缺代码:https://github.com/ting0130/chrome-extensions-translate