前言
周末学习了下Chrome插件的开发,总体来说入门还是比较容易的,动手配合一些demo就能了解基本的开发过程。这篇是一个学习笔记和总结,希望对大家也有所帮助。
什么是Chrome插件
Chrome插件其实和一个普通web应用一样都是由html
+css
+js
打包组成的,插件可以使用Chrome提供的浏览器API,增强浏扩展览器的功能。
Chrome插件通常是.crx后缀的文件,通过谷歌网上应用商店下载或者在开发者模式中可以直接拖入到浏览器进行安装
用户界面网页(popup)
点击插件图标出来的弹窗其实就是一个html页面,弹窗要显示的内容,和工具栏小图标在manifest.json
文件中配置。
弹窗在每次点击插件开始运行,弹窗关闭后结束,可以与background脚本交互。
基础用法
1.创建manifest.json
任何插件都必须要有这个文件,用来描述插件的元数据,插件的配置信息。
一个常用配置项如下:
{
// 必须
"manifest_version": 2,
"name": "插件名称a",
"version": "1.1.2",
// 推荐
"default_locale": "en",
"description": "插件的描述",
"icons": {
"16": "img/icon.png", // 扩展程序页面上的图标
"32": "img/icon.png", // Windows计算机通常需要此大小。提供此选项可防止尺寸失真缩小48x48选项。
"48": "img/icon.png", // 显示在扩展程序管理页面上
"128": "img/icon.png" // 在安装和Chrome Webstore中显示
},
// 可选
"background": {
"page": "background/background.html",
"scripts": ["background.js"],
// 推荐
"persistent": false
},
"browser_action": {
"default_icon": "img/icon.png",
// 特定于工具栏的图标,至少建议使用16x16和32x32尺寸,应为方形,
// 不然会变形
"default_title": "悬浮在工具栏插件图标上时的tooltip内容",
"default_popup": "hello.html" // 不允许内联JavaScript。
},
"content_scripts": [ {
"js": [ "inject.js" ],
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_start"
} ],
"permissions": [
"contextMenus",
"tabs",
"http://*/*",
"https://*/*"
],
"web_accessible_resources": [ "dist/*", "dist/**/*" ]
}
上面的配置项看起来内容较多,主要有以下几项:
- 定义当前插件的名字,描述版本号等信息。
- "manifest_version":现在应该总是2。
- background
- content_scripts
- permissions:在background或是popup的js里使用一些chrome api,需要授权才能使用,例如要使用chrome.tabs.xxx的api,就要在permissions引入“tabs”
background、content_scripts的使用后面会详细介绍
我们先使用如下基本信息
manifest.json
{
"name": "My QrCode",
"description" : "My QrCode Extension",
"version": "1.0",
"manifest_version": 2,
"browser_action": {
"default_popup": "./popup.html",
"default_icon": "./logo.png"
},
}
2.新建popup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>hello world!</h2>
</body>
</html>
并找个png的图作为logo
3.打包扩展程序
现在我们就可以看下效果了。
打包扩展程序:
打开chrome://extensions/,打开开发者模式,点击打包扩展程序,选择我们的开发目录
注意:第一次打包的时候,会生成一个pem个人密钥文件,以后再次打包的时候就需要密钥文件了。
4.加载已解压的扩展程序
选择刚刚打包出来的文件即可
现在我们就可以看到效果了:
实现一个二维码插件
在上面的基础上,我们实现一个简单的当前页面生成二维码插件:
popup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="qrcode.min.js"></script>
</head>
<body>
<div id="qrcode"></div>
<script type="text/javascript" src="popup.js"></script>
</body>
</html>
这里我们二维码生成用了这个开源项目:
https://github.com/davidshimjs/qrcodejs
把项目中的jquery.min.js、qrcode.min.js拷过来
注意:
由于popup不允许内联JavaScript,因此需要script标签引入外部脚本
popup.js
let url;
chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
url = tabs[0].url
var qrcode = new QRCode(document.getElementById("qrcode"), {
text: url,
width: 256,
height: 256,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.M
});
});
chrome.tabs.query()这部分用来获取当前页面url
在manifest.json中加入允许:
{
"name": "My QrCode",
...
"permissions": ["activeTab"]
}
这样,我们一个简单的生成二维码插件就完成了
background
问题:
我们想在弹窗中写一个计数器,每点击一次加1。但发现Popup 弹窗无法记录数据
实现代码如下:
popup.html
<input type="text" id="input" value="0">
<button id="btn">+1</button>
popup.js
var count = 0;
$(function(){
$('#input').val(count);
$('#btn').click(function(){
count = count+1;
$('#input').val(count);
});
})
当我们点击 “+1” ,输入框中的数字就会增加,然后关闭弹窗,再点开,发现数字又变成了0,这说明当我们关闭弹窗时,popup.html
就被销毁了,我们在popup.js
中用count
存储的全局变量,也被销毁了。
解法:
由于popup在弹窗关闭后就销毁了。如果想要在popup中记录上次关闭后的结果的话,需要引入background
概念
background可以理解为插件运行在浏览器中的一个后台网站/脚本,注意它是与当前浏览页面无关的。
实际上这部分内容的配置情况也会写在manifest里,对应的是background
配置项。单独拿出来讲,是彰显它的分量很重,也是一个插件常用的配置。从其中几个配置项项去了解一下什么是background script
manifest.json
"background": {
"page": "background/background.html",
"scripts": ["background.js"],
// 推荐
"persistent": false
},
page
可以理解为这个后台网站的主页,在这个主页中,有引用的脚本,其中一般都会有一个专门来管理插件各种交互以及监听浏览器行为的脚本,一般都起名为background.js。这个主页,不一定要求有。
scripts
这里的脚本其实跟写在page里html引入的脚本目的一样,个人的理解是,page的html在没有的情况下,那么脚本就需要通过这个属性引入了;
如果在存在page的情况下,一般在这里引入的脚本是专门为插件服务的脚本,而那些第三方脚本如jquery还是在page里引用比较好,或许这是一个众人的“潜规则”吧
persistent
所谓的后台脚本,在chrome扩展中又分为两类,分别运行于后台页面(background page)和事件页面(event page)中。两者区别在于,
前者(后台页面)持续运行,生存周期和浏览器相同,即从打开浏览器到关闭浏览器期间,后台脚本一直在运行,一直占据着内存等系统资源,persistent设为true;
而后者(事件页面)只在需要活动时活动,在完全不活动的状态持续几秒后,chrome将会终止其运行,从而释放其占据的系统资源,而在再次有事件需要后台脚本来处理时,重新载入它,persistent设为false。
保持后台脚本持久活动的唯一场合是扩展使用chrome.webRequest API来阻止或修改网络请求。webRequest API与非持久性后台页面不兼容。
popup与background的通信
popup与background的交流,常见于popup要获取background里的某些“东西”
通信方式:
popup:
var bg = chrome.extension.getBackgroundPage();
bg.someMethod(); //someMethod()是background中的一个方法
实现一个计数器
对于计数器的例子来说:
manifest.json
"background" : {
"scripts": ["background.js"],
"persistent": false
},
background.js
var count = 0;
popup.js
var bg = chrome.extension.getBackgroundPage();
$(function(){
$('#input').val(bg.count);
$('#btn').click(function(){
bg.count = bg.count+1;
$('#input').val(bg.count);
});
})
这样,按钮的点击次数就可以记录下来了
content script
概念
向符合条件的网页插入该js脚本,对网页做一些处理。它具有独立而富有包容性。
独立,指它的工作空间,命名空间,域等是独立的,不会说跟插入到的页面的某些函数和变量发生冲突;
包容性,指插件把自己的一些脚本(content script)插入到符合条件的页面里,作为页面的脚本,因此与插入的页面共享dom的,即用dom操作是针对插入的网页的,在这些脚本里使用的window对象跟插入页面的window是一样的。主要用在消息传递上(使用postMessage和onmessage)
配置也会写在manifest里,对应的是content_scripts
配置项。
manifest.json
"content_scripts": [ {
"js": [ "inject.js" ],
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_start"
} ],
js
要插入到页面里的脚本。例子很常见,例如在一个别人的网页上,你要打开你做的扩展,对别人的网页做一些处理或者获取一些数据等,那怎么跟别人的页面建立起联系呢?就是通过把js里的这些脚本嵌入都别人的网页里。
matches
必需。匹配规则组成的数组,用来匹配页面url的,符合条件的页面将会插入js的脚本。当然,有可以匹配的自然会有不匹配的——exclude_matches。匹配规则:
developer.chrome.com/extensions/…
run_at
js配置项里的脚本何时插入到页面里呢,这个配置项来控制插入时机。有三个选择项:
- document_start
- document_end
- document_idle(默认)
document_ start
style样式加载好,dom渲染完成和脚本执行前
document_end
dom渲染完成后,即DOMContentLoaded后马上执行
document_idle
在DOMContentLoaded 和 window load之间,具体是什么时刻,要视页面的复杂程度和加载时间,并针对页面加载速度进行了优化。
popup与content script通信
popup与content script通信 和 background与contentscript通信用法是一致的
发送消息:
chrome.runtime.sendMessege(
message,
function(response) {…}
)
第一个参数message为发送的消息(基础数据类型),回调函数里的第一个参数为background接收消息后返回的消息(如有)
接受消息:
chrome.runtime.onMessege.addListener(
function(request, sender, sendResponse) {…}
)
监听发来的消息,request
表示发来的消息,sendResponse
是一个函数,用于对发来的消息进行回应,如 sendResponse('我已收到你的消息:'+JSON.stringify(request));
这里需要注意的是,默认情况下sendResponse
函数的执行是同步的,如果在这个监听消息的处理函数的同步执行流程里没有发现sendResponse
,则默认返回undefined
,假设我们是要经过一个异步处理之后才调用sendResponse
,已经为时已晚了。因此,我们可能需要异步执行sendResponse
,这时我们在这个监听函数里的添加return true
就能实现了。
实现一个搜索请求
实现一个向百度搜索框发送关键词,并提交搜索请求。
这个功能其实没什么用,主要是为了了解content_scripts和popup通信的流程
manifest.json
"content_scripts": [
{
"matches": ["https://www.baidu.com/*"],
"js": ["jquery.min.js","search-in-baidu.js"]
}
],
上述配置表示当页面 url 地址匹配到 “https://www.baidu.com/*” 模式时才向页面中注入jquery.min.js
, search-in-baidu.js
两个js 文件
一个插件里content-script有多个(一个页面一个),那么怎么向特定的content-script发送消息?
首先我们需要知道要向哪个content scripts发送消息,一般一个页面一份content scripts,而一个页面对应一个浏览器tab,每个tab都有自己的tabId,因此首先要获取要发送消息的tab对应的tabId。
/**
* 获取当前选项卡id
* @param callback - 获取到id后要执行的回调函数
*/
function getCurrentTabId(callback) {
chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
if (callback) {
callback(tabs.length ? tabs[0].id: null);
}
});
}
当知道了tabId后,就使用该api进行发送消息
chrome.tabs.sendMessage(tabId, message, function(response) {...});
其中message为发送的消息,回调函数的response为content scripts接收到消息后的回传消息
在我们这个需求中:
popup.js
$(function(){
var state = $('#state');
$('#send').click(function () {//给对象绑定事件
chrome.tabs.query({active:true, currentWindow:true}, function (tab) {//获取当前tab
//向tab发送请求
chrome.tabs.sendMessage(tab[0].id, {
action: "send",
keyword: $('#keyword').val()
}, function (response) {
console.log(response);
state.html(response.state)
});
});
});
$('#submit').click(function () {
chrome.tabs.query({active:true, currentWindow:true}, function (tab) {
chrome.tabs.sendMessage(tab[0].id, {
action: "submit"
}, function (response) {
state.html(response.state)
});
});
})
})
search-in-baidu.js
var kw = $('#kw');
var form = $('#form');
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.action == "send") {
kw.val(request.keyword)
sendResponse({state:'关键词填写成功!'});
}
if (request.action == "submit") {
form.submit();
sendResponse({state:'提交成功!'});
}
}
);
实现效果:
在www.baidu.com中,使用插件,输入想要搜索的关键词,点击发送->点击提交,就查询出来了。
开发调试
开发过程中,如果有报错,直接点击错误就能看到具体报错
修改代码后插件没有自动更新的话也可以手动点击刷新按钮
-
popup调试:
右键插件图标,审查弹出内容
-
backgroud script调试:
点击背景页,就可以看到backgroud script进行调试了,而且,还能在控制台调用chrome api。请求也可以在这里看到。
content script调试:
平常我们打开F12选择到source选项的时候,一般都会显示在"page"下,选择content script,里边的就是各个扩展的内容脚本了。
总结
chrome扩展开发入门还是比较容易的,主要有一下几个基本概念
- manifest.json
- popup
- background
- content script
了解后在实际开发中查阅相关资料即可
参考
更详细的popup、background、content script的通信方法可以看这篇:
其他:
Chrome 扩展开发教程(2) ——Background的用法