前言
周末學(xué)習(xí)了下Chrome插件的開發(fā),總體來(lái)說入門還是比較容易的,動(dòng)手配合一些demo就能了解基本的開發(fā)過程。這篇是一個(gè)學(xué)習(xí)筆記和總結(jié),希望對(duì)大家也有所幫助。
什么是Chrome插件
Chrome插件其實(shí)和一個(gè)普通web應(yīng)用一樣都是由html+css+js打包組成的,插件可以使用Chrome提供的瀏覽器API,增強(qiáng)瀏擴(kuò)展覽器的功能。
Chrome插件通常是.crx后綴的文件,通過谷歌網(wǎng)上應(yīng)用商店下載或者在開發(fā)者模式中可以直接拖入到瀏覽器進(jìn)行安裝
用戶界面網(wǎng)頁(yè)(popup)
點(diǎn)擊插件圖標(biāo)出來(lái)的彈窗其實(shí)就是一個(gè)html頁(yè)面,彈窗要顯示的內(nèi)容,和工具欄小圖標(biāo)在manifest.json文件中配置。
彈窗在每次點(diǎn)擊插件開始運(yùn)行,彈窗關(guān)閉后結(jié)束,可以與background腳本交互。
基礎(chǔ)用法
1.創(chuàng)建manifest.json
任何插件都必須要有這個(gè)文件,用來(lái)描述插件的元數(shù)據(jù),插件的配置信息。
一個(gè)常用配置項(xiàng)如下:
{
// 必須
"manifest_version": 2,
"name": "插件名稱a",
"version": "1.1.2",
// 推薦
"default_locale": "en",
"description": "插件的描述",
"icons": {
"16": "img/icon.png", // 擴(kuò)展程序頁(yè)面上的圖標(biāo)
"32": "img/icon.png", // Windows計(jì)算機(jī)通常需要此大小。提供此選項(xiàng)可防止尺寸失真縮小48x48選項(xiàng)。
"48": "img/icon.png", // 顯示在擴(kuò)展程序管理頁(yè)面上
"128": "img/icon.png" // 在安裝和Chrome Webstore中顯示
},
// 可選
"background": {
"page": "background/background.html",
"scripts": ["background.js"],
// 推薦
"persistent": false
},
"browser_action": {
"default_icon": "img/icon.png",
// 特定于工具欄的圖標(biāo),至少建議使用16x16和32x32尺寸,應(yīng)為方形,
// 不然會(huì)變形
"default_title": "懸浮在工具欄插件圖標(biāo)上時(shí)的tooltip內(nèi)容",
"default_popup": "hello.html" // 不允許內(nèi)聯(lián)JavaScript。
},
"content_scripts": [ {
"js": [ "inject.js" ],
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_start"
} ],
"permissions": [
"contextMenus",
"tabs",
"http://*/*",
"https://*/*"
],
"web_accessible_resources": [ "dist/*", "dist/**/*" ]
}
上面的配置項(xiàng)看起來(lái)內(nèi)容較多,主要有以下幾項(xiàng):
- 定義當(dāng)前插件的名字,描述版本號(hào)等信息。
- "manifest_version":現(xiàn)在應(yīng)該總是2。
- background
- content_scripts
- permissions:在background或是popup的js里使用一些chrome api,需要授權(quán)才能使用,例如要使用chrome.tabs.xxx的api,就要在permissions引入“tabs”
background、content_scripts的使用后面會(huì)詳細(xì)介紹
我們先使用如下基本信息
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>
并找個(gè)png的圖作為logo
3.打包擴(kuò)展程序
現(xiàn)在我們就可以看下效果了。
打包擴(kuò)展程序:
打開chrome://extensions/,打開開發(fā)者模式,點(diǎn)擊打包擴(kuò)展程序,選擇我們的開發(fā)目錄

注意:第一次打包的時(shí)候,會(huì)生成一個(gè)pem個(gè)人密鑰文件,以后再次打包的時(shí)候就需要密鑰文件了。

4.加載已解壓的擴(kuò)展程序

選擇剛剛打包出來(lái)的文件即可
現(xiàn)在我們就可以看到效果了:

