架構(gòu)
-
總括:
- Manifest:程序清單
- Background:插件運(yùn)行環(huán)境/主程序
- Pop up:彈出頁(yè)面
- Content scripts:與瀏覽頁(yè)面交互的腳本
-
Manifest
每一個(gè)擴(kuò)展都有一個(gè)JSON格式的manifest文件,叫manifest.json,相當(dāng)于程序清單,其中定義了插件的一些元數(shù)據(jù)等信息,示例如下:
{
// 必須添加
"manifest_version": 2, //固定的,定義manifest版本信息
"name": "My Extension", //插件名稱
"version": "1.0", //插件本身的版本
// 推薦添加
"default_locale": "en",
"description": "This is my first extension.", //插件的描述
"icons": {...},
//browser_action和page_action只能添加一個(gè)
"browser_action": { //瀏覽器級(jí)別行為,所有頁(yè)面均生效
"default_icon": "cc.gif",//圖標(biāo)的圖片
"default_title": "Hello CC", //鼠標(biāo)移到圖標(biāo)顯示的文字
"default_popup": "popup.html" //單擊圖標(biāo)后彈窗頁(yè)面
},
"page_action":{ //頁(yè)面級(jí)別的行為,只在特定頁(yè)面下生效
"default_icon":{
"24":"icon_24.png",
"38":"icon_38.png"
},
"default_popup": "popup.html",
"default_title":"MyTitle"
},
// 可選
"author": ...,
"automation": ...,
//background script即插件運(yùn)行的環(huán)境
"background":{
"page":"background.html", //page和scripts只能設(shè)置一個(gè)
"persistent": false
//scripts定義一個(gè)腳本文件的數(shù)組,chrome會(huì)在擴(kuò)展啟動(dòng)時(shí)自動(dòng)創(chuàng)建一個(gè)包含所有指定腳本的頁(yè)面
// "scripts": ["js/jquery-1.9.1.min.js","js/background.js"]
},
"background_page": ...,
"chrome_settings_overrides": {...},
"chrome_ui_overrides": {
"bookmarks_ui": {
"remove_bookmark_shortcut": true,
"remove_button": true
}
},
"chrome_url_overrides": {...},
"commands": {...},
"content_capabilities": ...,
//定義對(duì)頁(yè)面內(nèi)容進(jìn)行操作的腳本
"content_scripts": [{
"matches": ["http://*/*","https://*/*"],//只在這些站點(diǎn)下 content_scripts會(huì)運(yùn)行
"js": ["js/jquery-1.9.1.min.js", "js/js.js"],
"run_at": "document_start", //在document加載時(shí)執(zhí)行該腳本,如果不指定,則在document加載完成后執(zhí)行
}]
"content_security_policy": "policyString",
"converted_from_user_script": ...,
"current_locale": ...,
"devtools_page": "devtools.html",
"event_rules": [{...}],
"externally_connectable": {
"matches": ["*://*.example.com/*"]
},
"file_browser_handlers": [...],
"file_system_provider_capabilities": {
"configurable": true,
"multiple_mounts": true,
"source": "network"
},
"homepage_url": "http://path/to/homepage",
"import": [{"id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}],
"incognito": "spanning, split, or not_allowed",
"input_components": ...,
"key": "publicKey",
"minimum_chrome_version": "versionString",
"nacl_modules": [...],
"oauth2": ...,
"offline_enabled": true,
"omnibox": {
"keyword": "aString"
},
"optional_permissions": ["tabs"],
"options_page": "options.html",
"options_ui": {
"chrome_style": true,
"page": "options.html"
},
//數(shù)組,聲明插件所需要的權(quán)限
"permissions": [
"http://*/",
"bookmarks",
"tabs",
"history",
"activeTab",
"storage"
],
"platforms": ...,
"plugins": [...],
"requirements": {...},
"sandbox": [...],
"short_name": "Short Name",
"signature": ...,
"spellcheck": ...,
"storage": {
"managed_schema": "schema.json"
},
"system_indicator": ...,
"tts_engine": {...},
"update_url": "http://path/to/updateInfo.xml",
"version_name": "aString",
"web_accessible_resources": [...]
}
-
Background Page
- background是插件的運(yùn)行環(huán)境,姑且叫做主程序。一旦插件被啟用,chrome就會(huì)為該插件開(kāi)辟一個(gè)獨(dú)立運(yùn)行環(huán)境,用來(lái)執(zhí)行background script。若設(shè)置了scripts字段,瀏覽器的擴(kuò)展系統(tǒng)會(huì)自動(dòng)根據(jù)scripts字段指定的所有js文件自動(dòng)生成背景頁(yè)。也可以直接page字段,指定背景頁(yè)。兩者只能設(shè)置一個(gè)。
- 一般情況下,我們會(huì)讓將擴(kuò)展的主要邏輯都放在 background 中比較便于管理。其它頁(yè)面可以通過(guò)消息傳遞的機(jī)制與 background 進(jìn)行通訊。理論上 content script 與 popup 之間也可以傳遞消息,但不建議這么做。
- 包含background.html和background.js兩個(gè)文件,包括如下兩種:
- Persistent background pages:一直開(kāi)啟
- Event pages:需要時(shí)開(kāi)啟,可通過(guò)將persistent設(shè)置為false來(lái)設(shè)置
- 開(kāi)啟時(shí)機(jī):
- 首次安裝或更新完版本時(shí)
- 觸發(fā)特定事件時(shí)
- content script 向其發(fā)送消息時(shí)
- 其他頁(yè)面(例如popup)調(diào)用
runtime.getBackgroundPage時(shí)
- 注冊(cè)到Manifest中:
- 開(kāi)啟時(shí)機(jī):
{
"name": "My extension",
...
"background": {
"scripts": ["eventPage.js"],
"persistent": false
},
...
}
-
UI Page
-
popup:
- popup.html和popup.js:popup和background page在同一個(gè)運(yùn)行環(huán)境中,均在插件主程序中。也就是說(shuō)popup可以通過(guò)特定的方式調(diào)background里的方法。注:出于安全考慮,javascript必須與html分開(kāi)存放且寫(xiě)在html里的js無(wú)效。
-
option:
-
-
Content Scripts
Content Scripts相當(dāng)于一個(gè)對(duì)當(dāng)前瀏覽頁(yè)面(符合matches定義的模式)的補(bǔ)充腳本,稱作內(nèi)容腳本,與popup不同,內(nèi)容腳本幾乎與主程序運(yùn)行環(huán)境相隔離、獨(dú)立。與主程序的交互只能通過(guò)發(fā)送消息的方式進(jìn)行。- CanDo:
可以訪問(wèn)和修改單前瀏覽頁(yè)面的DOM,但不能訪問(wèn)該頁(yè)面腳本定義的變量和方法,并且不會(huì)和該頁(yè)面原有js的方法和變量沖突。 - CanNot:
- 不能使用chrome獨(dú)有API,除了以下幾點(diǎn):
- extension(getURL,inIncognitoContext,lastError,onRequest,sendRequest)
- i18n
- runtime ( connect , getManifest , getURL , id , onConnect , onMessage , sendMessage )
- storage
- 不能使用插件里其他頁(yè)面定義的變量和方法
- 不能使用chrome獨(dú)有API,除了以下幾點(diǎn):
- 向訪問(wèn)的頁(yè)面注入腳本,訪問(wèn)和修改DOM的方式:
只有為要訪問(wèn)的頁(yè)面賦予權(quán)限才可以,需在Manifest中添加permissions節(jié)點(diǎn)。
向頁(yè)面插入腳本的API: - 注入腳本:chrome.tabs.executeScript,如下:
- CanDo:
//將訪問(wèn)頁(yè)面的背景變?yōu)榧t色
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript({
code: 'document.body.style.backgroundColor="red"'
});
});
- 注入CSS:chrome.tabs.insertCSS
- 注冊(cè)到Manifest中
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://www.google.com/*"],//只有在符合該pattern的站點(diǎn)才會(huì)運(yùn)行
"css": ["mystyles.css"],
"js": ["jquery.js", "myscript.js"]
}
],
//為以下頁(yè)面賦予訪問(wèn)并向注入腳本的權(quán)限
"permissions": [
"tabs", "http://www.google.com/*",
"activeTab"
],
...
}
- 消息傳遞:
雖然內(nèi)容腳本的執(zhí)行環(huán)境和托管它們的頁(yè)面是彼此隔離的,但它們共享對(duì)頁(yè)面DOM的訪問(wèn)權(quán)限,如果頁(yè)面希望與內(nèi)容腳本(或通過(guò)內(nèi)容腳本進(jìn)行擴(kuò)展)進(jìn)行通信,那么它必須通過(guò)共享的DOM進(jìn)行。示例如下:
//訪問(wèn)的頁(yè)面:
document.getElementById("theButton").addEventListener("click",
function() {
window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
}, false);
//content_scripts腳本;
var port = chrome.runtime.connect();
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log("Content script received: " + event.data.text);
port.postMessage(event.data.text);
}
}, false);
插件頁(yè)面間通訊:
插件各個(gè)部分之間的通訊有如下兩種模式:
-
簡(jiǎn)單的單次請(qǐng)求
- 內(nèi)容腳本到主程序:
chrome.extension.sendMessage({hello: "Cissy"}, function(response) {
console.log(response.farewell);
});
- 主程序到內(nèi)容腳本
chrome.tabs.query({active:true}, function(tab) {
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
- 接收消息
chrome.extension.sendMessage()向擴(kuò)展內(nèi)的其它監(jiān)聽(tīng)者發(fā)送一條消息。此消息發(fā)送后會(huì)觸發(fā)擴(kuò)展內(nèi)每個(gè)頁(yè)面的chrome.extension.onMessage()事件。
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
});
-
長(zhǎng)連接
- background 和 popup
background和popup運(yùn)行在同一個(gè)進(jìn)程中,都是運(yùn)行在主程序中的,所以background 和 popup 之間可以直接相互調(diào)用對(duì)方的方法,不需要消息傳遞。- popup調(diào)用background中變量或方法
- background 和 popup
var bg = chrome.extension.getBackgroundPage();//獲取background頁(yè)面
console.log(bg.a);//調(diào)用background的變量或方法。
注: 使用chrome.extension.getBackgroundPage()時(shí)要確保此時(shí)的backgroundpage在運(yùn)行,否則會(huì)返回null,解決方案:
- 將manifest的background的persistent屬性值設(shè)置為true,確保backgroundpage處于常開(kāi)狀態(tài)(我使用的該方法);
"background": {
"scripts": [
"background.js"
],
"persistent": true
}
- 使用異步的 chrome.runtime.getBackgroundPage代替(該方法沒(méi)測(cè)試)
- background調(diào)用popup中變量或方法
background是一個(gè)運(yùn)行在擴(kuò)展進(jìn)程中的HTML頁(yè)面。它在你的擴(kuò)展的整個(gè)生命周期都存在,而popup是在你點(diǎn)擊了圖標(biāo)之后才存在,所以,在獲取popup變量時(shí),請(qǐng)確認(rèn)popup已打開(kāi)。
var pop = chrome.extension.getViews({type:'popup'});//獲取popup頁(yè)面
console.log(pop[0].b);//調(diào)用第一個(gè)popup的變量或方法。
- background 和 content
持續(xù)長(zhǎng)時(shí)間的保持會(huì)話需要在content script和擴(kuò)展建立一個(gè)長(zhǎng)時(shí)間存在的通道。當(dāng)建立連接,兩端都有一個(gè)runtime.Port 對(duì)象通過(guò)這個(gè)連接發(fā)送和接收消息。- 內(nèi)容腳本發(fā)送消息到擴(kuò)展程序
var bac = chrome.extension.connect({name: "ConToBg"});//建立通道,并給通道命名
bac.postMessage({hello: "Cissy"});//利用通道發(fā)送一條消息。
- 擴(kuò)展程序發(fā)送消息到內(nèi)容腳本
var cab = chrome.tabs.connect(tabId, {name: "BgToCon"});//建立通道,指定tabId,并命名
cab.postMessage({ hello: "Cissy"});//利用通道發(fā)送一條消息。
- 接收消息
接收消息為了處理正在等待的連接,需要用chrome.extension.onConnect 事件監(jiān)聽(tīng)器,對(duì)于content script或者擴(kuò)展頁(yè)面,這個(gè)方法都是一樣的
chrome.extension.onConnect.addListener(function(bac) {//監(jiān)聽(tīng)是否連接,bac為Port對(duì)象
bac.onMessage.addListener(function(msg) {//監(jiān)聽(tīng)是否收到消息,msg為消息對(duì)象
console.log(msg.hello);
})
})
數(shù)據(jù)保存及隱身模式
可以使用HTML5的localStorage或保存到服務(wù)器,但是,無(wú)論何種方式,盡量保證在隱身模式的情況下不要保存任何數(shù)據(jù),或僅將數(shù)據(jù)保存在內(nèi)存中,如下:
function saveTabData(tab, data) {
if (tab.incognito) {
chrome.runtime.getBackgroundPage(function(bgPage) {
bgPage[tab.url] = data; // 匿名模式,保存為當(dāng)前頁(yè)面的數(shù)據(jù),只在內(nèi)存中
});
} else {
localStorage[tab.url] = data; // 保存到本地
}
}
調(diào)試
插件各部分的調(diào)試方式各不相同,具體如下:
- Content script:直接F12打開(kāi)開(kāi)發(fā)者工具,在Sources欄下的Content scripts即可調(diào)試
- background:在chrome的擴(kuò)展程序設(shè)置頁(yè)面chrome://extention 下找到相應(yīng)的插件,點(diǎn)擊檢查視圖即可調(diào)試
- popup:右擊工具欄上插件的圖標(biāo),選擇審查彈出內(nèi)容即可調(diào)試
Chrome API
除了web本身的API以外,Chrome插件還支持一些獨(dú)有的API可供使用
- 同步方法vs異步方法
例:
同步方法:返回string的chrome.runtime.getURL()方法
異步方法:
//獲取當(dāng)前頁(yè)面,并修改頁(yè)面url
chrome.tabs.query({'active': true}, function(tabs) {
chrome.tabs.update(tabs[0].id, {url: newUrl});
});
- 常用API:
- chrome.extension.getURL():獲取插件文件的URL,
例:chrome.extension.getURL("images/myimage.png");獲取文件myimage.png的URL - chrome.tabs.executeScript():向訪問(wèn)頁(yè)面注入腳本
- chrome.extension.getURL():獲取插件文件的URL,
最后附上一個(gè)我自己用的兩個(gè)小demo,一個(gè)可以自動(dòng)調(diào)整簡(jiǎn)書(shū)文章頁(yè)面的寬度,一個(gè)可以修改頁(yè)面樣式:
簡(jiǎn)書(shū)調(diào)寬:https://github.com/rascalquan/JianShuNoteWidth

簡(jiǎn)書(shū)調(diào)寬.png
CSS修改器:https://github.com/rascalquan/ChangePageStyle

Paste_Image.png