實(shí)現(xiàn)一個(gè)二維碼插件
在上面的基礎(chǔ)上,我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的當(dāng)前頁(yè)面生成二維碼插件:
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>
這里我們二維碼生成用了這個(gè)開源項(xiàng)目:
https://github.com/davidshimjs/qrcodejs
把項(xiàng)目中的jquery.min.js、qrcode.min.js拷過來(lái)
注意:
由于popup不允許內(nèi)聯(lián)JavaScript,因此需要script標(biāo)簽引入外部腳本
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()這部分用來(lái)獲取當(dāng)前頁(yè)面url
在manifest.json中加入允許:
{
"name": "My QrCode",
...
"permissions": ["activeTab"]
}
這樣,我們一個(gè)簡(jiǎn)單的生成二維碼插件就完成了
background
問題:
我們想在彈窗中寫一個(gè)計(jì)數(shù)器,每點(diǎn)擊一次加1。但發(fā)現(xiàn)Popup 彈窗無(wú)法記錄數(shù)據(jù)
實(shí)現(xiàn)代碼如下:
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);
});
})
當(dāng)我們點(diǎn)擊 “+1” ,輸入框中的數(shù)字就會(huì)增加,然后關(guān)閉彈窗,再點(diǎn)開,發(fā)現(xiàn)數(shù)字又變成了0,這說明當(dāng)我們關(guān)閉彈窗時(shí),popup.html就被銷毀了,我們?cè)?code>popup.js 中用count 存儲(chǔ)的全局變量,也被銷毀了。
解法:
由于popup在彈窗關(guān)閉后就銷毀了。如果想要在popup中記錄上次關(guān)閉后的結(jié)果的話,需要引入background
概念
background可以理解為插件運(yùn)行在瀏覽器中的一個(gè)后臺(tái)網(wǎng)站/腳本,注意它是與當(dāng)前瀏覽頁(yè)面無(wú)關(guān)的。
實(shí)際上這部分內(nèi)容的配置情況也會(huì)寫在manifest里,對(duì)應(yīng)的是background配置項(xiàng)。單獨(dú)拿出來(lái)講,是彰顯它的分量很重,也是一個(gè)插件常用的配置。從其中幾個(gè)配置項(xiàng)項(xiàng)去了解一下什么是background script
manifest.json
"background": {
"page": "background/background.html",
"scripts": ["background.js"],
// 推薦
"persistent": false
},
page
可以理解為這個(gè)后臺(tái)網(wǎng)站的主頁(yè),在這個(gè)主頁(yè)中,有引用的腳本,其中一般都會(huì)有一個(gè)專門來(lái)管理插件各種交互以及監(jiān)聽瀏覽器行為的腳本,一般都起名為background.js。這個(gè)主頁(yè),不一定要求有。
scripts
這里的腳本其實(shí)跟寫在page里html引入的腳本目的一樣,個(gè)人的理解是,page的html在沒有的情況下,那么腳本就需要通過這個(gè)屬性引入了;
如果在存在page的情況下,一般在這里引入的腳本是專門為插件服務(wù)的腳本,而那些第三方腳本如jquery還是在page里引用比較好,或許這是一個(gè)眾人的“潛規(guī)則”吧
persistent
所謂的后臺(tái)腳本,在chrome擴(kuò)展中又分為兩類,分別運(yùn)行于后臺(tái)頁(yè)面(background page)和事件頁(yè)面(event page)中。兩者區(qū)別在于,
前者(后臺(tái)頁(yè)面)持續(xù)運(yùn)行,生存周期和瀏覽器相同,即從打開瀏覽器到關(guān)閉瀏覽器期間,后臺(tái)腳本一直在運(yùn)行,一直占據(jù)著內(nèi)存等系統(tǒng)資源,persistent設(shè)為true;
而后者(事件頁(yè)面)只在需要活動(dòng)時(shí)活動(dòng),在完全不活動(dòng)的狀態(tài)持續(xù)幾秒后,chrome將會(huì)終止其運(yùn)行,從而釋放其占據(jù)的系統(tǒng)資源,而在再次有事件需要后臺(tái)腳本來(lái)處理時(shí),重新載入它,persistent設(shè)為false。
保持后臺(tái)腳本持久活動(dòng)的唯一場(chǎng)合是擴(kuò)展使用chrome.webRequest API來(lái)阻止或修改網(wǎng)絡(luò)請(qǐng)求。webRequest API與非持久性后臺(tái)頁(yè)面不兼容。
popup與background的通信
popup與background的交流,常見于popup要獲取background里的某些“東西”
通信方式:
popup:
var bg = chrome.extension.getBackgroundPage();
bg.someMethod(); //someMethod()是background中的一個(gè)方法
實(shí)現(xiàn)一個(gè)計(jì)數(shù)器
對(duì)于計(jì)數(shù)器的例子來(lái)說:
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);
});
})
這樣,按鈕的點(diǎn)擊次數(shù)就可以記錄下來(lái)了
content script
概念
向符合條件的網(wǎng)頁(yè)插入該js腳本,對(duì)網(wǎng)頁(yè)做一些處理。它具有獨(dú)立而富有包容性。
獨(dú)立,指它的工作空間,命名空間,域等是獨(dú)立的,不會(huì)說跟插入到的頁(yè)面的某些函數(shù)和變量發(fā)生沖突;
包容性,指插件把自己的一些腳本(content script)插入到符合條件的頁(yè)面里,作為頁(yè)面的腳本,因此與插入的頁(yè)面共享dom的,即用dom操作是針對(duì)插入的網(wǎng)頁(yè)的,在這些腳本里使用的window對(duì)象跟插入頁(yè)面的window是一樣的。主要用在消息傳遞上(使用postMessage和onmessage)
配置也會(huì)寫在manifest里,對(duì)應(yīng)的是content_scripts配置項(xiàng)。
manifest.json
"content_scripts": [ {
"js": [ "inject.js" ],
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_start"
} ],
js
要插入到頁(yè)面里的腳本。例子很常見,例如在一個(gè)別人的網(wǎng)頁(yè)上,你要打開你做的擴(kuò)展,對(duì)別人的網(wǎng)頁(yè)做一些處理或者獲取一些數(shù)據(jù)等,那怎么跟別人的頁(yè)面建立起聯(lián)系呢?就是通過把js里的這些腳本嵌入都別人的網(wǎng)頁(yè)里。
matches
必需。匹配規(guī)則組成的數(shù)組,用來(lái)匹配頁(yè)面url的,符合條件的頁(yè)面將會(huì)插入js的腳本。當(dāng)然,有可以匹配的自然會(huì)有不匹配的——exclude_matches。匹配規(guī)則:
developer.chrome.com/extensions/…
run_at
js配置項(xiàng)里的腳本何時(shí)插入到頁(yè)面里呢,這個(gè)配置項(xiàng)來(lái)控制插入時(shí)機(jī)。有三個(gè)選擇項(xiàng):
- document_start
- document_end
- document_idle(默認(rèn))
document_ start
style樣式加載好,dom渲染完成和腳本執(zhí)行前
document_end
dom渲染完成后,即DOMContentLoaded后馬上執(zhí)行
document_idle
在DOMContentLoaded 和 window load之間,具體是什么時(shí)刻,要視頁(yè)面的復(fù)雜程度和加載時(shí)間,并針對(duì)頁(yè)面加載速度進(jìn)行了優(yōu)化。
popup與content script通信
popup與content script通信 和 background與contentscript通信用法是一致的
發(fā)送消息:
chrome.runtime.sendMessege(
message,
function(response) {…}
)
第一個(gè)參數(shù)message為發(fā)送的消息(基礎(chǔ)數(shù)據(jù)類型),回調(diào)函數(shù)里的第一個(gè)參數(shù)為background接收消息后返回的消息(如有)
接受消息:
chrome.runtime.onMessege.addListener(
function(request, sender, sendResponse) {…}
)
監(jiān)聽發(fā)來(lái)的消息,request表示發(fā)來(lái)的消息,sendResponse是一個(gè)函數(shù),用于對(duì)發(fā)來(lái)的消息進(jìn)行回應(yīng),如 sendResponse('我已收到你的消息:'+JSON.stringify(request));
這里需要注意的是,默認(rèn)情況下sendResponse函數(shù)的執(zhí)行是同步的,如果在這個(gè)監(jiān)聽消息的處理函數(shù)的同步執(zhí)行流程里沒有發(fā)現(xiàn)sendResponse,則默認(rèn)返回undefined,假設(shè)我們是要經(jīng)過一個(gè)異步處理之后才調(diào)用sendResponse,已經(jīng)為時(shí)已晚了。因此,我們可能需要異步執(zhí)行sendResponse,這時(shí)我們?cè)谶@個(gè)監(jiān)聽函數(shù)里的添加return true就能實(shí)現(xiàn)了。
實(shí)現(xiàn)一個(gè)搜索請(qǐng)求
實(shí)現(xiàn)一個(gè)向百度搜索框發(fā)送關(guān)鍵詞,并提交搜索請(qǐng)求。
這個(gè)功能其實(shí)沒什么用,主要是為了了解content_scripts和popup通信的流程
manifest.json
"content_scripts": [
{
"matches": ["https://www.baidu.com/*"],
"js": ["jquery.min.js","search-in-baidu.js"]
}
],
上述配置表示當(dāng)頁(yè)面 url 地址匹配到 “https://www.baidu.com/*” 模式時(shí)才向頁(yè)面中注入jquery.min.js, search-in-baidu.js 兩個(gè)js 文件
一個(gè)插件里content-script有多個(gè)(一個(gè)頁(yè)面一個(gè)),那么怎么向特定的content-script發(fā)送消息?
首先我們需要知道要向哪個(gè)content scripts發(fā)送消息,一般一個(gè)頁(yè)面一份content scripts,而一個(gè)頁(yè)面對(duì)應(yīng)一個(gè)瀏覽器tab,每個(gè)tab都有自己的tabId,因此首先要獲取要發(fā)送消息的tab對(duì)應(yīng)的tabId。
/**
* 獲取當(dāng)前選項(xiàng)卡id
* @param callback - 獲取到id后要執(zhí)行的回調(diào)函數(shù)
*/
function getCurrentTabId(callback) {
chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
if (callback) {
callback(tabs.length ? tabs[0].id: null);
}
});
}
當(dāng)知道了tabId后,就使用該api進(jìn)行發(fā)送消息
chrome.tabs.sendMessage(tabId, message, function(response) {...});
其中message為發(fā)送的消息,回調(diào)函數(shù)的response為content scripts接收到消息后的回傳消息
在我們這個(gè)需求中:
popup.js
$(function(){
var state = $('#state');
$('#send').click(function () {//給對(duì)象綁定事件
chrome.tabs.query({active:true, currentWindow:true}, function (tab) {//獲取當(dāng)前tab
//向tab發(fā)送請(qǐng)求
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:'關(guān)鍵詞填寫成功!'});
}
if (request.action == "submit") {
form.submit();
sendResponse({state:'提交成功!'});
}
}
);
實(shí)現(xiàn)效果:
在www.baidu.com中,使用插件,輸入想要搜索的關(guān)鍵詞,點(diǎn)擊發(fā)送->點(diǎn)擊提交,就查詢出來(lái)了。



開發(fā)調(diào)試

開發(fā)過程中,如果有報(bào)錯(cuò),直接點(diǎn)擊錯(cuò)誤就能看到具體報(bào)錯(cuò)
修改代碼后插件沒有自動(dòng)更新的話也可以手動(dòng)點(diǎn)擊刷新按鈕
-
popup調(diào)試:
右鍵插件圖標(biāo),審查彈出內(nèi)容

-
backgroud script調(diào)試:
點(diǎn)擊背景頁(yè),就可以看到backgroud script進(jìn)行調(diào)試了,而且,還能在控制臺(tái)調(diào)用chrome api。請(qǐng)求也可以在這里看到。
content script調(diào)試:
平常我們打開F12選擇到source選項(xiàng)的時(shí)候,一般都會(huì)顯示在"page"下,選擇content script,里邊的就是各個(gè)擴(kuò)展的內(nèi)容腳本了。

總結(jié)
chrome擴(kuò)展開發(fā)入門還是比較容易的,主要有一下幾個(gè)基本概念
- manifest.json
- popup
- background
- content script
了解后在實(shí)際開發(fā)中查閱相關(guān)資料即可
參考
更詳細(xì)的popup、background、content script的通信方法可以看這篇:
一篇文章教你順利入門和開發(fā)chrome擴(kuò)展程序(插件)
其他:
Chrome 擴(kuò)展開發(fā)教程(2) ——Background的用法
Chrome 擴(kuò)展開發(fā)教程(3)——content_scripts用法
官網(wǎng)教程:https://developer.chrome.com/extensions/getstarted